diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h index dc9c7b4538..66197ae8fc 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -164,6 +165,8 @@ namespace AzFramework public: friend class SpawnableEntitiesDefinition; + AZ_CLASS_ALLOCATOR(AzFramework::EntitySpawnTicket, AZ::SystemAllocator, 0); + using Id = uint32_t; EntitySpawnTicket() = default; diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp index 37570caec7..3406057fca 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp @@ -499,12 +499,8 @@ namespace AzFramework for (auto it = newEntitiesBegin; it != newEntitiesEnd; ++it) { AZ::Entity* clone = (*it); - // The entity component framework doesn't handle entities without TransformComponent safely. - if (!clone->GetComponents().empty()) - { - clone->SetSpawnTicketId(request.m_ticketId); - GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it); - } + clone->SetSpawnTicketId(request.m_ticketId); + GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, clone); } // Let other systems know about newly spawned entities for any post-processing after adding to the scene/game context. @@ -636,12 +632,8 @@ namespace AzFramework for (auto it = ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount; it != ticket.m_spawnedEntities.end(); ++it) { AZ::Entity* clone = (*it); - // The entity component framework doesn't handle entities without TransformComponent safely. - if (!clone->GetComponents().empty()) - { - clone->SetSpawnTicketId(request.m_ticketId); - GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it); - } + clone->SetSpawnTicketId(request.m_ticketId); + GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it); } if (request.m_completionCallback) @@ -668,7 +660,7 @@ namespace AzFramework { if (entity != nullptr) { - // Setting it to 0 is needed to avoid the infite loop between GameEntityContext and SpawnableEntitiesManager. + // Setting it to 0 is needed to avoid the infinite loop between GameEntityContext and SpawnableEntitiesManager. entity->SetSpawnTicketId(0); GameEntityContextRequestBus::Broadcast( &GameEntityContextRequestBus::Events::DestroyGameEntity, entity->GetId()); @@ -702,7 +694,7 @@ namespace AzFramework { if (*entityIterator != nullptr && (*entityIterator)->GetId() == request.m_entityId) { - // Setting it to 0 is needed to avoid the infite loop between GameEntityContext and SpawnableEntitiesManager. + // Setting it to 0 is needed to avoid the infinite loop between GameEntityContext and SpawnableEntitiesManager. (*entityIterator)->SetSpawnTicketId(0); GameEntityContextRequestBus::Broadcast( &GameEntityContextRequestBus::Events::DestroyGameEntity, (*entityIterator)->GetId()); @@ -949,11 +941,6 @@ namespace AzFramework GameEntityContextRequestBus::Broadcast( &GameEntityContextRequestBus::Events::DestroyGameEntity, entity->GetId()); } - else - { - // Entities without components wouldn't have been send to the GameEntityContext. - delete entity; - } } delete request.m_ticket; diff --git a/Code/Framework/AzFramework/Tests/Spawnable/SpawnableEntitiesManagerTests.cpp b/Code/Framework/AzFramework/Tests/Spawnable/SpawnableEntitiesManagerTests.cpp index 0dc00f81dd..50365ff2ff 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,14 +90,19 @@ 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 { public: + constexpr static AZ::u64 EntityIdStartId = 40; + void SetUp() override { AllocatorsFixture::SetUp(); @@ -111,7 +122,7 @@ namespace UnitTest m_spawnable = aznew AzFramework::Spawnable( AZ::Data::AssetId::CreateString("{EB2E8A2B-F253-4A90-BBF4-55F2EED786B8}:0"), AZ::Data::AssetData::AssetStatus::Ready); m_spawnableAsset = new AZ::Data::Asset(m_spawnable, AZ::Data::AssetLoadBehavior::Default); - m_ticket = new AzFramework::EntitySpawnTicket(*m_spawnableAsset); + m_ticket = aznew AzFramework::EntitySpawnTicket(*m_spawnableAsset); auto managerInterface = AzFramework::SpawnableEntitiesInterface::Get(); m_manager = azrtti_cast(managerInterface); @@ -147,22 +158,43 @@ namespace UnitTest { auto entry = AZStd::make_unique(); entry->AddComponent(aznew SourceSpawnableComponent()); + entry->SetId(AZ::EntityId(EntityIdStartId + 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(EntityIdStartId + i - 1))); + } + else + { + entry->AddComponent(aznew TargetSpawnableComponent()); + } + entry->SetId(AZ::EntityId(EntityIdStartId + 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 +244,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; @@ -516,7 +580,7 @@ namespace UnitTest // Make sure we start with a fresh ticket each time, or else each iteration through this loop would continue to build up // more and more entities. delete m_ticket; - m_ticket = new AzFramework::EntitySpawnTicket(*m_spawnableAsset); + m_ticket = aznew AzFramework::EntitySpawnTicket(*m_spawnableAsset); constexpr size_t NumEntities = 4; FillSpawnable(NumEntities); @@ -599,7 +663,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 +673,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 +688,7 @@ namespace UnitTest EXPECT_EQ(4, spawnedEntitiesCount); EXPECT_TRUE(allReplaced); + EXPECT_TRUE(allEntityIdsPatched); } TEST_F(SpawnableEntitiesManagerTest, SpawnAllEntities_AllAliasesWithAdditional_SourceAndTargetComponentsMerged) @@ -628,7 +696,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 +706,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 +721,7 @@ namespace UnitTest EXPECT_EQ(8, spawnedEntitiesCount); EXPECT_TRUE(allAdded); + EXPECT_TRUE(allEntityIdsPatched); } TEST_F(SpawnableEntitiesManagerTest, SpawnAllEntities_AllAliasesWithMerge_SourceAndTargetComponentsMerged) @@ -657,7 +729,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 +739,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 +754,7 @@ namespace UnitTest EXPECT_EQ(4, spawnedEntitiesCount); EXPECT_TRUE(allMerged); + EXPECT_TRUE(allEntityIdsPatched); } // @@ -1095,7 +1171,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 +1183,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 +1198,7 @@ namespace UnitTest EXPECT_EQ(4, spawnedEntitiesCount); EXPECT_TRUE(allReplaced); + EXPECT_TRUE(allEntityIdsPatched); } TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_AllAliasesWithAdditional_SourceAndTargetComponentsMerged) @@ -1126,7 +1206,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 +1218,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 +1234,7 @@ namespace UnitTest EXPECT_EQ(8, spawnedEntitiesCount); EXPECT_TRUE(allAdded); + EXPECT_TRUE(allEntityIdsPatched); } TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_AllAliasesWithMerge_SourceAndTargetComponentsMerged) @@ -1158,7 +1242,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 +1254,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 +1269,7 @@ namespace UnitTest EXPECT_EQ(4, spawnedEntitiesCount); EXPECT_TRUE(allMerged); + EXPECT_TRUE(allEntityIdsPatched); } // @@ -1302,6 +1390,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) {}; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.cpp index b91a0aac93..418d176daa 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.cpp @@ -415,8 +415,8 @@ namespace AzToolsFramework { // Construct the runtime entities and products bool readyToCreateRootSpawnable = m_playInEditorData.m_assetsCache.IsActivated(); - if (!readyToCreateRootSpawnable && - !m_playInEditorData.m_assetsCache.Activate(Prefab::PrefabConversionUtils::PlayInEditor)) + if (!readyToCreateRootSpawnable && !m_playInEditorData.m_assetsCache.Activate(Prefab::PrefabConversionUtils::PlayInEditor)) + { AZ_Error("Prefab", false, "Failed to create a prefab processing stack from key '%.*s'.", AZ_STRING_ARG(Prefab::PrefabConversionUtils::PlayInEditor)); return; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.cpp index 490a151925..03d62dff0a 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.cpp @@ -142,14 +142,14 @@ namespace AzToolsFramework return m_templateSourcePath; } - void Instance::SetTemplateSourcePath(AZ::IO::PathView sourcePath) + void Instance::SetTemplateSourcePath(AZ::IO::Path sourcePath) { - m_templateSourcePath = sourcePath; + m_templateSourcePath = AZStd::move(sourcePath); } - void Instance::SetContainerEntityName(AZStd::string_view containerName) + void Instance::SetContainerEntityName(AZStd::string containerName) { - m_containerEntity->SetName(containerName); + m_containerEntity->SetName(AZStd::move(containerName)); } bool Instance::AddEntity(AZ::Entity& entity) @@ -608,6 +608,31 @@ namespace AzToolsFramework } AZ::EntityId Instance::GetEntityIdFromAliasPath(AliasPathView relativeAliasPath) const + { + return GetInstanceAndEntityIdFromAliasPath(relativeAliasPath).second; + } + + AZStd::pair Instance::GetInstanceAndEntityIdFromAliasPath(AliasPathView relativeAliasPath) + { + Instance* instance = this; + AliasPathView path = relativeAliasPath.ParentPath(); + for (auto it : path) + { + InstanceOptionalReference child = instance->FindNestedInstance(it.Native()); + if (child.has_value()) + { + instance = &(child->get()); + } + else + { + return AZStd::pair(nullptr, AZ::EntityId()); + } + } + + return AZStd::pair(instance, instance->GetEntityId(relativeAliasPath.Filename().Native())); + } + + AZStd::pair Instance::GetInstanceAndEntityIdFromAliasPath(AliasPathView relativeAliasPath) const { const Instance* instance = this; AliasPathView path = relativeAliasPath.ParentPath(); @@ -620,11 +645,11 @@ namespace AzToolsFramework } else { - return AZ::EntityId(); + return AZStd::pair(nullptr, AZ::EntityId()); } } - return instance->GetEntityId(relativeAliasPath.Filename().Native()); + return AZStd::pair(instance, instance->GetEntityId(relativeAliasPath.Filename().Native())); } AZStd::vector Instance::GetNestedInstanceAliases(TemplateId templateId) const diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.h index 25971093cd..b63eca309a 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.h @@ -80,8 +80,8 @@ namespace AzToolsFramework void SetTemplateId(TemplateId templateId); const AZ::IO::Path& GetTemplateSourcePath() const; - void SetTemplateSourcePath(AZ::IO::PathView sourcePath); - void SetContainerEntityName(AZStd::string_view containerName); + void SetTemplateSourcePath(AZ::IO::Path sourcePath); + void SetContainerEntityName(AZStd::string containerName); bool AddEntity(AZ::Entity& entity); bool AddEntity(AZStd::unique_ptr&& entity); @@ -169,6 +169,13 @@ namespace AzToolsFramework * @return entityId, invalid ID if not found */ AZ::EntityId GetEntityIdFromAliasPath(AliasPathView relativeAliasPath) const; + /** + * Retrieves the instance pointer and entity id from an alias path that's relative to this instance. + * + * @return A pair with the Instance and entity id. The Instance is set to null and entityId is set to invalid if not found. + */ + AZStd::pair GetInstanceAndEntityIdFromAliasPath(AliasPathView relativeAliasPath); + AZStd::pair GetInstanceAndEntityIdFromAliasPath(AliasPathView relativeAliasPath) const; /** diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/EditorInfoRemover.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/EditorInfoRemover.cpp index dfba1d54ba..9488b68ee8 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/EditorInfoRemover.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/EditorInfoRemover.cpp @@ -37,13 +37,13 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils } prefabProcessorContext.ListPrefabs( - [this, &serializeContext, &prefabProcessorContext]([[maybe_unused]] AZStd::string_view prefabName, PrefabDom& prefab) + [this, &serializeContext, &prefabProcessorContext](PrefabDocument& prefab) { auto result = RemoveEditorInfo(prefab, serializeContext, prefabProcessorContext); if (!result) { AZ_Error( - "Prefab", false, "Converting to runtime Prefab '%.*s' failed, Error: %s .", AZ_STRING_ARG(prefabName), + "Prefab", false, "Converting to runtime Prefab '%s' failed, Error: %s .", prefab.GetName().c_str(), result.GetError().c_str()); return; } @@ -58,10 +58,9 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils } } - void EditorInfoRemover::GetEntitiesFromInstance( - AZStd::unique_ptr& instance, EntityList& hierarchyEntities) + void EditorInfoRemover::GetEntitiesFromInstance(AzToolsFramework::Prefab::Instance& instance, EntityList& hierarchyEntities) { - instance->GetAllEntitiesInHierarchy( + instance.GetAllEntitiesInHierarchy( [&hierarchyEntities](const AZStd::unique_ptr& entity) { hierarchyEntities.emplace_back(entity.get()); @@ -498,7 +497,7 @@ exportComponent, prefabProcessorContext); } EditorInfoRemover::RemoveEditorInfoResult EditorInfoRemover::RemoveEditorInfo( - PrefabDom& prefab, + PrefabDocument& prefab, AZ::SerializeContext* serializeContext, PrefabProcessorContext& prefabProcessorContext) { @@ -510,28 +509,10 @@ exportComponent, prefabProcessorContext); m_componentRequirementsValidator.SetPlatformTags(prefabProcessorContext.GetPlatformTags()); - // convert Prefab DOM into Prefab Instance. - AZStd::unique_ptr instance(aznew Instance()); - if (!Prefab::PrefabDomUtils::LoadInstanceFromPrefabDom(*instance, prefab, - Prefab::PrefabDomUtils::LoadFlags::AssignRandomEntityId)) - { - PrefabDomValueReference sourceReference = PrefabDomUtils::FindPrefabDomValue(prefab, PrefabDomUtils::SourceName); - - AZStd::string errorMessage("Failed to Load Prefab Instance from given Prefab Dom during Removal of Editor Info."); - if (sourceReference.has_value() && - sourceReference->get().IsString() && - sourceReference->get().GetStringLength() != 0) - { - AZStd::string_view source(sourceReference->get().GetString(), sourceReference->get().GetStringLength()); - errorMessage += AZStd::string::format("Prefab Source: %.*s", AZ_STRING_ARG(source)); - } - - return AZ::Failure(errorMessage); - } - // grab all nested entities from the Instance as source entities. + Instance& sourceInstance = prefab.GetInstance(); EntityList sourceEntities; - GetEntitiesFromInstance(instance, sourceEntities); + GetEntitiesFromInstance(sourceInstance, sourceEntities); EntityList exportEntities; @@ -597,7 +578,7 @@ exportComponent, prefabProcessorContext); exportEntitiesMap.emplace(entity->GetId(), entity); } ); - instance->RemoveNestedEntities( + sourceInstance.RemoveNestedEntities( [&exportEntitiesMap](const AZStd::unique_ptr& entity) { return exportEntitiesMap.find(entity->GetId()) == exportEntitiesMap.end(); @@ -605,7 +586,7 @@ exportComponent, prefabProcessorContext); ); // replace entities of instance with exported ones. - instance->GetAllEntitiesInHierarchy( + sourceInstance.GetAllEntitiesInHierarchy( [&exportEntitiesMap](AZStd::unique_ptr& entity) { auto entityId = entity->GetId(); @@ -614,16 +595,6 @@ exportComponent, prefabProcessorContext); } ); - // save the final result in the target Prefab DOM. - PrefabDom filteredPrefab; - if (!PrefabDomUtils::StoreInstanceInPrefabDom(*instance, filteredPrefab)) - { - return AZ::Failure(AZStd::string::format( - "Saving exported Prefab Instance within a Prefab Dom failed.") - ); - } - prefab.Swap(filteredPrefab); - return AZ::Success(); } } // namespace AzToolsFramework::Prefab::PrefabConversionUtils diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/EditorInfoRemover.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/EditorInfoRemover.h index dc10952733..dbad58d2f5 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/EditorInfoRemover.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/EditorInfoRemover.h @@ -43,7 +43,7 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils using RemoveEditorInfoResult = AZ::Outcome; RemoveEditorInfoResult RemoveEditorInfo( - PrefabDom& prefab, + PrefabDocument& prefab, AZ::SerializeContext* serializeContext, PrefabProcessorContext& prefabProcessorContext); @@ -51,8 +51,7 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils protected: using EntityList = AZStd::vector; - static void GetEntitiesFromInstance( - AZStd::unique_ptr& instance, EntityList& hierarchyEntities); + static void GetEntitiesFromInstance(AzToolsFramework::Prefab::Instance& instance, EntityList& hierarchyEntities); static bool ReadComponentAttribute( AZ::Component* component, diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/EntityAliasTypes.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/EntityAliasTypes.h new file mode 100644 index 0000000000..9ab1f9bbd1 --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/EntityAliasTypes.h @@ -0,0 +1,69 @@ +/* + * 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 + * + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace AzToolsFramework::Prefab::PrefabConversionUtils +{ + enum class EntityAliasType : uint8_t + { + Disable, //!< No alias is added. + OptionalReplace, //!< At runtime the entity might be replaced. If the alias is disabled the original entity will be spawned. + //!< The original entity will be left in the spawnable and a copy is returned. + Replace, //!< At runtime the entity will be replaced. If the alias is disabled nothing will be spawned. The original + //!< entity is returned and a blank entity is left. + Additional, //!< At runtime the alias entity will be added as an additional but unrelated entity with a new entity id. + //!< An empty entity will be returned. + Merge //!< At runtime the components in both entities will be merged. An empty entity will be returned. The added + //!< components may no conflict with the entities already in the root entity. + }; + + enum class EntityAliasSpawnableLoadBehavior : uint8_t + { + 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. 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. + }; + + struct EntityAliasSpawnableLink + { + EntityAliasSpawnableLink(AzFramework::Spawnable& spawnable, AZ::EntityId index); + + AzFramework::Spawnable& m_spawnable; + AZ::EntityId m_index; + }; + + struct EntityAliasPrefabLink + { + EntityAliasPrefabLink(AZStd::string prefabName, AzToolsFramework::Prefab::AliasPath alias); + + AZStd::string m_prefabName; + AzToolsFramework::Prefab::AliasPath m_alias; + }; + + struct EntityAliasStore + { + using LinkStore = AZStd::variant; + + LinkStore m_source; + LinkStore m_target; + uint32_t m_tag; + AzFramework::Spawnable::EntityAliasType m_aliasType; + EntityAliasSpawnableLoadBehavior m_loadBehavior; + }; +} // namespace AzToolsFramework::Prefab::PrefabConversionUtils diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/InMemorySpawnableAssetContainer.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/InMemorySpawnableAssetContainer.cpp index 553ad8ece4..dc5cd299b8 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/InMemorySpawnableAssetContainer.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/InMemorySpawnableAssetContainer.cpp @@ -112,9 +112,9 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils // Use a random uuid as this is only a temporary source. PrefabConversionUtils::PrefabProcessorContext context(AZ::Uuid::CreateRandom()); - PrefabDom copy; - copy.CopyFrom(templateReference->get().GetPrefabDom(), copy.GetAllocator(), false); - context.AddPrefab(spawnableName, AZStd::move(copy)); + PrefabDocument document(spawnableName); + document.SetPrefabDom(templateReference->get().GetPrefabDom()); + context.AddPrefab(AZStd::move(document)); m_converter.ProcessPrefab(context); if (!context.HasCompletedSuccessfully() || context.GetProcessedObjects().empty()) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/PrefabCatchmentProcessor.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/PrefabCatchmentProcessor.cpp index 7a54950c47..40cd52cac8 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/PrefabCatchmentProcessor.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/PrefabCatchmentProcessor.cpp @@ -25,9 +25,9 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils { AZ::DataStream::StreamType serializationFormat = m_serializationFormat == SerializationFormats::Binary ? AZ::DataStream::StreamType::ST_BINARY : AZ::DataStream::StreamType::ST_XML; - context.ListPrefabs([&context, serializationFormat](AZStd::string_view prefabName, PrefabDom& prefab) + context.ListPrefabs([&context, serializationFormat](PrefabDocument& prefab) { - ProcessPrefab(context, prefabName, prefab, serializationFormat); + ProcessPrefab(context, prefab, serializationFormat); }); } @@ -45,12 +45,12 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils } } - void PrefabCatchmentProcessor::ProcessPrefab(PrefabProcessorContext& context, AZStd::string_view prefabName, PrefabDom& prefab, + void PrefabCatchmentProcessor::ProcessPrefab(PrefabProcessorContext& context, PrefabDocument& prefab, AZ::DataStream::StreamType serializationFormat) { using namespace AzToolsFramework::Prefab::SpawnableUtils; - AZStd::string uniqueName = prefabName; + AZStd::string uniqueName = prefab.GetName(); uniqueName += AzFramework::Spawnable::DotFileExtension; auto serializer = [serializationFormat](AZStd::vector& output, const ProcessedObjectStore& object) -> bool @@ -64,45 +64,34 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils AZStd::move(uniqueName), context.GetSourceUuid(), AZStd::move(serializer)); AZ_Assert(spawnable, "Failed to create a new spawnable."); - Instance instance; - if (Prefab::PrefabDomUtils::LoadInstanceFromPrefabDom( - instance, prefab, object.GetReferencedAssets(), - Prefab::PrefabDomUtils::LoadFlags::AssignRandomEntityId)) // Always assign random entity ids because the spawnable is - // going to be used to create clones of the entities. - { - // Resolve entity aliases that store PrefabDOM information to use the spawnable instead. This is done before the entities are - // moved from the instance as they'd otherwise can't be found. - context.ResolveSpawnableEntityAliases(prefabName, *spawnable, instance); + Instance& instance = prefab.GetInstance(); + // Resolve entity aliases that store PrefabDOM information to use the spawnable instead. This is done before the entities are + // moved from the instance as they'd otherwise can't be found. + context.ResolveSpawnableEntityAliases(prefab.GetName(), *spawnable, instance); - AzFramework::Spawnable::EntityList& entities = spawnable->GetEntities(); - instance.DetachAllEntitiesInHierarchy( - [&entities, &context](AZStd::unique_ptr entity) + AzFramework::Spawnable::EntityList& entities = spawnable->GetEntities(); + instance.DetachAllEntitiesInHierarchy( + [&entities, &context](AZStd::unique_ptr entity) + { + if (entity) { - if (entity) + entity->InvalidateDependencies(); + AZ::Entity::DependencySortOutcome evaluation = entity->EvaluateDependenciesGetDetails(); + if (evaluation.IsSuccess()) { - entity->InvalidateDependencies(); - AZ::Entity::DependencySortOutcome evaluation = entity->EvaluateDependenciesGetDetails(); - if (evaluation.IsSuccess()) - { - entities.emplace_back(AZStd::move(entity)); - } - else - { - AZ_Error( - "Prefabs", false, "Entity '%s' %s cannot be activated for the following reason: %s", - entity->GetName().c_str(), entity->GetId().ToString().c_str(), evaluation.GetError().m_message.c_str()); - context.ErrorEncountered(); - } + entities.emplace_back(AZStd::move(entity)); } - }); + else + { + AZ_Error( + "Prefabs", false, "Entity '%s' %s cannot be activated for the following reason: %s", + entity->GetName().c_str(), entity->GetId().ToString().c_str(), evaluation.GetError().m_message.c_str()); + context.ErrorEncountered(); + } + } + }); - SpawnableUtils::SortEntitiesByTransformHierarchy(*spawnable); - context.GetProcessedObjects().push_back(AZStd::move(object)); - } - else - { - AZ_Error("Prefabs", false, "Failed to convert prefab '%.*s' to a spawnable.", AZ_STRING_ARG(prefabName)); - context.ErrorEncountered(); - } + SpawnableUtils::SortEntitiesByTransformHierarchy(*spawnable); + context.GetProcessedObjects().push_back(AZStd::move(object)); } } // namespace AzToolsFramework::Prefab::PrefabConversionUtils diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/PrefabCatchmentProcessor.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/PrefabCatchmentProcessor.h index 253321f8e7..39a94c86b8 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/PrefabCatchmentProcessor.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/PrefabCatchmentProcessor.h @@ -40,8 +40,7 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils static void Reflect(AZ::ReflectContext* context); protected: - static void ProcessPrefab(PrefabProcessorContext& context, AZStd::string_view prefabName, PrefabDom& prefab, - AZ::DataStream::StreamType serializationFormat); + static void ProcessPrefab(PrefabProcessorContext& context, PrefabDocument& prefab, AZ::DataStream::StreamType serializationFormat); SerializationFormats m_serializationFormat{ SerializationFormats::Binary }; }; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/PrefabDocument.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/PrefabDocument.cpp new file mode 100644 index 0000000000..04fdea30d2 --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/PrefabDocument.cpp @@ -0,0 +1,151 @@ +/* + * 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 +#include +#include + +namespace AzToolsFramework::Prefab::PrefabConversionUtils +{ + PrefabDocument::PrefabDocument(AZStd::string name) + : m_name(AZStd::move(name)) + , m_instance(AZStd::make_unique()) + { + m_instance->SetTemplateSourcePath(AZ::IO::Path("InMemory") / m_name); + } + + bool PrefabDocument::SetPrefabDom(const PrefabDom& prefab) + { + if (ConstructInstanceFromPrefabDom(prefab)) + { + constexpr bool copyConstStrings = true; + m_dom.CopyFrom(prefab, m_dom.GetAllocator(), copyConstStrings); + return true; + } + else + { + return false; + } + } + + bool PrefabDocument::SetPrefabDom(PrefabDom&& prefab) + { + if (ConstructInstanceFromPrefabDom(prefab)) + { + m_dom = AZStd::move(prefab); + return true; + } + else + { + return false; + } + } + + const AZStd::string& PrefabDocument::GetName() const + { + return m_name; + } + + const PrefabDom& PrefabDocument::GetDom() const + { + if (m_isDirty) + { + m_isDirty = !PrefabDomUtils::StoreInstanceInPrefabDom(*m_instance, m_dom); + } + return m_dom; + } + + PrefabDom&& PrefabDocument::TakeDom() + { + if (m_isDirty) + { + [[maybe_unused]] bool storedSuccessfully = PrefabDomUtils::StoreInstanceInPrefabDom(*m_instance, m_dom); + AZ_Assert(storedSuccessfully, "Failed to store Instance '%s' to PrefabDom.", m_name.c_str()); + m_isDirty = false; + } + // After the PrefabDom is moved an empty PrefabDom is left behind. This should be reflected in the Instance, + // so reset it so it's empty as well. + m_instance->Reset(); + m_instance->SetTemplateSourcePath(AZ::IO::Path("InMemory") / m_name); + return AZStd::move(m_dom); + } + + void PrefabDocument::ListEntitiesWithComponentType( + AZ::TypeId componentType, const AZStd::function& callback) const + { + m_instance->GetAllEntitiesInHierarchyConst( + [this, &componentType, &callback](const AZ::Entity& entity) -> bool + { + if (entity.FindComponent(componentType)) + { + return callback(m_instance->GetAliasPathRelativeToInstance(entity.GetId())); + } + else + { + return true; + } + }); + } + + AZ::Entity* PrefabDocument::CreateEntityAlias( + PrefabDocument& source, + AzToolsFramework::Prefab::AliasPathView entity, + AzToolsFramework::Prefab::PrefabConversionUtils::EntityAliasType aliasType, + AzToolsFramework::Prefab::PrefabConversionUtils::EntityAliasSpawnableLoadBehavior loadBehavior, + uint32_t tag, + AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessorContext& context) + { + auto&& [sourceInstance, entityId] = source.m_instance->GetInstanceAndEntityIdFromAliasPath(entity); + if (sourceInstance != nullptr && entityId.IsValid()) + { + return SpawnableUtils::CreateEntityAlias( + source.m_name, *sourceInstance, m_name, *m_instance, entityId, aliasType, loadBehavior, tag, context); + } + else + { + return nullptr; + } + } + + AzToolsFramework::Prefab::Instance& PrefabDocument::GetInstance() + { + // Assume that changes will be made to the instance. + m_isDirty = true; + return *m_instance; + } + + const AzToolsFramework::Prefab::Instance& PrefabDocument::GetInstance() const + { + return *m_instance; + } + + bool PrefabDocument::ConstructInstanceFromPrefabDom(const PrefabDom& prefab) + { + using namespace AzToolsFramework::Prefab; + + m_instance->Reset(); + if (PrefabDomUtils::LoadInstanceFromPrefabDom(*m_instance, prefab, PrefabDomUtils::LoadFlags::AssignRandomEntityId)) + { + return true; + } + else + { +#ifdef AZ_ENABLE_TRACING + AZStd::string_view sourceName = m_name; + PrefabDomValueConstReference sourceReference = PrefabDomUtils::FindPrefabDomValue(prefab, PrefabDomUtils::SourceName); + if (sourceReference.has_value() && sourceReference->get().IsString() && sourceReference->get().GetStringLength() != 0) + { + sourceName = AZStd::string_view(sourceReference->get().GetString(), sourceReference->get().GetStringLength()); + } + AZ_Error( + "PrefabDocument", false, "Failed to construct Prefab instance from given PrefabDOM '%.*s'.", AZ_STRING_ARG(sourceName)); +#endif + return false; + } + } +} // namespace AzToolsFramework::Prefab::PrefabConversionUtils diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/PrefabDocument.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/PrefabDocument.h new file mode 100644 index 0000000000..215daf7f71 --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/PrefabDocument.h @@ -0,0 +1,66 @@ +/* + * 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 + * + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace AzToolsFramework::Prefab::PrefabConversionUtils +{ + class PrefabProcessorContext; + + class PrefabDocument final + { + public: + explicit PrefabDocument(AZStd::string name); + PrefabDocument(const PrefabDocument&) = delete; + PrefabDocument(PrefabDocument&&) = default; + + PrefabDocument& operator=(const PrefabDocument&) = delete; + PrefabDocument& operator=(PrefabDocument&&) = default; + + bool SetPrefabDom(const PrefabDom& prefab); + bool SetPrefabDom(PrefabDom&& prefab); + + const AZStd::string& GetName() const; + const PrefabDom& GetDom() const; + PrefabDom&& TakeDom(); + + template + void ListEntitiesWithComponentType(const AZStd::function& callback) const; + void ListEntitiesWithComponentType( + AZ::TypeId componentType, const AZStd::function& callback) const; + AZ::Entity* CreateEntityAlias( + PrefabDocument& source, + AzToolsFramework::Prefab::AliasPathView entity, + AzToolsFramework::Prefab::PrefabConversionUtils::EntityAliasType aliasType, + AzToolsFramework::Prefab::PrefabConversionUtils::EntityAliasSpawnableLoadBehavior loadBehavior, + uint32_t tag, + AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessorContext& context); + + // Where possible, prefer functions directly on the PrefabDocument Instead of using the Instance. + AzToolsFramework::Prefab::Instance& GetInstance(); + const AzToolsFramework::Prefab::Instance& GetInstance() const; + + private: + bool ConstructInstanceFromPrefabDom(const PrefabDom& prefab); + + mutable PrefabDom m_dom; + AZStd::unique_ptr m_instance; + AZStd::string m_name; + mutable bool m_isDirty{ false }; + }; +} // namespace AzToolsFramework::Prefab::PrefabConversionUtils + +#include diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/PrefabDocument.inl b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/PrefabDocument.inl new file mode 100644 index 0000000000..37b1f1c127 --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/PrefabDocument.inl @@ -0,0 +1,16 @@ +/* + * 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 + * + */ + +namespace AzToolsFramework::Prefab::PrefabConversionUtils +{ + template + void PrefabDocument::ListEntitiesWithComponentType(const AZStd::function& callback) const + { + ListEntitiesWithComponentType(azrtti_typeid(), callback); + } +} // namespace AzToolsFramework::Prefab::PrefabConversionUtils diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/PrefabProcessorContext.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/PrefabProcessorContext.cpp index efef0f53de..12625712b7 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/PrefabProcessorContext.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/PrefabProcessorContext.cpp @@ -7,8 +7,10 @@ */ #include +#include #include #include +#include #include #include @@ -30,27 +32,42 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils : m_sourceUuid(sourceUuid) {} - bool PrefabProcessorContext::AddPrefab(AZStd::string prefabName, PrefabDom prefab) + bool PrefabProcessorContext::AddPrefab(PrefabDocument&& document) { - auto result = m_prefabs.emplace(AZStd::move(prefabName), AZStd::move(prefab)); - return result.second; - } - - void PrefabProcessorContext::ListPrefabs(const AZStd::function& callback) - { - m_isIterating = true; - for (auto& it : m_prefabs) + AZStd::string name = document.GetName(); + if (!m_prefabNames.contains(name)) { - callback(it.first, it.second); + m_prefabNames.emplace(AZStd::move(name)); + // If currently iterating add to pending queue to avoid invalidating the container that's being iterated over. + PrefabContainer& container = m_isIterating ? m_pendingPrefabAdditions : m_prefabs; + container.push_back(AZStd::move(document)); + return true; } - m_isIterating = false; + return false; } - void PrefabProcessorContext::ListPrefabs(const AZStd::function& callback) const + void PrefabProcessorContext::ListPrefabs(const AZStd::function& callback) { - for (const auto& it : m_prefabs) + // Enable iterating state so the prefab container doesn't get invalided. Enabling this flag will cause new prefabs + // to be stored in a temporary buffer that can be moved into the regular prefab container after iterating. + m_isIterating = true; + for (PrefabDocument& document : m_prefabs) { - callback(it.first, it.second); + callback(document); + } + + m_isIterating = false; + m_prefabs.insert( + m_prefabs.end(), AZStd::make_move_iterator(m_pendingPrefabAdditions.begin()), + AZStd::make_move_iterator(m_pendingPrefabAdditions.end())); + m_pendingPrefabAdditions.clear(); + } + + void PrefabProcessorContext::ListPrefabs(const AZStd::function& callback) const + { + for (const PrefabDocument& document : m_prefabs) + { + callback(document); } } @@ -132,6 +149,7 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils { using namespace AzToolsFramework::Prefab; + // Resolve prefab links into spawnable links for the provided spawnable. for (EntityAliasStore& entityAlias : m_entityAliases) { auto sourcePrefab = AZStd::get_if(&entityAlias.m_source); @@ -224,11 +242,35 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils it = aliasVisitors.emplace(source->m_spawnable.GetId(), AZStd::move(visitor)).first; } it->second.AddAlias( - AZ::Data::Asset(&target->m_spawnable, loadBehavior), alias.m_tag, sourceIndex, targetIndex, + AZ::Data::Asset(target->m_spawnable.GetId(), azrtti_typeid()), alias.m_tag, + sourceIndex, targetIndex, alias.m_aliasType, alias.m_loadBehavior == EntityAliasSpawnableLoadBehavior::QueueLoad); // Register the dependency between the two spawnables. RegisterProductAssetDependency(source->m_spawnable.GetId(), target->m_spawnable.GetId(), loadBehavior); + + // Patch up all entity ids so the alias points to the same entity id if needed. + switch (alias.m_aliasType) + { + case AzFramework::Spawnable::EntityAliasType::Original: + continue; + case AzFramework::Spawnable::EntityAliasType::Disable: + continue; + case AzFramework::Spawnable::EntityAliasType::Replace: + break; // Requires entity id for alias in source and target spawnable matches. + case AzFramework::Spawnable::EntityAliasType::Additional: + continue; + case AzFramework::Spawnable::EntityAliasType::Merge: + break; // Requires entity id for alias in source and target spawnable matches. + default: + continue; + } + + auto entityIdMapper = [source, target](const AZ::EntityId& originalId, bool /*isEntityId*/) -> AZ::EntityId + { + return originalId == target->m_index ? source->m_index : originalId; + }; + AZ::EntityUtils::ReplaceEntityIdsAndEntityRefs(&target->m_spawnable, entityIdMapper); } } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/PrefabProcessorContext.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/PrefabProcessorContext.h index d35a09a574..d07761271b 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/PrefabProcessorContext.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/PrefabProcessorContext.h @@ -21,60 +21,12 @@ #include #include #include +#include +#include #include namespace AzToolsFramework::Prefab::PrefabConversionUtils { - enum class EntityAliasType : uint8_t - { - Disable, //!< No alias is added. - OptionalReplace, //!< At runtime the entity might be replaced. If the alias is disabled the original entity will be spawned. - //!< The original entity will be left in the spawnable and a copy is returned. - Replace, //!< At runtime the entity will be replaced. If the alias is disabled nothing will be spawned. The original - //!< entity is returned and a blank entity is left. - Additional, //!< At runtime the alias entity will be added as an additional but unrelated entity with a new entity id. - //!< An empty entity will be returned. - Merge //!< At runtime the components in both entities will be merged. An empty entity will be returned. The added - //!< components may no conflict with the entities already in the root entity. - }; - - enum class EntityAliasSpawnableLoadBehavior : uint8_t - { - 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. 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. - }; - - struct EntityAliasSpawnableLink - { - EntityAliasSpawnableLink(AzFramework::Spawnable& spawnable, AZ::EntityId index); - - AzFramework::Spawnable& m_spawnable; - AZ::EntityId m_index; - }; - - struct EntityAliasPrefabLink - { - EntityAliasPrefabLink(AZStd::string prefabName, AzToolsFramework::Prefab::AliasPath alias); - - AZStd::string m_prefabName; - AzToolsFramework::Prefab::AliasPath m_alias; - }; - - struct EntityAliasStore - { - using LinkStore = AZStd::variant; - - LinkStore m_source; - LinkStore m_target; - uint32_t m_tag; - AzFramework::Spawnable::EntityAliasType m_aliasType; - EntityAliasSpawnableLoadBehavior m_loadBehavior; - }; - struct AssetDependencyInfo { AZ::Data::AssetId m_assetId; @@ -93,9 +45,9 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils explicit PrefabProcessorContext(const AZ::Uuid& sourceUuid); virtual ~PrefabProcessorContext() = default; - virtual bool AddPrefab(AZStd::string prefabName, PrefabDom prefab); - virtual void ListPrefabs(const AZStd::function& callback); - virtual void ListPrefabs(const AZStd::function& callback) const; + virtual bool AddPrefab(PrefabDocument&& document); + virtual void ListPrefabs(const AZStd::function& callback); + virtual void ListPrefabs(const AZStd::function& callback) const; virtual bool HasPrefabs() const; virtual bool RegisterSpawnableProductAssetDependency( @@ -128,12 +80,15 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils virtual void ErrorEncountered(); protected: - using NamedPrefabContainer = AZStd::unordered_map; + using PrefabNames = AZStd::unordered_set; + using PrefabContainer = AZStd::vector; using SpawnableEntityAliasStore = AZStd::vector; AZ::Data::AssetLoadBehavior ToAssetLoadBehavior(EntityAliasSpawnableLoadBehavior loadBehavior) const; - NamedPrefabContainer m_prefabs; + PrefabContainer m_prefabs; + PrefabContainer m_pendingPrefabAdditions; + PrefabNames m_prefabNames; SpawnableEntityAliasStore m_entityAliases; ProcessedObjectStoreContainer m_products; ProductAssetDependencyContainer m_registeredProductAssetDependencies; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/SpawnableUtils.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/SpawnableUtils.cpp index 5e552aad3f..4f007f509e 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/SpawnableUtils.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/SpawnableUtils.cpp @@ -16,10 +16,12 @@ #include #include #include +#include #include #include #include #include +#include namespace AzToolsFramework::Prefab::SpawnableUtils { @@ -52,14 +54,32 @@ namespace AzToolsFramework::Prefab::SpawnableUtils return result; } + const AZ::Entity* FindEntity(AZ::EntityId entityId, const AzToolsFramework::Prefab::Instance& source) + { + const AZ::Entity* result = nullptr; + source.GetConstEntities( + [&result, entityId](const AZ::Entity& entity) + { + if (entity.GetId() != entityId) + { + return true; + } + else + { + result = &entity; + return false; + } + }); + return result; + } + AZ::Entity* FindEntity(AZ::EntityId entityId, AzFramework::Spawnable& source) { uint32_t index = AzToolsFramework::Prefab::SpawnableUtils::FindEntityIndex(entityId, source); return index != InvalidEntityIndex ? source.GetEntities()[index].get() : nullptr; } - template - AZStd::unique_ptr CloneEntity(AZ::EntityId entityId, T& source) + AZStd::unique_ptr CloneEntity(AZ::EntityId entityId, AzToolsFramework::Prefab::Instance& source) { AZ::Entity* target = Internal::FindEntity(entityId, source); AZ_Assert( @@ -74,41 +94,30 @@ namespace AzToolsFramework::Prefab::SpawnableUtils return clone; } - AZStd::unique_ptr ReplaceEntityWithPlaceholder(AZ::EntityId entityId, AzToolsFramework::Prefab::Instance& source) + AZStd::unique_ptr ReplaceEntityWithPlaceholder( + AZ::EntityId entityId, + [[maybe_unused]] AZStd::string_view sourcePrefabName, + AzToolsFramework::Prefab::Instance& source) { auto&& [instance, alias] = source.FindInstanceAndAlias(entityId); AZ_Assert( - instance, "SpawnbleUtils were unable to locate entity alias with id %zu in Instance '%s' for replacing.", - aznumeric_cast(entityId), source.GetTemplateSourcePath().c_str()); + instance, "SpawnbleUtils were unable to locate entity alias with id %zu in Instance '%.*s' for replacing.", + aznumeric_cast(entityId), AZ_STRING_ARG(sourcePrefabName)); EntityOptionalReference entityData = instance->GetEntity(alias); AZ_Assert( - entityData.has_value(), "SpawnbleUtils were unable to locate entity '%.*s' in Instance '%s' for replacing.", - AZ_STRING_ARG(alias), source.GetTemplateSourcePath().c_str()); - auto placeholder = AZStd::make_unique(entityData->get().GetId(), entityData->get().GetName()); + entityData.has_value(), "SpawnbleUtils were unable to locate entity '%.*s' in Instance '%.*s' for replacing.", + AZ_STRING_ARG(alias), AZ_STRING_ARG(sourcePrefabName)); + // A new entity id can be used for the placeholder as `ReplaceEntity` will swap the entity ids. + auto placeholder = AZStd::make_unique(AZ::Entity::MakeId(), entityData->get().GetName()); return instance->ReplaceEntity(AZStd::move(placeholder), alias); } - AZStd::unique_ptr ReplaceEntityWithPlaceholder(AZ::EntityId entityId, AzFramework::Spawnable& source) - { - uint32_t index = AzToolsFramework::Prefab::SpawnableUtils::FindEntityIndex(entityId, source); - AZ_Assert( - index != InvalidEntityIndex, "SpawnbleUtils were unable to locate entity alias with id %zu in Spawnable for replacing.", - aznumeric_cast(entityId)); - - AZStd::unique_ptr original = AZStd::move(source.GetEntities()[index]); - AZ_Assert( - original, "SpawnbleUtils were unable to locate entity with id %zu in Spawnable for replacing.", - aznumeric_cast(entityId)); - - source.GetEntities()[index] = AZStd::make_unique(original->GetId(), original->GetName()); - - return original; - } - - template AZStd::pair, AzFramework::Spawnable::EntityAliasType> ApplyAlias( - Source& source, AZ::EntityId entityId, AzToolsFramework::Prefab::PrefabConversionUtils::EntityAliasType aliasType) + AZStd::string_view sourcePrefabName, + AzToolsFramework::Prefab::Instance& source, + AZ::EntityId entityId, + AzToolsFramework::Prefab::PrefabConversionUtils::EntityAliasType aliasType) { namespace PCU = AzToolsFramework::Prefab::PrefabConversionUtils; using ResultPair = AZStd::pair, AzFramework::Spawnable::EntityAliasType>; @@ -121,12 +130,13 @@ namespace AzToolsFramework::Prefab::SpawnableUtils case PCU::EntityAliasType::OptionalReplace: return ResultPair(CloneEntity(entityId, source), AzFramework::Spawnable::EntityAliasType::Replace); case PCU::EntityAliasType::Replace: - return ResultPair(ReplaceEntityWithPlaceholder(entityId, source), AzFramework::Spawnable::EntityAliasType::Replace); + return ResultPair( + ReplaceEntityWithPlaceholder(entityId, sourcePrefabName, source), + AzFramework::Spawnable::EntityAliasType::Replace); case PCU::EntityAliasType::Additional: - ResultPair(AZStd::make_unique(AZ::Entity::MakeId()), AzFramework::Spawnable::EntityAliasType::Additional); + return ResultPair(AZStd::make_unique(), AzFramework::Spawnable::EntityAliasType::Additional); case PCU::EntityAliasType::Merge: - // Use the same entity id as the original entity so at runtime the entity ids can be verified to match. - ResultPair(AZStd::make_unique(entityId), AzFramework::Spawnable::EntityAliasType::Merge); + return ResultPair(AZStd::make_unique(), AzFramework::Spawnable::EntityAliasType::Merge); default: AZ_Assert( false, "Invalid PrefabProcessorContext::EntityAliasType type (%i) provided.", aznumeric_cast(aliasType)); @@ -182,7 +192,8 @@ namespace AzToolsFramework::Prefab::SpawnableUtils AliasPath alias = source.GetAliasPathRelativeToInstance(entityId); if (!alias.empty()) { - auto&& [replacement, storedAliasType] = Internal::ApplyAlias(source, entityId, aliasType); + auto&& [replacement, storedAliasType] = + Internal::ApplyAlias(sourcePrefabName, source, entityId, aliasType); if (replacement) { AZ::Entity* result = replacement.get(); @@ -212,84 +223,6 @@ namespace AzToolsFramework::Prefab::SpawnableUtils } } - AZ::Entity* CreateEntityAlias( - AZStd::string sourcePrefabName, - AzToolsFramework::Prefab::Instance& source, - AzFramework::Spawnable& target, - AZ::EntityId entityId, - AzToolsFramework::Prefab::PrefabConversionUtils::EntityAliasType aliasType, - AzToolsFramework::Prefab::PrefabConversionUtils::EntityAliasSpawnableLoadBehavior loadBehavior, - uint32_t tag, - AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessorContext& context) - { - using namespace AzToolsFramework::Prefab::PrefabConversionUtils; - - AliasPath alias = source.GetAliasPathRelativeToInstance(entityId); - if (!alias.empty()) - { - auto&& [replacement, storedAliasType] = Internal::ApplyAlias(source, entityId, aliasType); - if (replacement) - { - AZ::Entity* result = replacement.get(); - target.GetEntities().push_back(AZStd::move(replacement)); - - EntityAliasStore store; - store.m_aliasType = storedAliasType; - store.m_source.emplace(AZStd::move(sourcePrefabName), AZStd::move(alias)); - store.m_target.emplace(target, result->GetId()); - store.m_tag = tag; - store.m_loadBehavior = loadBehavior; - context.RegisterSpawnableEntityAlias(AZStd::move(store)); - - return result; - } - else - { - AZ_Assert(false, "A replacement for entity with id %zu could not be created.", static_cast(entityId)); - return nullptr; - } - } - else - { - AZ_Assert(false, "Entity with id %llu was not found in the source prefab.", static_cast(entityId)); - return nullptr; - } - } - - AZ::Entity* CreateEntityAlias( - AzFramework::Spawnable& source, - AzFramework::Spawnable& target, - AZ::EntityId entityId, - AzToolsFramework::Prefab::PrefabConversionUtils::EntityAliasType aliasType, - AzToolsFramework::Prefab::PrefabConversionUtils::EntityAliasSpawnableLoadBehavior loadBehavior, - uint32_t tag, - AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessorContext& context) - { - using namespace AzToolsFramework::Prefab::PrefabConversionUtils; - - auto&& [replacement, storedAliasType] = Internal::ApplyAlias(source, entityId, aliasType); - if (replacement) - { - AZ::Entity* result = replacement.get(); - target.GetEntities().push_back(AZStd::move(replacement)); - - EntityAliasStore store; - store.m_aliasType = storedAliasType; - store.m_source.emplace(source, entityId); - store.m_target.emplace(target, result->GetId()); - store.m_tag = tag; - store.m_loadBehavior = loadBehavior; - context.RegisterSpawnableEntityAlias(AZStd::move(store)); - - return result; - } - else - { - AZ_Assert(false, "A replacement for entity with id %zu could not be created.", static_cast(entityId)); - return nullptr; - } - } - uint32_t FindEntityIndex(AZ::EntityId entity, const AzFramework::Spawnable& spawnable) { auto begin = spawnable.GetEntities().begin(); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/SpawnableUtils.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/SpawnableUtils.h index ea8a49857e..53d007c0c1 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/SpawnableUtils.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/SpawnableUtils.h @@ -12,7 +12,7 @@ #include #include #include -#include +#include namespace AZ { @@ -24,6 +24,11 @@ namespace AzToolsFramework::Prefab class Instance; } +namespace AzToolsFramework::Prefab::PrefabConversionUtils +{ + class PrefabProcessorContext; +} + namespace AzToolsFramework::Prefab::SpawnableUtils { static constexpr uint32_t InvalidEntityIndex = AZStd::numeric_limits::max(); @@ -41,24 +46,7 @@ namespace AzToolsFramework::Prefab::SpawnableUtils AzToolsFramework::Prefab::PrefabConversionUtils::EntityAliasSpawnableLoadBehavior loadBehavior, uint32_t tag, AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessorContext& context); - AZ::Entity* CreateEntityAlias( - AZStd::string sourcePrefabName, - AzToolsFramework::Prefab::Instance& source, - AzFramework::Spawnable& target, - AZ::EntityId entityId, - AzToolsFramework::Prefab::PrefabConversionUtils::EntityAliasType aliasType, - AzToolsFramework::Prefab::PrefabConversionUtils::EntityAliasSpawnableLoadBehavior loadBehavior, - uint32_t tag, - AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessorContext& context); - AZ::Entity* CreateEntityAlias( - AzFramework::Spawnable& source, - AzFramework::Spawnable& target, - AZ::EntityId entityId, - AzToolsFramework::Prefab::PrefabConversionUtils::EntityAliasType aliasType, - AzToolsFramework::Prefab::PrefabConversionUtils::EntityAliasSpawnableLoadBehavior loadBehavior, - uint32_t tag, - AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessorContext& context); - + uint32_t FindEntityIndex(AZ::EntityId entity, const AzFramework::Spawnable& spawnable); void SortEntitiesByTransformHierarchy(AzFramework::Spawnable& spawnable); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake b/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake index 2c25cc9222..05c07afecd 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake @@ -723,6 +723,7 @@ set(FILES Prefab/Spawnable/EditorOnlyEntityHandler/UiEditorOnlyEntityHandler.cpp Prefab/Spawnable/EditorOnlyEntityHandler/WorldEditorOnlyEntityHandler.h Prefab/Spawnable/EditorOnlyEntityHandler/WorldEditorOnlyEntityHandler.cpp + Prefab/Spawnable/EntityAliasTypes.h Prefab/Spawnable/InMemorySpawnableAssetContainer.h Prefab/Spawnable/InMemorySpawnableAssetContainer.cpp Prefab/Spawnable/PrefabCatchmentProcessor.h @@ -730,6 +731,9 @@ set(FILES Prefab/Spawnable/PrefabConversionPipeline.h Prefab/Spawnable/PrefabConversionPipeline.cpp Prefab/Spawnable/PrefabConverterStackProfileNames.h + Prefab/Spawnable/PrefabDocument.h + Prefab/Spawnable/PrefabDocument.inl + Prefab/Spawnable/PrefabDocument.cpp Prefab/Spawnable/ProcesedObjectStore.h Prefab/Spawnable/ProcesedObjectStore.cpp Prefab/Spawnable/PrefabProcessor.h diff --git a/Code/Framework/AzToolsFramework/Tests/Prefab/Benchmark/Spawnable/SpawnAllEntitiesBenchmarks.cpp b/Code/Framework/AzToolsFramework/Tests/Prefab/Benchmark/Spawnable/SpawnAllEntitiesBenchmarks.cpp index ecbbe10c39..ba0c3a4838 100644 --- a/Code/Framework/AzToolsFramework/Tests/Prefab/Benchmark/Spawnable/SpawnAllEntitiesBenchmarks.cpp +++ b/Code/Framework/AzToolsFramework/Tests/Prefab/Benchmark/Spawnable/SpawnAllEntitiesBenchmarks.cpp @@ -25,7 +25,7 @@ namespace Benchmark for (auto _ : state) { state.PauseTiming(); - m_spawnTicket = new AzFramework::EntitySpawnTicket(m_spawnableAsset); + m_spawnTicket = aznew AzFramework::EntitySpawnTicket(m_spawnableAsset); state.ResumeTiming(); for (uint64_t spwanableCounter = 0; spwanableCounter < spawnAllEntitiesCallCount; spwanableCounter++) @@ -62,7 +62,7 @@ namespace Benchmark for (auto _ : state) { state.PauseTiming(); - m_spawnTicket = new AzFramework::EntitySpawnTicket(m_spawnableAsset); + m_spawnTicket = aznew AzFramework::EntitySpawnTicket(m_spawnableAsset); state.ResumeTiming(); AzFramework::SpawnableEntitiesInterface::Get()->SpawnAllEntities(*m_spawnTicket); @@ -93,15 +93,16 @@ namespace Benchmark SetUpSpawnableAsset(entityCountInSpawnable); + auto spawner = AzFramework::SpawnableEntitiesInterface::Get(); for (auto _ : state) { state.PauseTiming(); - m_spawnTicket = new AzFramework::EntitySpawnTicket(m_spawnableAsset); + m_spawnTicket = aznew AzFramework::EntitySpawnTicket(m_spawnableAsset); state.ResumeTiming(); for (uint64_t spawnCallCounter = 0; spawnCallCounter < spawnCallCount; spawnCallCounter++) { - AzFramework::SpawnableEntitiesInterface::Get()->SpawnAllEntities(*m_spawnTicket); + spawner->SpawnAllEntities(*m_spawnTicket); } m_rootSpawnableInterface->ProcessSpawnableQueue(); diff --git a/Code/Framework/AzToolsFramework/Tests/Prefab/SpawnableRemoveEditorInfoTestFixture.cpp b/Code/Framework/AzToolsFramework/Tests/Prefab/SpawnableRemoveEditorInfoTestFixture.cpp index b8ce3c7c42..0d75227e38 100644 --- a/Code/Framework/AzToolsFramework/Tests/Prefab/SpawnableRemoveEditorInfoTestFixture.cpp +++ b/Code/Framework/AzToolsFramework/Tests/Prefab/SpawnableRemoveEditorInfoTestFixture.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -201,15 +202,14 @@ namespace UnitTest { ConvertSourceEntitiesToPrefab(); + AzToolsFramework::Prefab::PrefabConversionUtils::PrefabDocument prefab("Test"); + prefab.SetPrefabDom(m_prefabDom); const bool actualResult = - m_editorInfoRemover.RemoveEditorInfo(m_prefabDom, m_serializeContext, m_prefabProcessorContext).IsSuccess(); + m_editorInfoRemover.RemoveEditorInfo(prefab, m_serializeContext, m_prefabProcessorContext).IsSuccess(); EXPECT_EQ(expectedResult, actualResult); - AZStd::unique_ptr convertedInstance(aznew Instance()); - ASSERT_TRUE(AzToolsFramework::Prefab::PrefabDomUtils::LoadInstanceFromPrefabDom(*convertedInstance, m_prefabDom)); - - convertedInstance->DetachAllEntitiesInHierarchy( + prefab.GetInstance().DetachAllEntitiesInHierarchy( [this](AZStd::unique_ptr entity) { m_runtimeEntities.emplace_back(entity.release()); diff --git a/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp b/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp index 2a6baed411..a797523bc0 100644 --- a/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp +++ b/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp @@ -20,11 +20,10 @@ namespace Multiplayer { - using AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessor; - using AzToolsFramework::Prefab::PrefabConversionUtils::ProcessedObjectStore; - - void NetworkPrefabProcessor::Process(PrefabProcessorContext& context) + void NetworkPrefabProcessor::Process(AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessorContext& context) { + using AzToolsFramework::Prefab::PrefabConversionUtils::PrefabDocument; + IMultiplayerTools* mpTools = AZ::Interface::Get(); if (mpTools) { @@ -33,11 +32,17 @@ namespace Multiplayer AZ::DataStream::StreamType serializationFormat = GetAzSerializationFormat(); - context.ListPrefabs([&context, serializationFormat](AZStd::string_view prefabName, PrefabDom& prefab) { - ProcessPrefab(context, prefabName, prefab, serializationFormat); - }); + bool networkPrefabsAdded = false; + context.ListPrefabs( + [&networkPrefabsAdded, &context, serializationFormat](PrefabDocument& prefab) + { + if (ProcessPrefab(context, prefab, serializationFormat)) + { + networkPrefabsAdded = true; + } + }); - if (mpTools && !context.GetProcessedObjects().empty()) + if (mpTools && networkPrefabsAdded) { mpTools->SetDidProcessNetworkPrefabs(true); } @@ -59,28 +64,6 @@ namespace Multiplayer } } - static AZStd::unique_ptr LoadInstanceFromPrefab(const PrefabDom& prefab) - { - using namespace AzToolsFramework::Prefab; - - // convert Prefab DOM into Prefab Instance. - AZStd::unique_ptr sourceInstance(aznew Instance()); - if (!PrefabDomUtils::LoadInstanceFromPrefabDom(*sourceInstance, prefab, PrefabDomUtils::LoadFlags::AssignRandomEntityId)) - { - PrefabDomValueConstReference sourceReference = PrefabDomUtils::FindPrefabDomValue(prefab, PrefabDomUtils::SourceName); - - AZStd::string errorMessage("NetworkPrefabProcessor: Failed to Load Prefab Instance from given Prefab Dom."); - if (sourceReference.has_value() && sourceReference->get().IsString() && sourceReference->get().GetStringLength() != 0) - { - AZStd::string_view source(sourceReference->get().GetString(), sourceReference->get().GetStringLength()); - errorMessage += AZStd::string::format("Prefab Source: %.*s", AZ_STRING_ARG(source)); - } - AZ_Error("NetworkPrefabProcessor", false, errorMessage.c_str()); - return nullptr; - } - return sourceInstance; - } - static void GatherNetEntities( AzToolsFramework::Prefab::Instance* instance, AZStd::unordered_map& entityToInstanceMap, @@ -103,18 +86,15 @@ namespace Multiplayer }); } - void NetworkPrefabProcessor::ProcessPrefab(PrefabProcessorContext& context, AZStd::string_view prefabName, PrefabDom& prefab, AZ::DataStream::StreamType serializationFormat) + bool NetworkPrefabProcessor::ProcessPrefab( + AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessorContext& context, + AzToolsFramework::Prefab::PrefabConversionUtils::PrefabDocument& prefab, + AZ::DataStream::StreamType serializationFormat) { + using AzToolsFramework::Prefab::PrefabConversionUtils::ProcessedObjectStore; using namespace AzToolsFramework::Prefab; - // convert Prefab DOM into Prefab Instance. - AZStd::unique_ptr sourceInstance = LoadInstanceFromPrefab(prefab); - if (!sourceInstance) - { - return; - } - - AZStd::string uniqueName = prefabName; + AZStd::string uniqueName = prefab.GetName(); uniqueName += ".network.spawnable"; auto serializer = [serializationFormat](AZStd::vector& output, const ProcessedObjectStore& object) -> bool { @@ -127,15 +107,16 @@ namespace Multiplayer ProcessedObjectStore::Create(uniqueName, context.GetSourceUuid(), AZStd::move(serializer)); auto& netSpawnableEntities = networkSpawnable->GetEntities(); + Instance& sourceInstance = prefab.GetInstance(); // Grab all net entities with their corresponding Instances to handle nested prefabs correctly AZStd::unordered_map netEntityToInstanceMap; AZStd::vector prefabNetEntities; - GatherNetEntities(sourceInstance.get(), netEntityToInstanceMap, prefabNetEntities); + GatherNetEntities(&sourceInstance, netEntityToInstanceMap, prefabNetEntities); if (prefabNetEntities.empty()) { // No networked entities in the prefab, no need to do anything in this processor. - return; + return false; } // Sort the entities prior to processing. The entities will end up in the net spawnable in this order. @@ -182,7 +163,7 @@ namespace Multiplayer // Add net spawnable asset holder to the prefab root { - EntityOptionalReference containerEntityRef = sourceInstance->GetContainerEntity(); + EntityOptionalReference containerEntityRef = sourceInstance.GetContainerEntity(); if (containerEntityRef.has_value()) { auto* networkSpawnableHolderComponent = containerEntityRef.value().get().CreateComponent(); @@ -193,18 +174,12 @@ namespace Multiplayer AZ::Entity* networkSpawnableHolderEntity = aznew AZ::Entity(uniqueName); auto* networkSpawnableHolderComponent = networkSpawnableHolderEntity->CreateComponent(); networkSpawnableHolderComponent->SetNetworkSpawnableAsset(networkSpawnableAsset); - sourceInstance->AddEntity(*networkSpawnableHolderEntity); + sourceInstance.AddEntity(*networkSpawnableHolderEntity); } } - // save the final result in the target Prefab DOM. - if (!PrefabDomUtils::StoreInstanceInPrefabDom(*sourceInstance, prefab)) - { - AZ_Error("NetworkPrefabProcessor", false, "Saving exported Prefab Instance within a Prefab Dom failed."); - return; - } - context.GetProcessedObjects().push_back(AZStd::move(object)); + return true; } AZ::DataStream::StreamType NetworkPrefabProcessor::GetAzSerializationFormat() const diff --git a/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.h b/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.h index 0fd3529db7..ef8912467d 100644 --- a/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.h +++ b/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.h @@ -14,23 +14,23 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils { class PrefabProcessorContext; + class PrefabDocument; } namespace Multiplayer { - using AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessor; - using AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessorContext; - using AzToolsFramework::Prefab::PrefabDom; - - class NetworkPrefabProcessor : public PrefabProcessor + class NetworkPrefabProcessor : public AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessor { public: AZ_CLASS_ALLOCATOR(NetworkPrefabProcessor, AZ::SystemAllocator, 0); - AZ_RTTI(Multiplayer::NetworkPrefabProcessor, "{AF6C36DA-CBB9-4DF4-AE2D-7BC6CCE65176}", PrefabProcessor); + AZ_RTTI( + Multiplayer::NetworkPrefabProcessor, + "{AF6C36DA-CBB9-4DF4-AE2D-7BC6CCE65176}", + AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessor); ~NetworkPrefabProcessor() override = default; - void Process(PrefabProcessorContext& context) override; + void Process(AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessorContext& context) override; static void Reflect(AZ::ReflectContext* context); @@ -44,7 +44,10 @@ namespace Multiplayer AZ::DataStream::StreamType GetAzSerializationFormat() const; protected: - static void ProcessPrefab(PrefabProcessorContext& context, AZStd::string_view prefabName, PrefabDom& prefab, AZ::DataStream::StreamType serializationFormat); + static bool ProcessPrefab( + AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessorContext& context, + AzToolsFramework::Prefab::PrefabConversionUtils::PrefabDocument& prefab, + AZ::DataStream::StreamType serializationFormat); SerializationFormats m_serializationFormat = SerializationFormats::Binary; }; diff --git a/Gems/Multiplayer/Code/Tests/PrefabProcessingTests.cpp b/Gems/Multiplayer/Code/Tests/PrefabProcessingTests.cpp index 42a817987e..aef13fbfe2 100644 --- a/Gems/Multiplayer/Code/Tests/PrefabProcessingTests.cpp +++ b/Gems/Multiplayer/Code/Tests/PrefabProcessingTests.cpp @@ -57,6 +57,7 @@ namespace UnitTest TEST_F(PrefabProcessingTestFixture, NetworkPrefabProcessor_ProcessPrefabTwoEntities_NetEntityGoesToNetSpawnable) { using AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessorContext; + using AzToolsFramework::Prefab::PrefabConversionUtils::PrefabDocument; AZStd::vector entities; @@ -74,7 +75,9 @@ namespace UnitTest // Add the prefab into the Prefab Processor Context const AZStd::string prefabName = "testPrefab"; PrefabProcessorContext prefabProcessorContext{AZ::Uuid::CreateRandom()}; - prefabProcessorContext.AddPrefab(prefabName, AZStd::move(prefabDom)); + PrefabDocument document(prefabName); + ASSERT_TRUE(document.SetPrefabDom(AZStd::move(prefabDom))); + prefabProcessorContext.AddPrefab(AZStd::move(document)); // Request NetworkPrefabProcessor to process the prefab Multiplayer::NetworkPrefabProcessor processor; diff --git a/Gems/Prefab/PrefabBuilder/PrefabBuilderComponent.cpp b/Gems/Prefab/PrefabBuilder/PrefabBuilderComponent.cpp index 6107226783..71464501ba 100644 --- a/Gems/Prefab/PrefabBuilder/PrefabBuilderComponent.cpp +++ b/Gems/Prefab/PrefabBuilder/PrefabBuilderComponent.cpp @@ -237,7 +237,7 @@ namespace AZ::Prefab bool PrefabBuilderComponent::ProcessPrefab( const AZ::PlatformTagSet& platformTags, const char* filePath, AZ::IO::PathView tempDirPath, const AZ::Uuid& sourceFileUuid, - AzToolsFramework::Prefab::PrefabDom& mutableRootDom, AZStd::vector& jobProducts) + AzToolsFramework::Prefab::PrefabDom&& rootDom, AZStd::vector& jobProducts) { AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessorContext context(sourceFileUuid); AZStd::string rootPrefabName; @@ -247,7 +247,9 @@ namespace AZ::Prefab filePath); return false; } - context.AddPrefab(AZStd::move(rootPrefabName), AZStd::move(mutableRootDom)); + AzToolsFramework::Prefab::PrefabConversionUtils::PrefabDocument rootDocument(AZStd::move(rootPrefabName)); + rootDocument.SetPrefabDom(AZStd::move(rootDom)); + context.AddPrefab(AZStd::move(rootDocument)); context.SetPlatformTags(AZStd::move(platformTags)); @@ -319,8 +321,8 @@ namespace AZ::Prefab }); if (ProcessPrefab( - platformTags, request.m_fullPath.c_str(), request.m_tempDirPath.c_str(), request.m_sourceFileUUID, mutableRootDom, - response.m_outputProducts)) + platformTags, request.m_fullPath.c_str(), request.m_tempDirPath.c_str(), request.m_sourceFileUUID, + AZStd::move(mutableRootDom), response.m_outputProducts)) { response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success; } diff --git a/Gems/Prefab/PrefabBuilder/PrefabBuilderComponent.h b/Gems/Prefab/PrefabBuilder/PrefabBuilderComponent.h index 9ebdd690f5..73ef6b0426 100644 --- a/Gems/Prefab/PrefabBuilder/PrefabBuilderComponent.h +++ b/Gems/Prefab/PrefabBuilder/PrefabBuilderComponent.h @@ -53,7 +53,7 @@ namespace AZ::Prefab const AzToolsFramework::Prefab::PrefabDom& genericDocument); bool ProcessPrefab( const AZ::PlatformTagSet& platformTags, const char* filePath, AZ::IO::PathView tempDirPath, const AZ::Uuid& sourceFileUuid, - AzToolsFramework::Prefab::PrefabDom& mutableRootDom, + AzToolsFramework::Prefab::PrefabDom&& rootDom, AZStd::vector& jobProducts); protected: diff --git a/Gems/Prefab/PrefabBuilder/PrefabBuilderTests.cpp b/Gems/Prefab/PrefabBuilder/PrefabBuilderTests.cpp index 2ed05b4dfe..5ee23a9e3c 100644 --- a/Gems/Prefab/PrefabBuilder/PrefabBuilderTests.cpp +++ b/Gems/Prefab/PrefabBuilder/PrefabBuilderTests.cpp @@ -106,7 +106,8 @@ namespace UnitTest AzToolsFramework::Prefab::PrefabDom prefabDom; prefabDom.CopyFrom(prefabSystemComponentInterface->FindTemplateDom(parentInstance->GetTemplateId()), prefabDom.GetAllocator(), false); - ASSERT_TRUE(prefabBuilderComponent.ProcessPrefab({AZ::Crc32("pc")}, "parent.prefab", "unused", AZ::Uuid(), prefabDom, jobProducts)); + ASSERT_TRUE(prefabBuilderComponent.ProcessPrefab( + { AZ::Crc32("pc") }, "parent.prefab", "unused", AZ::Uuid(), AZStd::move(prefabDom), jobProducts)); ASSERT_EQ(jobProducts.size(), 1); ASSERT_EQ(jobProducts[0].m_dependencies.size(), 1); diff --git a/Gems/Vegetation/Code/Source/PrefabInstanceSpawner.cpp b/Gems/Vegetation/Code/Source/PrefabInstanceSpawner.cpp index b386f17cab..c7c0ef5d3d 100644 --- a/Gems/Vegetation/Code/Source/PrefabInstanceSpawner.cpp +++ b/Gems/Vegetation/Code/Source/PrefabInstanceSpawner.cpp @@ -342,7 +342,7 @@ namespace Vegetation // 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); + AzFramework::EntitySpawnTicket* ticket = aznew AzFramework::EntitySpawnTicket(m_spawnableAsset); if (ticket->IsValid()) { // Track the ticket that we've created.