@@ -568,6 +568,10 @@ namespace FlaxEditor.Modules
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip if already added
|
||||
if (SceneGraphFactory.Nodes.ContainsKey(actor.ID))
|
||||
return;
|
||||
|
||||
var node = SceneGraphFactory.BuildActorNode(actor);
|
||||
if (node != null)
|
||||
{
|
||||
|
||||
@@ -304,6 +304,7 @@ bool PrefabInstanceData::SynchronizePrefabInstances(PrefabInstancesData& prefabI
|
||||
{
|
||||
for (int32 instanceIndex = 0; instanceIndex < prefabInstancesData.Count(); instanceIndex++)
|
||||
{
|
||||
PROFILE_CPU_NAMED("Instance");
|
||||
auto& instance = prefabInstancesData[instanceIndex];
|
||||
ISerializeModifierCacheType modifier = Cache::ISerializeModifier.Get();
|
||||
Scripting::ObjectsLookupIdMapping.Set(&modifier.Value->IdsMapping);
|
||||
@@ -377,6 +378,10 @@ bool PrefabInstanceData::SynchronizePrefabInstances(PrefabInstancesData& prefabI
|
||||
sceneObjects->Add(obj);
|
||||
}
|
||||
|
||||
// Generate nested prefab instances to properly handle Ids Mapping within each nested prefab
|
||||
SceneObjectsFactory::PrefabSyncData prefabSyncData(*sceneObjects.Value, instance.Data, modifier.Value);
|
||||
SceneObjectsFactory::SetupPrefabInstances(context, prefabSyncData);
|
||||
|
||||
// Apply modifications
|
||||
for (int32 i = existingObjectsCount - 1; i >= 0; i--)
|
||||
{
|
||||
@@ -388,6 +393,7 @@ bool PrefabInstanceData::SynchronizePrefabInstances(PrefabInstancesData& prefabI
|
||||
if (prefabObjectIdToDiffData.TryGet(obj->GetPrefabObjectID(), data))
|
||||
{
|
||||
// Apply prefab changes
|
||||
context.SetupIdsMapping(obj, modifier.Value);
|
||||
obj->Deserialize(*(ISerializable::DeserializeStream*)data, modifier.Value);
|
||||
}
|
||||
else
|
||||
@@ -424,7 +430,6 @@ bool PrefabInstanceData::SynchronizePrefabInstances(PrefabInstancesData& prefabI
|
||||
for (int32 i = 0; i < sceneObjects->Count(); i++)
|
||||
{
|
||||
SceneObject* obj = sceneObjects->At(i);
|
||||
|
||||
int32 dataIndex;
|
||||
if (instance.PrefabInstanceIdToDataIndex.TryGet(obj->GetSceneObjectId(), dataIndex))
|
||||
{
|
||||
@@ -440,6 +445,7 @@ bool PrefabInstanceData::SynchronizePrefabInstances(PrefabInstancesData& prefabI
|
||||
data.RemoveMember("ParentID");
|
||||
#endif
|
||||
|
||||
context.SetupIdsMapping(obj, modifier.Value);
|
||||
obj->Deserialize(data, modifier.Value);
|
||||
|
||||
// Preserve order in parent (values from prefab are used)
|
||||
@@ -527,6 +533,7 @@ bool PrefabInstanceData::SynchronizePrefabInstances(PrefabInstancesData& prefabI
|
||||
{
|
||||
if (prefabInstancesData.IsEmpty())
|
||||
return false;
|
||||
PROFILE_CPU();
|
||||
|
||||
// Fully serialize default instance scene objects (accumulate all prefab and nested prefabs changes into a single linear list of objects)
|
||||
rapidjson_flax::Document defaultInstanceData;
|
||||
@@ -926,7 +933,6 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr
|
||||
// TODO: what if user applied prefab with references to the other objects from scene? clear them or what?
|
||||
JsonTools::ChangeIds(diffDataDocument, objectInstanceIdToPrefabObjectId);
|
||||
}
|
||||
dataBuffer.Clear();
|
||||
CollectionPoolCache<ActorsCache::SceneObjectsListType>::ScopeCache sceneObjects = ActorsCache::SceneObjectsListCache.Get();
|
||||
|
||||
// Destroy default instance and some cache data in Prefab
|
||||
@@ -1002,6 +1008,32 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr
|
||||
obj->RegisterObject();
|
||||
}
|
||||
|
||||
// Generate nested prefab instances to properly handle Ids Mapping within each nested prefab
|
||||
rapidjson_flax::Document targetDataDocument;
|
||||
if (NestedPrefabs.HasItems())
|
||||
{
|
||||
targetDataDocument.Parse(dataBuffer.GetString(), dataBuffer.GetSize());
|
||||
SceneObjectsFactory::PrefabSyncData prefabSyncData(*sceneObjects.Value, targetDataDocument, modifier.Value);
|
||||
SceneObjectsFactory::SetupPrefabInstances(context, prefabSyncData);
|
||||
|
||||
if (context.Instances.HasItems())
|
||||
{
|
||||
// Only main prefab instance is allowed (in case nested prefab was added to this prefab)
|
||||
for (auto i = context.ObjectToInstance.Begin(); i.IsNotEnd(); ++i)
|
||||
{
|
||||
if (i->Value != 0)
|
||||
context.ObjectToInstance.Remove(i);
|
||||
}
|
||||
context.Instances.Resize(1);
|
||||
|
||||
// Trash object mapping to prevent messing up prefab structure when applying hierarchy changes (only nested instances are used)
|
||||
context.Instances[0].IdsMapping.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
dataBuffer.Clear();
|
||||
auto originalIdsMapping = modifier.Value->IdsMapping;
|
||||
|
||||
// Deserialize prefab objects and apply modifications
|
||||
for (int32 i = 0; i < ObjectsCount; i++)
|
||||
{
|
||||
@@ -1037,6 +1069,9 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr
|
||||
}
|
||||
}
|
||||
|
||||
// Use the initial Ids Mapping (SetupIdsMapping overrides it for instanced prefabs)
|
||||
modifier.Value->IdsMapping = originalIdsMapping;
|
||||
|
||||
// Deserialize new prefab objects
|
||||
newPrefabInstanceIdToDataIndexCounter = 0;
|
||||
for (auto i = newPrefabInstanceIdToDataIndex.Begin(); i.IsNotEnd(); ++i)
|
||||
@@ -1283,6 +1318,10 @@ bool Prefab::UpdateInternal(const Array<SceneObject*>& defaultInstanceObjects, r
|
||||
ObjectsDataCache.Add(objectId, &objData);
|
||||
ObjectsCount++;
|
||||
|
||||
Guid parentID;
|
||||
if (JsonTools::GetGuidIfValid(parentID, objData, "ParentID"))
|
||||
ObjectsHierarchyCache[parentID].Add(objectId);
|
||||
|
||||
Guid prefabId = JsonTools::GetGuid(objData, "PrefabID");
|
||||
if (prefabId.IsValid() && !NestedPrefabs.Contains(prefabId))
|
||||
{
|
||||
|
||||
@@ -172,7 +172,9 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const SpawnOptions& options)
|
||||
SceneObjectsFactory::HandleObjectDeserializationError(stream);
|
||||
}
|
||||
SceneObjectsFactory::PrefabSyncData prefabSyncData(*sceneObjects.Value, data, modifier.Value);
|
||||
if (options.WithSync)
|
||||
bool withSync = options.WithSync || prefab->NestedPrefabs.HasItems(); // Nested prefabs needs prefab instances generation for correct IdsMapping if the same prefab exists multiple times
|
||||
// TODO: let prefab check if has multiple nested prefabs at cook time?
|
||||
if (withSync)
|
||||
{
|
||||
// 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
|
||||
@@ -187,14 +189,14 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const SpawnOptions& options)
|
||||
if (obj)
|
||||
SceneObjectsFactory::Deserialize(context, obj, stream);
|
||||
}
|
||||
Scripting::ObjectsLookupIdMapping.Set(prevIdMapping);
|
||||
|
||||
// Synchronize prefab instances (prefab may have new objects added or some removed so deserialized instances need to synchronize with it)
|
||||
if (options.WithSync)
|
||||
if (withSync)
|
||||
{
|
||||
// TODO: resave and force sync scenes during game cooking so this step could be skipped in game
|
||||
SceneObjectsFactory::SynchronizePrefabInstances(context, prefabSyncData);
|
||||
}
|
||||
Scripting::ObjectsLookupIdMapping.Set(prevIdMapping);
|
||||
|
||||
// Pick prefab root object
|
||||
Actor* root = nullptr;
|
||||
|
||||
@@ -101,13 +101,25 @@ ISerializeModifier* SceneObjectsFactory::Context::GetModifier()
|
||||
void SceneObjectsFactory::Context::SetupIdsMapping(const SceneObject* obj, ISerializeModifier* modifier) const
|
||||
{
|
||||
int32 instanceIndex;
|
||||
if (ObjectToInstance.TryGet(obj->GetID(), instanceIndex) && instanceIndex != modifier->CurrentInstance)
|
||||
const Guid id = obj->GetID();
|
||||
if (ObjectToInstance.TryGet(id, instanceIndex))
|
||||
{
|
||||
// Apply the current prefab instance objects ids table to resolve references inside a prefab properly
|
||||
modifier->CurrentInstance = instanceIndex;
|
||||
const auto& instance = Instances[instanceIndex];
|
||||
for (const auto& e : instance.IdsMapping)
|
||||
modifier->IdsMapping[e.Key] = e.Value;
|
||||
if (instanceIndex != modifier->CurrentInstance)
|
||||
{
|
||||
modifier->CurrentInstance = instanceIndex;
|
||||
for (const auto& e : instance.IdsMapping)
|
||||
modifier->IdsMapping[e.Key] = e.Value;
|
||||
}
|
||||
int32 nestedIndex;
|
||||
if (instance.ObjectToNested.TryGet(id, nestedIndex))
|
||||
{
|
||||
// Each nested prefab has own object ids mapping that takes precedence
|
||||
const auto& nested = instance.Nested[nestedIndex];
|
||||
for (const auto& e : nested.IdsMapping)
|
||||
modifier->IdsMapping[e.Key] = e.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -499,6 +511,34 @@ void SceneObjectsFactory::SetupPrefabInstances(Context& context, const PrefabSyn
|
||||
prefab = Content::LoadAsync<Prefab>(prefabId);
|
||||
if (prefab && !prefab->WaitForLoaded())
|
||||
{
|
||||
// If prefab instance contains multiple nested prefabs, then need to build separate Ids remapping for each of them to prevent collisions
|
||||
int32 nestedIndex = -1;
|
||||
if (prefabInstance.Nested.HasItems())
|
||||
{
|
||||
// Check only the last instance if the current object belongs to it
|
||||
for (int32 j = prefabInstance.Nested.Count() - 1; j >= 0; j--)
|
||||
{
|
||||
const auto& e = prefabInstance.Nested[j];
|
||||
if (e.Prefab == prefab && e.RootObjectId != prefabObjectId)
|
||||
{
|
||||
nestedIndex = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nestedIndex == -1)
|
||||
{
|
||||
nestedIndex = prefabInstance.Nested.Count();
|
||||
auto& e = prefabInstance.Nested.AddOne();
|
||||
e.Prefab = prefab;
|
||||
e.RootObjectId = prefabObjectId;
|
||||
}
|
||||
|
||||
// Map this object into this instance to inherit all objects from it when looking up via IdsMapping
|
||||
prefabInstance.ObjectToNested[id] = nestedIndex;
|
||||
auto& nestedInstance = prefabInstance.Nested[nestedIndex];
|
||||
nestedInstance.IdsMapping[prefabObjectId] = id;
|
||||
|
||||
// Map prefab object ID to the deserialized instance ID
|
||||
prefabInstance.IdsMapping[prefabObjectId] = id;
|
||||
goto NESTED_PREFAB_WALK;
|
||||
@@ -787,23 +827,20 @@ void SceneObjectsFactory::SynchronizeNewPrefabInstances(Context& context, Prefab
|
||||
if (spawned)
|
||||
continue;
|
||||
|
||||
// Map prefab object ID to this actor's prefab instance so the new objects gets added to it
|
||||
// Map prefab object ID to this actor's prefab instance so the new objects get added to it
|
||||
context.SetupIdsMapping(actor, data.Modifier);
|
||||
data.Modifier->IdsMapping[actorPrefabObjectId] = actorId;
|
||||
Scripting::ObjectsLookupIdMapping.Set(&data.Modifier->IdsMapping);
|
||||
|
||||
// Create instance (including all children)
|
||||
SynchronizeNewPrefabInstance(context, data, prefab, actor, prefabObjectId);
|
||||
SynchronizeNewPrefabInstance(context, data, prefab, actor, prefabObjectId, actor->GetID());
|
||||
}
|
||||
}
|
||||
|
||||
void SceneObjectsFactory::SynchronizeNewPrefabInstance(Context& context, PrefabSyncData& data, Prefab* prefab, Actor* actor, const Guid& prefabObjectId)
|
||||
void SceneObjectsFactory::SynchronizeNewPrefabInstance(Context& context, PrefabSyncData& data, Prefab* prefab, Actor* actor, const Guid& prefabObjectId, const Guid& nestedInstanceId)
|
||||
{
|
||||
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))
|
||||
@@ -823,6 +860,7 @@ void SceneObjectsFactory::SynchronizeNewPrefabInstance(Context& context, PrefabS
|
||||
LOG(Warning, "Failed to create object {1} from prefab {0}.", prefab->ToString(), prefabObjectId);
|
||||
return;
|
||||
}
|
||||
LOG(Info, "Actor {0} has missing child object '{4}' (PrefabObjectID: {1}, PrefabID: {2}, Path: {3})", actor->ToString(), prefabObjectId, prefab->GetID(), prefab->GetPath(), child->GetType().ToString());
|
||||
|
||||
// Register object
|
||||
child->RegisterObject();
|
||||
@@ -839,17 +877,51 @@ void SceneObjectsFactory::SynchronizeNewPrefabInstance(Context& context, PrefabS
|
||||
context.ObjectToInstance[id] = instanceIndex;
|
||||
auto& prefabInstance = context.Instances[instanceIndex];
|
||||
prefabInstance.IdsMapping[prefabObjectId] = id;
|
||||
|
||||
// Check if it's a nested prefab
|
||||
const ISerializable::DeserializeStream* nestedPrefabData;
|
||||
Guid nestedPrefabObjectId;
|
||||
if (prefab->ObjectsDataCache.TryGet(prefabObjectId, nestedPrefabData) && JsonTools::GetGuidIfValid(nestedPrefabObjectId, *nestedPrefabData, "PrefabObjectID"))
|
||||
{
|
||||
// Try reusing parent nested instance (or make a new one)
|
||||
int32 nestedIndex = -1;
|
||||
if (!prefabInstance.ObjectToNested.TryGet(nestedInstanceId, nestedIndex))
|
||||
{
|
||||
if (auto* nestedPrefab = Content::LoadAsync<Prefab>(JsonTools::GetGuid(*prefabData, "PrefabID")))
|
||||
{
|
||||
if (prefabInstance.Nested.HasItems())
|
||||
{
|
||||
// Check only the last instance if the current object belongs to it
|
||||
const auto& e = prefabInstance.Nested.Last();
|
||||
if (e.Prefab == nestedPrefab && e.RootObjectId != nestedPrefabObjectId)
|
||||
nestedIndex = prefabInstance.Nested.Count() - 1;
|
||||
}
|
||||
if (nestedIndex == -1)
|
||||
{
|
||||
nestedIndex = prefabInstance.Nested.Count();
|
||||
auto& e = prefabInstance.Nested.AddOne();
|
||||
e.Prefab = nestedPrefab;
|
||||
e.RootObjectId = nestedPrefabObjectId;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nestedIndex != -1)
|
||||
{
|
||||
// Insert into nested instance
|
||||
prefabInstance.ObjectToNested[id] = nestedIndex;
|
||||
auto& nestedInstance = prefabInstance.Nested[nestedIndex];
|
||||
nestedInstance.IdsMapping[nestedPrefabObjectId] = 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)
|
||||
const auto* hierarchy = prefab->ObjectsHierarchyCache.TryGet(prefabObjectId);
|
||||
if (hierarchy)
|
||||
{
|
||||
Guid qParentId;
|
||||
if (JsonTools::GetGuidIfValid(qParentId, *q->Value, "ParentID") && qParentId == prefabObjectId)
|
||||
for (const Guid& e : *hierarchy)
|
||||
{
|
||||
const Guid qPrefabObjectId = JsonTools::GetGuid(*q->Value, "ID");
|
||||
SynchronizeNewPrefabInstance(context, data, prefab, actor, qPrefabObjectId);
|
||||
SynchronizeNewPrefabInstance(context, data, prefab, actor, e, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,13 @@
|
||||
class FLAXENGINE_API SceneObjectsFactory
|
||||
{
|
||||
public:
|
||||
struct NestedPrefabInstance
|
||||
{
|
||||
Prefab* Prefab;
|
||||
Guid RootObjectId;
|
||||
Dictionary<Guid, Guid> IdsMapping;
|
||||
};
|
||||
|
||||
struct PrefabInstance
|
||||
{
|
||||
int32 StatIndex;
|
||||
@@ -21,6 +28,8 @@ public:
|
||||
Prefab* Prefab;
|
||||
bool FixRootParent = false;
|
||||
Dictionary<Guid, Guid> IdsMapping;
|
||||
Array<NestedPrefabInstance> Nested;
|
||||
Dictionary<Guid, int32> ObjectToNested;
|
||||
};
|
||||
|
||||
struct Context
|
||||
@@ -133,5 +142,5 @@ public:
|
||||
|
||||
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);
|
||||
static void SynchronizeNewPrefabInstance(Context& context, PrefabSyncData& data, Prefab* prefab, Actor* actor, const Guid& prefabObjectId, const Guid& nestedInstanceId);
|
||||
};
|
||||
|
||||
@@ -687,6 +687,171 @@ TEST_CASE("Prefabs")
|
||||
instanceA->DeleteObject();
|
||||
instanceB->DeleteObject();
|
||||
instanceC->DeleteObject();
|
||||
}
|
||||
SECTION("Test Loading Nested Prefab With Multiple Instances of Nested Prefab")
|
||||
{
|
||||
// https://github.com/FlaxEngine/FlaxEngine/issues/3255
|
||||
|
||||
// Create Prefab C with basic cross-object references
|
||||
AssetReference<Prefab> prefabC = Content::CreateVirtualAsset<Prefab>();
|
||||
REQUIRE(prefabC);
|
||||
Guid id;
|
||||
Guid::Parse("cccbe4b0416be0777a6ce59e8788b10f", id);
|
||||
prefabC->ChangeID(id);
|
||||
auto prefabCInit = prefabC->Init(Prefab::TypeName,
|
||||
"["
|
||||
"{"
|
||||
"\"ID\": \"aac6b9644492fbca1a6ab0a7904a557e\","
|
||||
"\"TypeName\": \"FlaxEngine.ExponentialHeightFog\","
|
||||
"\"Name\": \"Prefab C.Root\","
|
||||
"\"DirectionalInscatteringLight\": \"bbb6b9644492fbca1a6ab0a7904a557e\""
|
||||
"},"
|
||||
"{"
|
||||
"\"ID\": \"bbb6b9644492fbca1a6ab0a7904a557e\","
|
||||
"\"TypeName\": \"FlaxEngine.DirectionalLight\","
|
||||
"\"ParentID\": \"aac6b9644492fbca1a6ab0a7904a557e\","
|
||||
"\"Name\": \"Prefab C.Light\""
|
||||
"}"
|
||||
"]");
|
||||
REQUIRE(!prefabCInit);
|
||||
|
||||
// Create Prefab B with two nested Prefab C attached to the root
|
||||
AssetReference<Prefab> prefabB = Content::CreateVirtualAsset<Prefab>();
|
||||
REQUIRE(prefabB);
|
||||
SCOPE_EXIT{ Content::DeleteAsset(prefabB); };
|
||||
Guid::Parse("bbb744714f746e31855f41815612d14b", id);
|
||||
prefabB->ChangeID(id);
|
||||
auto prefabBInit = prefabB->Init(Prefab::TypeName,
|
||||
"["
|
||||
"{"
|
||||
"\"ID\": \"244274a04cc60d56a2f024bfeef5772d\","
|
||||
"\"TypeName\": \"FlaxEngine.SpotLight\","
|
||||
"\"Name\": \"Prefab B.Root\""
|
||||
"},"
|
||||
"{"
|
||||
"\"ID\": \"1111f1094f430733333f8280e78dfcc3\","
|
||||
"\"PrefabID\": \"cccbe4b0416be0777a6ce59e8788b10f\","
|
||||
"\"PrefabObjectID\": \"aac6b9644492fbca1a6ab0a7904a557e\","
|
||||
"\"ParentID\": \"244274a04cc60d56a2f024bfeef5772d\""
|
||||
"},"
|
||||
"{"
|
||||
"\"ID\": \"2221f1094f430733333f8280e78dfcc3\","
|
||||
"\"PrefabID\": \"cccbe4b0416be0777a6ce59e8788b10f\","
|
||||
"\"PrefabObjectID\": \"bbb6b9644492fbca1a6ab0a7904a557e\","
|
||||
"\"ParentID\": \"1111f1094f430733333f8280e78dfcc3\""
|
||||
"},"
|
||||
"{"
|
||||
"\"ID\": \"3331f1094f430733333f8280e78dfcc3\","
|
||||
"\"PrefabID\": \"cccbe4b0416be0777a6ce59e8788b10f\","
|
||||
"\"PrefabObjectID\": \"aac6b9644492fbca1a6ab0a7904a557e\","
|
||||
"\"ParentID\": \"244274a04cc60d56a2f024bfeef5772d\""
|
||||
"},"
|
||||
"{"
|
||||
"\"ID\": \"4441f1094f430733333f8280e78dfcc3\","
|
||||
"\"PrefabID\": \"cccbe4b0416be0777a6ce59e8788b10f\","
|
||||
"\"PrefabObjectID\": \"bbb6b9644492fbca1a6ab0a7904a557e\","
|
||||
"\"ParentID\": \"3331f1094f430733333f8280e78dfcc3\""
|
||||
"}"
|
||||
"]");
|
||||
REQUIRE(!prefabBInit);
|
||||
|
||||
// Create Prefab A as variant of Prefab B (no local changes, just object remapped)
|
||||
AssetReference<Prefab> prefabA = Content::CreateVirtualAsset<Prefab>();
|
||||
REQUIRE(prefabA);
|
||||
SCOPE_EXIT{ Content::DeleteAsset(prefabA); };
|
||||
Guid::Parse("aaa744714f746e31855f41815612d14b", id);
|
||||
prefabA->ChangeID(id);
|
||||
auto prefabAInit = prefabA->Init(Prefab::TypeName,
|
||||
"["
|
||||
"{"
|
||||
"\"ID\": \"123274a04cc60d56a2f024bfeef5772d\","
|
||||
"\"PrefabID\": \"bbb744714f746e31855f41815612d14b\","
|
||||
"\"PrefabObjectID\": \"244274a04cc60d56a2f024bfeef5772d\","
|
||||
"\"Name\": \"Prefab A.Root\""
|
||||
"},"
|
||||
"{"
|
||||
"\"ID\": \"1211f1094f430733333f8280e78dfcc3\","
|
||||
"\"PrefabID\": \"bbb744714f746e31855f41815612d14b\","
|
||||
"\"PrefabObjectID\": \"1111f1094f430733333f8280e78dfcc3\","
|
||||
"\"ParentID\": \"123274a04cc60d56a2f024bfeef5772d\""
|
||||
"},"
|
||||
"{"
|
||||
"\"ID\": \"4221f1094f430733333f8280e78dfcc3\","
|
||||
"\"PrefabID\": \"bbb744714f746e31855f41815612d14b\","
|
||||
"\"PrefabObjectID\": \"2221f1094f430733333f8280e78dfcc3\","
|
||||
"\"ParentID\": \"1211f1094f430733333f8280e78dfcc3\""
|
||||
"},"
|
||||
"{"
|
||||
"\"ID\": \"3131f1094f430733333f8280e78dfcc3\","
|
||||
"\"PrefabID\": \"bbb744714f746e31855f41815612d14b\","
|
||||
"\"PrefabObjectID\": \"3331f1094f430733333f8280e78dfcc3\","
|
||||
"\"ParentID\": \"123274a04cc60d56a2f024bfeef5772d\""
|
||||
"},"
|
||||
"{"
|
||||
"\"ID\": \"5441f1094f430733333f8280e78dfcc3\","
|
||||
"\"PrefabID\": \"bbb744714f746e31855f41815612d14b\","
|
||||
"\"PrefabObjectID\": \"4441f1094f430733333f8280e78dfcc3\","
|
||||
"\"ParentID\": \"3131f1094f430733333f8280e78dfcc3\""
|
||||
"}"
|
||||
"]");
|
||||
REQUIRE(!prefabAInit);
|
||||
|
||||
// Spawn test instances of both prefabs
|
||||
ScriptingObjectReference<Actor> instanceA = PrefabManager::SpawnPrefab(prefabA);
|
||||
ScriptingObjectReference<Actor> instanceB = PrefabManager::SpawnPrefab(prefabB);
|
||||
ScriptingObjectReference<Actor> instanceC = PrefabManager::SpawnPrefab(prefabC);
|
||||
|
||||
// Check state of objects
|
||||
REQUIRE(instanceC);
|
||||
REQUIRE(instanceC->Is<ExponentialHeightFog>());
|
||||
REQUIRE(instanceC->Children.Count() == 1);
|
||||
CHECK(instanceC.As<ExponentialHeightFog>()->DirectionalInscatteringLight == instanceC->Children[0]);
|
||||
REQUIRE(instanceB);
|
||||
REQUIRE(instanceB->Children.Count() == 2);
|
||||
ScriptingObjectReference<Actor> instanceB1 = instanceB->Children[0];
|
||||
ScriptingObjectReference<Actor> instanceB2 = instanceB->Children[1];
|
||||
REQUIRE(instanceB1->Is<ExponentialHeightFog>());
|
||||
REQUIRE(instanceB1->Children.Count() == 1);
|
||||
CHECK(instanceB1.As<ExponentialHeightFog>()->DirectionalInscatteringLight == instanceB1->Children[0]);
|
||||
REQUIRE(instanceB2->Is<ExponentialHeightFog>());
|
||||
REQUIRE(instanceB2->Children.Count() == 1);
|
||||
CHECK(instanceB2.As<ExponentialHeightFog>()->DirectionalInscatteringLight == instanceB2->Children[0]);
|
||||
REQUIRE(instanceA);
|
||||
REQUIRE(instanceA->Children.Count() == 2);
|
||||
ScriptingObjectReference<Actor> instanceA1 = instanceA->Children[0];
|
||||
ScriptingObjectReference<Actor> instanceA2 = instanceA->Children[1];
|
||||
REQUIRE(instanceA1->Is<ExponentialHeightFog>());
|
||||
REQUIRE(instanceA1->Children.Count() == 1);
|
||||
CHECK(instanceA1.As<ExponentialHeightFog>()->DirectionalInscatteringLight == instanceA1->Children[0]);
|
||||
REQUIRE(instanceA2->Is<ExponentialHeightFog>());
|
||||
REQUIRE(instanceA1->Children.Count() == 1);
|
||||
CHECK(instanceA2.As<ExponentialHeightFog>()->DirectionalInscatteringLight == instanceA2->Children[0]);
|
||||
|
||||
// Add instance of Prefab C to Prefab B
|
||||
instanceC->SetName(StringView(TEXT("New")));
|
||||
instanceC->SetParent(instanceB);
|
||||
bool applyResult = PrefabManager::ApplyAll(instanceB);
|
||||
REQUIRE(!applyResult);
|
||||
|
||||
// Check if Prefab A reflects that change
|
||||
REQUIRE(instanceA);
|
||||
REQUIRE(instanceA->Children.Count() == 3);
|
||||
instanceA1 = instanceA->Children[0];
|
||||
instanceA2 = instanceA->Children[1];
|
||||
ScriptingObjectReference<Actor> instanceA3 = instanceA->Children[2];
|
||||
REQUIRE(instanceA1->Is<ExponentialHeightFog>());
|
||||
REQUIRE(instanceA1->Children.Count() == 1);
|
||||
CHECK(instanceA1.As<ExponentialHeightFog>()->DirectionalInscatteringLight == instanceA1->Children[0]);
|
||||
REQUIRE(instanceA2->Is<ExponentialHeightFog>());
|
||||
REQUIRE(instanceA2->Children.Count() == 1);
|
||||
CHECK(instanceA2.As<ExponentialHeightFog>()->DirectionalInscatteringLight == instanceA2->Children[0]);
|
||||
REQUIRE(instanceA3->Is<ExponentialHeightFog>());
|
||||
REQUIRE(instanceA3->Children.Count() == 1);
|
||||
CHECK(instanceA3.As<ExponentialHeightFog>()->DirectionalInscatteringLight == instanceA3->Children[0]);
|
||||
|
||||
// Cleanup
|
||||
instanceA->DeleteObject();
|
||||
instanceB->DeleteObject();
|
||||
instanceC->DeleteObject();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user