Fix object ids remapping inside nested prefabs

#3255
This commit is contained in:
Wojtek Figat
2025-09-02 21:41:36 +02:00
parent 8fdda1a71a
commit 1042ad4e7d
6 changed files with 313 additions and 22 deletions

View File

@@ -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();
}
}