// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. #include "SceneObjectsFactory.h" #include "Engine/Level/Actor.h" #include "Engine/Level/Prefabs/Prefab.h" #include "Engine/Content/Content.h" #include "Engine/Core/Log.h" #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" SceneObjectsFactory::Context::Context(ISerializeModifier* modifier) : Modifier(modifier) { } SceneObject* SceneObjectsFactory::Spawn(Context& context, ISerializable::DeserializeStream& stream) { // Get object id Guid id = JsonTools::GetGuid(stream, "ID"); context.Modifier->IdsMapping.TryGet(id, id); if (!id.IsValid()) { LOG(Warning, "Invalid object id."); return nullptr; } SceneObject* obj = nullptr; // Check for prefab instance Guid prefabObjectId; if (JsonTools::GetGuidIfValid(prefabObjectId, stream, "PrefabObjectID")) { // Get prefab asset id const Guid prefabId = JsonTools::GetGuid(stream, "PrefabID"); if (!prefabId.IsValid()) { LOG(Warning, "Invalid prefab id."); return nullptr; } // Load prefab auto prefab = Content::LoadAsync(prefabId); if (prefab == nullptr) { LOG(Warning, "Missing prefab with id={0}.", prefabId); return nullptr; } if (prefab->WaitForLoaded()) { LOG(Warning, "Failed to load prefab {0}.", prefab->ToString()); return nullptr; } // Get prefab object data from the prefab const ISerializable::DeserializeStream* prefabData; if (!prefab->ObjectsDataCache.TryGet(prefabObjectId, prefabData)) { LOG(Warning, "Missing object {1} data in prefab {0}.", prefab->ToString(), prefabObjectId); return nullptr; } // Map prefab object ID to the deserialized instance ID context.Modifier->IdsMapping[prefabObjectId] = id; // Create prefab instance (recursive prefab loading to support nested prefabs) obj = Spawn(context, *(ISerializable::DeserializeStream*)prefabData); } else { const auto typeNameMember = stream.FindMember("TypeName"); if (typeNameMember != stream.MemberEnd()) { if (!typeNameMember->value.IsString()) { LOG(Warning, "Invalid object type (TypeName must be an object type full name string)."); return nullptr; } const StringAnsiView typeName(typeNameMember->value.GetStringAnsiView()); const ScriptingTypeHandle type = Scripting::FindScriptingType(typeName); if (type) { const ScriptingObjectSpawnParams params(id, type); obj = (SceneObject*)type.GetType().Script.Spawn(params); if (obj == nullptr) { LOG(Warning, "Failed to spawn object of type {0}.", type.ToString(true)); return nullptr; } } else { LOG(Warning, "Unknown object type '{0}', ID: {1}", String(typeName.Get(), typeName.Length()), id.ToString()); return nullptr; } } else { // [Deprecated: 18.07.2019 expires 18.07.2020] const auto typeIdMember = stream.FindMember("TypeID"); if (typeIdMember == stream.MemberEnd()) { LOG(Warning, "Missing object type."); return nullptr; } if (typeIdMember->value.IsString()) { // Script const char* scriptTypeName = typeIdMember->value.GetString(); const ScriptingTypeHandle type = Scripting::FindScriptingType(scriptTypeName); if (type) { const ScriptingObjectSpawnParams params(id, type); obj = (SceneObject*)type.GetType().Script.Spawn(params); if (obj == nullptr) { LOG(Warning, "Failed to spawn object of type {0}.", type.ToString(true)); return nullptr; } } else { LOG(Warning, "Failed to create script. Invalid type name '{0}'.", String(scriptTypeName)); return nullptr; } } else if (typeIdMember->value.IsInt()) { // Actor const int32 typeId = typeIdMember->value.GetInt(); obj = CreateActor(typeId, id); } else { LOG(Warning, "Invalid object type."); } } } return obj; } void SceneObjectsFactory::Deserialize(Context& context, SceneObject* obj, ISerializable::DeserializeStream& stream) { // Check for prefab instance Guid prefabObjectId; if (JsonTools::GetGuidIfValid(prefabObjectId, stream, "PrefabObjectID")) { // Get prefab asset id const Guid prefabId = JsonTools::GetGuid(stream, "PrefabID"); if (!prefabId.IsValid()) { LOG(Warning, "Invalid prefab id."); return; } // Load prefab auto prefab = Content::LoadAsync(prefabId); if (prefab == nullptr) { LOG(Warning, "Missing prefab with id={0}.", prefabId); return; } if (prefab->WaitForLoaded()) { LOG(Warning, "Failed to load prefab {0}.", prefab->ToString()); return; } // Get prefab object data from the prefab const ISerializable::DeserializeStream* prefabData; if (!prefab->ObjectsDataCache.TryGet(prefabObjectId, prefabData)) { LOG(Warning, "Missing object {1} data in prefab {0}.", prefab->ToString(), prefabObjectId); return; } // Deserialize prefab data (recursive prefab loading to support nested prefabs) 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, context.Modifier); } void SceneObjectsFactory::HandleObjectDeserializationError(const ISerializable::DeserializeStream& value) { // Print invalid object data contents rapidjson_flax::StringBuffer buffer; PrettyJsonWriter writer(buffer); value.Accept(writer.GetWriter()); LOG(Warning, "Failed to deserialize scene object from data: {0}", String(buffer.GetString())); // Try to log some useful info about missing object (eg. it's parent name for faster fixing) const auto parentIdMember = value.FindMember("ParentID"); if (parentIdMember != value.MemberEnd() && parentIdMember->value.IsString()) { const Guid parentId = JsonTools::GetGuid(parentIdMember->value); Actor* parent = Scripting::FindObject(parentId); if (parent) { LOG(Warning, "Parent actor of the missing object: {0}", parent->GetName()); } } } Actor* SceneObjectsFactory::CreateActor(int32 typeId, const Guid& id) { // [Deprecated: 18.07.2019 expires 18.07.2020] // Convert deprecated typeId into actor type name StringAnsiView typeName; switch (typeId) { #define CASE(type, typeId) case typeId: typeName = type; break; CASE("FlaxEngine.StaticModel", 1); CASE("FlaxEngine.Camera", 2); CASE("FlaxEngine.EmptyActor", 3); CASE("FlaxEngine.DirectionalLight", 4); CASE("FlaxEngine.PointLight", 5); CASE("FlaxEngine.Skybox", 6); CASE("FlaxEngine.EnvironmentProbe", 7); CASE("FlaxEngine.BoxBrush", 8); CASE("FlaxEngine.Scene", 9); CASE("FlaxEngine.Sky", 10); CASE("FlaxEngine.RigidBody", 11); CASE("FlaxEngine.SpotLight", 12); CASE("FlaxEngine.PostFxVolume", 13); CASE("FlaxEngine.BoxCollider", 14); CASE("FlaxEngine.SphereCollider", 15); CASE("FlaxEngine.CapsuleCollider", 16); CASE("FlaxEngine.CharacterController", 17); CASE("FlaxEngine.FixedJoint", 18); CASE("FlaxEngine.DistanceJoint", 19); CASE("FlaxEngine.HingeJoint", 20); CASE("FlaxEngine.SliderJoint", 21); CASE("FlaxEngine.SphericalJoint", 22); CASE("FlaxEngine.D6Joint", 23); CASE("FlaxEngine.MeshCollider", 24); CASE("FlaxEngine.SkyLight", 25); CASE("FlaxEngine.ExponentialHeightFog", 26); CASE("FlaxEngine.TextRender", 27); CASE("FlaxEngine.AudioSource", 28); CASE("FlaxEngine.AudioListener", 29); CASE("FlaxEngine.AnimatedModel", 30); CASE("FlaxEngine.BoneSocket", 31); CASE("FlaxEngine.Decal", 32); CASE("FlaxEngine.UICanvas", 33); CASE("FlaxEngine.UIControl", 34); CASE("FlaxEngine.Terrain", 35); CASE("FlaxEngine.Foliage", 36); CASE("FlaxEngine.NavMeshBoundsVolume", 37); CASE("FlaxEngine.NavLink", 38); CASE("FlaxEngine.ParticleEffect", 39); #undef CASE default: LOG(Warning, "Unknown actor type id \'{0}\'", typeId); return nullptr; } const ScriptingTypeHandle type = Scripting::FindScriptingType(typeName); if (type) { const ScriptingObjectSpawnParams params(id, type); const auto result = dynamic_cast(type.GetType().Script.Spawn(params)); if (result == nullptr) { LOG(Warning, "Failed to spawn object of type {0}.", type.ToString(true)); return nullptr; } return result; } LOG(Warning, "Unknown actor type \'{0}\'", String(typeName.Get(), typeName.Length())); return nullptr; } 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"); // Missing object found! LOG(Info, "Actor {0} has missing child object (PrefabObjectID: {1}, PrefabID: {2}, Path: {3})", actor->ToString(), prefabObjectId, prefab->GetID(), prefab->GetPath()); // Get prefab object data from the prefab const ISerializable::DeserializeStream* prefabData; if (!prefab->ObjectsDataCache.TryGet(prefabObjectId, prefabData)) { LOG(Warning, "Missing object {1} data in prefab {0}.", prefab->ToString(), prefabObjectId); return; } // Map prefab object ID to the new prefab object instance Guid id = Guid::New(); data.Modifier->IdsMapping[prefabObjectId] = id; // Create prefab instance (recursive prefab loading to support nested prefabs) auto child = Spawn(context, *(ISerializable::DeserializeStream*)prefabData); if (!child) { LOG(Warning, "Failed to create object {1} from prefab {0}.", prefab->ToString(), prefabObjectId); return; } // Register object 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 for (auto q = prefab->ObjectsDataCache.Begin(); q.IsNotEnd(); ++q) { Guid qParentId; if (JsonTools::GetGuidIfValid(qParentId, *q->Value, "ParentID") && qParentId == prefabObjectId) { const Guid qPrefabObjectId = JsonTools::GetGuid(*q->Value, "ID"); SynchronizeNewPrefabInstance(context, data, prefab, actor, qPrefabObjectId); } } }