From d17390befbb463fa371ed1874e39ec89635172df Mon Sep 17 00:00:00 2001 From: pereslav Date: Fri, 2 Jul 2021 16:01:30 +0100 Subject: [PATCH] LYN-4866 Fixed net entities indices when creating net spawnable Signed-off-by: pereslav --- .../Prefab/Spawnable/SpawnableUtils.cpp | 26 ++++--- .../Prefab/Spawnable/SpawnableUtils.h | 7 ++ .../Pipeline/NetworkPrefabProcessor.cpp | 69 +++++++------------ 3 files changed, 48 insertions(+), 54 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/SpawnableUtils.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/SpawnableUtils.cpp index f23f9c6187..65e4c14276 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/SpawnableUtils.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/SpawnableUtils.cpp @@ -46,10 +46,11 @@ namespace AzToolsFramework::Prefab::SpawnableUtils } } + template void OrganizeEntitiesForSorting( - AzFramework::Spawnable::EntityList& entities, + AZStd::vector& entities, AZStd::unordered_set& existingEntityIds, - AZStd::unordered_map& parentIdToChildren, + AZStd::unordered_map>& parentIdToChildren, AZStd::vector& candidateIds, size_t& removedEntitiesCount) { @@ -90,7 +91,7 @@ namespace AzToolsFramework::Prefab::SpawnableUtils // entities with no transform component will be treated like entities with no parent. AZ::EntityId parentId; if (AZ::TransformInterface* transformInterface = - AZ::EntityUtils::FindFirstDerivedComponent(entity.get())) + AZ::EntityUtils::FindFirstDerivedComponent(&(*entity))) { parentId = transformInterface->GetParentId(); if (parentId == entityId) @@ -104,8 +105,7 @@ namespace AzToolsFramework::Prefab::SpawnableUtils } auto& children = parentIdToChildren[parentId]; - children.emplace_back(nullptr); - children.back().swap(entity); + children.emplace_back(AZStd::move(entity)); } // clear 'entities', we'll refill it in sorted order. @@ -125,9 +125,10 @@ namespace AzToolsFramework::Prefab::SpawnableUtils } + template void TraceParentingLoop( const AZ::EntityId& parentFromLoopId, - const AZStd::unordered_map& parentIdToChildren) + const AZStd::unordered_map>& parentIdToChildren) { // Find name to use in warning message @@ -153,16 +154,22 @@ namespace AzToolsFramework::Prefab::SpawnableUtils parentFromLoopId.ToString().c_str()); } + void SortEntitiesByTransformHierarchy(AzFramework::Spawnable& spawnable) { - auto& entities = spawnable.GetEntities(); + SortEntitiesByTransformHierarchy(spawnable.GetEntities()); + } + + template + void SortEntitiesByTransformHierarchy(AZStd::vector& entities) + { const size_t originalEntityCount = entities.size(); // IDs of those present in 'entities'. Does not include parent ID if parent not found in 'entities' AZStd::unordered_set existingEntityIds; // map children by their parent ID (even if parent not found in 'entities') - AZStd::unordered_map parentIdToChildren; + AZStd::unordered_map> parentIdToChildren; // use 'candidateIds' to track the parent IDs we're going to process next. AZStd::vector candidateIds; @@ -199,8 +206,7 @@ namespace AzToolsFramework::Prefab::SpawnableUtils for (auto& child : foundChildren->second) { candidateIds.push_back(child->GetId()); - entities.emplace_back(nullptr); - entities.back().swap(child); + entities.emplace_back(AZStd::move(child)); } parentIdToChildren.erase(foundChildren); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/SpawnableUtils.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/SpawnableUtils.h index e727f3487d..f46c94d4ec 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/SpawnableUtils.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/SpawnableUtils.h @@ -16,4 +16,11 @@ namespace AzToolsFramework::Prefab::SpawnableUtils bool CreateSpawnable(AzFramework::Spawnable& spawnable, const PrefabDom& prefabDom, AZStd::vector>& referencedAssets); void SortEntitiesByTransformHierarchy(AzFramework::Spawnable& spawnable); + + template + void SortEntitiesByTransformHierarchy(AZStd::vector& entities); + + // Explicit specializations + template void SortEntitiesByTransformHierarchy(AZStd::vector& entities); + template void SortEntitiesByTransformHierarchy(AZStd::vector>& entities); } // namespace AzToolsFramework::Prefab::SpawnableUtils diff --git a/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp b/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp index 3452a79e25..41b8466bc5 100644 --- a/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp +++ b/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp @@ -72,21 +72,24 @@ namespace Multiplayer } static void GatherNetEntities( - AzToolsFramework::Prefab::Instance* instance, - AZStd::vector>& output) + AzToolsFramework::Prefab::Instance* instance, + AZStd::unordered_map& entityToInstanceMap, + AZStd::vector& netEntities) { - instance->GetEntities([instance, &output](AZStd::unique_ptr& prefabEntity) + instance->GetEntities([instance, &entityToInstanceMap, &netEntities](AZStd::unique_ptr& prefabEntity) { if (prefabEntity->FindComponent()) { - output.push_back(AZStd::make_pair(prefabEntity.get(), instance)); + AZ::Entity* entity = prefabEntity.get(); + entityToInstanceMap[entity] = instance; + netEntities.push_back(entity); } return true; }); - instance->GetNestedInstances([&output](AZStd::unique_ptr& nestedInstance) + instance->GetNestedInstances([&entityToInstanceMap, &netEntities](AZStd::unique_ptr& nestedInstance) { - GatherNetEntities(nestedInstance.get(), output); + GatherNetEntities(nestedInstance.get(), entityToInstanceMap, netEntities); }); } @@ -112,33 +115,32 @@ namespace Multiplayer auto&& [object, networkSpawnable] = ProcessedObjectStore::Create(uniqueName, context.GetSourceUuid(), AZStd::move(serializer)); + auto& netSpawnableEntities = networkSpawnable->GetEntities(); // Grab all net entities with their corresponding Instances to handle nested prefabs correctly - AZStd::vector> netEntities; - GatherNetEntities(sourceInstance.get(), netEntities); + AZStd::unordered_map netEntityToInstanceMap; + AZStd::vector prefabNetEntities; + GatherNetEntities(sourceInstance.get(), netEntityToInstanceMap, prefabNetEntities); - if (netEntities.empty()) + if (prefabNetEntities.empty()) { // No networked entities in the prefab, no need to do anything in this processor. return; } - // Instance container for net entities - AZStd::unique_ptr networkInstance(aznew Instance()); - networkInstance->SetTemplateSourcePath(AZ::IO::PathView(uniqueName)); + // Sort the entities prior to processing. The entities will end up in the net spawnable in this order. + SpawnableUtils::SortEntitiesByTransformHierarchy(prefabNetEntities); // Create an asset for our future network spawnable: this allows us to put references to the asset in the components AZ::Data::Asset networkSpawnableAsset; networkSpawnableAsset.Create(networkSpawnable->GetId()); networkSpawnableAsset.SetAutoLoadBehavior(AZ::Data::AssetLoadBehavior::PreLoad); - // Each spawnable has a root meta-data entity at position 0, so starting net indices from 1 - size_t netEntitiesIndexCounter = 1; + size_t netEntitiesIndexCounter = 0; - for (auto& entityInstancePair : netEntities) + for (auto* prefabEntity : prefabNetEntities) { - AZ::Entity* prefabEntity = entityInstancePair.first; - Instance* instance = entityInstancePair.second; + Instance* instance = netEntityToInstanceMap[prefabEntity]; AZ::EntityId entityId = prefabEntity->GetId(); AZ::Entity* netEntity = instance->DetachEntity(entityId).release(); @@ -147,7 +149,11 @@ namespace Multiplayer // Net entity will need a new ID to avoid IDs collision netEntity->SetId(AZ::Entity::MakeId()); - networkInstance->AddEntity(*netEntity); + netEntity->InvalidateDependencies(); + netEntity->EvaluateDependencies(); + + // Insert the entity into the target net spawnable + netSpawnableEntities.emplace_back(netEntity); // Use the old ID for the breadcrumb entity to keep parent-child relationship in the original spawnable AZ::Entity* breadcrumbEntity = aznew AZ::Entity(entityId, netEntity->GetName()); @@ -185,37 +191,12 @@ namespace Multiplayer } // save the final result in the target Prefab DOM. - PrefabDom networkPrefab; - if (!PrefabDomUtils::StoreInstanceInPrefabDom(*networkInstance, networkPrefab)) - { - AZ_Error("NetworkPrefabProcessor", false, "Saving exported Prefab Instance within a Prefab Dom failed."); - return; - } - if (!PrefabDomUtils::StoreInstanceInPrefabDom(*sourceInstance, prefab)) { AZ_Error("NetworkPrefabProcessor", false, "Saving exported Prefab Instance within a Prefab Dom failed."); return; } - bool result = SpawnableUtils::CreateSpawnable(*networkSpawnable, networkPrefab); - if (result) - { - AzFramework::Spawnable::EntityList& entities = networkSpawnable->GetEntities(); - for (auto it = entities.begin(); it != entities.end(); ++it) - { - (*it)->InvalidateDependencies(); - (*it)->EvaluateDependencies(); - } - - SpawnableUtils::SortEntitiesByTransformHierarchy(*networkSpawnable); - - 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(); - } + context.GetProcessedObjects().push_back(AZStd::move(object)); } }