Add support for loading prefab instance if the root was changed or deleted

#2050
This commit is contained in:
2023-12-14 10:47:22 +01:00
parent 1874382816
commit e0a085adfe
9 changed files with 360 additions and 115 deletions
+4
View File
@@ -310,6 +310,10 @@ void Asset::ChangeID(const Guid& newId)
if (!IsVirtual())
CRASH;
// ID has to be unique
if (Content::GetAsset(newId) != nullptr)
CRASH;
const Guid oldId = _id;
ManagedScriptingObject::ChangeID(newId);
Content::onAssetChangeId(this, oldId, newId);
+8 -8
View File
@@ -934,13 +934,13 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou
// Loaded scene objects list
CollectionPoolCache<ActorsCache::SceneObjectsListType>::ScopeCache sceneObjects = ActorsCache::SceneObjectsListCache.Get();
const int32 objectsCount = (int32)data.Size();
sceneObjects->Resize(objectsCount);
const int32 dataCount = (int32)data.Size();
sceneObjects->Resize(dataCount);
sceneObjects->At(0) = scene;
// Spawn all scene objects
SceneObjectsFactory::Context context(modifier.Value);
context.Async = JobSystem::GetThreadsCount() > 1 && objectsCount > 10;
context.Async = JobSystem::GetThreadsCount() > 1 && dataCount > 10;
{
PROFILE_CPU_NAMED("Spawn");
SceneObject** objects = sceneObjects->Get();
@@ -963,12 +963,12 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou
}
else
SceneObjectsFactory::HandleObjectDeserializationError(stream);
}, objectsCount - 1);
}, dataCount - 1);
ScenesLock.Lock();
}
else
{
for (int32 i = 1; i < objectsCount; i++) // start from 1. at index [0] was scene
for (int32 i = 1; i < dataCount; i++) // start from 1. at index [0] was scene
{
auto& stream = data[i];
auto obj = SceneObjectsFactory::Spawn(context, stream);
@@ -1012,13 +1012,13 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou
SceneObjectsFactory::Deserialize(context, obj, data[i]);
idMapping = nullptr;
}
}, objectsCount - 1);
}, dataCount - 1);
ScenesLock.Lock();
}
else
{
Scripting::ObjectsLookupIdMapping.Set(&modifier.Value->IdsMapping);
for (int32 i = 1; i < objectsCount; i++) // start from 1. at index [0] was scene
for (int32 i = 1; i < dataCount; i++) // start from 1. at index [0] was scene
{
auto& objData = data[i];
auto obj = objects[i];
@@ -1049,7 +1049,7 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou
PROFILE_CPU_NAMED("Initialize");
SceneObject** objects = sceneObjects->Get();
for (int32 i = 0; i < objectsCount; i++)
for (int32 i = 0; i < dataCount; i++)
{
SceneObject* obj = objects[i];
if (obj)
-5
View File
@@ -87,17 +87,12 @@ SceneObject* Prefab::GetDefaultInstance(const Guid& objectId)
const auto result = GetDefaultInstance();
if (!result)
return nullptr;
if (objectId.IsValid())
{
SceneObject* object;
if (ObjectsCache.TryGet(objectId, object))
{
// Actor or Script
return object;
}
}
return result;
}
+22 -20
View File
@@ -94,8 +94,8 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Ac
LOG(Warning, "Waiting for prefab asset be loaded failed. {0}", prefab->ToString());
return nullptr;
}
const int32 objectsCount = prefab->ObjectsCount;
if (objectsCount == 0)
const int32 dataCount = prefab->ObjectsCount;
if (dataCount == 0)
{
LOG(Warning, "Prefab has no objects. {0}", prefab->ToString());
return nullptr;
@@ -107,7 +107,7 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Ac
// Prepare
CollectionPoolCache<ActorsCache::SceneObjectsListType>::ScopeCache sceneObjects = ActorsCache::SceneObjectsListCache.Get();
sceneObjects->Resize(objectsCount);
sceneObjects->Resize(dataCount);
CollectionPoolCache<ISerializeModifier, Cache::ISerializeModifierClearCallback>::ScopeCache modifier = Cache::ISerializeModifier.Get();
modifier->EngineBuild = prefab->DataEngineBuild;
modifier->IdsMapping.EnsureCapacity(prefab->ObjectsIds.Count() * 4);
@@ -126,7 +126,7 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Ac
// Deserialize prefab objects
auto prevIdMapping = Scripting::ObjectsLookupIdMapping.Get();
Scripting::ObjectsLookupIdMapping.Set(&modifier.Value->IdsMapping);
for (int32 i = 0; i < objectsCount; i++)
for (int32 i = 0; i < dataCount; i++)
{
auto& stream = data[i];
SceneObject* obj = SceneObjectsFactory::Spawn(context, stream);
@@ -145,7 +145,7 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Ac
SceneObjectsFactory::SynchronizeNewPrefabInstances(context, prefabSyncData);
Scripting::ObjectsLookupIdMapping.Set(&modifier.Value->IdsMapping);
}
for (int32 i = 0; i < objectsCount; i++)
for (int32 i = 0; i < dataCount; i++)
{
auto& stream = data[i];
SceneObject* obj = sceneObjects->At(i);
@@ -154,20 +154,29 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Ac
}
Scripting::ObjectsLookupIdMapping.Set(prevIdMapping);
// Pick prefab root object
if (sceneObjects->IsEmpty())
// Synchronize prefab instances (prefab may have new objects added or some removed so deserialized instances need to synchronize with it)
if (withSynchronization)
{
LOG(Warning, "No valid objects in prefab. {0}", prefab->ToString());
return nullptr;
// TODO: resave and force sync scenes during game cooking so this step could be skipped in game
SceneObjectsFactory::SynchronizePrefabInstances(context, prefabSyncData);
}
// Pick prefab root object
Actor* root = nullptr;
const Guid prefabRootObjectId = prefab->GetRootObjectId();
for (int32 i = 0; i < objectsCount; i++)
for (int32 i = 0; i < dataCount && !root; i++)
{
if (JsonTools::GetGuid(data[i], "ID") == prefabRootObjectId)
{
root = dynamic_cast<Actor*>(sceneObjects->At(i));
break;
}
if (!root)
{
// Fallback to the first actor that has no parent
for (int32 i = 0; i < sceneObjects->Count() && !root; i++)
{
SceneObject* obj = sceneObjects->At(i);
if (obj && !obj->GetParent())
root = dynamic_cast<Actor*>(obj);
}
}
if (!root)
@@ -176,13 +185,6 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Ac
return nullptr;
}
// Synchronize prefab instances (prefab may have new objects added or some removed so deserialized instances need to synchronize with it)
if (withSynchronization)
{
// TODO: resave and force sync scenes during game cooking so this step could be skipped in game
SceneObjectsFactory::SynchronizePrefabInstances(context, prefabSyncData);
}
// Prepare parent linkage for prefab root actor
if (root->_parent)
root->_parent->Children.Remove(root);
@@ -264,7 +266,7 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Ac
}
// Link objects to prefab (only deserialized from prefab data)
for (int32 i = 0; i < objectsCount; i++)
for (int32 i = 0; i < dataCount; i++)
{
auto& stream = data[i];
SceneObject* obj = sceneObjects->At(i);
+2 -2
View File
@@ -89,7 +89,7 @@ API_CLASS(Static) class FLAXENGINE_API PrefabManager
/// <param name="objectsCache">The options output objects cache that can be filled with prefab object id mapping to deserialized object (actor or script).</param>
/// <param name="withSynchronization">True if perform prefab changes synchronization for the spawned objects. It will check if need to add new objects due to nested prefab modifications.</param>
/// <returns>The created actor (root) or null if failed.</returns>
static Actor* SpawnPrefab(Prefab* prefab, Actor* parent, Dictionary<Guid, SceneObject*, HeapAllocation>* objectsCache, bool withSynchronization = false);
static Actor* SpawnPrefab(Prefab* prefab, Actor* parent, Dictionary<Guid, SceneObject*, HeapAllocation>* objectsCache, bool withSynchronization = true);
/// <summary>
/// Spawns the instance of the prefab objects. If parent actor is specified then created actors are fully initialized (OnLoad event and BeginPlay is called if parent actor is already during gameplay).
@@ -100,7 +100,7 @@ API_CLASS(Static) class FLAXENGINE_API PrefabManager
/// <param name="objectsCache">The options output objects cache that can be filled with prefab object id mapping to deserialized object (actor or script).</param>
/// <param name="withSynchronization">True if perform prefab changes synchronization for the spawned objects. It will check if need to add new objects due to nested prefab modifications.</param>
/// <returns>The created actor (root) or null if failed.</returns>
static Actor* SpawnPrefab(Prefab* prefab, const Transform& transform, Actor* parent, Dictionary<Guid, SceneObject*, HeapAllocation>* objectsCache, bool withSynchronization = false);
static Actor* SpawnPrefab(Prefab* prefab, const Transform& transform, Actor* parent, Dictionary<Guid, SceneObject*, HeapAllocation>* objectsCache, bool withSynchronization = true);
#if USE_EDITOR
+172 -79
View File
@@ -138,7 +138,7 @@ SceneObject* SceneObjectsFactory::Spawn(Context& context, const ISerializable::D
auto prefab = Content::LoadAsync<Prefab>(prefabId);
if (prefab == nullptr)
{
LOG(Warning, "Missing prefab with id={0}.", prefabId);
LOG(Warning, "Missing prefab {0}.", prefabId);
return nullptr;
}
if (prefab->WaitForLoaded())
@@ -264,7 +264,7 @@ void SceneObjectsFactory::Deserialize(Context& context, SceneObject* obj, ISeria
auto prefab = Content::LoadAsync<Prefab>(prefabId);
if (prefab == nullptr)
{
LOG(Warning, "Missing prefab with id={0}.", prefabId);
LOG(Warning, "Missing prefab {0}.", prefabId);
return;
}
if (prefab->WaitForLoaded())
@@ -424,9 +424,6 @@ void SceneObjectsFactory::SetupPrefabInstances(Context& context, const PrefabSyn
ASSERT(count <= data.SceneObjects.Count());
for (int32 i = 0; i < count; i++)
{
const SceneObject* obj = data.SceneObjects[i];
if (!obj)
continue;
const auto& stream = data.Data[i];
Guid prefabObjectId, prefabId;
if (!JsonTools::GetGuidIfValid(prefabObjectId, stream, "PrefabObjectID"))
@@ -436,14 +433,15 @@ void SceneObjectsFactory::SetupPrefabInstances(Context& context, const PrefabSyn
Guid parentId = JsonTools::GetGuid(stream, "ParentID");
for (int32 j = i - 1; j >= 0; j--)
{
// Find instance ID of the parent to this object (use data in json for relationship)
// Find ID of the parent to this object (use data in json for relationship)
if (parentId == JsonTools::GetGuid(data.Data[j], "ID") && data.SceneObjects[j])
{
parentId = data.SceneObjects[j]->GetID();
break;
}
}
const Guid id = obj->GetID();
const SceneObject* obj = data.SceneObjects[i];
const Guid id = obj ? obj->GetID() : JsonTools::GetGuid(stream, "ID");
auto prefab = Content::LoadAsync<Prefab>(prefabId);
// Check if it's parent is in the same prefab
@@ -459,6 +457,8 @@ void SceneObjectsFactory::SetupPrefabInstances(Context& context, const PrefabSyn
auto& e = context.Instances.AddOne();
e.Prefab = prefab;
e.RootId = id;
e.RootIndex = i;
e.StatIndex = i;
}
context.ObjectToInstance[id] = index;
@@ -490,6 +490,85 @@ void SceneObjectsFactory::SynchronizeNewPrefabInstances(Context& context, Prefab
Scripting::ObjectsLookupIdMapping.Set(&data.Modifier->IdsMapping);
data.InitialCount = data.SceneObjects.Count();
// Recreate any missing prefab root objects that were deleted (eg. spawned prefab got its root changed and deleted so old prefab instance needs to respawn it)
for (int32 instanceIndex = 0; instanceIndex < context.Instances.Count(); instanceIndex++)
{
PrefabInstance& instance = context.Instances[instanceIndex];
SceneObject* root = data.SceneObjects[instance.RootIndex];
if (!root && instance.Prefab)
{
instance.FixRootParent = true;
// Check if current prefab root existed in the deserialized data
const auto& oldRootData = data.Data[instance.RootIndex];
const Guid oldRootId = JsonTools::GetGuid(oldRootData, "ID");
const Guid prefabObjectId = JsonTools::GetGuid(oldRootData, "PrefabObjectID");
const Guid prefabRootId = instance.Prefab->GetRootObjectId();
Guid id;
int32 idInstance = -1;
bool syncNewRoot = false;
if (instance.IdsMapping.TryGet(prefabRootId, id) && context.ObjectToInstance.TryGet(id, idInstance) && idInstance == instanceIndex)
{
// Update the missing root with the valid object from this prefab instance
LOG(Warning, "Changed prefab instance root from ID={0}, PrefabObjectID={1} to ID={2}, PrefabObjectID={3} ({4})", instance.RootId, prefabObjectId, id, prefabRootId, instance.Prefab->ToString());
}
else
{
LOG(Warning, "Missing prefab instance root (ID={0}, PrefabObjectID={1}, {2})", instance.RootId, prefabObjectId, instance.Prefab->ToString());
// Get prefab object data from the prefab
const ISerializable::DeserializeStream* prefabData;
if (!instance.Prefab->ObjectsDataCache.TryGet(prefabRootId, prefabData))
{
LOG(Warning, "Missing object {1} data in prefab {0}.", instance.Prefab->ToString(), prefabObjectId);
continue;
}
// Map prefab object ID to the new prefab object instance
id = Guid::New();
data.Modifier->IdsMapping[prefabRootId] = id;
// Create prefab instance (recursive prefab loading to support nested prefabs)
root = Spawn(context, *prefabData);
if (!root)
{
LOG(Warning, "Failed to create object {1} from prefab {0}.", instance.Prefab->ToString(), prefabRootId);
continue;
}
// Register object
root->RegisterObject();
data.SceneObjects.Add(root);
auto& newObj = data.NewObjects.AddOne();
newObj.Prefab = instance.Prefab;
newObj.PrefabData = prefabData;
newObj.PrefabObjectId = prefabRootId;
newObj.Id = id;
context.ObjectToInstance[id] = instanceIndex;
syncNewRoot = true;
}
// Update prefab root info
instance.RootId = id;
instance.RootIndex = data.SceneObjects.Find(Scripting::FindObject<SceneObject>(id));
CHECK(instance.RootIndex != -1);
// Remap removed prefab root into the current root (can be different type but is needed for proper hierarchy linkage)
instance.IdsMapping[prefabObjectId] = id;
instance.IdsMapping[prefabRootId] = id;
instance.IdsMapping[oldRootId] = id;
data.Modifier->IdsMapping[prefabObjectId] = id;
data.Modifier->IdsMapping[prefabRootId] = id;
data.Modifier->IdsMapping[oldRootId] = id;
// Add any sub-objects that are missing (in case new root was created)
if (syncNewRoot)
{
SynchronizeNewPrefabInstances(context, data, instance.Prefab, (Actor*)root, prefabRootId, instance.RootIndex, oldRootData);
}
}
}
// Check all actors with prefab linkage for adding missing objects
for (int32 i = 0; i < data.InitialCount; i++)
{
@@ -504,13 +583,12 @@ void SceneObjectsFactory::SynchronizeNewPrefabInstances(Context& context, Prefab
continue;
if (!JsonTools::GetGuidIfValid(actorId, stream, "ID"))
continue;
const Guid actorParentId = JsonTools::GetGuid(stream, "ParentID");
// Load prefab
auto prefab = Content::LoadAsync<Prefab>(prefabId);
if (prefab == nullptr)
{
LOG(Warning, "Missing prefab with id={0}.", prefabId);
LOG(Warning, "Missing prefab {0}.", prefabId);
continue;
}
if (prefab->WaitForLoaded())
@@ -519,66 +597,7 @@ void SceneObjectsFactory::SynchronizeNewPrefabInstances(Context& context, Prefab
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<int32>(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;
// 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();
Scripting::ObjectsLookupIdMapping.Set(&data.Modifier->IdsMapping);
// Create instance (including all children)
SynchronizeNewPrefabInstance(context, data, prefab, actor, prefabObjectId);
}
SynchronizeNewPrefabInstances(context, data, prefab, actor, actorPrefabObjectId, i, stream);
}
Scripting::ObjectsLookupIdMapping.Set(nullptr);
@@ -605,7 +624,7 @@ void SceneObjectsFactory::SynchronizePrefabInstances(Context& context, PrefabSyn
auto prefab = Content::LoadAsync<Prefab>(prefabId);
if (prefab == nullptr)
{
LOG(Warning, "Missing prefab with id={0}.", prefabId);
LOG(Warning, "Missing prefab {0}.", prefabId);
continue;
}
if (prefab->WaitForLoaded())
@@ -626,7 +645,7 @@ void SceneObjectsFactory::SynchronizePrefabInstances(Context& context, PrefabSyn
// 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
// Map actual prefab object ID to the current scene objects collection
context.SetupIdsMapping(obj, data.Modifier);
data.Modifier->IdsMapping.TryGet(actualParentPrefabId, actualParentPrefabId);
@@ -646,15 +665,12 @@ void SceneObjectsFactory::SynchronizePrefabInstances(Context& context, PrefabSyn
if (i != 0)
{
const auto defaultInstance = prefab->GetDefaultInstance(obj->GetPrefabObjectID());
if (defaultInstance)
{
obj->SetOrderInParent(defaultInstance->GetOrderInParent());
}
const int32 order = defaultInstance ? defaultInstance->GetOrderInParent() : -1;
if (order != -1)
obj->SetOrderInParent(order);
}
}
Scripting::ObjectsLookupIdMapping.Set(&data.Modifier->IdsMapping);
// Synchronize new prefab objects
for (int32 i = 0; i < data.NewObjects.Count(); i++)
{
@@ -662,20 +678,97 @@ void SceneObjectsFactory::SynchronizePrefabInstances(Context& context, PrefabSyn
auto& newObj = data.NewObjects[i];
// Deserialize object with prefab data
Scripting::ObjectsLookupIdMapping.Set(&data.Modifier->IdsMapping);
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)
const int32 order = defaultInstance ? defaultInstance->GetOrderInParent() : -1;
if (order != -1)
obj->SetOrderInParent(order);
}
// Setup hierarchy for the prefab instances (ensure any new objects are connected)
for (const auto& instance : context.Instances)
{
const auto& prefabStartData = data.Data[instance.StatIndex];
Guid prefabStartParentId;
if (instance.FixRootParent && JsonTools::GetGuidIfValid(prefabStartParentId, prefabStartData, "ParentID"))
{
obj->SetOrderInParent(defaultInstance->GetOrderInParent());
auto* root = data.SceneObjects[instance.RootIndex];
const auto rootParent = Scripting::FindObject<Actor>(prefabStartParentId);
root->SetParent(rootParent, false);
}
}
Scripting::ObjectsLookupIdMapping.Set(nullptr);
}
void SceneObjectsFactory::SynchronizeNewPrefabInstances(Context& context, PrefabSyncData& data, Prefab* prefab, Actor* actor, const Guid& actorPrefabObjectId, int32 i, const ISerializable::DeserializeStream& stream)
{
// 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<int32>(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;
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)
{
// Start searching from the beginning of that prefab instance (eg. in case prefab objects were reordered)
childSearchStart = Math::Min(childSearchStart, context.Instances[instanceIndex].StatIndex);
}
for (int32 j = childSearchStart; j < data.InitialCount; j++)
{
if (JsonTools::GetGuid(data.Data[j], "PrefabObjectID") == prefabObjectId)
{
// This object exists in the saved scene objects list
spawned = true;
break;
}
}
if (spawned)
continue;
// 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();
Scripting::ObjectsLookupIdMapping.Set(&data.Modifier->IdsMapping);
// Create instance (including all children)
SynchronizeNewPrefabInstance(context, data, prefab, actor, prefabObjectId);
}
}
void SceneObjectsFactory::SynchronizeNewPrefabInstance(Context& context, PrefabSyncData& data, Prefab* prefab, Actor* actor, const Guid& prefabObjectId)
{
PROFILE_CPU_NAMED("SynchronizeNewPrefabInstance");
@@ -15,8 +15,11 @@ class FLAXENGINE_API SceneObjectsFactory
public:
struct PrefabInstance
{
int32 StatIndex;
int32 RootIndex;
Guid RootId;
Prefab* Prefab;
bool FixRootParent = false;
Dictionary<Guid, Guid> IdsMapping;
};
@@ -70,6 +73,8 @@ public:
struct PrefabSyncData
{
friend SceneObjectsFactory;
friend class PrefabManager;
// The created scene objects. Collection can be modified (eg. for spawning missing objects).
Array<SceneObject*>& SceneObjects;
// The scene objects data.
@@ -124,5 +129,6 @@ public:
static void SynchronizePrefabInstances(Context& context, PrefabSyncData& data);
private:
static void SynchronizeNewPrefabInstances(Context& context, PrefabSyncData& data, Prefab* prefab, Actor* actor, const Guid& actorPrefabObjectId, int32 i, const ISerializable::DeserializeStream& stream);
static void SynchronizeNewPrefabInstance(Context& context, PrefabSyncData& data, Prefab* prefab, Actor* actor, const Guid& prefabObjectId);
};
+1 -1
View File
@@ -481,7 +481,7 @@ void JsonWriter::SceneObject(::SceneObject* obj)
}
else
{
LOG(Warning, "Missing prefab with id={0}.", obj->GetPrefabID());
LOG(Warning, "Missing prefab {0}.", obj->GetPrefabID());
}
}
+145
View File
@@ -412,4 +412,149 @@ TEST_CASE("Prefabs")
Content::DeleteAsset(prefabNested);
Content::DeleteAsset(prefabBase);
}
SECTION("Test Loading Nested Prefab After Changing and Deleting Root")
{
// https://github.com/FlaxEngine/FlaxEngine/issues/2050
// Create base prefab with 1 object
AssetReference<Prefab> prefabBase = Content::CreateVirtualAsset<Prefab>();
REQUIRE(prefabBase);
Guid id;
Guid::Parse("3b3334524c696dcfa93cabacd2a4f404", id);
prefabBase->ChangeID(id);
auto prefabBaseInit = prefabBase->Init(Prefab::TypeName,
"["
"{"
"\"ID\": \"82ce814f4d913e58eb35ab8b0b7e2eef\","
"\"TypeName\": \"FlaxEngine.DirectionalLight\","
"\"Name\": \"New Root\""
"},"
"{"
"\"ID\": \"f8fbee1349f749396ab6c2ad34f3afec\","
"\"TypeName\": \"FlaxEngine.Camera\","
"\"Name\": \"Child 1\","
"\"ParentID\": \"82ce814f4d913e58eb35ab8b0b7e2eef\""
"},"
"{"
"\"ID\": \"5632561847cf96fe2e8919848b7eca79\","
"\"TypeName\": \"FlaxEngine.EmptyActor\","
"\"Name\": \"Child 1.Child\","
"\"ParentID\": \"f8fbee1349f749396ab6c2ad34f3afec\""
"},"
"{"
"\"ID\": \"4e4f3a1847cf96fe2e8919848b7eca79\","
"\"TypeName\": \"FlaxEngine.UICanvas\","
"\"Name\": \"Child 2\","
"\"ParentID\": \"82ce814f4d913e58eb35ab8b0b7e2eef\""
"}"
"]");
REQUIRE(!prefabBaseInit);
// Create nested prefab but with 'old' state where root object is different
AssetReference<Prefab> prefabNested1 = Content::CreateVirtualAsset<Prefab>();
REQUIRE(prefabNested1);
Guid::Parse("671447e947cbd2deea018a8377636ce6", id);
prefabNested1->ChangeID(id);
auto prefabNestedInit1 = prefabNested1->Init(Prefab::TypeName,
"["
"{"
"\"ID\": \"597ab8ea43a5c58b8d06f58f9364d261\","
"\"PrefabID\": \"3b3334524c696dcfa93cabacd2a4f404\","
"\"PrefabObjectID\": \"589bcfaa4bd1a53435129480e5bbdb3b\","
"\"Name\": \"Old Root\""
"},"
"{"
"\"ID\": \"1a6228d84897ff3b2f444ea263c3657e\","
"\"PrefabID\": \"3b3334524c696dcfa93cabacd2a4f404\","
"\"PrefabObjectID\": \"f8fbee1349f749396ab6c2ad34f3afec\","
"\"ParentID\": \"597ab8ea43a5c58b8d06f58f9364d261\""
"},"
"{"
"\"ID\": \"1212124f4d913e58eb35ab8b0b7e2eef\","
"\"PrefabID\": \"3b3334524c696dcfa93cabacd2a4f404\","
"\"PrefabObjectID\": \"82ce814f4d913e58eb35ab8b0b7e2eef\","
"\"ParentID\": \"597ab8ea43a5c58b8d06f58f9364d261\","
"\"Name\": \"New Root\""
"},"
"{"
"\"ID\": \"468028d84897ff3b2f444ea263c3657e\","
"\"PrefabID\": \"3b3334524c696dcfa93cabacd2a4f404\","
"\"PrefabObjectID\": \"2468902349f749396ab6c2ad34f3afec\","
"\"ParentID\": \"597ab8ea43a5c58b8d06f58f9364d261\","
"\"Name\": \"Old Child\""
"}"
"]");
REQUIRE(!prefabNestedInit1);
// Create nested prefab but with 'old' state where root object is different and doesn't exist anymore
AssetReference<Prefab> prefabNested2 = Content::CreateVirtualAsset<Prefab>();
REQUIRE(prefabNested2);
Guid::Parse("b71447e947cbd2deea018a8377636ce6", id);
prefabNested2->ChangeID(id);
auto prefabNestedInit2 = prefabNested2->Init(Prefab::TypeName,
"["
"{"
"\"ID\": \"597ab8ea43a5c58b8d06f58f9364d261\","
"\"PrefabID\": \"3b3334524c696dcfa93cabacd2a4f404\","
"\"PrefabObjectID\": \"589bcfaa4bd1a53435129480e5bbdb3b\","
"\"Name\": \"Old Root\""
"},"
"{"
"\"ID\": \"1a6228d84897ff3b2f444ea263c3657e\","
"\"PrefabID\": \"3b3334524c696dcfa93cabacd2a4f404\","
"\"PrefabObjectID\": \"f8fbee1349f749396ab6c2ad34f3afec\","
"\"ParentID\": \"597ab8ea43a5c58b8d06f58f9364d261\""
"},"
"{"
"\"ID\": \"468028d84897ff3b2f444ea263c3657e\","
"\"PrefabID\": \"3b3334524c696dcfa93cabacd2a4f404\","
"\"PrefabObjectID\": \"2468902349f749396ab6c2ad34f3afec\","
"\"ParentID\": \"597ab8ea43a5c58b8d06f58f9364d261\","
"\"Name\": \"Old Child\""
"}"
"]");
REQUIRE(!prefabNestedInit2);
// Spawn test instances of both prefabs
ScriptingObjectReference<Actor> instanceBase = PrefabManager::SpawnPrefab(prefabBase);
ScriptingObjectReference<Actor> instanceNested1 = PrefabManager::SpawnPrefab(prefabNested1);
ScriptingObjectReference<Actor> instanceNested2 = PrefabManager::SpawnPrefab(prefabNested2);
// Verify scenario
REQUIRE(instanceBase);
REQUIRE(instanceBase->GetName() == TEXT("New Root"));
REQUIRE(instanceBase->GetChildrenCount() == 2);
REQUIRE(instanceBase->Children[0]->GetName() == TEXT("Child 1"));
REQUIRE(instanceBase->Children[0]->GetChildrenCount() == 1);
REQUIRE(instanceBase->Children[1]->GetName() == TEXT("Child 2"));
REQUIRE(instanceBase->Children[1]->GetChildrenCount() == 0);
REQUIRE(instanceBase->Children[0]->Children[0]->GetName() == TEXT("Child 1.Child"));
REQUIRE(instanceBase->Children[0]->Children[0]->GetChildrenCount() == 0);
REQUIRE(instanceNested1);
REQUIRE(instanceNested1->GetName() == TEXT("New Root"));
REQUIRE(instanceNested1->GetChildrenCount() == 2);
REQUIRE(instanceNested1->Children[0]->GetName() == TEXT("Child 1"));
REQUIRE(instanceNested1->Children[0]->GetChildrenCount() == 1);
REQUIRE(instanceNested1->Children[1]->GetName() == TEXT("Child 2"));
REQUIRE(instanceNested1->Children[1]->GetChildrenCount() == 0);
REQUIRE(instanceNested1->Children[0]->Children[0]->GetName() == TEXT("Child 1.Child"));
REQUIRE(instanceNested1->Children[0]->Children[0]->GetChildrenCount() == 0);
REQUIRE(instanceNested2);
REQUIRE(instanceNested2->GetName() == TEXT("New Root"));
REQUIRE(instanceNested2->GetChildrenCount() == 2);
REQUIRE(instanceNested2->Children[0]->GetName() == TEXT("Child 1"));
REQUIRE(instanceNested2->Children[0]->GetChildrenCount() == 1);
REQUIRE(instanceNested2->Children[1]->GetName() == TEXT("Child 2"));
REQUIRE(instanceNested2->Children[1]->GetChildrenCount() == 0);
REQUIRE(instanceNested2->Children[0]->Children[0]->GetName() == TEXT("Child 1.Child"));
REQUIRE(instanceNested2->Children[0]->Children[0]->GetChildrenCount() == 0);
// Cleanup
instanceNested2->DeleteObject();
instanceNested1->DeleteObject();
instanceBase->DeleteObject();
Content::DeleteAsset(prefabNested2);
Content::DeleteAsset(prefabNested1);
Content::DeleteAsset(prefabBase);
}
}