Add support for loading prefab instance if the root was changed or deleted
#2050
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user