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
@@ -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