Prefab support for dynamic vegetation (#1374)
* First version of prefab support for dynamic vegetation * Addressed PR feedback - Made MockSpawnableEntitiesInterface a proper GMock in AzFramework - Added Get/SetSpawnableAssetId - Added lots of comments to better explain things that were asked about in the PR * Exposed AzFrameworkTestShared on all platforms, not just host platforms
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
|
||||
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS)
|
||||
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
|
||||
|
||||
ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME})
|
||||
|
||||
@@ -27,40 +27,42 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS)
|
||||
AZ::AzFramework
|
||||
)
|
||||
|
||||
ly_add_target(
|
||||
NAME ProcessLaunchTest EXECUTABLE
|
||||
NAMESPACE AZ
|
||||
FILES_CMAKE
|
||||
process_launch_test_files.cmake
|
||||
INCLUDE_DIRECTORIES
|
||||
PRIVATE
|
||||
.
|
||||
BUILD_DEPENDENCIES
|
||||
PRIVATE
|
||||
AZ::AzCore
|
||||
AZ::AzFramework
|
||||
)
|
||||
if(PAL_TRAIT_BUILD_HOST_TOOLS)
|
||||
ly_add_target(
|
||||
NAME ProcessLaunchTest EXECUTABLE
|
||||
NAMESPACE AZ
|
||||
FILES_CMAKE
|
||||
process_launch_test_files.cmake
|
||||
INCLUDE_DIRECTORIES
|
||||
PRIVATE
|
||||
.
|
||||
BUILD_DEPENDENCIES
|
||||
PRIVATE
|
||||
AZ::AzCore
|
||||
AZ::AzFramework
|
||||
)
|
||||
|
||||
ly_add_target(
|
||||
NAME Framework.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
|
||||
NAMESPACE AZ
|
||||
FILES_CMAKE
|
||||
frameworktests_files.cmake
|
||||
INCLUDE_DIRECTORIES
|
||||
PRIVATE
|
||||
.
|
||||
${pal_dir}
|
||||
BUILD_DEPENDENCIES
|
||||
PRIVATE
|
||||
AZ::AzTest
|
||||
AZ::AzToolsFramework
|
||||
AZ::AzTestShared
|
||||
AZ::AzFrameworkTestShared
|
||||
RUNTIME_DEPENDENCIES
|
||||
AZ::ProcessLaunchTest
|
||||
)
|
||||
ly_add_googletest(
|
||||
NAME AZ::Framework.Tests
|
||||
)
|
||||
ly_add_target(
|
||||
NAME Framework.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
|
||||
NAMESPACE AZ
|
||||
FILES_CMAKE
|
||||
frameworktests_files.cmake
|
||||
INCLUDE_DIRECTORIES
|
||||
PRIVATE
|
||||
.
|
||||
${pal_dir}
|
||||
BUILD_DEPENDENCIES
|
||||
PRIVATE
|
||||
AZ::AzTest
|
||||
AZ::AzToolsFramework
|
||||
AZ::AzTestShared
|
||||
AZ::AzFrameworkTestShared
|
||||
RUNTIME_DEPENDENCIES
|
||||
AZ::ProcessLaunchTest
|
||||
)
|
||||
ly_add_googletest(
|
||||
NAME AZ::Framework.Tests
|
||||
)
|
||||
endif()
|
||||
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
* its licensors.
|
||||
*
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this
|
||||
* distribution (the "License"). All use of this software is governed by the License,
|
||||
* or, if provided, by the license below or the license accompanying this file. Do not
|
||||
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/UnitTest/UnitTest.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <AzFramework/Spawnable/SpawnableEntitiesInterface.h>
|
||||
#include <AzFramework/Spawnable/SpawnableEntitiesManager.h>
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
class MockSpawnableEntitiesInterface;
|
||||
using NiceSpawnableEntitiesInterfaceMock = ::testing::NiceMock<MockSpawnableEntitiesInterface>;
|
||||
|
||||
class MockSpawnableEntitiesInterface : public SpawnableEntitiesDefinition
|
||||
{
|
||||
public:
|
||||
AZ_RTTI(MockSpawnableEntitiesInterface, "{2A20FF73-C445-4F32-ABB9-5CF0A5778404}", SpawnableEntitiesDefinition);
|
||||
|
||||
MockSpawnableEntitiesInterface()
|
||||
{
|
||||
AZ::Interface<SpawnableEntitiesDefinition>::Register(this);
|
||||
}
|
||||
|
||||
virtual ~MockSpawnableEntitiesInterface()
|
||||
{
|
||||
AZ::Interface<SpawnableEntitiesDefinition>::Unregister(this);
|
||||
}
|
||||
|
||||
MOCK_METHOD2(SpawnAllEntities, void(EntitySpawnTicket& ticket, SpawnAllEntitiesOptionalArgs optionalArgs));
|
||||
|
||||
MOCK_METHOD3(
|
||||
SpawnEntities,
|
||||
void(EntitySpawnTicket& ticket, AZStd::vector<size_t> entityIndices, SpawnEntitiesOptionalArgs optionalArgs));
|
||||
|
||||
MOCK_METHOD2(DespawnAllEntities, void(EntitySpawnTicket& ticket, DespawnAllEntitiesOptionalArgs optionalArgs));
|
||||
|
||||
MOCK_METHOD3(
|
||||
ReloadSpawnable,
|
||||
void(EntitySpawnTicket& ticket, AZ::Data::Asset<Spawnable> spawnable, ReloadSpawnableOptionalArgs optionalArgs));
|
||||
|
||||
MOCK_METHOD3(
|
||||
ListEntities, void(EntitySpawnTicket& ticket, ListEntitiesCallback listCallback, ListEntitiesOptionalArgs optionalArgs));
|
||||
|
||||
MOCK_METHOD3(
|
||||
ListIndicesAndEntities,
|
||||
void(EntitySpawnTicket& ticket, ListIndicesEntitiesCallback listCallback, ListEntitiesOptionalArgs optionalArgs));
|
||||
|
||||
MOCK_METHOD3(
|
||||
ClaimEntities,
|
||||
void(EntitySpawnTicket& ticket, ClaimEntitiesCallback listCallback, ClaimEntitiesOptionalArgs optionalArgs));
|
||||
|
||||
MOCK_METHOD3(Barrier, void(EntitySpawnTicket& ticket, BarrierCallback completionCallback, BarrierOptionalArgs optionalArgs));
|
||||
|
||||
MOCK_METHOD1(CreateTicket, AZStd::pair<EntitySpawnTicket::Id, void*>(AZ::Data::Asset<Spawnable>&& spawnable));
|
||||
MOCK_METHOD1(DestroyTicket, void(void* ticket));
|
||||
|
||||
/** Installs some default result values for the above functions.
|
||||
* Note that you can always override these in scope of your test by adding additional ON_CALL / EXPECT_CALL
|
||||
* statements in the body of your test or after calling this function, and yours will take precedence.
|
||||
**/
|
||||
static void InstallDefaultReturns(NiceSpawnableEntitiesInterfaceMock& target)
|
||||
{
|
||||
using namespace ::testing;
|
||||
|
||||
// The ID and pointer are completely arbitrary, they just need to both be non-zero to look like a valid ticket.
|
||||
constexpr EntitySpawnTicket::Id ticketId(1);
|
||||
static int ticketPayload = 0;
|
||||
ON_CALL(target, CreateTicket(_)).WillByDefault(
|
||||
Return(AZStd::make_pair<AzFramework::EntitySpawnTicket::Id, void*>(ticketId, &ticketPayload)));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
} // namespace AzFramework
|
||||
@@ -10,6 +10,7 @@
|
||||
#
|
||||
|
||||
set(FILES
|
||||
Mocks/MockSpawnableEntitiesInterface.h
|
||||
Utils/Utils.h
|
||||
Utils/Utils.cpp
|
||||
)
|
||||
|
||||
@@ -105,6 +105,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
|
||||
BUILD_DEPENDENCIES
|
||||
PRIVATE
|
||||
AZ::AzTest
|
||||
AZ::AzFrameworkTestShared
|
||||
Gem::Vegetation.Static
|
||||
)
|
||||
ly_add_googletest(
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
* its licensors.
|
||||
*
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this
|
||||
* distribution (the "License"). All use of this software is governed by the License,
|
||||
* or, if provided, by the license below or the license accompanying this file. Do not
|
||||
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <Vegetation/InstanceSpawner.h>
|
||||
#include <AzCore/Asset/AssetCommon.h>
|
||||
#include <AzFramework/Entity/EntityContextBus.h>
|
||||
#include <AzFramework/Spawnable/SpawnableEntitiesInterface.h>
|
||||
|
||||
namespace Vegetation
|
||||
{
|
||||
/**
|
||||
* Instance spawner of prefab instances.
|
||||
*/
|
||||
class PrefabInstanceSpawner
|
||||
: public InstanceSpawner
|
||||
, private AZ::Data::AssetBus::MultiHandler
|
||||
{
|
||||
public:
|
||||
AZ_RTTI(PrefabInstanceSpawner, "{74BEEDB5-81CF-409F-B375-0D93D81EF2E3}", InstanceSpawner);
|
||||
AZ_CLASS_ALLOCATOR(PrefabInstanceSpawner, AZ::SystemAllocator, 0);
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
|
||||
PrefabInstanceSpawner();
|
||||
virtual ~PrefabInstanceSpawner();
|
||||
|
||||
//! Start loading any assets that the spawner will need.
|
||||
void LoadAssets() override;
|
||||
|
||||
//! Unload any assets that the spawner loaded.
|
||||
void UnloadAssets() override;
|
||||
|
||||
//! Perform any extra initialization needed at the point of registering with the vegetation system.
|
||||
void OnRegisterUniqueDescriptor() override;
|
||||
|
||||
//! Perform any extra cleanup needed at the point of unregistering with the vegetation system.
|
||||
void OnReleaseUniqueDescriptor() override;
|
||||
|
||||
//! Does this exist but have empty asset references?
|
||||
bool HasEmptyAssetReferences() const override;
|
||||
|
||||
//! Has this finished loading any assets that are needed?
|
||||
bool IsLoaded() const override;
|
||||
|
||||
//! Are the assets loaded, initialized, and spawnable?
|
||||
bool IsSpawnable() const override;
|
||||
|
||||
//! Display name of the instances that will be spawned.
|
||||
AZStd::string GetName() const override;
|
||||
|
||||
//! Create a single instance.
|
||||
InstancePtr CreateInstance(const InstanceData& instanceData) override;
|
||||
|
||||
//! Destroy a single instance.
|
||||
void DestroyInstance(InstanceId id, InstancePtr instance) override;
|
||||
|
||||
AZStd::string GetSpawnableAssetPath() const;
|
||||
void SetSpawnableAssetPath(const AZStd::string& assetPath);
|
||||
|
||||
AZ::Data::AssetId GetSpawnableAssetId() const;
|
||||
void SetSpawnableAssetId(const AZ::Data::AssetId& assetId);
|
||||
|
||||
private:
|
||||
bool DataIsEquivalent(const InstanceSpawner& rhs) const override;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// AZ::Data::AssetBus::Handler
|
||||
void OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
|
||||
void OnAssetReloaded(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
|
||||
|
||||
AZ::u32 SpawnableAssetChanged();
|
||||
void ResetSpawnableAsset();
|
||||
|
||||
void UpdateCachedValues();
|
||||
|
||||
//! Verify that the spawnable asset only contains data compatible with the dynamic vegetation system.
|
||||
bool ValidateAssetContents(const AZ::Data::Asset<AZ::Data::AssetData> asset) const;
|
||||
|
||||
//! Despawn an instance of a spawnable asset
|
||||
void DespawnAssetInstance(AzFramework::EntitySpawnTicket* ticket);
|
||||
|
||||
//! Cached values so that asset isn't accessed on other threads
|
||||
bool m_assetLoadedAndSpawnable = false;
|
||||
|
||||
//! Collection of spawned instance tickets, needed for destroying the instances.
|
||||
AZStd::unordered_set<AzFramework::EntitySpawnTicket*> m_instanceTickets;
|
||||
|
||||
//! asset data
|
||||
AZ::Data::Asset<AzFramework::Spawnable> m_spawnableAsset;
|
||||
};
|
||||
|
||||
} // namespace Vegetation
|
||||
@@ -0,0 +1,401 @@
|
||||
/*
|
||||
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
* its licensors.
|
||||
*
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this
|
||||
* distribution (the "License"). All use of this software is governed by the License,
|
||||
* or, if provided, by the license below or the license accompanying this file. Do not
|
||||
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "Vegetation_precompiled.h"
|
||||
#include <Vegetation/PrefabInstanceSpawner.h>
|
||||
|
||||
#include <AzCore/Asset/AssetManager.h>
|
||||
#include <AzCore/Math/Transform.h>
|
||||
#include <AzCore/RTTI/BehaviorContext.h>
|
||||
#include <AzCore/Serialization/EditContext.h>
|
||||
#include <AzFramework/Components/TransformComponent.h>
|
||||
#include <AzFramework/StringFunc/StringFunc.h>
|
||||
#include <Vegetation/AreaComponentBase.h>
|
||||
#include <Vegetation/InstanceData.h>
|
||||
#include <Vegetation/Ebuses/DescriptorNotificationBus.h>
|
||||
#include <Vegetation/Ebuses/InstanceSystemRequestBus.h>
|
||||
|
||||
|
||||
|
||||
namespace Vegetation
|
||||
{
|
||||
|
||||
PrefabInstanceSpawner::PrefabInstanceSpawner()
|
||||
{
|
||||
UnloadAssets();
|
||||
}
|
||||
|
||||
PrefabInstanceSpawner::~PrefabInstanceSpawner()
|
||||
{
|
||||
UnloadAssets();
|
||||
AZ_Assert(m_instanceTickets.empty(), "Destroying spawner while %u spawn tickets still exist!", m_instanceTickets.size());
|
||||
}
|
||||
|
||||
void PrefabInstanceSpawner::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
|
||||
if (serialize)
|
||||
{
|
||||
serialize->Class<PrefabInstanceSpawner, InstanceSpawner>()
|
||||
->Version(0)->Field(
|
||||
"SpawnableAsset", &PrefabInstanceSpawner::m_spawnableAsset)
|
||||
;
|
||||
|
||||
AZ::EditContext* edit = serialize->GetEditContext();
|
||||
if (edit)
|
||||
{
|
||||
edit->Class<PrefabInstanceSpawner>(
|
||||
"Prefab", "Prefab Instance")
|
||||
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
|
||||
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
|
||||
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
|
||||
|
||||
->DataElement(AZ::Edit::UIHandlers::Default, &PrefabInstanceSpawner::m_spawnableAsset, "Prefab Asset", "Prefab asset")
|
||||
->Attribute(AZ::Edit::Attributes::ShowProductAssetFileName, false)
|
||||
->Attribute(AZ::Edit::Attributes::HideProductFilesInAssetPicker, true)
|
||||
->Attribute(AZ::Edit::Attributes::AssetPickerTitle, "a Prefab")
|
||||
->Attribute(AZ::Edit::Attributes::ChangeNotify, &PrefabInstanceSpawner::SpawnableAssetChanged)
|
||||
;
|
||||
}
|
||||
}
|
||||
if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
|
||||
{
|
||||
behaviorContext->Class<PrefabInstanceSpawner>()
|
||||
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
|
||||
->Attribute(AZ::Script::Attributes::Category, "Vegetation")
|
||||
->Attribute(AZ::Script::Attributes::Module, "vegetation")
|
||||
->Constructor()
|
||||
->Method("GetPrefabAssetPath", &PrefabInstanceSpawner::GetSpawnableAssetPath)
|
||||
->Method("SetPrefabAssetPath", &PrefabInstanceSpawner::SetSpawnableAssetPath)
|
||||
->Method("GetPrefabAssetId", &PrefabInstanceSpawner::GetSpawnableAssetId)
|
||||
->Method("SetPrefabAssetId", &PrefabInstanceSpawner::SetSpawnableAssetId);
|
||||
}
|
||||
}
|
||||
|
||||
bool PrefabInstanceSpawner::DataIsEquivalent(const InstanceSpawner& baseRhs) const
|
||||
{
|
||||
if (const auto* rhs = azrtti_cast<const PrefabInstanceSpawner*>(&baseRhs))
|
||||
{
|
||||
return m_spawnableAsset == rhs->m_spawnableAsset;
|
||||
}
|
||||
|
||||
// Not the same subtypes, so definitely not a data match.
|
||||
return false;
|
||||
}
|
||||
|
||||
void PrefabInstanceSpawner::LoadAssets()
|
||||
{
|
||||
UnloadAssets();
|
||||
|
||||
// Note that the spawnable tickets manage and track asset loading as well. We *could* just rely on that and mark
|
||||
// the spawner as immediately ready for use (i.e. always return "true" in IsLoaded() and IsSpawnable() ), but this
|
||||
// would cause us to wait until the first instance is spawned to load the asset, creating a delay right at the point
|
||||
// that the vegetation is becoming visible. It would also cause the asset to get auto-unloaded every time all the
|
||||
// instances using it are despawned. By loading it *prior* to marking things as ready, we can ensure that we have the
|
||||
// asset at the point that the first instance is spawned, and that it won't get auto-unloaded every time the instances
|
||||
// are despawned.
|
||||
m_spawnableAsset.QueueLoad();
|
||||
AZ::Data::AssetBus::MultiHandler::BusConnect(m_spawnableAsset.GetId());
|
||||
}
|
||||
|
||||
void PrefabInstanceSpawner::UnloadAssets()
|
||||
{
|
||||
// It's possible under some circumstances that we might unload assets before destroying all spawned instances
|
||||
// due to the way the vegetation system queues up delete requests and descriptor unregistrations. If so,
|
||||
// despawn the actual spawned instances here, but leave the ticket entries in the instance ticket map and don't
|
||||
// delete the ticket pointers. The tickets will get cleaned up when the vegetation system gets around to requesting
|
||||
// the instance destroy.
|
||||
if (!m_instanceTickets.empty())
|
||||
{
|
||||
for (auto& ticket : m_instanceTickets)
|
||||
{
|
||||
DespawnAssetInstance(ticket);
|
||||
}
|
||||
}
|
||||
ResetSpawnableAsset();
|
||||
NotifyOnAssetsUnloaded();
|
||||
}
|
||||
|
||||
void PrefabInstanceSpawner::ResetSpawnableAsset()
|
||||
{
|
||||
AZ::Data::AssetBus::MultiHandler::BusDisconnect();
|
||||
|
||||
m_spawnableAsset.Release();
|
||||
UpdateCachedValues();
|
||||
m_spawnableAsset.SetAutoLoadBehavior(AZ::Data::AssetLoadBehavior::QueueLoad);
|
||||
}
|
||||
|
||||
void PrefabInstanceSpawner::UpdateCachedValues()
|
||||
{
|
||||
// Once our assets are loaded and at the point that they're getting registered,
|
||||
// cache off the spawnable state for use from multiple threads.
|
||||
|
||||
m_assetLoadedAndSpawnable = m_spawnableAsset.IsReady();
|
||||
}
|
||||
|
||||
void PrefabInstanceSpawner::OnRegisterUniqueDescriptor()
|
||||
{
|
||||
UpdateCachedValues();
|
||||
}
|
||||
|
||||
void PrefabInstanceSpawner::OnReleaseUniqueDescriptor()
|
||||
{
|
||||
}
|
||||
|
||||
bool PrefabInstanceSpawner::HasEmptyAssetReferences() const
|
||||
{
|
||||
// If we don't have a valid Spawnable Asset, then that means we're expecting to spawn empty instances.
|
||||
return !m_spawnableAsset.GetId().IsValid();
|
||||
}
|
||||
|
||||
bool PrefabInstanceSpawner::IsLoaded() const
|
||||
{
|
||||
return m_assetLoadedAndSpawnable;
|
||||
}
|
||||
|
||||
bool PrefabInstanceSpawner::IsSpawnable() const
|
||||
{
|
||||
return m_assetLoadedAndSpawnable;
|
||||
}
|
||||
|
||||
AZStd::string PrefabInstanceSpawner::GetName() const
|
||||
{
|
||||
AZStd::string assetName;
|
||||
if (!HasEmptyAssetReferences())
|
||||
{
|
||||
// Get the asset file name
|
||||
assetName = m_spawnableAsset.GetHint();
|
||||
if (!m_spawnableAsset.GetHint().empty())
|
||||
{
|
||||
AzFramework::StringFunc::Path::GetFileName(m_spawnableAsset.GetHint().c_str(), assetName);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
assetName = "<asset name>";
|
||||
}
|
||||
|
||||
return assetName;
|
||||
}
|
||||
|
||||
bool PrefabInstanceSpawner::ValidateAssetContents(const AZ::Data::Asset<AZ::Data::AssetData> asset) const
|
||||
{
|
||||
bool validAsset = true;
|
||||
|
||||
// Basic safety check: Make sure the asset is a spawnable.
|
||||
auto spawnableAsset = azrtti_cast<AzFramework::Spawnable*>(asset.GetData());
|
||||
if (!spawnableAsset)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Loop through all the components on all the entities in the spawnable, looking for any type of Vegetation Area.
|
||||
// If we try to dynamically spawn vegetation areas, as they spawn in they will non-deterministically start spawning
|
||||
// (or blocking) other vegetation while we're in the midst of spawning the higher-level vegetation area. Threading
|
||||
// and timing affects which one wins out. It may also cause other bugs.
|
||||
|
||||
const AzFramework::Spawnable::EntityList& entities = spawnableAsset->GetEntities();
|
||||
for (auto& entity : entities)
|
||||
{
|
||||
auto components = entity->GetComponents();
|
||||
for (auto component : components)
|
||||
{
|
||||
if (azrtti_istypeof<AreaComponentBase*>(component))
|
||||
{
|
||||
validAsset = false;
|
||||
AZ_Error("Vegetation", false,
|
||||
"Vegetation system cannot spawn prefabs containing a component of type '%s'",
|
||||
component->RTTI_GetTypeName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return validAsset;
|
||||
}
|
||||
|
||||
void PrefabInstanceSpawner::OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset)
|
||||
{
|
||||
if (m_spawnableAsset.GetId() == asset.GetId())
|
||||
{
|
||||
// Make sure that the spawnable asset we're loading doesn't contain any data incompatible with
|
||||
// the dynamic vegetation system.
|
||||
// This check needs to be performed at asset loading time as opposed to authoring / configuration
|
||||
// time because the spawnable asset can be changed independently from the authoring of this component.
|
||||
bool validAsset = ValidateAssetContents(asset);
|
||||
|
||||
ResetSpawnableAsset();
|
||||
if (validAsset)
|
||||
{
|
||||
m_spawnableAsset = asset;
|
||||
}
|
||||
UpdateCachedValues();
|
||||
NotifyOnAssetsLoaded();
|
||||
}
|
||||
}
|
||||
|
||||
void PrefabInstanceSpawner::OnAssetReloaded(AZ::Data::Asset<AZ::Data::AssetData> asset)
|
||||
{
|
||||
OnAssetReady(asset);
|
||||
}
|
||||
|
||||
AZStd::string PrefabInstanceSpawner::GetSpawnableAssetPath() const
|
||||
{
|
||||
AZStd::string assetPathString;
|
||||
AZ::Data::AssetCatalogRequestBus::BroadcastResult(
|
||||
assetPathString, &AZ::Data::AssetCatalogRequests::GetAssetPathById, m_spawnableAsset.GetId());
|
||||
return assetPathString;
|
||||
}
|
||||
|
||||
void PrefabInstanceSpawner::SetSpawnableAssetPath(const AZStd::string& assetPath)
|
||||
{
|
||||
if (!assetPath.empty())
|
||||
{
|
||||
AZ::Data::AssetId assetId;
|
||||
AZ::Data::AssetCatalogRequestBus::BroadcastResult(
|
||||
assetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, assetPath.c_str(),
|
||||
AZ::Data::s_invalidAssetType, false);
|
||||
if (assetId.IsValid())
|
||||
{
|
||||
SetSpawnableAssetId(assetId);
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Error("Vegetation", false, "Asset '%s' is invalid.", assetPath.c_str());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SetSpawnableAssetId(AZ::Data::AssetId());
|
||||
}
|
||||
}
|
||||
|
||||
AZ::Data::AssetId PrefabInstanceSpawner::GetSpawnableAssetId() const
|
||||
{
|
||||
return m_spawnableAsset.GetId();
|
||||
}
|
||||
|
||||
void PrefabInstanceSpawner::SetSpawnableAssetId(const AZ::Data::AssetId& assetId)
|
||||
{
|
||||
if (assetId.IsValid())
|
||||
{
|
||||
AZ::Data::AssetInfo assetInfo;
|
||||
AZ::Data::AssetCatalogRequestBus::BroadcastResult(
|
||||
assetInfo, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetInfoById, assetId);
|
||||
if (assetInfo.m_assetType == m_spawnableAsset.GetType())
|
||||
{
|
||||
m_spawnableAsset.Create(assetId, false);
|
||||
LoadAssets();
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Error(
|
||||
"Vegetation", false, "Asset '%s' is of type %s, but expected a Spawnable type.",
|
||||
assetId.ToString<AZStd::string>().c_str(), assetInfo.m_assetType.ToString<AZStd::string>().c_str());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// An invalid asset ID is treated as a valid way to spawn "empty" instances, so don't print an error, just clear out
|
||||
// the asset to that it has an invalid asset reference. (See also HasEmptyAssetReferences() above)
|
||||
m_spawnableAsset = AZ::Data::Asset<AzFramework::Spawnable>();
|
||||
LoadAssets();
|
||||
}
|
||||
}
|
||||
|
||||
AZ::u32 PrefabInstanceSpawner::SpawnableAssetChanged()
|
||||
{
|
||||
// Whenever we change the spawnable asset, force a refresh of the Entity Inspector
|
||||
// since we want the Descriptor List to refresh the name of the entry.
|
||||
NotifyOnAssetsUnloaded();
|
||||
return AZ::Edit::PropertyRefreshLevels::AttributesAndValues;
|
||||
}
|
||||
|
||||
InstancePtr PrefabInstanceSpawner::CreateInstance(const InstanceData& instanceData)
|
||||
{
|
||||
InstancePtr opaqueInstanceData = nullptr;
|
||||
|
||||
// Create a Transform that represents our instance.
|
||||
AZ::Transform world = AZ::Transform::CreateFromQuaternionAndTranslation(
|
||||
instanceData.m_alignment * instanceData.m_rotation, instanceData.m_position);
|
||||
world.MultiplyByUniformScale(instanceData.m_scale);
|
||||
|
||||
// Create a callback for SpawnAllEntities that will set the transform of the root entity to the correct position / rotation / scale
|
||||
// for our spawned instance.
|
||||
auto preSpawnCB = [this, world](
|
||||
[[maybe_unused]] AzFramework::EntitySpawnTicket::Id ticketId, AzFramework::SpawnableEntityContainerView view)
|
||||
{
|
||||
AZ::Entity* rootEntity = *view.begin();
|
||||
|
||||
AzFramework::TransformComponent* entityTransform = rootEntity->FindComponent<AzFramework::TransformComponent>();
|
||||
|
||||
if (entityTransform)
|
||||
{
|
||||
entityTransform->SetWorldTM(world);
|
||||
}
|
||||
};
|
||||
|
||||
// Create the EntitySpawnTicket here. This pointer is going to get handed off to the vegetation system as opaque instance data,
|
||||
// where it will be tracked and held onto for the lifetime of the vegetation instance. The vegetation system will pass it back
|
||||
// in to DestroyInstance at the end of the lifetime, so that's the one place where we will delete the ticket pointers.
|
||||
AzFramework::EntitySpawnTicket* ticket = new AzFramework::EntitySpawnTicket(m_spawnableAsset);
|
||||
if (ticket->IsValid())
|
||||
{
|
||||
// Track the ticket that we've created.
|
||||
m_instanceTickets.emplace(ticket);
|
||||
|
||||
AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs;
|
||||
optionalArgs.m_preInsertionCallback = AZStd::move(preSpawnCB);
|
||||
AzFramework::SpawnableEntitiesInterface::Get()->SpawnAllEntities(*ticket, AZStd::move(optionalArgs));
|
||||
|
||||
opaqueInstanceData = ticket;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Something went wrong!
|
||||
AZ_Assert(ticket->IsValid(), "Unable to instantiate spawnable asset");
|
||||
delete ticket;
|
||||
}
|
||||
|
||||
return opaqueInstanceData;
|
||||
}
|
||||
|
||||
void PrefabInstanceSpawner::DespawnAssetInstance(AzFramework::EntitySpawnTicket* ticket)
|
||||
{
|
||||
if (ticket->IsValid())
|
||||
{
|
||||
AzFramework::SpawnableEntitiesInterface::Get()->DespawnAllEntities(*ticket);
|
||||
}
|
||||
}
|
||||
|
||||
void PrefabInstanceSpawner::DestroyInstance([[maybe_unused]] InstanceId id, InstancePtr instance)
|
||||
{
|
||||
if (instance)
|
||||
{
|
||||
auto ticket = reinterpret_cast<AzFramework::EntitySpawnTicket*>(instance);
|
||||
|
||||
// If the spawnable asset instantiated successfully, we should have a record of it.
|
||||
auto foundInstance = m_instanceTickets.find(ticket);
|
||||
AZ_Assert(foundInstance != m_instanceTickets.end(), "Couldn't find CreateInstance entry for the EntitySpawnTicket.");
|
||||
if (foundInstance != m_instanceTickets.end())
|
||||
{
|
||||
// The call to DespawnAssetInstance above is technically redundant right now, because when we delete the ticket pointer
|
||||
// below it will automatically despawn everything anyways. However, it's nice to have a single explicit call to despawn,
|
||||
// in case we ever need a place to add logging, or have a callback when despawning is complete, etc.
|
||||
DespawnAssetInstance(ticket);
|
||||
m_instanceTickets.erase(foundInstance);
|
||||
}
|
||||
|
||||
// The vegetation system has stopped tracking this instance, so it's now safe to delete the ticket pointer.
|
||||
delete ticket;
|
||||
}
|
||||
}
|
||||
} // namespace Vegetation
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <Vegetation/InstanceSpawner.h>
|
||||
#include <Vegetation/EmptyInstanceSpawner.h>
|
||||
#include <Vegetation/DynamicSliceInstanceSpawner.h>
|
||||
#include <Vegetation/PrefabInstanceSpawner.h>
|
||||
|
||||
#include <CrySystemBus.h>
|
||||
|
||||
@@ -73,6 +74,7 @@ namespace Vegetation
|
||||
InstanceSpawner::Reflect(context);
|
||||
EmptyInstanceSpawner::Reflect(context);
|
||||
DynamicSliceInstanceSpawner::Reflect(context);
|
||||
PrefabInstanceSpawner::Reflect(context);
|
||||
Descriptor::Reflect(context);
|
||||
AreaConfig::Reflect(context);
|
||||
AreaComponentBase::Reflect(context);
|
||||
|
||||
@@ -0,0 +1,347 @@
|
||||
/*
|
||||
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
* its licensors.
|
||||
*
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this
|
||||
* distribution (the "License"). All use of this software is governed by the License,
|
||||
* or, if provided, by the license below or the license accompanying this file. Do not
|
||||
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
*/
|
||||
#include "Vegetation_precompiled.h"
|
||||
|
||||
#include "VegetationTest.h"
|
||||
#include "VegetationMocks.h"
|
||||
|
||||
#include <AzCore/Component/Entity.h>
|
||||
#include <AzTest/AzTest.h>
|
||||
#include <AzCore/UnitTest/Mocks/MockFileIOBase.h>
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
#include <AzCore/Asset/AssetManagerBus.h>
|
||||
#include <AzCore/Asset/AssetManagerComponent.h>
|
||||
#include <AzCore/Asset/AssetManager.h>
|
||||
#include <AzCore/Jobs/JobManager.h>
|
||||
#include <AzCore/Jobs/JobContext.h>
|
||||
#include <AzCore/Memory/PoolAllocator.h>
|
||||
#include <AzFramework/Entity/GameEntityContextBus.h>
|
||||
#include <AzFramework/Spawnable/SpawnableEntitiesManager.h>
|
||||
#include <Tests/FileIOBaseTestTypes.h>
|
||||
#include <Mocks/MockSpawnableEntitiesInterface.h>
|
||||
|
||||
#include <Vegetation/PrefabInstanceSpawner.h>
|
||||
#include <Vegetation/EmptyInstanceSpawner.h>
|
||||
#include <Vegetation/Ebuses/DescriptorNotificationBus.h>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
// Mock VegetationSystemComponent is needed to reflect only the PrefabInstanceSpawner.
|
||||
class MockPrefabInstanceVegetationSystemComponent
|
||||
: public AZ::Component
|
||||
{
|
||||
public:
|
||||
AZ_COMPONENT(MockPrefabInstanceVegetationSystemComponent, "{5EC9AA35-2653-4326-853F-F2056F0DE36C}", AZ::Component);
|
||||
|
||||
void Activate() override {}
|
||||
void Deactivate() override {}
|
||||
|
||||
static void Reflect(AZ::ReflectContext* reflect)
|
||||
{
|
||||
Vegetation::InstanceSpawner::Reflect(reflect);
|
||||
Vegetation::PrefabInstanceSpawner::Reflect(reflect);
|
||||
Vegetation::EmptyInstanceSpawner::Reflect(reflect);
|
||||
}
|
||||
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
|
||||
{
|
||||
provided.push_back(AZ_CRC_CE("VegetationSystemService"));
|
||||
}
|
||||
};
|
||||
|
||||
// To test prefab spawning, we need to mock up enough of the asset management system and the spawnable
|
||||
// asset handling to pretend like we're loading/unloading spawnables successfully.
|
||||
class PrefabInstanceSpawnerTests
|
||||
: public VegetationComponentTests
|
||||
, public UnitTest::SetRestoreFileIOBaseRAII
|
||||
, public Vegetation::DescriptorNotificationBus::Handler
|
||||
, public AZ::Data::AssetCatalogRequestBus::Handler
|
||||
, public AZ::Data::AssetHandler
|
||||
, public AZ::Data::AssetCatalog
|
||||
{
|
||||
public:
|
||||
PrefabInstanceSpawnerTests()
|
||||
: UnitTest::SetRestoreFileIOBaseRAII(m_fileIOMock)
|
||||
{
|
||||
AZ::IO::MockFileIOBase::InstallDefaultReturns(m_fileIOMock);
|
||||
AzFramework::MockSpawnableEntitiesInterface::InstallDefaultReturns(m_spawnableEntitiesInterfaceMock);
|
||||
}
|
||||
|
||||
void RegisterComponentDescriptors() override
|
||||
{
|
||||
m_app.RegisterComponentDescriptor(MockPrefabInstanceVegetationSystemComponent::CreateDescriptor());
|
||||
}
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
VegetationComponentTests::SetUp();
|
||||
|
||||
// Create a real Asset Mananger, and point to ourselves as the handler for Spawnable.
|
||||
AZ::AllocatorInstance<AZ::PoolAllocator>::Create();
|
||||
AZ::AllocatorInstance<AZ::ThreadPoolAllocator>::Create();
|
||||
|
||||
|
||||
// Initialize the job manager with 1 thread for the AssetManager to use.
|
||||
AZ::JobManagerDesc jobDesc;
|
||||
AZ::JobManagerThreadDesc threadDesc;
|
||||
jobDesc.m_workerThreads.push_back(threadDesc);
|
||||
m_jobManager = aznew AZ::JobManager(jobDesc);
|
||||
m_jobContext = aznew AZ::JobContext(*m_jobManager);
|
||||
AZ::JobContext::SetGlobalContext(m_jobContext);
|
||||
|
||||
AZ::Data::AssetManager::Descriptor descriptor;
|
||||
AZ::Data::AssetManager::Create(descriptor);
|
||||
AZ::Data::AssetManager::Instance().RegisterHandler(this, AZ::AzTypeInfo<AzFramework::Spawnable>::Uuid());
|
||||
AZ::Data::AssetManager::Instance().RegisterCatalog(this, AZ::AzTypeInfo<AzFramework::Spawnable>::Uuid());
|
||||
|
||||
// Intercept messages for finding assets by name.
|
||||
AZ::Data::AssetCatalogRequestBus::Handler::BusConnect();
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
// Give the AssetManager a chance to fire off any lingering events and perform cleanup for any
|
||||
// spawnable assets we loaded.
|
||||
AZ::Data::AssetManager::Instance().DispatchEvents();
|
||||
|
||||
AZ::Data::AssetManager::Instance().UnregisterCatalog(this);
|
||||
AZ::Data::AssetManager::Instance().UnregisterHandler(this);
|
||||
|
||||
AZ::Data::AssetCatalogRequestBus::Handler::BusDisconnect();
|
||||
|
||||
AZ::Data::AssetManager::Destroy();
|
||||
|
||||
AZ::JobContext::SetGlobalContext(nullptr);
|
||||
delete m_jobContext;
|
||||
delete m_jobManager;
|
||||
|
||||
AZ::AllocatorInstance<AZ::ThreadPoolAllocator>::Destroy();
|
||||
AZ::AllocatorInstance<AZ::PoolAllocator>::Destroy();
|
||||
|
||||
VegetationComponentTests::TearDown();
|
||||
}
|
||||
|
||||
// Helper methods:
|
||||
|
||||
// Set up a mock asset with the given name and id and direct the instance spawner to use it.
|
||||
void CreateAndSetMockAsset(Vegetation::PrefabInstanceSpawner& instanceSpawner, AZ::Data::AssetId assetId, AZStd::string assetPath)
|
||||
{
|
||||
// Save these off for use from our mock AssetCatalogRequestBus
|
||||
m_assetId = assetId;
|
||||
m_assetPath = assetPath;
|
||||
|
||||
Vegetation::DescriptorNotificationBus::Handler::BusConnect(&instanceSpawner);
|
||||
|
||||
// Tell the spawner to use this asset. Note that this also triggers a LoadAssets() call internally.
|
||||
instanceSpawner.SetSpawnableAssetPath(m_assetPath);
|
||||
|
||||
// Our instance spawner should now have a valid asset reference.
|
||||
// It may or may not be loaded already by the time we get here,
|
||||
// depending on how quickly the Asset Processor job thread picks it up.
|
||||
EXPECT_FALSE(instanceSpawner.HasEmptyAssetReferences());
|
||||
|
||||
// Since the asset load is going through the real AssetManager, there's a delay while a separate
|
||||
// job thread executes and actually loads our mock spawnable asset.
|
||||
// If our asset hasn't loaded successfully after 5 seconds, it's unlikely to succeed.
|
||||
// This choice of delay should be *reasonably* safe because it's all CPU-based processing,
|
||||
// no actual I/O occurs as a part of the test.
|
||||
constexpr int sleepMs = 10;
|
||||
constexpr int totalWaitTimeMs = 5000;
|
||||
int numRetries = totalWaitTimeMs / sleepMs;
|
||||
while ((m_numOnLoadedCalls < 1) && (numRetries >= 0))
|
||||
{
|
||||
AZ::Data::AssetManager::Instance().DispatchEvents();
|
||||
AZ::SystemTickBus::Broadcast(&AZ::SystemTickBus::Events::OnSystemTick);
|
||||
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(sleepMs));
|
||||
numRetries--;
|
||||
}
|
||||
|
||||
ASSERT_TRUE(m_numOnLoadedCalls == 1);
|
||||
EXPECT_TRUE(instanceSpawner.IsLoaded());
|
||||
EXPECT_TRUE(instanceSpawner.IsSpawnable());
|
||||
|
||||
Vegetation::DescriptorNotificationBus::Handler::BusDisconnect();
|
||||
}
|
||||
|
||||
|
||||
// AssetHandler
|
||||
// Minimalist mocks to look like a Spawnable has been created/loaded/destroyed successfully
|
||||
AZ::Data::AssetPtr CreateAsset(const AZ::Data::AssetId& id, [[maybe_unused]] const AZ::Data::AssetType& type) override
|
||||
{
|
||||
AzFramework::Spawnable* spawnableAsset = new AzFramework::Spawnable(id);
|
||||
MockAssetData* temp = reinterpret_cast<MockAssetData*>(spawnableAsset);
|
||||
temp->SetStatus(AZ::Data::AssetData::AssetStatus::NotLoaded);
|
||||
|
||||
return spawnableAsset;
|
||||
}
|
||||
|
||||
void DestroyAsset(AZ::Data::AssetPtr ptr) override { delete ptr; }
|
||||
void GetHandledAssetTypes(AZStd::vector<AZ::Data::AssetType>& assetTypes) override
|
||||
{
|
||||
assetTypes.push_back(AZ::AzTypeInfo<AzFramework::Spawnable>::Uuid());
|
||||
}
|
||||
AZ::Data::AssetHandler::LoadResult LoadAssetData(
|
||||
const AZ::Data::Asset<AZ::Data::AssetData>& asset,
|
||||
AZStd::shared_ptr<AZ::Data::AssetDataStream> stream,
|
||||
[[maybe_unused]] const AZ::Data::AssetFilterCB& assetLoadFilterCB)
|
||||
{
|
||||
MockAssetData* temp = reinterpret_cast<MockAssetData*>(asset.GetData());
|
||||
temp->SetStatus(AZ::Data::AssetData::AssetStatus::Ready);
|
||||
return AZ::Data::AssetHandler::LoadResult::LoadComplete;
|
||||
}
|
||||
|
||||
// DescriptorNotificationBus
|
||||
// Keep track of whether or not the Spawner successfully loaded the asset and notified listeners
|
||||
void OnDescriptorAssetsLoaded() override { m_numOnLoadedCalls++; }
|
||||
|
||||
// AssetCatalogRequestBus
|
||||
// Minimalist mocks to provide our desired asset path or asset id
|
||||
AZStd::string GetAssetPathById(const AZ::Data::AssetId& /*id*/) override { return m_assetPath; }
|
||||
AZ::Data::AssetId GetAssetIdByPath(const char* /*path*/, const AZ::Data::AssetType& /*typeToRegister*/, bool /*autoRegisterIfNotFound*/) override { return m_assetId; }
|
||||
AZ::Data::AssetInfo GetAssetInfoById(const AZ::Data::AssetId& /*id*/) override
|
||||
{
|
||||
AZ::Data::AssetInfo assetInfo;
|
||||
assetInfo.m_assetId = m_assetId;
|
||||
assetInfo.m_assetType = AZ::AzTypeInfo<AzFramework::Spawnable>::Uuid();
|
||||
assetInfo.m_relativePath = m_assetPath;
|
||||
return assetInfo;
|
||||
}
|
||||
|
||||
// AssetCatalog
|
||||
// Minimalist mock to pretend like we've loaded a Spawnable asset
|
||||
AZ::Data::AssetStreamInfo GetStreamInfoForLoad(
|
||||
[[maybe_unused]] const AZ::Data::AssetId& id, const AZ::Data::AssetType& type) override
|
||||
{
|
||||
EXPECT_TRUE(type == AZ::AzTypeInfo<AzFramework::Spawnable>::Uuid());
|
||||
AZ::Data::AssetStreamInfo info;
|
||||
info.m_dataOffset = 0;
|
||||
info.m_streamName = m_assetPath;
|
||||
info.m_dataLen = 0;
|
||||
info.m_streamFlags = AZ::IO::OpenMode::ModeRead;
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
AZStd::string m_assetPath;
|
||||
AZ::Data::AssetId m_assetId;
|
||||
int m_numOnLoadedCalls = 0;
|
||||
|
||||
AZ::JobManager* m_jobManager{ nullptr };
|
||||
AZ::JobContext* m_jobContext{ nullptr };
|
||||
::testing::NiceMock<AZ::IO::MockFileIOBase> m_fileIOMock;
|
||||
::testing::NiceMock<AzFramework::MockSpawnableEntitiesInterface> m_spawnableEntitiesInterfaceMock;
|
||||
};
|
||||
|
||||
TEST_F(PrefabInstanceSpawnerTests, BasicInitializationTest)
|
||||
{
|
||||
// Basic test to make sure we can construct / destroy without errors.
|
||||
|
||||
Vegetation::PrefabInstanceSpawner instanceSpawner;
|
||||
}
|
||||
|
||||
TEST_F(PrefabInstanceSpawnerTests, DefaultSpawnersAreEqual)
|
||||
{
|
||||
// Two different instances of the default PrefabInstanceSpawner should be considered data-equivalent.
|
||||
|
||||
Vegetation::PrefabInstanceSpawner instanceSpawner1;
|
||||
Vegetation::PrefabInstanceSpawner instanceSpawner2;
|
||||
|
||||
EXPECT_TRUE(instanceSpawner1 == instanceSpawner2);
|
||||
}
|
||||
|
||||
TEST_F(PrefabInstanceSpawnerTests, DifferentSpawnersAreNotEqual)
|
||||
{
|
||||
// Two spawners with different data should *not* be data-equivalent.
|
||||
|
||||
Vegetation::PrefabInstanceSpawner instanceSpawner1;
|
||||
Vegetation::PrefabInstanceSpawner instanceSpawner2;
|
||||
|
||||
// Give the second instance spawner a non-default asset reference.
|
||||
CreateAndSetMockAsset(instanceSpawner2, AZ::Uuid::CreateRandom(), "test");
|
||||
|
||||
// The test is written this way because only the == operator is overloaded.
|
||||
EXPECT_TRUE(!(instanceSpawner1 == instanceSpawner2));
|
||||
}
|
||||
|
||||
TEST_F(PrefabInstanceSpawnerTests, LoadAndUnloadAssets)
|
||||
{
|
||||
// The spawner should successfully load/unload assets without errors.
|
||||
|
||||
Vegetation::PrefabInstanceSpawner instanceSpawner;
|
||||
|
||||
// Our instance spawner should be empty before we set the assets.
|
||||
EXPECT_TRUE(instanceSpawner.HasEmptyAssetReferences());
|
||||
|
||||
// This will test the asset load.
|
||||
CreateAndSetMockAsset(instanceSpawner, AZ::Uuid::CreateRandom(), "test");
|
||||
|
||||
// Test the asset unload works too.
|
||||
Vegetation::DescriptorNotificationBus::Handler::BusConnect(&instanceSpawner);
|
||||
instanceSpawner.UnloadAssets();
|
||||
EXPECT_FALSE(instanceSpawner.IsLoaded());
|
||||
EXPECT_FALSE(instanceSpawner.IsSpawnable());
|
||||
Vegetation::DescriptorNotificationBus::Handler::BusDisconnect();
|
||||
}
|
||||
|
||||
TEST_F(PrefabInstanceSpawnerTests, CreateAndDestroyInstance)
|
||||
{
|
||||
// The spawner should successfully create and destroy an instance without errors.
|
||||
|
||||
Vegetation::PrefabInstanceSpawner instanceSpawner;
|
||||
|
||||
CreateAndSetMockAsset(instanceSpawner, AZ::Uuid::CreateRandom(), "test");
|
||||
|
||||
instanceSpawner.OnRegisterUniqueDescriptor();
|
||||
|
||||
Vegetation::InstanceData instanceData;
|
||||
Vegetation::InstancePtr instance = instanceSpawner.CreateInstance(instanceData);
|
||||
EXPECT_TRUE(instance);
|
||||
instanceSpawner.DestroyInstance(0, instance);
|
||||
|
||||
instanceSpawner.OnReleaseUniqueDescriptor();
|
||||
}
|
||||
|
||||
TEST_F(PrefabInstanceSpawnerTests, SpawnerRegisteredWithDescriptor)
|
||||
{
|
||||
// Validate that the Descriptor successfully gets PrefabInstanceSpawner registered with it,
|
||||
// as long as InstanceSpawner and PrefabInstanceSpawner have been reflected.
|
||||
|
||||
MockPrefabInstanceVegetationSystemComponent* component = nullptr;
|
||||
auto entity = CreateEntity(&component);
|
||||
|
||||
Vegetation::Descriptor descriptor;
|
||||
descriptor.RefreshSpawnerTypeList();
|
||||
auto spawnerTypes = descriptor.GetSpawnerTypeList();
|
||||
EXPECT_TRUE(spawnerTypes.size() > 0);
|
||||
const auto& prefabSpawnerEntry = AZStd::find(
|
||||
spawnerTypes.begin(), spawnerTypes.end(),
|
||||
AZStd::pair<AZ::TypeId, AZStd::string>(Vegetation::PrefabInstanceSpawner::RTTI_Type(), "PrefabInstanceSpawner"));
|
||||
EXPECT_NE(prefabSpawnerEntry, spawnerTypes.end());
|
||||
}
|
||||
|
||||
TEST_F(PrefabInstanceSpawnerTests, DescriptorCreatesCorrectSpawner)
|
||||
{
|
||||
// Validate that the Descriptor successfully creates a new PrefabInstanceSpawner if we change
|
||||
// the spawner type on the Descriptor.
|
||||
|
||||
MockPrefabInstanceVegetationSystemComponent* component = nullptr;
|
||||
auto entity = CreateEntity(&component);
|
||||
|
||||
// We expect the Descriptor to start off with something other than Prefab spawner, but then should correctly get an
|
||||
// PrefabInstanceSpawner after we change spawnerType.
|
||||
Vegetation::Descriptor descriptor;
|
||||
EXPECT_NE(azrtti_typeid(*(descriptor.GetInstanceSpawner())),Vegetation::PrefabInstanceSpawner::RTTI_Type());
|
||||
descriptor.m_spawnerType = Vegetation::PrefabInstanceSpawner::RTTI_Type();
|
||||
descriptor.RefreshSpawnerTypeList();
|
||||
descriptor.SpawnerTypeChanged();
|
||||
EXPECT_EQ(azrtti_typeid(*(descriptor.GetInstanceSpawner())), Vegetation::PrefabInstanceSpawner::RTTI_Type());
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ set(FILES
|
||||
Include/Vegetation/InstanceSpawner.h
|
||||
Include/Vegetation/DynamicSliceInstanceSpawner.h
|
||||
Include/Vegetation/EmptyInstanceSpawner.h
|
||||
Include/Vegetation/PrefabInstanceSpawner.h
|
||||
Include/Vegetation/AreaComponentBase.h
|
||||
Include/Vegetation/Ebuses/AreaSystemRequestBus.h
|
||||
Include/Vegetation/Ebuses/AreaNotificationBus.h
|
||||
@@ -104,6 +105,7 @@ set(FILES
|
||||
Source/Descriptor.cpp
|
||||
Source/DynamicSliceInstanceSpawner.cpp
|
||||
Source/EmptyInstanceSpawner.cpp
|
||||
Source/PrefabInstanceSpawner.cpp
|
||||
Source/VegetationSystemComponent.cpp
|
||||
Source/VegetationSystemComponent.h
|
||||
Source/InstanceData.cpp
|
||||
|
||||
@@ -17,6 +17,7 @@ set(FILES
|
||||
Tests/VegetationComponentFilterTests.cpp
|
||||
Tests/DynamicSliceInstanceSpawnerTests.cpp
|
||||
Tests/EmptyInstanceSpawnerTests.cpp
|
||||
Tests/PrefabInstanceSpawnerTests.cpp
|
||||
Tests/VegetationAreaSystemComponentTest.cpp
|
||||
Tests/VegetationTest.cpp
|
||||
Tests/VegetationTest.h
|
||||
|
||||
Reference in New Issue
Block a user