From c4c25fc21fbaa6b89790643164c6d2c48f859498 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 16 Aug 2024 14:49:43 +0200 Subject: [PATCH] Optimize `SceneObjectsFactory::SynchronizeNewPrefabInstances` when loading large scenes --- Source/Engine/Level/Prefabs/Prefab.cpp | 6 ++++ Source/Engine/Level/Prefabs/Prefab.h | 5 +++ Source/Engine/Level/SceneObjectsFactory.cpp | 36 ++++++++++++--------- 3 files changed, 31 insertions(+), 16 deletions(-) diff --git a/Source/Engine/Level/Prefabs/Prefab.cpp b/Source/Engine/Level/Prefabs/Prefab.cpp index 066ae04be..b85be7287 100644 --- a/Source/Engine/Level/Prefabs/Prefab.cpp +++ b/Source/Engine/Level/Prefabs/Prefab.cpp @@ -148,6 +148,10 @@ Asset::LoadResult Prefab::loadAsset() ObjectsDataCache.Add(objectId, &objData); ObjectsCount++; + Guid parentID; + if (JsonTools::GetGuidIfValid(parentID, objData, "ParentID")) + ObjectsHierarchyCache[parentID].Add(objectId); + Guid prefabId = JsonTools::GetGuid(objData, "PrefabID"); if (prefabId.IsValid() && !NestedPrefabs.Contains(prefabId)) { @@ -186,6 +190,8 @@ void Prefab::unload(bool isReloading) NestedPrefabs.Resize(0); ObjectsDataCache.Clear(); ObjectsDataCache.SetCapacity(0); + ObjectsHierarchyCache.Clear(); + ObjectsHierarchyCache.SetCapacity(0); ObjectsCache.Clear(); ObjectsCache.SetCapacity(0); if (_defaultInstance) diff --git a/Source/Engine/Level/Prefabs/Prefab.h b/Source/Engine/Level/Prefabs/Prefab.h index f5339c9ec..5b7de8637 100644 --- a/Source/Engine/Level/Prefabs/Prefab.h +++ b/Source/Engine/Level/Prefabs/Prefab.h @@ -41,6 +41,11 @@ public: /// Dictionary ObjectsDataCache; + /// + /// The object hierarchy cache that maps the PrefabObjectID into the list of children (identified also by PrefabObjectID). Objects without any children are not included for sake of optimization. Used for quick validation of the structure of loaded prefab instances. Valid only if asset is loaded. + /// + Dictionary> ObjectsHierarchyCache; + /// /// The objects cache maps the id of the object contained in the prefab asset (actor or script) to the default instance deserialized from prefab data. Valid only if asset is loaded and GetDefaultInstance was called. /// diff --git a/Source/Engine/Level/SceneObjectsFactory.cpp b/Source/Engine/Level/SceneObjectsFactory.cpp index 6f7b672fa..520154b28 100644 --- a/Source/Engine/Level/SceneObjectsFactory.cpp +++ b/Source/Engine/Level/SceneObjectsFactory.cpp @@ -719,26 +719,21 @@ void SceneObjectsFactory::SynchronizePrefabInstances(Context& context, PrefabSyn void SceneObjectsFactory::SynchronizeNewPrefabInstances(Context& context, PrefabSyncData& data, Prefab* prefab, Actor* actor, const Guid& actorPrefabObjectId, int32 i, const ISerializable::DeserializeStream& stream) { - // Check for RemovedObjects list + // Use cached acceleration structure for prefab hierarchy validation + const auto* hierarchy = prefab->ObjectsHierarchyCache.TryGet(actorPrefabObjectId); + if (!hierarchy) + return; + const Guid actorId = actor->GetID(); const auto removedObjects = SERIALIZE_FIND_MEMBER(stream, "RemovedObjects"); - - // Check if the given actor has new children or scripts added (inside the prefab that it uses) - // TODO: consider caching prefab objects structure maybe to boost this logic? - for (auto it = prefab->ObjectsDataCache.Begin(); it.IsNotEnd(); ++it) + for (const Guid& prefabObjectId : *hierarchy) { - // Use only objects that are linked to the current actor - const Guid parentId = JsonTools::GetGuid(*it->Value, "ParentID"); - if (parentId != actorPrefabObjectId) - continue; - // Skip if object was marked to be removed per instance - const Guid prefabObjectId = JsonTools::GetGuid(*it->Value, "ID"); if (removedObjects != stream.MemberEnd()) { auto& list = removedObjects->value; - const int32 size = static_cast(list.Size()); + const rapidjson::SizeType size = list.Size(); bool removed = false; - for (int32 j = 0; j < size; j++) + for (rapidjson::SizeType j = 0; j < size; j++) { if (JsonTools::GetGuid(list[j]) == prefabObjectId) { @@ -754,10 +749,19 @@ void SceneObjectsFactory::SynchronizeNewPrefabInstances(Context& context, Prefab bool spawned = false; int32 childSearchStart = i + 1; // Objects are serialized with parent followed by its children int32 instanceIndex = -1; - if (context.ObjectToInstance.TryGet(actor->GetID(), instanceIndex) && context.Instances[instanceIndex].Prefab == prefab) + if (context.ObjectToInstance.TryGet(actorId, instanceIndex) && context.Instances[instanceIndex].Prefab == prefab) { + // Quickly check if that object exists + auto& prefabInstance = context.Instances[instanceIndex]; + Guid id; + int32 idInstanceIndex; + if (prefabInstance.IdsMapping.TryGet(prefabObjectId, id) && + context.ObjectToInstance.TryGet(id, idInstanceIndex) && + idInstanceIndex == instanceIndex) + continue; + // Start searching from the beginning of that prefab instance (eg. in case prefab objects were reordered) - childSearchStart = Math::Min(childSearchStart, context.Instances[instanceIndex].StatIndex); + childSearchStart = Math::Min(childSearchStart, prefabInstance.StatIndex); } for (int32 j = childSearchStart; j < data.InitialCount; j++) { @@ -773,7 +777,7 @@ void SceneObjectsFactory::SynchronizeNewPrefabInstances(Context& context, Prefab // Map prefab object ID to this actor's prefab instance so the new objects gets added to it context.SetupIdsMapping(actor, data.Modifier); - data.Modifier->IdsMapping[actorPrefabObjectId] = actor->GetID(); + data.Modifier->IdsMapping[actorPrefabObjectId] = actorId; Scripting::ObjectsLookupIdMapping.Set(&data.Modifier->IdsMapping); // Create instance (including all children)