diff --git a/Source/Editor/Modules/SceneModule.cs b/Source/Editor/Modules/SceneModule.cs index 56c420964..1f3490c67 100644 --- a/Source/Editor/Modules/SceneModule.cs +++ b/Source/Editor/Modules/SceneModule.cs @@ -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) { diff --git a/Source/Engine/Level/Prefabs/Prefab.Apply.cpp b/Source/Engine/Level/Prefabs/Prefab.Apply.cpp index 4409ae161..0bed02f0b 100644 --- a/Source/Engine/Level/Prefabs/Prefab.Apply.cpp +++ b/Source/Engine/Level/Prefabs/Prefab.Apply.cpp @@ -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::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& 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)) { diff --git a/Source/Engine/Level/Prefabs/PrefabManager.cpp b/Source/Engine/Level/Prefabs/PrefabManager.cpp index d1ee99721..a0b4bb807 100644 --- a/Source/Engine/Level/Prefabs/PrefabManager.cpp +++ b/Source/Engine/Level/Prefabs/PrefabManager.cpp @@ -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; diff --git a/Source/Engine/Level/SceneObjectsFactory.cpp b/Source/Engine/Level/SceneObjectsFactory.cpp index b0f968808..d4e96b9d7 100644 --- a/Source/Engine/Level/SceneObjectsFactory.cpp +++ b/Source/Engine/Level/SceneObjectsFactory.cpp @@ -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(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(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); } } } diff --git a/Source/Engine/Level/SceneObjectsFactory.h b/Source/Engine/Level/SceneObjectsFactory.h index ab4138ea1..2bd6c6b21 100644 --- a/Source/Engine/Level/SceneObjectsFactory.h +++ b/Source/Engine/Level/SceneObjectsFactory.h @@ -13,6 +13,13 @@ class FLAXENGINE_API SceneObjectsFactory { public: + struct NestedPrefabInstance + { + Prefab* Prefab; + Guid RootObjectId; + Dictionary IdsMapping; + }; + struct PrefabInstance { int32 StatIndex; @@ -21,6 +28,8 @@ public: Prefab* Prefab; bool FixRootParent = false; Dictionary IdsMapping; + Array Nested; + Dictionary 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); }; diff --git a/Source/Engine/Tests/TestPrefabs.cpp b/Source/Engine/Tests/TestPrefabs.cpp index 5e19a5fa0..6389f1cf7 100644 --- a/Source/Engine/Tests/TestPrefabs.cpp +++ b/Source/Engine/Tests/TestPrefabs.cpp @@ -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 prefabC = Content::CreateVirtualAsset(); + 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 prefabB = Content::CreateVirtualAsset(); + 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 prefabA = Content::CreateVirtualAsset(); + 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 instanceA = PrefabManager::SpawnPrefab(prefabA); + ScriptingObjectReference instanceB = PrefabManager::SpawnPrefab(prefabB); + ScriptingObjectReference instanceC = PrefabManager::SpawnPrefab(prefabC); + + // Check state of objects + REQUIRE(instanceC); + REQUIRE(instanceC->Is()); + REQUIRE(instanceC->Children.Count() == 1); + CHECK(instanceC.As()->DirectionalInscatteringLight == instanceC->Children[0]); + REQUIRE(instanceB); + REQUIRE(instanceB->Children.Count() == 2); + ScriptingObjectReference instanceB1 = instanceB->Children[0]; + ScriptingObjectReference instanceB2 = instanceB->Children[1]; + REQUIRE(instanceB1->Is()); + REQUIRE(instanceB1->Children.Count() == 1); + CHECK(instanceB1.As()->DirectionalInscatteringLight == instanceB1->Children[0]); + REQUIRE(instanceB2->Is()); + REQUIRE(instanceB2->Children.Count() == 1); + CHECK(instanceB2.As()->DirectionalInscatteringLight == instanceB2->Children[0]); + REQUIRE(instanceA); + REQUIRE(instanceA->Children.Count() == 2); + ScriptingObjectReference instanceA1 = instanceA->Children[0]; + ScriptingObjectReference instanceA2 = instanceA->Children[1]; + REQUIRE(instanceA1->Is()); + REQUIRE(instanceA1->Children.Count() == 1); + CHECK(instanceA1.As()->DirectionalInscatteringLight == instanceA1->Children[0]); + REQUIRE(instanceA2->Is()); + REQUIRE(instanceA1->Children.Count() == 1); + CHECK(instanceA2.As()->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 instanceA3 = instanceA->Children[2]; + REQUIRE(instanceA1->Is()); + REQUIRE(instanceA1->Children.Count() == 1); + CHECK(instanceA1.As()->DirectionalInscatteringLight == instanceA1->Children[0]); + REQUIRE(instanceA2->Is()); + REQUIRE(instanceA2->Children.Count() == 1); + CHECK(instanceA2.As()->DirectionalInscatteringLight == instanceA2->Children[0]); + REQUIRE(instanceA3->Is()); + REQUIRE(instanceA3->Children.Count() == 1); + CHECK(instanceA3.As()->DirectionalInscatteringLight == instanceA3->Children[0]); + + // Cleanup + instanceA->DeleteObject(); + instanceB->DeleteObject(); + instanceC->DeleteObject(); } }