diff --git a/Code/Framework/AzFramework/Tests/Spawnable/SpawnableEntitiesManagerTests.cpp b/Code/Framework/AzFramework/Tests/Spawnable/SpawnableEntitiesManagerTests.cpp index d39bfed1e2..1a483a7851 100644 --- a/Code/Framework/AzFramework/Tests/Spawnable/SpawnableEntitiesManagerTests.cpp +++ b/Code/Framework/AzFramework/Tests/Spawnable/SpawnableEntitiesManagerTests.cpp @@ -77,6 +77,12 @@ namespace UnitTest public: AZ_COMPONENT(TargetSpawnableComponent, "{B4041561-63A7-4E1E-80F1-78C08D497960}"); + TargetSpawnableComponent() = default; + explicit TargetSpawnableComponent(AZ::EntityId parent) + : m_parent(parent) + { + } + void Activate() override {} void Deactivate() override {} @@ -84,9 +90,12 @@ namespace UnitTest { if (auto* serializeContext = azrtti_cast(reflection)) { - serializeContext->Class(); + serializeContext->Class() + ->Field("Parent", &TargetSpawnableComponent::m_parent); } } + + AZ::EntityId m_parent; }; class SpawnableEntitiesManagerTest : public AllocatorsFixture @@ -147,22 +156,43 @@ namespace UnitTest { auto entry = AZStd::make_unique(); entry->AddComponent(aznew SourceSpawnableComponent()); + entry->SetId(AZ::EntityId(40 + i)); entities.push_back(AZStd::move(entry)); } } - AZ::Data::Asset CreateTargetSpawnable(size_t numElements) + AZ::Data::Asset CreateTargetSpawnable(size_t numElements, bool requiresMatchingEntityIds) { 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) + if (requiresMatchingEntityIds) { - auto entry = AZStd::make_unique(); - entry->AddComponent(aznew TargetSpawnableComponent()); - entities.push_back(AZStd::move(entry)); + for (size_t i = 0; i < numElements; ++i) + { + auto entry = AZStd::make_unique(); + if (i != 0) + { + entry->AddComponent(aznew TargetSpawnableComponent(AZ::EntityId(40 + i - 1))); + } + else + { + entry->AddComponent(aznew TargetSpawnableComponent()); + } + entry->SetId(AZ::EntityId(40 + i)); + entities.push_back(AZStd::move(entry)); + } + } + else + { + for (size_t i = 0; i < numElements; ++i) + { + auto entry = AZStd::make_unique(); + entry->AddComponent(aznew TargetSpawnableComponent()); + entities.push_back(AZStd::move(entry)); + } } return AZ::Data::Asset(target, AZ::Data::AssetLoadBehavior::NoLoad); @@ -212,6 +242,38 @@ namespace UnitTest return true; } + static bool DoParentEntityIdsMatch(AzFramework::SpawnableConstEntityContainerView entities) + { + if (entities.empty()) + { + return false; + } + + const AZ::Entity* previous = nullptr; + for (const AZ::Entity* entity : entities) + { + if (entity) + { + if (previous) + { + if (TargetSpawnableComponent* link = entity->FindComponent(); link != nullptr) + { + if (link->m_parent != previous->GetId()) + { + return false; + } + } + previous = entity; + } + } + else + { + return false; + } + } + return true; + } + static bool IsEveryOtherEntityAReplacement(AzFramework::SpawnableConstEntityContainerView entities) { bool onAlternative = true; @@ -599,7 +661,8 @@ namespace UnitTest using namespace AzFramework; static constexpr size_t NumEntities = 4; FillSpawnable(NumEntities); - AZ::Data::Asset target = CreateTargetSpawnable(4); + constexpr bool requiresMatchingEntityIds = true; + AZ::Data::Asset target = CreateTargetSpawnable(4, requiresMatchingEntityIds); InsertEntityAliases<4>( { 0, 1, 2, 3 }, { 0, 1, 2, 3 }, { Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, @@ -608,11 +671,13 @@ namespace UnitTest size_t spawnedEntitiesCount = 0; bool allReplaced = false; - auto callback = [&spawnedEntitiesCount, &allReplaced]( + bool allEntityIdsPatched = false; + auto callback = [&spawnedEntitiesCount, &allReplaced, &allEntityIdsPatched]( AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities) { spawnedEntitiesCount += entities.size(); allReplaced = AreAllEntitiesReplaced(entities); + allEntityIdsPatched = DoParentEntityIdsMatch(entities); }; AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs; optionalArgs.m_completionCallback = AZStd::move(callback); @@ -621,6 +686,7 @@ namespace UnitTest EXPECT_EQ(4, spawnedEntitiesCount); EXPECT_TRUE(allReplaced); + EXPECT_TRUE(allEntityIdsPatched); } TEST_F(SpawnableEntitiesManagerTest, SpawnAllEntities_AllAliasesWithAdditional_SourceAndTargetComponentsMerged) @@ -628,7 +694,8 @@ namespace UnitTest using namespace AzFramework; static constexpr size_t NumEntities = 4; FillSpawnable(NumEntities); - AZ::Data::Asset target = CreateTargetSpawnable(4); + constexpr bool requiresMatchingEntityIds = false; + AZ::Data::Asset target = CreateTargetSpawnable(4, requiresMatchingEntityIds); InsertEntityAliases<4>( { 0, 1, 2, 3 }, { 0, 1, 2, 3 }, { Spawnable::EntityAliasType::Additional, Spawnable::EntityAliasType::Additional, Spawnable::EntityAliasType::Additional, @@ -637,11 +704,13 @@ namespace UnitTest size_t spawnedEntitiesCount = 0; bool allAdded = false; - auto callback = [&spawnedEntitiesCount, &allAdded]( + bool allEntityIdsPatched = false; + auto callback = [&spawnedEntitiesCount, &allAdded, &allEntityIdsPatched]( AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities) { spawnedEntitiesCount += entities.size(); allAdded = IsEveryOtherEntityAReplacement(entities); + allEntityIdsPatched = DoParentEntityIdsMatch(entities); }; AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs; optionalArgs.m_completionCallback = AZStd::move(callback); @@ -650,6 +719,7 @@ namespace UnitTest EXPECT_EQ(8, spawnedEntitiesCount); EXPECT_TRUE(allAdded); + EXPECT_TRUE(allEntityIdsPatched); } TEST_F(SpawnableEntitiesManagerTest, SpawnAllEntities_AllAliasesWithMerge_SourceAndTargetComponentsMerged) @@ -657,7 +727,8 @@ namespace UnitTest using namespace AzFramework; static constexpr size_t NumEntities = 4; FillSpawnable(NumEntities); - AZ::Data::Asset target = CreateTargetSpawnable(4); + constexpr bool requiresMatchingEntityIds = true; + AZ::Data::Asset target = CreateTargetSpawnable(4, requiresMatchingEntityIds); InsertEntityAliases<4>( { 0, 1, 2, 3 }, { 0, 1, 2, 3 }, { Spawnable::EntityAliasType::Merge, Spawnable::EntityAliasType::Merge, Spawnable::EntityAliasType::Merge, @@ -666,11 +737,13 @@ namespace UnitTest size_t spawnedEntitiesCount = 0; bool allMerged = false; - auto callback = [&spawnedEntitiesCount, &allMerged]( + bool allEntityIdsPatched = false; + auto callback = [&spawnedEntitiesCount, &allMerged, &allEntityIdsPatched]( AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities) { spawnedEntitiesCount += entities.size(); allMerged = AreAllMerged(entities); + allEntityIdsPatched = DoParentEntityIdsMatch(entities); }; AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs; optionalArgs.m_completionCallback = AZStd::move(callback); @@ -679,6 +752,7 @@ namespace UnitTest EXPECT_EQ(4, spawnedEntitiesCount); EXPECT_TRUE(allMerged); + EXPECT_TRUE(allEntityIdsPatched); } // @@ -1095,7 +1169,8 @@ namespace UnitTest using namespace AzFramework; static constexpr size_t NumEntities = 4; FillSpawnable(NumEntities); - AZ::Data::Asset target = CreateTargetSpawnable(4); + constexpr bool requiresMatchingEntityIds = true; + AZ::Data::Asset target = CreateTargetSpawnable(4, requiresMatchingEntityIds); InsertEntityAliases<4>( { 0, 1, 2, 3 }, { 0, 1, 2, 3 }, { Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, @@ -1106,11 +1181,13 @@ namespace UnitTest size_t spawnedEntitiesCount = 0; bool allReplaced = false; - auto callback = [&spawnedEntitiesCount, &allReplaced]( + bool allEntityIdsPatched = false; + auto callback = [&spawnedEntitiesCount, &allReplaced, &allEntityIdsPatched]( AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities) { spawnedEntitiesCount += entities.size(); allReplaced = AreAllEntitiesReplaced(entities); + allEntityIdsPatched = DoParentEntityIdsMatch(entities); }; AzFramework::SpawnEntitiesOptionalArgs optionalArgs; optionalArgs.m_completionCallback = AZStd::move(callback); @@ -1119,6 +1196,7 @@ namespace UnitTest EXPECT_EQ(4, spawnedEntitiesCount); EXPECT_TRUE(allReplaced); + EXPECT_TRUE(allEntityIdsPatched); } TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_AllAliasesWithAdditional_SourceAndTargetComponentsMerged) @@ -1126,7 +1204,8 @@ namespace UnitTest using namespace AzFramework; static constexpr size_t NumEntities = 4; FillSpawnable(NumEntities); - AZ::Data::Asset target = CreateTargetSpawnable(4); + constexpr bool requiresMatchingEntityIds = false; + AZ::Data::Asset target = CreateTargetSpawnable(4, requiresMatchingEntityIds); InsertEntityAliases<4>( { 0, 1, 2, 3 }, { 0, 1, 2, 3 }, { Spawnable::EntityAliasType::Additional, Spawnable::EntityAliasType::Additional, Spawnable::EntityAliasType::Additional, @@ -1137,12 +1216,14 @@ namespace UnitTest size_t spawnedEntitiesCount = 0; bool allAdded = false; + bool allEntityIdsPatched = false; auto callback = - [&spawnedEntitiesCount, &allAdded]( + [&spawnedEntitiesCount, &allAdded, &allEntityIdsPatched]( AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities) { spawnedEntitiesCount += entities.size(); allAdded = IsEveryOtherEntityAReplacement(entities); + allEntityIdsPatched = DoParentEntityIdsMatch(entities); }; AzFramework::SpawnEntitiesOptionalArgs optionalArgs; optionalArgs.m_completionCallback = AZStd::move(callback); @@ -1151,6 +1232,7 @@ namespace UnitTest EXPECT_EQ(8, spawnedEntitiesCount); EXPECT_TRUE(allAdded); + EXPECT_TRUE(allEntityIdsPatched); } TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_AllAliasesWithMerge_SourceAndTargetComponentsMerged) @@ -1158,7 +1240,8 @@ namespace UnitTest using namespace AzFramework; static constexpr size_t NumEntities = 4; FillSpawnable(NumEntities); - AZ::Data::Asset target = CreateTargetSpawnable(4); + constexpr bool requiresMatchingEntityIds = true; + AZ::Data::Asset target = CreateTargetSpawnable(4, requiresMatchingEntityIds); InsertEntityAliases<4>( { 0, 1, 2, 3 }, { 0, 1, 2, 3 }, { Spawnable::EntityAliasType::Merge, Spawnable::EntityAliasType::Merge, Spawnable::EntityAliasType::Merge, @@ -1169,11 +1252,13 @@ namespace UnitTest size_t spawnedEntitiesCount = 0; bool allMerged = false; - auto callback = [&spawnedEntitiesCount, &allMerged]( + bool allEntityIdsPatched = false; + auto callback = [&spawnedEntitiesCount, &allMerged, &allEntityIdsPatched]( AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities) { spawnedEntitiesCount += entities.size(); allMerged = AreAllMerged(entities); + allEntityIdsPatched = DoParentEntityIdsMatch(entities); }; AzFramework::SpawnEntitiesOptionalArgs optionalArgs; optionalArgs.m_completionCallback = AZStd::move(callback); @@ -1182,6 +1267,7 @@ namespace UnitTest EXPECT_EQ(4, spawnedEntitiesCount); EXPECT_TRUE(allMerged); + EXPECT_TRUE(allEntityIdsPatched); } // @@ -1302,6 +1388,36 @@ namespace UnitTest // ClaimEntities // + TEST_F(SpawnableEntitiesManagerTest, ClaimEntities_Call_AllEntitiesWereClaimedAndNotDeleted) + { + static constexpr size_t NumEntities = 4; + FillSpawnable(NumEntities); + + AZStd::vector claimedEntities; + auto callback = [&claimedEntities](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableEntityContainerView container) + { + for (AZ::Entity* entity : container) + { + claimedEntities.push_back(entity); + } + }; + + { + AzFramework::EntitySpawnTicket ticket(*m_spawnableAsset); + m_manager->SpawnAllEntities(ticket); + m_manager->ClaimEntities(ticket, AZStd::move(callback)); + m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); + } + + EXPECT_EQ(NumEntities, claimedEntities.size()); + + // If these calls fail it means that the ticket has still deleted the entities, so they weren't properly claimed. + for (AZ::Entity* entity : claimedEntities) + { + delete entity; + } + } + TEST_F(SpawnableEntitiesManagerTest, ClaimEntities_DeleteTicketBeforeCall_NoCrash) { auto callback = [](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableEntityContainerView) {};