Added unit tests for spawnable entity aliases.

This also fixes several issues discovered through the unit tests and renames a few functions to be clearer.

Signed-off-by: AMZN-koppersr <82230785+AMZN-koppersr@users.noreply.github.com>
This commit is contained in:
AMZN-koppersr
2021-10-28 09:57:33 -07:00
parent 66df146554
commit 6587e149b7
9 changed files with 1057 additions and 84 deletions
@@ -9,7 +9,9 @@
#include <AzCore/Asset/AssetSerializer.h>
#include <AzCore/RTTI/ReflectContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/numeric.h>
#include <AzCore/std/sort.h>
#include <AzCore/std/typetraits/typetraits.h>
#include <AzFramework/Spawnable/Spawnable.h>
namespace AzFramework
@@ -31,7 +33,7 @@ namespace AzFramework
// EntityAliasVisitorBase
//
bool Spawnable::EntityAliasVisitorBase::HasLock(const EntityAliasList* aliases) const
bool Spawnable::EntityAliasVisitorBase::IsSet(const EntityAliasList* aliases) const
{
return aliases != nullptr;
}
@@ -92,11 +94,15 @@ namespace AzFramework
AZStd::unordered_set<AZ::Data::AssetId> spawnableIds;
for (const Spawnable::EntityAlias& alias : *aliases)
{
auto it = spawnableIds.find(alias.m_spawnable.GetId());
if (it == spawnableIds.end())
// If the spawnable id is not valid it means that the alias is referencing the spawnable it's stored on.
if (alias.m_spawnable.GetId().IsValid())
{
callback(alias.m_spawnable);
spawnableIds.emplace(alias.m_spawnable.GetId());
auto it = spawnableIds.find(alias.m_spawnable.GetId());
if (it == spawnableIds.end())
{
callback(alias.m_spawnable);
spawnableIds.emplace(alias.m_spawnable.GetId());
}
}
}
}
@@ -108,7 +114,8 @@ namespace AzFramework
AZStd::unordered_set<AZ::Data::AssetId> spawnableIds;
for (const Spawnable::EntityAlias& alias : *aliases)
{
if (alias.m_tag == tag)
// If the spawnable id is not valid it means that the alias is referencing the spawnable it's stored on.
if (alias.m_tag == tag && alias.m_spawnable.GetId().IsValid())
{
auto it = spawnableIds.find(alias.m_spawnable.GetId());
if (it == spawnableIds.end())
@@ -134,7 +141,7 @@ namespace AzFramework
Spawnable::EntityAliasVisitor::~EntityAliasVisitor()
{
if (HasLock())
if (IsSet())
{
Optimize();
@@ -169,9 +176,9 @@ namespace AzFramework
return *this;
}
bool Spawnable::EntityAliasVisitor::HasLock() const
bool Spawnable::EntityAliasVisitor::IsSet() const
{
return EntityAliasVisitorBase::HasLock(m_entityAliasList);
return EntityAliasVisitorBase::IsSet(m_entityAliasList);
}
bool Spawnable::EntityAliasVisitor::HasAliases() const
@@ -235,7 +242,7 @@ namespace AzFramework
m_dirty = true;
}
void Spawnable::EntityAliasVisitor::ListSpawnablesPendingLoad(const ListSpawnablesPendingLoadCallback& callback)
void Spawnable::EntityAliasVisitor::ListSpawnablesRequiringLoad(const ListSpawnablesRequiringLoadCallback& callback)
{
AZ_Assert(m_entityAliasList, "Attempting to visit entity aliases on a spawnable that wasn't locked.");
for (Spawnable::EntityAlias& alias : *m_entityAliasList)
@@ -306,75 +313,92 @@ namespace AzFramework
// aliases, for instance Networking can decide to disable certain aliases when running on a client. This in turn also requires
// the aliases to be in their recorded order during building as the ebus handlers may depend on that order to determine what
// entities need to be updated.
Spawnable::EntityAlias* compare = m_entityAliasList->begin();
Spawnable::EntityAlias* it = m_entityAliasList->begin() + 1;
uint32_t previousIndex = AZStd::numeric_limits<uint32_t>::max();
Spawnable::EntityAliasType previousType =
static_cast<Spawnable::EntityAliasType>(AZStd::numeric_limits<AZStd::underlying_type_t<Spawnable::EntityAliasType>>::max());
Spawnable::EntityAlias* it = m_entityAliasList->begin();
Spawnable::EntityAlias* end = m_entityAliasList->end();
while (it < end)
{
// If there's a switch to a new source index and the previous index only had an original it can
// be removed.
if (previousType == Spawnable::EntityAliasType::Original && previousIndex != it->m_sourceIndex)
{
it = m_entityAliasList->erase(it - 1);
end = m_entityAliasList->end();
if (it == end)
{
break;
}
}
switch (it->m_aliasType)
{
case Spawnable::EntityAliasType::Original:
// If this is the only alias for the entity then the original can be removed.
{
Spawnable::EntityAlias* next = it + 1;
if (next == end || next->m_sourceIndex != it->m_sourceIndex)
{
// Erase instead of a swap-and-pop in order to preserver the order.
m_entityAliasList->erase(compare);
--end;
break;
}
}
[[fallthrough]];
case Spawnable::EntityAliasType::Disabled:
[[fallthrough]];
case Spawnable::EntityAliasType::Replace:
// If the previous entry was a disabled, original or replace alias then remove it as it will be overwritten by the
// current entry.
if (compare->m_sourceIndex == it->m_sourceIndex &&
(compare->m_aliasType == Spawnable::EntityAliasType::Original ||
compare->m_aliasType == Spawnable::EntityAliasType::Disabled ||
compare->m_aliasType == Spawnable::EntityAliasType::Replace))
if (previousIndex == it->m_sourceIndex &&
(previousType == Spawnable::EntityAliasType::Original ||
previousType == Spawnable::EntityAliasType::Disabled ||
previousType == Spawnable::EntityAliasType::Replace))
{
previousIndex = it->m_sourceIndex;
previousType = it->m_aliasType;
// Erase instead of a swap-and-pop in order to preserver the order.
m_entityAliasList->erase(compare);
--end;
it = m_entityAliasList->erase(it - 1) + 1;
end = m_entityAliasList->end();
}
else
{
++compare;
previousIndex = it->m_sourceIndex;
previousType = it->m_aliasType;
++it;
}
break;
case Spawnable::EntityAliasType::Additional:
[[fallthrough]];
case Spawnable::EntityAliasType::Merge:
// If this is the first entry for this type insert an original in front of it so the spawnable entity manager
// does have to check for the case there's a merge and/or addition without a prefix.
if (compare->m_sourceIndex != it->m_sourceIndex)
// If this is the first entry for this index then insert an original in front of it so the spawnable entity manager
// doesn't have to check for the case there's a merge and/or addition without an entity to extend.
if (previousIndex != it->m_sourceIndex)
{
Spawnable::EntityAlias insert;
// No load, as the asset is already loaded.
insert.m_spawnable = AZ::Data::Asset<Spawnable>(&m_owner, AZ::Data::AssetLoadBehavior::NoLoad);
insert.m_spawnable = AZ::Data::Asset<Spawnable>({}, azrtti_typeid<Spawnable>());
insert.m_sourceIndex = it->m_sourceIndex;
insert.m_targetIndex = it->m_sourceIndex; // Source index as the original entry for this slot is added.
insert.m_aliasType = Spawnable::EntityAliasType::Original;
m_entityAliasList->insert(compare, AZStd::move(insert));
compare += 2;
previousIndex = it->m_sourceIndex;
previousType = it->m_aliasType;
// Insert to maintain the order.
it = m_entityAliasList->insert(it, AZStd::move(insert));
it += 2;
++end;
end = m_entityAliasList->end();
}
else
{
++compare;
previousType = it->m_aliasType;
++it;
}
break;
default:
AZ_Assert(false, "Invalid Spawnable entity alias type found during asset loading: %i", compare->m_aliasType);
AZ_Assert(false, "Invalid Spawnable entity alias type found during asset loading: %i", it->m_aliasType);
break;
}
}
// Check if the last entry is an "Original" in which case it can be removed.
if (!m_entityAliasList->empty() && m_entityAliasList->back().m_aliasType == Spawnable::EntityAliasType::Original)
{
m_entityAliasList->pop_back();
}
// Reclaim memory because after this point the aliases will not change anymore.
m_entityAliasList->shrink_to_fit();
m_dirty = false;
@@ -395,7 +419,7 @@ namespace AzFramework
Spawnable::EntityAliasConstVisitor::~EntityAliasConstVisitor()
{
if (HasLock())
if (IsSet())
{
AZ_Assert(
m_owner.m_shareState <= ShareState::Read, "Attempting to unlock a read shared spawnable that was not in a read shared mode (%i).",
@@ -404,9 +428,9 @@ namespace AzFramework
}
}
bool Spawnable::EntityAliasConstVisitor::HasLock() const
bool Spawnable::EntityAliasConstVisitor::IsSet() const
{
return EntityAliasVisitorBase::HasLock(m_entityAliasList);
return EntityAliasVisitorBase::IsSet(m_entityAliasList);
}
bool Spawnable::EntityAliasConstVisitor::HasAliases() const
@@ -71,7 +71,8 @@ namespace AzFramework
class EntityAliasVisitorBase
{
protected:
bool HasLock(const EntityAliasList* aliases) const;
bool IsSet(const EntityAliasList* aliases) const;
bool HasAliases(const EntityAliasList* aliases) const;
bool AreAllSpawnablesReady(const EntityAliasList* aliases) const;
@@ -98,7 +99,9 @@ namespace AzFramework
EntityAliasVisitor(const EntityAliasVisitor& rhs) = delete;
EntityAliasVisitor& operator=(const EntityAliasVisitor& rhs) = delete;
bool HasLock() const;
//! Checks if the visitor was able to retrieve data. This needs to be checked before calling any other functions.
bool IsSet() const;
bool HasAliases() const;
bool AreAllSpawnablesReady() const;
@@ -118,8 +121,8 @@ namespace AzFramework
Spawnable::EntityAliasType aliasType,
bool queueLoad);
using ListSpawnablesPendingLoadCallback = AZStd::function<void(AZ::Data::Asset<Spawnable>& spawnablePendingLoad)>;
void ListSpawnablesPendingLoad(const ListSpawnablesPendingLoadCallback& callback);
using ListSpawnablesRequiringLoadCallback = AZStd::function<void(AZ::Data::Asset<Spawnable>& spawnablePendingLoad)>;
void ListSpawnablesRequiringLoad(const ListSpawnablesRequiringLoadCallback& callback);
using UpdateCallback = AZStd::function<void(
Spawnable::EntityAliasType& aliasType,
@@ -146,7 +149,9 @@ namespace AzFramework
EntityAliasConstVisitor(const Spawnable& owner, const EntityAliasList* m_entityAliasList);
~EntityAliasConstVisitor();
bool HasLock() const;
//! Checks if the visitor was able to retrieve data. This needs to be checked before calling any other functions.
bool IsSet() const;
bool HasAliases() const;
bool AreAllSpawnablesReady() const;
@@ -161,7 +166,6 @@ namespace AzFramework
private:
const Spawnable& m_owner;
const EntityAliasList* m_entityAliasList;
};
inline static constexpr const char* FileExtension = "spawnable";
@@ -103,7 +103,7 @@ namespace AzFramework
const AZ::Data::AssetFilterCB& assetLoadFilterCB)
{
Spawnable::EntityAliasVisitor aliases = spawnable->TryGetAliases();
AZ_Assert(aliases.HasLock(), "Newly created Spawnable '%s' was already locked.", asset.GetHint().c_str());
AZ_Assert(aliases.IsSet(), "Newly created Spawnable '%s' was already locked.", asset.GetHint().c_str());
if (aliases.HasAliases())
{
AZ_Assert(
@@ -119,7 +119,7 @@ namespace AzFramework
&SpawnableAssetEvents::OnResolveAliases, aliases, spawnable->GetMetaData(), spawnable->GetEntities());
aliases.Optimize();
aliases.ListSpawnablesPendingLoad(
aliases.ListSpawnablesRequiringLoad(
[&assetLoadFilterCB, streamingDeadline, streamingPriority](AZ::Data::Asset<Spawnable>& assetPendingLoad)
{
AZ::Data::AssetLoadParameters loadInfo;
@@ -342,9 +342,6 @@ namespace AzFramework
return clone;
case Spawnable::EntityAliasType::Merge:
AZ_Assert(previouslySpawnedEntity != nullptr, "Merging components but there's no entity to add to yet.");
AZ_Assert(
previouslySpawnedEntity->GetId() == alias.m_spawnable->GetEntities()[alias.m_targetIndex]->GetId(),
"Entity ids for merging spawnables don't match.");
AppendComponents(
*previouslySpawnedEntity, alias.m_spawnable->GetEntities()[alias.m_targetIndex]->GetComponents(), templateToCloneMap, serializeContext);
return nullptr;
@@ -412,7 +409,7 @@ namespace AzFramework
if (ticket.m_spawnable.IsReady() && request.m_requestId == ticket.m_currentRequestId)
{
if (Spawnable::EntityAliasConstVisitor aliases = ticket.m_spawnable->TryGetAliasesConst();
aliases.HasLock() && aliases.AreAllSpawnablesReady())
aliases.IsSet() && aliases.AreAllSpawnablesReady())
{
AZStd::vector<AZ::Entity*>& spawnedEntities = ticket.m_spawnedEntities;
AZStd::vector<uint32_t>& spawnedEntityIndices = ticket.m_spawnedEntityIndices;
@@ -475,9 +472,12 @@ namespace AzFramework
AZ::Entity* clone = CloneSingleAliasedEntity(
*entitiesToSpawn[i], *aliasIt, ticket.m_entityIdReferenceMap, previousEntity,
*request.m_serializeContext);
previousEntity = clone;
spawnedEntities.emplace_back(clone);
spawnedEntityIndices.push_back(i);
previousEntity = clone;
if (clone)
{
spawnedEntities.emplace_back(clone);
spawnedEntityIndices.push_back(i);
}
++aliasIt;
} while (aliasIt != aliasEnd && aliasIt->m_sourceIndex == i);
}
@@ -527,7 +527,7 @@ namespace AzFramework
if (ticket.m_spawnable.IsReady() && request.m_requestId == ticket.m_currentRequestId)
{
if (Spawnable::EntityAliasConstVisitor aliases = ticket.m_spawnable->TryGetAliasesConst();
aliases.HasLock() && aliases.AreAllSpawnablesReady())
aliases.IsSet() && aliases.AreAllSpawnablesReady())
{
AZStd::vector<AZ::Entity*>& spawnedEntities = ticket.m_spawnedEntities;
AZStd::vector<uint32_t>& spawnedEntityIndices = ticket.m_spawnedEntityIndices;
@@ -593,7 +593,7 @@ namespace AzFramework
return lhs.m_sourceIndex < rhs;
});
if (aliasIt == aliasEnd)
if (aliasIt == aliasEnd || aliasIt->m_sourceIndex != index)
{
spawnedEntities.emplace_back(
CloneSingleEntity(*entitiesToSpawn[index], ticket.m_entityIdReferenceMap, *request.m_serializeContext));
@@ -610,8 +610,11 @@ namespace AzFramework
*entitiesToSpawn[index], *aliasIt, ticket.m_entityIdReferenceMap, previousEntity,
*request.m_serializeContext);
previousEntity = clone;
spawnedEntities.emplace_back(clone);
spawnedEntityIndices.push_back(index);
if (clone)
{
spawnedEntities.emplace_back(clone);
spawnedEntityIndices.push_back(index);
}
++aliasIt;
} while (aliasIt != aliasEnd && aliasIt->m_sourceIndex == index);
@@ -816,7 +819,7 @@ namespace AzFramework
Ticket& ticket = *request.m_ticket;
if (ticket.m_spawnable.IsReady() && request.m_requestId == ticket.m_currentRequestId)
{
if (Spawnable::EntityAliasVisitor aliases = ticket.m_spawnable->TryGetAliases(); aliases.HasLock())
if (Spawnable::EntityAliasVisitor aliases = ticket.m_spawnable->TryGetAliases(); aliases.IsSet())
{
for (EntityAliasTypeChange& replacement : request.m_entityAliases)
{
@@ -918,7 +921,7 @@ namespace AzFramework
if (request.m_checkAliasSpawnables)
{
if (Spawnable::EntityAliasConstVisitor visitor = ticket.m_spawnable->TryGetAliasesConst();
!visitor.HasLock() || !visitor.AreAllSpawnablesReady())
!visitor.IsSet() || !visitor.AreAllSpawnablesReady())
{
return CommandResult::Requeue;
}
@@ -55,6 +55,40 @@ namespace UnitTest
AZ::EntityId m_entityReference;
};
class SourceSpawnableComponent : public AZ::Component
{
public:
AZ_COMPONENT(SourceSpawnableComponent, "{47FF79CE-A95B-420E-8BEB-F1CC58087B87}");
void Activate() override {}
void Deactivate() override {}
static void Reflect(AZ::ReflectContext* reflection)
{
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection))
{
serializeContext->Class<SourceSpawnableComponent, AZ::Component>();
}
}
};
class TargetSpawnableComponent : public AZ::Component
{
public:
AZ_COMPONENT(TargetSpawnableComponent, "{B4041561-63A7-4E1E-80F1-78C08D497960}");
void Activate() override {}
void Deactivate() override {}
static void Reflect(AZ::ReflectContext* reflection)
{
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection))
{
serializeContext->Class<TargetSpawnableComponent, AZ::Component>();
}
}
};
class SpawnableEntitiesManagerTest : public AllocatorsFixture
{
public:
@@ -66,6 +100,8 @@ namespace UnitTest
AZ::ComponentApplication::Descriptor descriptor;
m_application->Start(descriptor);
m_application->RegisterComponentDescriptor(ComponentWithEntityReference::CreateDescriptor());
m_application->RegisterComponentDescriptor(SourceSpawnableComponent::CreateDescriptor());
m_application->RegisterComponentDescriptor(TargetSpawnableComponent::CreateDescriptor());
// Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is
// shared across the whole engine, if multiple tests are run in parallel, the saving could cause a crash
@@ -109,7 +145,50 @@ namespace UnitTest
entities.reserve(numElements);
for (size_t i=0; i<numElements; ++i)
{
entities.push_back(AZStd::make_unique<AZ::Entity>());
auto entry = AZStd::make_unique<AZ::Entity>();
entry->AddComponent(aznew SourceSpawnableComponent());
entities.push_back(AZStd::move(entry));
}
}
AZ::Data::Asset<AzFramework::Spawnable> CreateTargetSpawnable(size_t numElements)
{
auto target = aznew AzFramework::Spawnable(
AZ::Data::AssetId(AZ::Uuid("{716CD8C3-0BA8-4F32-B579-0EC7C967796F}")), AZ::Data::AssetData::AssetStatus::Ready);
AzFramework::Spawnable::EntityList& entities = target->GetEntities();
entities.reserve(numElements);
for (size_t i = 0; i < numElements; ++i)
{
auto entry = AZStd::make_unique<AZ::Entity>();
entry->AddComponent(aznew TargetSpawnableComponent());
entities.push_back(AZStd::move(entry));
}
return AZ::Data::Asset<AzFramework::Spawnable>(target, AZ::Data::AssetLoadBehavior::NoLoad);
}
template<size_t AliasCount>
void InsertEntityAliases(
const AZStd::array<uint32_t, AliasCount>& sourceIds,
const AZStd::array<uint32_t, AliasCount>& targetIds,
const AZStd::array<AzFramework::Spawnable::EntityAliasType, AliasCount>& aliasTypes,
AZ::Data::Asset<AzFramework::Spawnable>* target = nullptr)
{
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
for (uint32_t i = 0; i < AliasCount; ++i)
{
if (target)
{
visitor.AddAlias(*target, AZ::Crc32(i), sourceIds[i], targetIds[i], aliasTypes[i], false);
}
else
{
AZ::Data::Asset<AzFramework::Spawnable> spawnable(
AZ::Data::AssetId(AZ::Uuid("{4CBEC17A-52D6-42D5-9037-F4C05B9CE1D9}"), i), azrtti_typeid<AzFramework::Spawnable>());
visitor.AddAlias(AZStd::move(spawnable), AZ::Crc32(i), sourceIds[i], targetIds[i], aliasTypes[i], false);
}
}
}
@@ -245,6 +324,30 @@ namespace UnitTest
TestApplication* m_application { nullptr };
};
//
// Constructors
//
TEST_F(SpawnableEntitiesManagerTest, EntitySpawnTicket_Move_Works)
{
AzFramework::EntitySpawnTicket ticket1(*m_spawnableAsset);
AzFramework::EntitySpawnTicket ticket2(*m_spawnableAsset);
const AzFramework::EntitySpawnTicket::Id ticket1Id = ticket1.GetId();
const AzFramework::EntitySpawnTicket::Id ticket2Id = ticket2.GetId();
AzFramework::EntitySpawnTicket ticketMoveConstructor(AZStd::move(ticket1));
EXPECT_TRUE(ticketMoveConstructor.IsValid());
EXPECT_EQ(ticketMoveConstructor.GetId(), ticket1Id);
AzFramework::EntitySpawnTicket ticketMoveOperator;
ticketMoveOperator = AZStd::move(ticket2);
EXPECT_TRUE(ticketMoveOperator.IsValid());
EXPECT_EQ(ticketMoveOperator.GetId(), ticket2Id);
}
//
// SpawnAllEntitities
//
@@ -366,24 +469,6 @@ namespace UnitTest
}
}
TEST_F(SpawnableEntitiesManagerTest, EntitySpawnTicket_Move_Works)
{
AzFramework::EntitySpawnTicket ticket1(*m_spawnableAsset);
AzFramework::EntitySpawnTicket ticket2(*m_spawnableAsset);
const AzFramework::EntitySpawnTicket::Id ticket1Id = ticket1.GetId();
const AzFramework::EntitySpawnTicket::Id ticket2Id = ticket2.GetId();
AzFramework::EntitySpawnTicket ticketMoveConstructor(AZStd::move(ticket1));
EXPECT_TRUE(ticketMoveConstructor.IsValid());
EXPECT_EQ(ticketMoveConstructor.GetId(), ticket1Id);
AzFramework::EntitySpawnTicket ticketMoveOperator;
ticketMoveOperator = AZStd::move(ticket2);
EXPECT_TRUE(ticketMoveOperator.IsValid());
EXPECT_EQ(ticketMoveOperator.GetId(), ticket2Id);
}
TEST_F(SpawnableEntitiesManagerTest, SpawnAllEntities_DeleteTicketBeforeCall_NoCrash)
{
{
@@ -393,6 +478,178 @@ namespace UnitTest
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
}
TEST_F(SpawnableEntitiesManagerTest, SpawnAllEntities_AllAliasesWithDisabled_NoEntitiesSpawned)
{
using namespace AzFramework;
static constexpr size_t NumEntities = 4;
FillSpawnable(NumEntities);
InsertEntityAliases<NumEntities>(
{ 0, 1, 2, 3 }, { 0, 1, 2, 3 },
{ Spawnable::EntityAliasType::Disabled, Spawnable::EntityAliasType::Disabled, Spawnable::EntityAliasType::Disabled,
Spawnable::EntityAliasType::Disabled });
size_t spawnedEntitiesCount = 0;
auto callback = [&spawnedEntitiesCount](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
{
spawnedEntitiesCount += entities.size();
};
AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs;
optionalArgs.m_completionCallback = AZStd::move(callback);
m_manager->SpawnAllEntities(*m_ticket, AZStd::move(optionalArgs));
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
EXPECT_EQ(0, spawnedEntitiesCount);
}
TEST_F(SpawnableEntitiesManagerTest, SpawnAllEntities_SomeAliasesWithDisabled_RegularEntitiesAreSpawned)
{
using namespace AzFramework;
static constexpr size_t NumEntities = 8;
FillSpawnable(NumEntities);
InsertEntityAliases<2>({ 1, 3 }, { 1, 3 }, { Spawnable::EntityAliasType::Disabled, Spawnable::EntityAliasType::Disabled });
size_t spawnedEntitiesCount = 0;
auto callback = [&spawnedEntitiesCount](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
{
spawnedEntitiesCount += entities.size();
};
AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs;
optionalArgs.m_completionCallback = AZStd::move(callback);
m_manager->SpawnAllEntities(*m_ticket, AZStd::move(optionalArgs));
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
EXPECT_EQ(6, spawnedEntitiesCount);
}
TEST_F(SpawnableEntitiesManagerTest, SpawnAllEntities_AllAliasesWithReplace_EntitiesSpawnedFromTarget)
{
using namespace AzFramework;
static constexpr size_t NumEntities = 4;
FillSpawnable(NumEntities);
AZ::Data::Asset<Spawnable> target = CreateTargetSpawnable(4);
InsertEntityAliases<4>(
{ 0, 1, 2, 3 }, { 0, 1, 2, 3 },
{ Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace,
Spawnable::EntityAliasType::Replace },
&target);
size_t spawnedEntitiesCount = 0;
bool allReplaced = true;
auto callback = [&spawnedEntitiesCount, &allReplaced](
AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
{
spawnedEntitiesCount += entities.size();
for (const AZ::Entity* entity : entities)
{
if (entity)
{
allReplaced = allReplaced && entity->FindComponent<SourceSpawnableComponent>() == nullptr;
allReplaced = allReplaced && entity->FindComponent<TargetSpawnableComponent>() != nullptr;
}
else
{
allReplaced = false;
}
}
};
AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs;
optionalArgs.m_completionCallback = AZStd::move(callback);
m_manager->SpawnAllEntities(*m_ticket, AZStd::move(optionalArgs));
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
EXPECT_EQ(4, spawnedEntitiesCount);
EXPECT_TRUE(allReplaced);
}
TEST_F(SpawnableEntitiesManagerTest, SpawnAllEntities_AllAliasesWithAdditional_SourceAndTargetComponentsMerged)
{
using namespace AzFramework;
static constexpr size_t NumEntities = 4;
FillSpawnable(NumEntities);
AZ::Data::Asset<Spawnable> target = CreateTargetSpawnable(4);
InsertEntityAliases<4>(
{ 0, 1, 2, 3 }, { 0, 1, 2, 3 },
{ Spawnable::EntityAliasType::Additional, Spawnable::EntityAliasType::Additional, Spawnable::EntityAliasType::Additional,
Spawnable::EntityAliasType::Additional },
&target);
size_t spawnedEntitiesCount = 0;
bool allReplaced = true;
auto callback = [&spawnedEntitiesCount, &allReplaced](
AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
{
spawnedEntitiesCount += entities.size();
bool onSource = true;
for (const AZ::Entity* entity : entities)
{
if (entity)
{
if (onSource)
{
allReplaced = allReplaced && entity->FindComponent<SourceSpawnableComponent>() != nullptr;
allReplaced = allReplaced && entity->FindComponent<TargetSpawnableComponent>() == nullptr;
}
else
{
allReplaced = allReplaced && entity->FindComponent<SourceSpawnableComponent>() == nullptr;
allReplaced = allReplaced && entity->FindComponent<TargetSpawnableComponent>() != nullptr;
}
onSource = !onSource;
}
else
{
allReplaced = false;
}
}
};
AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs;
optionalArgs.m_completionCallback = AZStd::move(callback);
m_manager->SpawnAllEntities(*m_ticket, AZStd::move(optionalArgs));
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
EXPECT_EQ(8, spawnedEntitiesCount);
EXPECT_TRUE(allReplaced);
}
TEST_F(SpawnableEntitiesManagerTest, SpawnAllEntities_AllAliasesWithMerge_SourceAndTargetComponentsMerged)
{
using namespace AzFramework;
static constexpr size_t NumEntities = 4;
FillSpawnable(NumEntities);
AZ::Data::Asset<Spawnable> target = CreateTargetSpawnable(4);
InsertEntityAliases<4>(
{ 0, 1, 2, 3 }, { 0, 1, 2, 3 },
{ Spawnable::EntityAliasType::Merge, Spawnable::EntityAliasType::Merge, Spawnable::EntityAliasType::Merge,
Spawnable::EntityAliasType::Merge },
&target);
size_t spawnedEntitiesCount = 0;
bool allReplaced = true;
auto callback = [&spawnedEntitiesCount, &allReplaced](
AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
{
spawnedEntitiesCount += entities.size();
for (const AZ::Entity* entity : entities)
{
if (entity)
{
allReplaced = allReplaced && entity->FindComponent<SourceSpawnableComponent>() != nullptr;
allReplaced = allReplaced && entity->FindComponent<TargetSpawnableComponent>() != nullptr;
}
else
{
allReplaced = false;
}
}
};
AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs;
optionalArgs.m_completionCallback = AZStd::move(callback);
m_manager->SpawnAllEntities(*m_ticket, AZStd::move(optionalArgs));
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
EXPECT_EQ(4, spawnedEntitiesCount);
EXPECT_TRUE(allReplaced);
}
//
// SpawnEntities
@@ -754,6 +1011,190 @@ namespace UnitTest
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
}
TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_AllAliasesWithDisabled_NoEntitiesSpawned)
{
using namespace AzFramework;
static constexpr size_t NumEntities = 4;
FillSpawnable(NumEntities);
InsertEntityAliases<NumEntities>(
{ 0, 1, 2, 3 }, { 0, 1, 2, 3 },
{ Spawnable::EntityAliasType::Disabled, Spawnable::EntityAliasType::Disabled, Spawnable::EntityAliasType::Disabled,
Spawnable::EntityAliasType::Disabled });
AZStd::vector<uint32_t> indices = { 0, 2, 3, 1 };
size_t spawnedEntitiesCount = 0;
auto callback = [&spawnedEntitiesCount](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
{
spawnedEntitiesCount += entities.size();
};
AzFramework::SpawnEntitiesOptionalArgs optionalArgs;
optionalArgs.m_completionCallback = AZStd::move(callback);
m_manager->SpawnEntities(*m_ticket, AZStd::move(indices), AZStd::move(optionalArgs));
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
EXPECT_EQ(0, spawnedEntitiesCount);
}
TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_SomeAliasesWithDisabled_RegularEntitiesAreSpawned)
{
using namespace AzFramework;
FillSpawnable(8);
InsertEntityAliases<3>(
{ 1, 3, 6 }, { 1, 3, 6 },
{ Spawnable::EntityAliasType::Disabled, Spawnable::EntityAliasType::Disabled, Spawnable::EntityAliasType::Disabled });
AZStd::vector<uint32_t> indices = { 0, 2, 3, 1, 2, 3, 0, 1, 6, 4, 5, 7, 4, 1, 0, 6 };
size_t spawnedEntitiesCount = 0;
auto callback = [&spawnedEntitiesCount](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
{
spawnedEntitiesCount += entities.size();
};
AzFramework::SpawnEntitiesOptionalArgs optionalArgs;
optionalArgs.m_completionCallback = AZStd::move(callback);
m_manager->SpawnEntities(*m_ticket, AZStd::move(indices), AZStd::move(optionalArgs));
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
EXPECT_EQ(9, spawnedEntitiesCount);
}
TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_AllAliasesWithReplace_EntitiesSpawnedFromTarget)
{
using namespace AzFramework;
static constexpr size_t NumEntities = 4;
FillSpawnable(NumEntities);
AZ::Data::Asset<Spawnable> target = CreateTargetSpawnable(4);
InsertEntityAliases<4>(
{ 0, 1, 2, 3 }, { 0, 1, 2, 3 },
{ Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace,
Spawnable::EntityAliasType::Replace },
&target);
AZStd::vector<uint32_t> indices = { 0, 2, 3, 1 };
size_t spawnedEntitiesCount = 0;
bool allReplaced = true;
auto callback = [&spawnedEntitiesCount, &allReplaced](
AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
{
spawnedEntitiesCount += entities.size();
for (const AZ::Entity* entity : entities)
{
if (entity)
{
allReplaced = allReplaced && entity->FindComponent<SourceSpawnableComponent>() == nullptr;
allReplaced = allReplaced && entity->FindComponent<TargetSpawnableComponent>() != nullptr;
}
else
{
allReplaced = false;
}
}
};
AzFramework::SpawnEntitiesOptionalArgs optionalArgs;
optionalArgs.m_completionCallback = AZStd::move(callback);
m_manager->SpawnEntities(*m_ticket, AZStd::move(indices), AZStd::move(optionalArgs));
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
EXPECT_EQ(4, spawnedEntitiesCount);
EXPECT_TRUE(allReplaced);
}
TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_AllAliasesWithAdditional_SourceAndTargetComponentsMerged)
{
using namespace AzFramework;
static constexpr size_t NumEntities = 4;
FillSpawnable(NumEntities);
AZ::Data::Asset<Spawnable> target = CreateTargetSpawnable(4);
InsertEntityAliases<4>(
{ 0, 1, 2, 3 }, { 0, 1, 2, 3 },
{ Spawnable::EntityAliasType::Additional, Spawnable::EntityAliasType::Additional, Spawnable::EntityAliasType::Additional,
Spawnable::EntityAliasType::Additional },
&target);
AZStd::vector<uint32_t> indices = { 0, 2, 3, 1 };
size_t spawnedEntitiesCount = 0;
bool allReplaced = true;
auto callback = [&spawnedEntitiesCount, &allReplaced](
AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
{
spawnedEntitiesCount += entities.size();
bool onSource = true;
for (const AZ::Entity* entity : entities)
{
if (entity)
{
if (onSource)
{
allReplaced = allReplaced && entity->FindComponent<SourceSpawnableComponent>() != nullptr;
allReplaced = allReplaced && entity->FindComponent<TargetSpawnableComponent>() == nullptr;
}
else
{
allReplaced = allReplaced && entity->FindComponent<SourceSpawnableComponent>() == nullptr;
allReplaced = allReplaced && entity->FindComponent<TargetSpawnableComponent>() != nullptr;
}
onSource = !onSource;
}
else
{
allReplaced = false;
}
}
};
AzFramework::SpawnEntitiesOptionalArgs optionalArgs;
optionalArgs.m_completionCallback = AZStd::move(callback);
m_manager->SpawnEntities(*m_ticket, AZStd::move(indices), AZStd::move(optionalArgs));
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
EXPECT_EQ(8, spawnedEntitiesCount);
EXPECT_TRUE(allReplaced);
}
TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_AllAliasesWithMerge_SourceAndTargetComponentsMerged)
{
using namespace AzFramework;
static constexpr size_t NumEntities = 4;
FillSpawnable(NumEntities);
AZ::Data::Asset<Spawnable> target = CreateTargetSpawnable(4);
InsertEntityAliases<4>(
{ 0, 1, 2, 3 }, { 0, 1, 2, 3 },
{ Spawnable::EntityAliasType::Merge, Spawnable::EntityAliasType::Merge, Spawnable::EntityAliasType::Merge,
Spawnable::EntityAliasType::Merge },
&target);
AZStd::vector<uint32_t> indices = { 0, 2, 3, 1 };
size_t spawnedEntitiesCount = 0;
bool allReplaced = true;
auto callback = [&spawnedEntitiesCount, &allReplaced](
AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
{
spawnedEntitiesCount += entities.size();
for (const AZ::Entity* entity : entities)
{
if (entity)
{
allReplaced = allReplaced && entity->FindComponent<SourceSpawnableComponent>() != nullptr;
allReplaced = allReplaced && entity->FindComponent<TargetSpawnableComponent>() != nullptr;
}
else
{
allReplaced = false;
}
}
};
AzFramework::SpawnEntitiesOptionalArgs optionalArgs;
optionalArgs.m_completionCallback = AZStd::move(callback);
m_manager->SpawnEntities(*m_ticket, AZStd::move(indices), AZStd::move(optionalArgs));
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
EXPECT_EQ(4, spawnedEntitiesCount);
EXPECT_TRUE(allReplaced);
}
//
// DespawnAllEntities
@@ -0,0 +1,499 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/UnitTest/TestTypes.h>
#include <AzFramework/Spawnable/Spawnable.h>
#include <AzTest/AzTest.h>
namespace UnitTest
{
class SpawnableTest : public AllocatorsFixture
{
public:
void SetUp() override
{
AllocatorsFixture::SetUp();
m_spawnable = aznew AzFramework::Spawnable();
}
void TearDown() override
{
delete m_spawnable;
m_spawnable = nullptr;
AllocatorsFixture::TearDown();
}
void InsertEightEntities()
{
AzFramework::Spawnable::EntityList& entities = m_spawnable->GetEntities();
entities.reserve(entities.size() + 8);
for (size_t i = 0; i < 8; ++i)
{
entities.emplace_back(AZStd::make_unique<AZ::Entity>());
}
}
void InsertEightEntityAliases(
const AZStd::array<uint32_t, 8>& sourceIds,
const AZStd::array<uint32_t, 8>& targetIds,
const AZStd::array<AzFramework::Spawnable::EntityAliasType, 8>& aliasTypes,
bool queueLoad = false)
{
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
for (uint32_t i = 0; i < 8; ++i)
{
AZ::Data::Asset<AzFramework::Spawnable> spawnable(
AZ::Data::AssetId(AZ::Uuid("{4CBEC17A-52D6-42D5-9037-F4C05B9CE1D9}"), i), azrtti_typeid<AzFramework::Spawnable>());
visitor.AddAlias(spawnable, AZ::Crc32(i), sourceIds[i], targetIds[i], aliasTypes[i], queueLoad);
}
}
void InsertEightEntityAliases(bool queueLoad)
{
using namespace AzFramework;
InsertEightEntityAliases(
{ 0, 1, 2, 3, 4, 5, 6, 7 }, { 0, 1, 2, 3, 4, 5, 6, 7 },
{ Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace,
Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace,
Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace },
queueLoad);
}
void InsertEightEntityAliases()
{
InsertEightEntityAliases(false);
}
protected:
AzFramework::Spawnable* m_spawnable;
};
//
// TryGetAliasesConst
//
TEST_F(SpawnableTest, TryGetAliasesConst_GetVisitor_VisitorDataIsAvailable)
{
AzFramework::Spawnable::EntityAliasConstVisitor visitor = m_spawnable->TryGetAliasesConst();
EXPECT_TRUE(visitor.IsSet());
}
TEST_F(SpawnableTest, TryGetAliasesConst_VisitorThatIsNotReadShared_VisitorDataIsNotAvailable)
{
AzFramework::Spawnable::EntityAliasVisitor readWriteVisitor = m_spawnable->TryGetAliases();
ASSERT_TRUE(readWriteVisitor.IsSet());
AzFramework::Spawnable::EntityAliasConstVisitor visitor = m_spawnable->TryGetAliasesConst();
EXPECT_FALSE(visitor.IsSet());
}
TEST_F(SpawnableTest, TryGetAliasesConst_VisitorThatIsAlreadyReadShared_VisitorDataIsAvailable)
{
AzFramework::Spawnable::EntityAliasConstVisitor readVisitor = m_spawnable->TryGetAliasesConst();
ASSERT_TRUE(readVisitor.IsSet());
AzFramework::Spawnable::EntityAliasConstVisitor visitor = m_spawnable->TryGetAliasesConst();
EXPECT_TRUE(visitor.IsSet());
}
//
// TryGetAliases
//
TEST_F(SpawnableTest, TryGetAliases_GetVisitor_VisitorDataIsAvailable)
{
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
EXPECT_TRUE(visitor.IsSet());
}
TEST_F(SpawnableTest, TryGetAliasesConst_VisitorThatIsAlreadyShared_VisitorDataNotIsAvailable)
{
AzFramework::Spawnable::EntityAliasConstVisitor readVisitor = m_spawnable->TryGetAliasesConst();
ASSERT_TRUE(readVisitor.IsSet());
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
EXPECT_FALSE(visitor.IsSet());
}
//
// EntityAliasVisitor
//
//
// HasAliases
//
TEST_F(SpawnableTest, EntityAliasVisitor_HasAliases_EmptyAliasList_ReturnsFalse)
{
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
ASSERT_TRUE(visitor.IsSet());
EXPECT_FALSE(visitor.HasAliases());
}
TEST_F(SpawnableTest, EntityAliasVisitor_HasAliases_FilledInAliasList_ReturnsTue)
{
InsertEightEntities();
InsertEightEntityAliases();
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
ASSERT_TRUE(visitor.IsSet());
EXPECT_TRUE(visitor.HasAliases());
}
//
// Optimize
//
TEST_F(SpawnableTest, EntityAliasVisitor_Optimize_SortEntityAliases_AliasesAreSortedBySourceAndTargetId)
{
InsertEightEntities();
InsertEightEntityAliases();
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
ASSERT_TRUE(visitor.IsSet());
// Optimize doesn't need to be explicitly called because the setup of the aliases will cause the alias list to be sorted and optimized.
uint32_t sourceIndex = 0;
uint32_t targetIndex = 0;
for (const AzFramework::Spawnable::EntityAlias& alias : visitor)
{
if (alias.m_sourceIndex != sourceIndex)
{
ASSERT_LE(sourceIndex, alias.m_sourceIndex);
}
else
{
ASSERT_LE(targetIndex, alias.m_targetIndex);
}
sourceIndex = alias.m_sourceIndex;
targetIndex = alias.m_targetIndex;
}
}
TEST_F(
SpawnableTest, EntityAliasVisitor_Optimize_RemoveUnused_OnlySecondToLastAliasRemains)
{
using namespace AzFramework;
InsertEightEntities();
InsertEightEntityAliases(
{ 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 1, 2, 3, 4, 5, 6, 7 },
{ Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Disabled,
Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Disabled,
Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Original });
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
ASSERT_TRUE(visitor.IsSet());
EXPECT_EQ(1, AZStd::distance(visitor.begin(), visitor.end()));
EXPECT_EQ(Spawnable::EntityAliasType::Replace, visitor.begin()->m_aliasType);
EXPECT_EQ(6, visitor.begin()->m_targetIndex);
}
TEST_F(SpawnableTest, EntityAliasVisitor_Optimize_AddAdditional_ThreeAdditionalAliasesAreAdded)
{
using namespace AzFramework;
InsertEightEntities();
InsertEightEntityAliases(
{ 0, 0, 0, 0, 1, 2, 2, 2 }, { 0, 1, 2, 3, 4, 5, 6, 7 },
{ Spawnable::EntityAliasType::Additional, Spawnable::EntityAliasType::Additional, Spawnable::EntityAliasType::Additional,
Spawnable::EntityAliasType::Additional, Spawnable::EntityAliasType::Additional, Spawnable::EntityAliasType::Additional,
Spawnable::EntityAliasType::Additional, Spawnable::EntityAliasType::Additional });
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
ASSERT_TRUE(visitor.IsSet());
EXPECT_EQ(11, AZStd::distance(visitor.begin(), visitor.end()));
EXPECT_EQ(Spawnable::EntityAliasType::Original, visitor.begin()->m_aliasType);
EXPECT_EQ(Spawnable::EntityAliasType::Original, visitor.begin()[5].m_aliasType);
EXPECT_EQ(Spawnable::EntityAliasType::Original, visitor.begin()[7].m_aliasType);
}
TEST_F(SpawnableTest, EntityAliasVisitor_Optimize_OriginalsOnly_AliasListIsEmpty)
{
using namespace AzFramework;
InsertEightEntities();
InsertEightEntityAliases(
{ 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 1, 2, 3, 4, 5, 6, 7 },
{ Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Original,
Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Original,
Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Original });
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
ASSERT_TRUE(visitor.IsSet());
EXPECT_EQ(0, AZStd::distance(visitor.begin(), visitor.end()));
}
TEST_F(SpawnableTest, EntityAliasVisitor_Optimize_MixedOriginals_AllOriginalsRemoved)
{
using namespace AzFramework;
InsertEightEntities();
InsertEightEntityAliases(
{ 0, 0, 0, 1, 1, 2, 2, 2 }, { 0, 1, 2, 3, 4, 5, 6, 7 },
{ Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Original,
Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Disabled, Spawnable::EntityAliasType::Original,
Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Original });
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
ASSERT_TRUE(visitor.IsSet());
EXPECT_EQ(2, AZStd::distance(visitor.begin(), visitor.end()));
EXPECT_EQ(Spawnable::EntityAliasType::Disabled, visitor.begin()->m_aliasType);
EXPECT_EQ(Spawnable::EntityAliasType::Replace, visitor.begin()[1].m_aliasType);
}
TEST_F(SpawnableTest, EntityAliasVisitor_Optimize_MergeAfterOriginal_NoAdditionalOriginalIsInserted)
{
using namespace AzFramework;
InsertEightEntities();
InsertEightEntityAliases(
{ 0, 0, 1, 1, 2, 2, 2, 2 }, { 0, 1, 2, 3, 4, 5, 6, 7 },
{ Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Merge, Spawnable::EntityAliasType::Replace,
Spawnable::EntityAliasType::Merge, Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Original,
Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Original });
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
ASSERT_TRUE(visitor.IsSet());
EXPECT_EQ(4, AZStd::distance(visitor.begin(), visitor.end()));
EXPECT_EQ(Spawnable::EntityAliasType::Original, visitor.begin()->m_aliasType);
EXPECT_EQ(Spawnable::EntityAliasType::Merge, visitor.begin()[1].m_aliasType);
EXPECT_EQ(Spawnable::EntityAliasType::Replace, visitor.begin()[2].m_aliasType);
EXPECT_EQ(Spawnable::EntityAliasType::Merge, visitor.begin()[3].m_aliasType);
}
//
// UpdateAliasType
//
TEST_F(SpawnableTest, EntityAliasVisitor_UpdateAliasType_AllToOriginal_NoAliasesAfterOptimization)
{
using namespace AzFramework;
InsertEightEntities();
InsertEightEntityAliases(
{ 0, 1, 2, 3, 4, 5, 6, 7 }, { 0, 1, 2, 3, 4, 5, 6, 7 },
{ Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace,
Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace,
Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace });
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
ASSERT_TRUE(visitor.IsSet());
for (uint32_t i = 0; i < 8; ++i)
{
visitor.UpdateAliasType(i, Spawnable::EntityAliasType::Original);
}
for (const Spawnable::EntityAlias& alias : visitor)
{
EXPECT_EQ(Spawnable::EntityAliasType::Original, alias.m_aliasType);
}
visitor.Optimize();
EXPECT_EQ(0, AZStd::distance(visitor.begin(), visitor.end()));
}
//
// UpdateAliases
//
TEST_F(SpawnableTest, EntityAliasVisitor_UpdateAliases_AllToOriginal_NoAliasesAfterOptimization)
{
using namespace AzFramework;
InsertEightEntities();
InsertEightEntityAliases(
{ 0, 1, 2, 3, 4, 5, 6, 7 }, { 0, 1, 2, 3, 4, 5, 6, 7 },
{ Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace,
Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace,
Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace });
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
ASSERT_TRUE(visitor.IsSet());
auto callback =
[](Spawnable::EntityAliasType& aliasType, bool& /*queueLoad*/, const AZ::Data::Asset<Spawnable>& /*aliasedSpawnable*/,
const AZ::Crc32 /*tag*/, const uint32_t /*sourceIndex*/, const uint32_t /*targetIndex*/)
{
aliasType = Spawnable::EntityAliasType::Original;
};
visitor.UpdateAliases(AZStd::move(callback));
for (const Spawnable::EntityAlias& alias : visitor)
{
EXPECT_EQ(Spawnable::EntityAliasType::Original, alias.m_aliasType);
}
visitor.Optimize();
EXPECT_EQ(0, AZStd::distance(visitor.begin(), visitor.end()));
}
TEST_F(SpawnableTest, EntityAliasVisitor_UpdateAliases_FilterByTag_OnlyOneAliasUpdated)
{
using namespace AzFramework;
InsertEightEntities();
InsertEightEntityAliases(
{ 0, 1, 2, 3, 4, 5, 6, 7 }, { 0, 1, 2, 3, 4, 5, 6, 7 },
{ Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace,
Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace,
Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace });
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
ASSERT_TRUE(visitor.IsSet());
bool correctTag = false;
size_t numberOfUpdates = 0;
auto callback = [&correctTag, &numberOfUpdates](Spawnable::EntityAliasType& aliasType, bool& /*queueLoad*/,
const AZ::Data::Asset<Spawnable>& /*aliasedSpawnable*/, const AZ::Crc32 tag, const uint32_t /*sourceIndex*/,
const uint32_t /*targetIndex*/)
{
correctTag = (tag == AZ::Crc32(3));
numberOfUpdates++;
aliasType = Spawnable::EntityAliasType::Original;
};
visitor.UpdateAliases(AZ::Crc32(3), AZStd::move(callback));
EXPECT_EQ(Spawnable::EntityAliasType::Original, visitor.begin()[3].m_aliasType);
}
//
// AreAllSpawnablesReady
//
TEST_F(SpawnableTest, EntityAliasVisitor_AreAllSpawnablesReady_CheckFakeLoadedAssets_ReturnsTrue)
{
using namespace AzFramework;
InsertEightEntities();
InsertEightEntityAliases();
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
ASSERT_TRUE(visitor.IsSet());
EXPECT_TRUE(visitor.AreAllSpawnablesReady());
}
TEST_F(SpawnableTest, EntityAliasVisitor_AreAllSpawnablesReady_CheckFakeNotLoadedAssets_ReturnsFalse)
{
using namespace AzFramework;
InsertEightEntities();
InsertEightEntityAliases(true);
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
ASSERT_TRUE(visitor.IsSet());
EXPECT_FALSE(visitor.AreAllSpawnablesReady());
}
//
// ListTargetSpawnables
//
TEST_F(SpawnableTest, EntityAliasVisitor_ListTargetSpawnables_ListAllTargetAssets_AllTargetsListed)
{
using namespace AzFramework;
InsertEightEntities();
InsertEightEntityAliases();
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
ASSERT_TRUE(visitor.IsSet());
size_t count = 0;
bool correctAssets = true;
auto callback = [&count, &correctAssets](const AZ::Data::Asset<Spawnable>& targetSpawnable)
{
correctAssets = correctAssets && (targetSpawnable.GetId().m_subId == count);
count++;
};
visitor.ListTargetSpawnables(callback);
EXPECT_EQ(8, count);
EXPECT_TRUE(correctAssets);
}
TEST_F(SpawnableTest, EntityAliasVisitor_ListTargetSpawnables_ListTaggedTargetAssets_OneAssetListed)
{
using namespace AzFramework;
InsertEightEntities();
InsertEightEntityAliases();
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
ASSERT_TRUE(visitor.IsSet());
size_t count = 0;
bool correctAsset = false;
auto callback = [&count, &correctAsset](const AZ::Data::Asset<Spawnable>& targetSpawnable)
{
correctAsset = (targetSpawnable.GetId().m_subId == 3);
count++;
};
visitor.ListTargetSpawnables(AZ::Crc32(3), callback);
EXPECT_EQ(1, count);
EXPECT_TRUE(correctAsset);
}
//
// ListSpawnablesRequiringLoad
//
TEST_F(SpawnableTest, EntityAliasVisitor_ListSpawnablesRequiringLoad_AllSetToLoaded_AllTargetsListed)
{
using namespace AzFramework;
InsertEightEntities();
InsertEightEntityAliases(true);
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
ASSERT_TRUE(visitor.IsSet());
size_t count = 0;
bool correctAssets = true;
auto callback = [&count, &correctAssets](const AZ::Data::Asset<Spawnable>& targetSpawnable)
{
correctAssets = correctAssets && (targetSpawnable.GetId().m_subId == count);
count++;
};
visitor.ListSpawnablesRequiringLoad(callback);
EXPECT_EQ(8, count);
EXPECT_TRUE(correctAssets);
}
TEST_F(SpawnableTest, EntityAliasVisitor_ListSpawnablesRequiringLoad_AllSetToNotLoaded_NoTargetsListed)
{
using namespace AzFramework;
InsertEightEntities();
InsertEightEntityAliases(false);
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
ASSERT_TRUE(visitor.IsSet());
size_t count = 0;
auto callback = [&count](const AZ::Data::Asset<Spawnable>& /*targetSpawnable*/)
{
count++;
};
visitor.ListSpawnablesRequiringLoad(callback);
EXPECT_EQ(0, count);
}
} // namespace UnitTest
@@ -10,6 +10,7 @@ set(FILES
Main.cpp
Spawnable/SpawnableEntitiesInterfaceTests.cpp
Spawnable/SpawnableEntitiesManagerTests.cpp
Spawnable/SpawnableTests.cpp
ArchiveCompressionTests.cpp
ArchiveTests.cpp
BehaviorEntityTests.cpp
@@ -220,7 +220,7 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
if (it == aliasVisitors.end())
{
AzFramework::Spawnable::EntityAliasVisitor visitor = source->m_spawnable.TryGetAliases();
AZ_Assert(visitor.HasLock(), "Unable to obtain lock for a newly create spawnable.");
AZ_Assert(visitor.IsSet(), "Unable to obtain lock for a newly create spawnable.");
it = aliasVisitors.emplace(source->m_spawnable.GetId(), AZStd::move(visitor)).first;
}
it->second.AddAlias(
@@ -42,7 +42,8 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
NoLoad, //!< Don't load the spawnable referenced in the entity alias. Loading will be up to the caller.
QueueLoad, //!< Queue the spawnable referenced in the entity alias for loading. This will be an async load because asset
//!< handlers aren't allowed to start a blocking load as this can lead to deadlocks.
//!< handlers aren't allowed to start a blocking load as this can lead to deadlocks. This option will allow
//!< to disable loading the referenced spawnable through the event fired from the spawnables asset handler.
DependentLoad //!< The spawnable referenced in the entity alias is made a dependency of the spawnable that holds the entity
//!< alias. This will cause the spawnable to be automatically loaded along with the owning spawnable.
};