From 4c1dbb7f8e1af62569f0f99b131a88b78fad18f1 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 19 Aug 2021 13:02:56 +0200 Subject: [PATCH] Refactor prefab instances loading to improve refs loading between prefab objects --- Source/Engine/Level/Actor.cpp | 5 +- Source/Engine/Level/Level.cpp | 46 +- Source/Engine/Level/Prefabs/Prefab.Apply.cpp | 65 +-- Source/Engine/Level/Prefabs/PrefabManager.cpp | 51 +- Source/Engine/Level/SceneObjectsFactory.cpp | 460 +++++++++++------- Source/Engine/Level/SceneObjectsFactory.h | 98 +++- 6 files changed, 435 insertions(+), 290 deletions(-) diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp index 1bbae93fa..fd494a86b 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -1593,6 +1593,7 @@ bool Actor::FromBytes(const Span& data, Array& output, ISerializeM modifier->EngineBuild = engineBuild; CollectionPoolCache::ScopeCache sceneObjects = ActorsCache::SceneObjectsListCache.Get(); sceneObjects->Resize(objectsCount); + SceneObjectsFactory::Context context(modifier); // Deserialize objects Scripting::ObjectsLookupIdMapping.Set(&modifier->IdsMapping); @@ -1623,7 +1624,7 @@ bool Actor::FromBytes(const Span& data, Array& output, ISerializeM } // Create object - auto obj = SceneObjectsFactory::Spawn(document, modifier); + auto obj = SceneObjectsFactory::Spawn(context, document); sceneObjects->At(i) = obj; if (obj == nullptr) { @@ -1668,7 +1669,7 @@ bool Actor::FromBytes(const Span& data, Array& output, ISerializeM // Deserialize object auto obj = sceneObjects->At(i); if (obj) - SceneObjectsFactory::Deserialize(obj, document, modifier); + SceneObjectsFactory::Deserialize(context, obj, document); else SceneObjectsFactory::HandleObjectDeserializationError(document); } diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index 5d8c32ea1..3dae9a8ce 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -933,42 +933,35 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou // Fire event CallSceneEvent(SceneEventType::OnSceneLoading, scene, sceneId); - // Maps the loaded actor object to the json data with the RemovedObjects array (used to skip restoring objects removed per prefab instance) - SceneObjectsFactory::ActorToRemovedObjectsDataLookup actorToRemovedObjectsData; - // Loaded scene objects list CollectionPoolCache::ScopeCache sceneObjects = ActorsCache::SceneObjectsListCache.Get(); sceneObjects->Resize(objectsCount); sceneObjects->At(0) = scene; + SceneObjectsFactory::Context context(modifier.Value); { PROFILE_CPU_NAMED("Spawn"); // Spawn all scene objects for (int32 i = 1; i < objectsCount; i++) // start from 1. at index [0] was scene { - auto& objData = data[i]; - auto obj = SceneObjectsFactory::Spawn(objData, modifier.Value); + auto& stream = data[i]; + auto obj = SceneObjectsFactory::Spawn(context, stream); sceneObjects->At(i) = obj; if (obj) - { - // Register object so it can be later referenced during deserialization (FindObject will work to link references between objects) obj->RegisterObject(); - - // Special case for actors - if (auto actor = dynamic_cast(obj)) - { - // Check for RemovedObjects listing - const auto removedObjects = SERIALIZE_FIND_MEMBER(objData, "RemovedObjects"); - if (removedObjects != objData.MemberEnd()) - { - actorToRemovedObjectsData.Add(actor, &removedObjects->value); - } - } - } + else + SceneObjectsFactory::HandleObjectDeserializationError(stream); } } + SceneObjectsFactory::PrefabSyncData prefabSyncData(*sceneObjects.Value, data, modifier.Value); + + SceneObjectsFactory::SetupPrefabInstances(context, prefabSyncData); + + // TODO: resave and force sync scenes during game cooking so this step could be skipped in game + SceneObjectsFactory::SynchronizeNewPrefabInstances(context, prefabSyncData); + // /\ all above this has to be done on an any thread // \/ all below this has to be done on multiple threads at once @@ -984,9 +977,7 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou auto& objData = data[i]; auto obj = sceneObjects->At(i); if (obj) - SceneObjectsFactory::Deserialize(obj, objData, modifier.Value); - else - SceneObjectsFactory::HandleObjectDeserializationError(objData); + SceneObjectsFactory::Deserialize(context, obj, objData); } Scripting::ObjectsLookupIdMapping.Set(nullptr); } @@ -994,11 +985,15 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou // /\ all above this has to be done on multiple threads at once // \/ all below this has to be done on an any thread + // Synchronize prefab instances (prefab may have objects removed or reordered so deserialized instances need to synchronize with it) + // TODO: resave and force sync scenes during game cooking so this step could be skipped in game + SceneObjectsFactory::SynchronizePrefabInstances(context, prefabSyncData); + // Call post load event to connect all scene actors { PROFILE_CPU_NAMED("Post Load"); - for (int32 i = 0; i < objectsCount; i++) + for (int32 i = 0; i < sceneObjects->Count(); i++) { SceneObject* obj = sceneObjects->At(i); if (obj) @@ -1006,17 +1001,12 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou } } - // Synchronize prefab instances (prefab may have new objects added or some removed so deserialized instances need to synchronize with it) - // TODO: resave and force sync scenes during game cooking so this step could be skipped in game - SceneObjectsFactory::SynchronizePrefabInstances(*sceneObjects.Value, actorToRemovedObjectsData, modifier.Value); - // Delete objects without parent for (int32 i = 1; i < objectsCount; i++) { SceneObject* obj = sceneObjects->At(i); if (obj && obj->GetParent() == nullptr) { - sceneObjects->At(i) = nullptr; LOG(Warning, "Scene object {0} {1} has missing parent object after load. Removing it.", obj->GetID(), obj->ToString()); obj->DeleteObject(); } diff --git a/Source/Engine/Level/Prefabs/Prefab.Apply.cpp b/Source/Engine/Level/Prefabs/Prefab.Apply.cpp index dda9a73f9..655dae14b 100644 --- a/Source/Engine/Level/Prefabs/Prefab.Apply.cpp +++ b/Source/Engine/Level/Prefabs/Prefab.Apply.cpp @@ -128,7 +128,7 @@ public: rapidjson_flax::Document Data; /// - /// The mapping from prefab instance object id to serialized objects array index (in Data) + /// The mapping from prefab instance object id to serialized objects array index (in Data). /// Dictionary PrefabInstanceIdToDataIndex; @@ -322,6 +322,26 @@ bool PrefabInstanceData::SynchronizePrefabInstances(Array& p modifier->IdsMapping.Add(newPrefabObjectIds[i], Guid::New()); } + // Create new objects added to prefab + int32 deserializeSceneObjectIndex = sceneObjects->Count(); + SceneObjectsFactory::Context context(modifier.Value); + for (int32 i = 0; i < newPrefabObjectIds.Count(); i++) + { + const Guid prefabObjectId = newPrefabObjectIds[i]; + const ISerializable::DeserializeStream* data; + if (!prefabObjectIdToDiffData.TryGet(prefabObjectId, data)) + { + LOG(Warning, "Missing object linkage to the prefab object diff data."); + continue; + } + + SceneObject* obj = SceneObjectsFactory::Spawn(context, *(ISerializable::DeserializeStream*)data); + if (!obj) + continue; + obj->RegisterObject(); + sceneObjects->Add(obj); + } + // Apply modifications for (int32 i = existingObjectsCount - 1; i >= 0; i--) { @@ -349,25 +369,6 @@ bool PrefabInstanceData::SynchronizePrefabInstances(Array& p } } - // Create new objects added to prefab - int32 deserializeSceneObjectIndex = sceneObjects->Count(); - for (int32 i = 0; i < newPrefabObjectIds.Count(); i++) - { - const Guid prefabObjectId = newPrefabObjectIds[i]; - const ISerializable::DeserializeStream* data; - if (!prefabObjectIdToDiffData.TryGet(prefabObjectId, data)) - { - LOG(Warning, "Missing object linkage to the prefab object diff data."); - continue; - } - - SceneObject* obj = SceneObjectsFactory::Spawn(*(ISerializable::DeserializeStream*)data, modifier.Value); - if (!obj) - continue; - obj->RegisterObject(); - sceneObjects->Add(obj); - } - // Deserialize new objects added to prefab for (int32 i = 0; i < newPrefabObjectIds.Count(); i++) { @@ -377,7 +378,7 @@ bool PrefabInstanceData::SynchronizePrefabInstances(Array& p continue; SceneObject* obj = sceneObjects->At(deserializeSceneObjectIndex); - SceneObjectsFactory::Deserialize(obj, *(ISerializable::DeserializeStream*)data, modifier.Value); + SceneObjectsFactory::Deserialize(context, obj, *(ISerializable::DeserializeStream*)data); // Link new prefab instance to prefab and prefab object obj->LinkPrefab(prefabId, prefabObjectId); @@ -492,6 +493,9 @@ bool PrefabInstanceData::SynchronizePrefabInstances(Array& p bool PrefabInstanceData::SynchronizePrefabInstances(Array& prefabInstancesData, Actor* defaultInstance, SceneObjectsListCacheType& sceneObjects, const Guid& prefabId, rapidjson_flax::StringBuffer& tmpBuffer, const Array& oldObjectsIds, const Array& newObjectIds) { + if (prefabInstancesData.IsEmpty()) + return false; + // Fully serialize default instance scene objects (accumulate all prefab and nested prefabs changes into a single linear list of objects) rapidjson_flax::Document defaultInstanceData; { @@ -852,9 +856,10 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr // Create prefab objects auto& data = *Data; sceneObjects->Resize(ObjectsCount + newPrefabInstanceIdToDataIndex.Count()); + SceneObjectsFactory::Context context(modifier.Value); for (int32 i = 0; i < ObjectsCount; i++) { - SceneObject* obj = SceneObjectsFactory::Spawn(data[i], modifier.Value); + SceneObject* obj = SceneObjectsFactory::Spawn(context, data[i]); sceneObjects->At(i) = obj; if (!obj) { @@ -871,7 +876,7 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr for (auto i = newPrefabInstanceIdToDataIndex.Begin(); i.IsNotEnd(); ++i) { const int32 dataIndex = i->Value; - SceneObject* obj = SceneObjectsFactory::Spawn(diffDataDocument[dataIndex], modifier.Value); + SceneObject* obj = SceneObjectsFactory::Spawn(context, diffDataDocument[dataIndex]); sceneObjects->At(newPrefabInstanceIdToDataIndexStart + newPrefabInstanceIdToDataIndexCounter++) = obj; if (!obj) { @@ -888,7 +893,7 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr SceneObject* obj = sceneObjects->At(i); if (!obj) continue; - SceneObjectsFactory::Deserialize(obj, data[i], modifier.Value); + SceneObjectsFactory::Deserialize(context, obj, data[i]); int32 dataIndex; if (diffPrefabObjectIdToDataIndex.TryGet(obj->GetSceneObjectId(), dataIndex)) @@ -919,11 +924,6 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr sceneObjects->At(i) = nullptr; } } - for (int32 i = 0; i < sceneObjects->Count(); i++) - { - if (sceneObjects->At(i) == nullptr) - sceneObjects->RemoveAtKeepOrder(i); - } // Deserialize new prefab objects newPrefabInstanceIdToDataIndexCounter = 0; @@ -933,7 +933,7 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr SceneObject* obj = sceneObjects->At(newPrefabInstanceIdToDataIndexStart + newPrefabInstanceIdToDataIndexCounter++); if (!obj) continue; - SceneObjectsFactory::Deserialize(obj, diffDataDocument[dataIndex], modifier.Value); + SceneObjectsFactory::Deserialize(context, obj, diffDataDocument[dataIndex]); } for (int32 j = 0; j < targetObjects->Count(); j++) { @@ -954,6 +954,11 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr } } } + for (int32 i = 0; i < sceneObjects->Count(); i++) + { + if (sceneObjects->At(i) == nullptr) + sceneObjects->RemoveAtKeepOrder(i); + } Scripting::ObjectsLookupIdMapping.Set(nullptr); if (sceneObjects.Value->IsEmpty()) diff --git a/Source/Engine/Level/Prefabs/PrefabManager.cpp b/Source/Engine/Level/Prefabs/PrefabManager.cpp index 1691d8720..65f63439f 100644 --- a/Source/Engine/Level/Prefabs/PrefabManager.cpp +++ b/Source/Engine/Level/Prefabs/PrefabManager.cpp @@ -123,8 +123,6 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, Dictionary::ScopeCache sceneObjects = ActorsCache::SceneObjectsListCache.Get(); sceneObjects->Resize(objectsCount); - CollectionPoolCache::ScopeCache prefabDataIndexToSceneObject = ActorsCache::SceneObjectsListCache.Get(); - prefabDataIndexToSceneObject->Resize(objectsCount); CollectionPoolCache::ScopeCache modifier = Cache::ISerializeModifier.Get(); modifier->IdsMapping.EnsureCapacity(prefab->ObjectsIds.Count() * 4); for (int32 i = 0; i < prefab->ObjectsIds.Count(); i++) @@ -136,34 +134,35 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, DictionaryClear(); objectsCache->SetCapacity(prefab->ObjectsDataCache.Capacity()); } + auto& data = *prefab->Data; + SceneObjectsFactory::Context context(modifier.Value); // Deserialize prefab objects - auto& data = *prefab->Data; Scripting::ObjectsLookupIdMapping.Set(&modifier.Value->IdsMapping); for (int32 i = 0; i < objectsCount; i++) { auto& stream = data[i]; - - SceneObject* obj = SceneObjectsFactory::Spawn(stream, modifier.Value); - prefabDataIndexToSceneObject->operator[](i) = obj; + auto obj = SceneObjectsFactory::Spawn(context, stream); sceneObjects->At(i) = obj; if (obj) - { obj->RegisterObject(); - } else - { SceneObjectsFactory::HandleObjectDeserializationError(stream); - } + } + SceneObjectsFactory::PrefabSyncData prefabSyncData(*sceneObjects.Value, data, modifier.Value); + if (withSynchronization) + { + // Synchronize new prefab instances (prefab may have new objects added so deserialized instances need to synchronize with it) + // TODO: resave and force sync prefabs during game cooking so this step could be skipped in game + SceneObjectsFactory::SynchronizeNewPrefabInstances(context, prefabSyncData); + Scripting::ObjectsLookupIdMapping.Set(&modifier.Value->IdsMapping); } for (int32 i = 0; i < objectsCount; i++) { auto& stream = data[i]; - SceneObject* obj = prefabDataIndexToSceneObject->At(i); + SceneObject* obj = sceneObjects->At(i); if (obj) - { - SceneObjectsFactory::Deserialize(obj, stream, modifier.Value); - } + SceneObjectsFactory::Deserialize(context, obj, stream); } Scripting::ObjectsLookupIdMapping.Set(nullptr); @@ -198,26 +197,8 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, Dictionary(prefabDataIndexToSceneObject->At(i)); - if (!actor) - continue; - - // Check for RemovedObjects listing - const auto removedObjects = stream.FindMember("RemovedObjects"); - if (removedObjects != stream.MemberEnd()) - { - actorToRemovedObjectsData.Add(actor, &removedObjects->value); - } - } - - // TODO: consider caching actorToRemovedObjectsData per prefab - - SceneObjectsFactory::SynchronizePrefabInstances(*sceneObjects.Value, actorToRemovedObjectsData, modifier.Value); + // TODO: resave and force sync scenes during game cooking so this step could be skipped in game + SceneObjectsFactory::SynchronizePrefabInstances(context, prefabSyncData); } // Delete objects without parent or with invalid linkage to the prefab @@ -274,7 +255,7 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, DictionaryAt(i); + SceneObject* obj = sceneObjects->At(i); if (!obj) continue; diff --git a/Source/Engine/Level/SceneObjectsFactory.cpp b/Source/Engine/Level/SceneObjectsFactory.cpp index 897066d5e..6f54f02ad 100644 --- a/Source/Engine/Level/SceneObjectsFactory.cpp +++ b/Source/Engine/Level/SceneObjectsFactory.cpp @@ -8,15 +8,21 @@ #include "Engine/Scripting/Scripting.h" #include "Engine/Serialization/JsonTools.h" #include "Engine/Serialization/ISerializeModifier.h" +#include "Engine/Serialization/SerializationFwd.h" #include "Engine/Serialization/JsonWriters.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Threading/ThreadLocal.h" -SceneObject* SceneObjectsFactory::Spawn(ISerializable::DeserializeStream& stream, ISerializeModifier* modifier) +SceneObjectsFactory::Context::Context(ISerializeModifier* modifier) + : Modifier(modifier) +{ +} + +SceneObject* SceneObjectsFactory::Spawn(Context& context, ISerializable::DeserializeStream& stream) { // Get object id Guid id = JsonTools::GetGuid(stream, "ID"); - modifier->IdsMapping.TryGet(id, id); + context.Modifier->IdsMapping.TryGet(id, id); if (!id.IsValid()) { LOG(Warning, "Invalid object id."); @@ -59,10 +65,10 @@ SceneObject* SceneObjectsFactory::Spawn(ISerializable::DeserializeStream& stream } // Map prefab object ID to the deserialized instance ID - modifier->IdsMapping[prefabObjectId] = id; + context.Modifier->IdsMapping[prefabObjectId] = id; // Create prefab instance (recursive prefab loading to support nested prefabs) - obj = Spawn(*(ISerializable::DeserializeStream*)prefabData, modifier); + obj = Spawn(context, *(ISerializable::DeserializeStream*)prefabData); } else { @@ -139,7 +145,7 @@ SceneObject* SceneObjectsFactory::Spawn(ISerializable::DeserializeStream& stream return obj; } -void SceneObjectsFactory::Deserialize(SceneObject* obj, ISerializable::DeserializeStream& stream, ISerializeModifier* modifier) +void SceneObjectsFactory::Deserialize(Context& context, SceneObject* obj, ISerializable::DeserializeStream& stream) { // Check for prefab instance Guid prefabObjectId; @@ -175,171 +181,21 @@ void SceneObjectsFactory::Deserialize(SceneObject* obj, ISerializable::Deseriali } // Deserialize prefab data (recursive prefab loading to support nested prefabs) - Deserialize(obj, *(ISerializable::DeserializeStream*)prefabData, modifier); + Deserialize(context, obj, *(ISerializable::DeserializeStream*)prefabData); + } + + int32 instanceIndex; + if (context.ObjectToInstance.TryGet(obj->GetID(), instanceIndex) && instanceIndex != context.CurrentInstance) + { + // Apply the current prefab instance objects ids table to resolve references inside a prefab properly + context.CurrentInstance = instanceIndex; + auto& instance = context.Instances[instanceIndex]; + for (auto& e : instance.IdsMapping) + context.Modifier->IdsMapping[e.Key] = e.Value; } // Load data - obj->Deserialize(stream, modifier); -} - -bool Contains(Actor* actor, const SceneObjectsFactory::ActorToRemovedObjectsDataLookup& actorToRemovedObjectsData, const Guid& prefabObjectId) -{ - // Check if actor has any removed objects registered - const rapidjson_flax::Value* data; - if (actorToRemovedObjectsData.TryGet(actor, data)) - { - const int32 size = static_cast(data->Size()); - for (int32 i = 0; i < size; i++) - { - if (JsonTools::GetGuid(data->operator[](i)) == prefabObjectId) - { - return true; - } - } - } - - return false; -} - -void SceneObjectsFactory::SynchronizePrefabInstances(Array& sceneObjects, const ActorToRemovedObjectsDataLookup& actorToRemovedObjectsData, ISerializeModifier* modifier) -{ - PROFILE_CPU_NAMED("SynchronizePrefabInstances"); - - Scripting::ObjectsLookupIdMapping.Set(&modifier->IdsMapping); - - // Check all objects with prefab linkage for moving to a proper parent - const int32 objectsToCheckCount = sceneObjects.Count(); - for (int32 i = 0; i < objectsToCheckCount; i++) - { - SceneObject* obj = sceneObjects[i]; - if (!obj) - continue; - SceneObject* parent = obj->GetParent(); - const Guid prefabId = obj->GetPrefabID(); - if (parent == nullptr || !obj->HasPrefabLink() || !parent->HasPrefabLink() || parent->GetPrefabID() != prefabId) - continue; - const Guid prefabObjectId = obj->GetPrefabObjectID(); - const Guid parentPrefabObjectId = parent->GetPrefabObjectID(); - - // Load prefab - auto prefab = Content::LoadAsync(prefabId); - if (prefab == nullptr) - { - LOG(Warning, "Missing prefab with id={0}.", prefabId); - continue; - } - if (prefab->WaitForLoaded()) - { - LOG(Warning, "Failed to load prefab {0}.", prefab->ToString()); - continue; - } - - // Get the actual parent object stored in the prefab data - const ISerializable::DeserializeStream* objData; - Guid actualParentPrefabId; - if (!prefab->ObjectsDataCache.TryGet(prefabObjectId, objData) || !JsonTools::GetGuidIfValid(actualParentPrefabId, *objData, "ParentID")) - continue; - - // Validate - if (actualParentPrefabId != parentPrefabObjectId) - { - // Invalid connection object found! - LOG(Info, "Object {0} has invalid parent object {4} -> {5} (PrefabObjectID: {1}, PrefabID: {2}, Path: {3})", obj->GetSceneObjectId(), prefabObjectId, prefab->GetID(), prefab->GetPath(), parentPrefabObjectId, actualParentPrefabId); - - // Map actual prefab object id to the current scene objects collection - modifier->IdsMapping.TryGet(actualParentPrefabId, actualParentPrefabId); - - // Find parent - const auto actualParent = Scripting::FindObject(actualParentPrefabId); - if (!actualParent) - { - LOG(Warning, "The actual parent is missing."); - continue; - } - - // Reparent - obj->SetParent(actualParent, false); - } - - // Preserve order in parent (values from prefab are used) - if (i != 0) - { - const auto defaultInstance = prefab->GetDefaultInstance(obj->GetPrefabObjectID()); - if (defaultInstance) - { - obj->SetOrderInParent(defaultInstance->GetOrderInParent()); - } - } - } - - // Check all actors with prefab linkage for adding missing objects - for (int32 i = 0; i < objectsToCheckCount; i++) - { - Actor* actor = dynamic_cast(sceneObjects[i]); - if (!actor || !actor->HasPrefabLink()) - continue; - const Guid actorId = actor->GetID(); - const Guid prefabId = actor->GetPrefabID(); - const Guid actorPrefabObjectId = actor->GetPrefabObjectID(); - - // Map prefab object id to this actor so the new objects gets added to it - modifier->IdsMapping[actorPrefabObjectId] = actorId; - - // Load prefab - auto prefab = Content::LoadAsync(prefabId); - if (prefab == nullptr) - { - LOG(Warning, "Missing prefab with id={0}.", prefabId); - continue; - } - if (prefab->WaitForLoaded()) - { - LOG(Warning, "Failed to load prefab {0}.", prefab->ToString()); - continue; - } - - // 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) - { - // Use only objects that are linked to the current actor - const Guid parentId = JsonTools::GetGuid(*it->Value, "ParentID"); - if (parentId != actorPrefabObjectId) - continue; - - // Use only objects that are missing - const Guid prefabObjectId = JsonTools::GetGuid(*it->Value, "ID"); - if (actor->GetChildByPrefabObjectId(prefabObjectId) != nullptr || - actor->GetScriptByPrefabObjectId(prefabObjectId) != nullptr) - continue; - - // Skip if object was marked to be removed per instance - if (Contains(actor, actorToRemovedObjectsData, prefabObjectId)) - continue; - - // Create instance (including all children) - Scripting::ObjectsLookupIdMapping.Set(&modifier->IdsMapping); - SynchronizeNewPrefabInstance(prefab, actor, prefabObjectId, sceneObjects, modifier); - } - } - - // Call post load event to the new objects - for (int32 i = objectsToCheckCount; i < sceneObjects.Count(); i++) - { - SceneObject* obj = sceneObjects[i]; - - // Preserve order in parent (values from prefab are used) - auto prefab = Content::LoadAsync(obj->GetPrefabID()); - const auto defaultInstance = prefab && prefab->IsLoaded() ? prefab->GetDefaultInstance(obj->GetPrefabObjectID()) : nullptr; - if (defaultInstance) - { - obj->SetOrderInParent(defaultInstance->GetOrderInParent()); - } - - obj->PostLoad(); - } - - Scripting::ObjectsLookupIdMapping.Set(nullptr); + obj->Deserialize(stream, context.Modifier); } void SceneObjectsFactory::HandleObjectDeserializationError(const ISerializable::DeserializeStream& value) @@ -434,7 +290,248 @@ Actor* SceneObjectsFactory::CreateActor(int32 typeId, const Guid& id) return nullptr; } -void SceneObjectsFactory::SynchronizeNewPrefabInstance(Prefab* prefab, Actor* actor, const Guid& prefabObjectId, Array& sceneObjects, ISerializeModifier* modifier) +SceneObjectsFactory::PrefabSyncData::PrefabSyncData(Array& sceneObjects, const ISerializable::DeserializeStream& data, ISerializeModifier* modifier) + : SceneObjects(sceneObjects) + , Data(data) + , Modifier(modifier) + , InitialCount(0) +{ +} + +void SceneObjectsFactory::SetupPrefabInstances(Context& context, PrefabSyncData& data) +{ + PROFILE_CPU_NAMED("SetupPrefabInstances"); + const int32 count = data.Data.Size(); + ASSERT(count <= data.SceneObjects.Count()) + for (int32 i = 0; i < count; i++) + { + SceneObject* obj = data.SceneObjects[i]; + if (!obj) + continue; + const auto& stream = data.Data[i]; + Guid prefabObjectId, prefabId; + if (!JsonTools::GetGuidIfValid(prefabObjectId, stream, "PrefabObjectID")) + continue; + if (!JsonTools::GetGuidIfValid(prefabId, stream, "PrefabID")) + continue; + const Guid parentId = JsonTools::GetGuid(stream, "ParentID"); + const Guid id = obj->GetID(); + auto prefab = Content::LoadAsync(prefabId); + + // Check if it's parent is in the same prefab + int32 index; + if (context.ObjectToInstance.TryGet(parentId, index) && context.Instances[index].Prefab == prefab) + { + // Use parent object as prefab instance + } + else + { + // Use new prefab instance + index = context.Instances.Count(); + auto& e = context.Instances.AddOne(); + e.Prefab = prefab; + e.RootId = id; + } + context.ObjectToInstance[id] = index; + + // Add to the prefab instance IDs mapping + auto& prefabInstance = context.Instances[index]; + prefabInstance.IdsMapping[prefabObjectId] = id; + } +} + +void SceneObjectsFactory::SynchronizeNewPrefabInstances(Context& context, PrefabSyncData& data) +{ + PROFILE_CPU_NAMED("SynchronizeNewPrefabInstances"); + + Scripting::ObjectsLookupIdMapping.Set(&data.Modifier->IdsMapping); + data.InitialCount = data.SceneObjects.Count(); + + // Check all actors with prefab linkage for adding missing objects + for (int32 i = 0; i < data.InitialCount; i++) + { + Actor* actor = dynamic_cast(data.SceneObjects[i]); + if (!actor) + continue; + const auto& stream = data.Data[i]; + Guid actorId, actorPrefabObjectId, prefabId; + if (!JsonTools::GetGuidIfValid(actorPrefabObjectId, stream, "PrefabObjectID")) + continue; + if (!JsonTools::GetGuidIfValid(prefabId, stream, "PrefabID")) + continue; + if (!JsonTools::GetGuidIfValid(actorId, stream, "ID")) + continue; + const Guid actorParentId = JsonTools::GetGuid(stream, "ParentID"); + + // Map prefab object id to this actor so the new objects gets added to it + data.Modifier->IdsMapping[actorPrefabObjectId] = actor->GetID(); + + // Load prefab + auto prefab = Content::LoadAsync(prefabId); + if (prefab == nullptr) + { + LOG(Warning, "Missing prefab with id={0}.", prefabId); + continue; + } + if (prefab->WaitForLoaded()) + { + LOG(Warning, "Failed to load prefab {0}.", prefab->ToString()); + continue; + } + + // Check for RemovedObjects list + 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) + { + // 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()); + bool removed = false; + for (int32 j = 0; j < size; j++) + { + if (JsonTools::GetGuid(list[j]) == prefabObjectId) + { + removed = true; + break; + } + } + if (removed) + continue; + } + + // Use only objects that are missing + bool spawned = false; + for (int32 j = i + 1; j < data.InitialCount; j++) + { + const auto& jData = data.Data[j]; + const Guid jParentId = JsonTools::GetGuid(jData, "ParentID"); + //if (jParentId == actorParentId) + // break; + if (jParentId != actorId) + continue; + const Guid jPrefabObjectId = JsonTools::GetGuid(jData, "PrefabObjectID"); + if (jPrefabObjectId != prefabObjectId) + continue; + + // This object exists in the saved scene objects list + spawned = true; + break; + } + if (spawned) + continue; + + // Create instance (including all children) + Scripting::ObjectsLookupIdMapping.Set(&data.Modifier->IdsMapping); + SynchronizeNewPrefabInstance(context, data, prefab, actor, prefabObjectId); + } + } + + Scripting::ObjectsLookupIdMapping.Set(nullptr); +} + +void SceneObjectsFactory::SynchronizePrefabInstances(Context& context, PrefabSyncData& data) +{ + PROFILE_CPU_NAMED("SynchronizePrefabInstances"); + + // Check all objects with prefab linkage for moving to a proper parent + for (int32 i = 0; i < data.InitialCount; i++) + { + SceneObject* obj = data.SceneObjects[i]; + if (!obj) + continue; + SceneObject* parent = obj->GetParent(); + const Guid prefabId = obj->GetPrefabID(); + if (parent == nullptr || !obj->HasPrefabLink() || !parent->HasPrefabLink() || parent->GetPrefabID() != prefabId) + continue; + const Guid prefabObjectId = obj->GetPrefabObjectID(); + const Guid parentPrefabObjectId = parent->GetPrefabObjectID(); + + // Load prefab + auto prefab = Content::LoadAsync(prefabId); + if (prefab == nullptr) + { + LOG(Warning, "Missing prefab with id={0}.", prefabId); + continue; + } + if (prefab->WaitForLoaded()) + { + LOG(Warning, "Failed to load prefab {0}.", prefab->ToString()); + continue; + } + + // Get the actual parent object stored in the prefab data + const ISerializable::DeserializeStream* objData; + Guid actualParentPrefabId; + if (!prefab->ObjectsDataCache.TryGet(prefabObjectId, objData) || !JsonTools::GetGuidIfValid(actualParentPrefabId, *objData, "ParentID")) + continue; + + // Validate + if (actualParentPrefabId != parentPrefabObjectId) + { + // Invalid connection object found! + LOG(Info, "Object {0} has invalid parent object {4} -> {5} (PrefabObjectID: {1}, PrefabID: {2}, Path: {3})", obj->GetSceneObjectId(), prefabObjectId, prefab->GetID(), prefab->GetPath(), parentPrefabObjectId, actualParentPrefabId); + + // Map actual prefab object id to the current scene objects collection + data.Modifier->IdsMapping.TryGet(actualParentPrefabId, actualParentPrefabId); + + // Find parent + const auto actualParent = Scripting::FindObject(actualParentPrefabId); + if (!actualParent) + { + LOG(Warning, "The actual parent is missing."); + continue; + } + + // Reparent + obj->SetParent(actualParent, false); + } + + // Preserve order in parent (values from prefab are used) + if (i != 0) + { + const auto defaultInstance = prefab->GetDefaultInstance(obj->GetPrefabObjectID()); + if (defaultInstance) + { + obj->SetOrderInParent(defaultInstance->GetOrderInParent()); + } + } + } + + Scripting::ObjectsLookupIdMapping.Set(&data.Modifier->IdsMapping); + + // Synchronize new prefab objects + for (int32 i = 0; i < data.NewObjects.Count(); i++) + { + SceneObject* obj = data.SceneObjects[data.InitialCount + i]; + auto& newObj = data.NewObjects[i]; + + // Deserialize object with prefab data + Deserialize(context, obj, *(ISerializable::DeserializeStream*)newObj.PrefabData); + obj->LinkPrefab(newObj.Prefab->GetID(), newObj.PrefabObjectId); + + // Preserve order in parent (values from prefab are used) + const auto defaultInstance = newObj.Prefab->GetDefaultInstance(newObj.PrefabObjectId); + if (defaultInstance) + { + obj->SetOrderInParent(defaultInstance->GetOrderInParent()); + } + } + + Scripting::ObjectsLookupIdMapping.Set(nullptr); +} + +void SceneObjectsFactory::SynchronizeNewPrefabInstance(Context& context, PrefabSyncData& data, Prefab* prefab, Actor* actor, const Guid& prefabObjectId) { PROFILE_CPU_NAMED("SynchronizeNewPrefabInstance"); @@ -450,22 +547,33 @@ void SceneObjectsFactory::SynchronizeNewPrefabInstance(Prefab* prefab, Actor* ac } // Map prefab object ID to the new prefab object instance - modifier->IdsMapping[prefabObjectId] = Guid::New(); - Scripting::ObjectsLookupIdMapping.Set(&modifier->IdsMapping); + Guid id = Guid::New(); + data.Modifier->IdsMapping[prefabObjectId] = id; // Create prefab instance (recursive prefab loading to support nested prefabs) - auto child = Spawn(*(ISerializable::DeserializeStream*)prefabData, modifier); + auto child = Spawn(context, *(ISerializable::DeserializeStream*)prefabData); if (!child) { LOG(Warning, "Failed to create object {1} from prefab {0}.", prefab->ToString(), prefabObjectId); return; } - child->RegisterObject(); - Deserialize(child, *(ISerializable::DeserializeStream*)prefabData, modifier); // Register object - child->LinkPrefab(prefab->GetID(), prefabObjectId); - sceneObjects.Add(child); + child->RegisterObject(); + data.SceneObjects.Add(child); + auto& newObj = data.NewObjects.AddOne(); + newObj.Prefab = prefab; + newObj.PrefabData = prefabData; + newObj.PrefabObjectId = prefabObjectId; + newObj.Id = id; + int32 instanceIndex = -1; + if (context.ObjectToInstance.TryGet(actor->GetID(), instanceIndex) && context.Instances[instanceIndex].Prefab == prefab) + { + // Add to the prefab instance IDs mapping + context.ObjectToInstance[id] = instanceIndex; + auto& prefabInstance = context.Instances[instanceIndex]; + prefabInstance.IdsMapping[prefabObjectId] = id; + } // Use loop to add even more objects to added objects (prefab can have one new object that has another child, we need to add that child) // TODO: prefab could cache lookup object id -> children ids @@ -475,7 +583,7 @@ void SceneObjectsFactory::SynchronizeNewPrefabInstance(Prefab* prefab, Actor* ac if (JsonTools::GetGuidIfValid(qParentId, *q->Value, "ParentID") && qParentId == prefabObjectId) { const Guid qPrefabObjectId = JsonTools::GetGuid(*q->Value, "ID"); - SynchronizeNewPrefabInstance(prefab, actor, qPrefabObjectId, sceneObjects, modifier); + SynchronizeNewPrefabInstance(context, data, prefab, actor, qPrefabObjectId); } } } diff --git a/Source/Engine/Level/SceneObjectsFactory.h b/Source/Engine/Level/SceneObjectsFactory.h index 210b92c65..bec910e26 100644 --- a/Source/Engine/Level/SceneObjectsFactory.h +++ b/Source/Engine/Level/SceneObjectsFactory.h @@ -3,43 +3,46 @@ #pragma once #include "SceneObject.h" +#include "Engine/Core/Collections/Dictionary.h" /// /// Helper class for scene objects creation and deserialization utilities. /// -class SceneObjectsFactory +class FLAXENGINE_API SceneObjectsFactory { public: - typedef Dictionary ActorToRemovedObjectsDataLookup; + struct PrefabInstance + { + Guid RootId; + Prefab* Prefab; + Dictionary IdsMapping; + }; -public: + struct Context + { + ISerializeModifier* Modifier; + int32 CurrentInstance = -1; + Array Instances; + Dictionary ObjectToInstance; + + Context(ISerializeModifier* modifier); + }; /// /// Creates the scene object from the specified data value. Does not perform deserialization. /// + /// The serialization context. /// The serialized data stream. - /// The serialization modifier. Cannot be null. - static SceneObject* Spawn(ISerializable::DeserializeStream& stream, ISerializeModifier* modifier); + static SceneObject* Spawn(Context& context, ISerializable::DeserializeStream& stream); /// /// Deserializes the scene object from the specified data value. /// + /// The serialization context. /// The instance to deserialize. /// The serialized data stream. - /// The serialization modifier. Cannot be null. - static void Deserialize(SceneObject* obj, ISerializable::DeserializeStream& stream, ISerializeModifier* modifier); - - /// - /// Synchronizes the prefab instances. Prefabs may have new objects added or some removed so deserialized instances need to synchronize with it. Handles also changing prefab object parent in the instance. - /// - /// - /// Should be called after scene objects deserialization and PostLoad event when scene objects hierarchy is ready (parent-child relation exists). But call it before Init and BeginPlay events. - /// - /// The loaded scene objects. Collection will be modified after usage. - /// Maps the loaded actor object to the json data with the RemovedObjects array (used to skip restoring objects removed per prefab instance). - /// The objects deserialization modifier. Collection will be modified after usage. - static void SynchronizePrefabInstances(Array& sceneObjects, const ActorToRemovedObjectsDataLookup& actorToRemovedObjectsData, ISerializeModifier* modifier); + static void Deserialize(Context& context, SceneObject* obj, ISerializable::DeserializeStream& stream); /// /// Handles the object deserialization error. @@ -56,7 +59,64 @@ public: /// The created actor, or null if failed. static Actor* CreateActor(int32 typeId, const Guid& id); +public: + + struct PrefabSyncData + { + friend SceneObjectsFactory; + // The created scene objects. Collection can be modified (eg. for spawning missing objects). + Array& SceneObjects; + // The scene objects data. + const ISerializable::DeserializeStream& Data; + // The objects deserialization modifier. Collection will be modified (eg. for spawned objects mapping). + ISerializeModifier* Modifier; + + PrefabSyncData(Array& sceneObjects, const ISerializable::DeserializeStream& data, ISerializeModifier* modifier); + + private: + struct NewObj + { + Prefab* Prefab; + const ISerializable::DeserializeStream* PrefabData; + Guid PrefabObjectId; + Guid Id; + }; + + int32 InitialCount; + Array NewObjects; + }; + + /// + /// Initializes the prefab instances inside the scene objects for proper references deserialization. + /// + /// + /// Should be called after spawning scene objects but before scene objects deserialization. + /// + /// The serialization context. + /// The sync data. + static void SetupPrefabInstances(Context& context, PrefabSyncData& data); + + /// + /// Synchronizes the new prefab instances by spawning missing objects that were added to prefab but were not saved with scene objects collection. + /// + /// + /// Should be called after spawning scene objects but before scene objects deserialization and PostLoad event when scene objects hierarchy is ready (parent-child relation exists). But call it before Init and BeginPlay events. + /// + /// The serialization context. + /// The sync data. + static void SynchronizeNewPrefabInstances(Context& context, PrefabSyncData& data); + + /// + /// Synchronizes the prefab instances. Prefabs may have objects removed so deserialized instances need to synchronize with it. Handles also changing prefab object parent in the instance. + /// + /// + /// Should be called after scene objects deserialization and PostLoad event when scene objects hierarchy is ready (parent-child relation exists). But call it before Init and BeginPlay events. + /// + /// The serialization context. + /// The sync data. + static void SynchronizePrefabInstances(Context& context, PrefabSyncData& data); + private: - static void SynchronizeNewPrefabInstance(Prefab* prefab, Actor* actor, const Guid& prefabObjectId, Array& sceneObjects, ISerializeModifier* modifier); + static void SynchronizeNewPrefabInstance(Context& context, PrefabSyncData& data, Prefab* prefab, Actor* actor, const Guid& prefabObjectId); };