Fix error when applying prefab changes with missing (deleted) nested prefabs
#3244
This commit is contained in:
@@ -69,8 +69,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
|||||||
Values.SetReferenceValue(prefabInstance);
|
Values.SetReferenceValue(prefabInstance);
|
||||||
|
|
||||||
// Display prefab UI (when displaying object inside Prefab Window then display only nested prefabs)
|
// Display prefab UI (when displaying object inside Prefab Window then display only nested prefabs)
|
||||||
var prefabId = prefab.ID;
|
prefab.GetNestedObject(ref prefabObjectId, out var nestedPrefabId, out var nestedPrefabObjectId);
|
||||||
Editor.GetPrefabNestedObject(ref prefabId, ref prefabObjectId, out var nestedPrefabId, out var nestedPrefabObjectId);
|
|
||||||
var nestedPrefab = FlaxEngine.Content.Load<Prefab>(nestedPrefabId);
|
var nestedPrefab = FlaxEngine.Content.Load<Prefab>(nestedPrefabId);
|
||||||
var panel = layout.CustomContainer<UniformGridPanel>();
|
var panel = layout.CustomContainer<UniformGridPanel>();
|
||||||
panel.CustomControl.Height = 20.0f;
|
panel.CustomControl.Height = 20.0f;
|
||||||
|
|||||||
@@ -617,21 +617,6 @@ void ManagedEditor::WipeOutLeftoverSceneObjects()
|
|||||||
ObjectsRemovalService::Flush();
|
ObjectsRemovalService::Flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ManagedEditor::GetPrefabNestedObject(const Guid& prefabId, const Guid& prefabObjectId, Guid& outPrefabId, Guid& outPrefabObjectId)
|
|
||||||
{
|
|
||||||
outPrefabId = Guid::Empty;
|
|
||||||
outPrefabObjectId = Guid::Empty;
|
|
||||||
const auto prefab = Content::Load<Prefab>(prefabId);
|
|
||||||
if (!prefab)
|
|
||||||
return;
|
|
||||||
const ISerializable::DeserializeStream** prefabObjectDataPtr = prefab->ObjectsDataCache.TryGet(prefabObjectId);
|
|
||||||
if (!prefabObjectDataPtr)
|
|
||||||
return;
|
|
||||||
const ISerializable::DeserializeStream& prefabObjectData = **prefabObjectDataPtr;
|
|
||||||
JsonTools::GetGuidIfValid(outPrefabId, prefabObjectData, "PrefabID");
|
|
||||||
JsonTools::GetGuidIfValid(outPrefabObjectId, prefabObjectData, "PrefabObjectID");
|
|
||||||
}
|
|
||||||
|
|
||||||
void ManagedEditor::OnEditorAssemblyLoaded(MAssembly* assembly)
|
void ManagedEditor::OnEditorAssemblyLoaded(MAssembly* assembly)
|
||||||
{
|
{
|
||||||
ASSERT(!HasManagedInstance());
|
ASSERT(!HasManagedInstance());
|
||||||
|
|||||||
@@ -259,7 +259,6 @@ public:
|
|||||||
API_FUNCTION(Internal) static Array<VisualScriptLocal> GetVisualScriptLocals();
|
API_FUNCTION(Internal) static Array<VisualScriptLocal> GetVisualScriptLocals();
|
||||||
API_FUNCTION(Internal) static bool EvaluateVisualScriptLocal(VisualScript* script, API_PARAM(Ref) VisualScriptLocal& local);
|
API_FUNCTION(Internal) static bool EvaluateVisualScriptLocal(VisualScript* script, API_PARAM(Ref) VisualScriptLocal& local);
|
||||||
API_FUNCTION(Internal) static void WipeOutLeftoverSceneObjects();
|
API_FUNCTION(Internal) static void WipeOutLeftoverSceneObjects();
|
||||||
API_FUNCTION(Internal) static void GetPrefabNestedObject(API_PARAM(Ref) const Guid& prefabId, API_PARAM(Ref) const Guid& prefabObjectId, API_PARAM(Out) Guid& outPrefabId, API_PARAM(Out) Guid& outPrefabObjectId);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void OnEditorAssemblyLoaded(MAssembly* assembly);
|
void OnEditorAssemblyLoaded(MAssembly* assembly);
|
||||||
|
|||||||
@@ -33,9 +33,14 @@ namespace FlaxEditor.Actions
|
|||||||
// Check if this object comes from another nested prefab (to break link only from the top-level prefab)
|
// Check if this object comes from another nested prefab (to break link only from the top-level prefab)
|
||||||
Item nested;
|
Item nested;
|
||||||
nested.ID = ID;
|
nested.ID = ID;
|
||||||
Editor.GetPrefabNestedObject(ref PrefabID, ref PrefabObjectID, out nested.PrefabID, out nested.PrefabObjectID);
|
var prefab = FlaxEngine.Content.Load<Prefab>(PrefabID);
|
||||||
if (nested.PrefabID != Guid.Empty && nested.PrefabObjectID != Guid.Empty)
|
if (prefab != null &&
|
||||||
|
prefab.GetNestedObject(ref PrefabObjectID, out nested.PrefabID, out nested.PrefabObjectID) &&
|
||||||
|
nested.PrefabID != Guid.Empty &&
|
||||||
|
nested.PrefabObjectID != Guid.Empty)
|
||||||
|
{
|
||||||
nestedPrefabLinks.Add(nested);
|
nestedPrefabLinks.Add(nested);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -707,7 +707,7 @@ bool Prefab::ApplyAll(Actor* targetActor)
|
|||||||
for (int32 i = 0; i < nestedPrefabIds.Count(); i++)
|
for (int32 i = 0; i < nestedPrefabIds.Count(); i++)
|
||||||
{
|
{
|
||||||
const auto nestedPrefab = Content::LoadAsync<Prefab>(nestedPrefabIds[i]);
|
const auto nestedPrefab = Content::LoadAsync<Prefab>(nestedPrefabIds[i]);
|
||||||
if (nestedPrefab && nestedPrefab != this && (nestedPrefab->Flags & ObjectFlags::WasMarkedToDelete) == ObjectFlags::None)
|
if (nestedPrefab && nestedPrefab != this && EnumHasNoneFlags(nestedPrefab->Flags, ObjectFlags::WasMarkedToDelete))
|
||||||
{
|
{
|
||||||
allPrefabs.Add(nestedPrefab);
|
allPrefabs.Add(nestedPrefab);
|
||||||
}
|
}
|
||||||
@@ -778,6 +778,29 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr
|
|||||||
for (int32 i = 0; i < targetObjects->Count(); i++)
|
for (int32 i = 0; i < targetObjects->Count(); i++)
|
||||||
{
|
{
|
||||||
SceneObject* obj = targetObjects.Value->At(i);
|
SceneObject* obj = targetObjects.Value->At(i);
|
||||||
|
|
||||||
|
// Check the whole chain of prefab references to be valid for this object
|
||||||
|
bool brokenPrefab = false;
|
||||||
|
Guid nestedPrefabId = obj->GetPrefabID(), nestedPrefabObjectId = obj->GetPrefabObjectID();
|
||||||
|
while (!brokenPrefab && nestedPrefabId.IsValid() && nestedPrefabObjectId.IsValid())
|
||||||
|
{
|
||||||
|
auto prefab = Content::Load<Prefab>(nestedPrefabId);
|
||||||
|
if (prefab)
|
||||||
|
{
|
||||||
|
prefab->GetNestedObject(nestedPrefabObjectId, nestedPrefabId, nestedPrefabObjectId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LOG(Warning, "Missing prefab {0}.", nestedPrefabId);
|
||||||
|
brokenPrefab = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (brokenPrefab)
|
||||||
|
{
|
||||||
|
LOG(Warning, "Broken prefab reference on object {0}. Breaking linkage to inline object inside prefab.", GetObjectName(obj));
|
||||||
|
obj->BreakPrefabLink();
|
||||||
|
}
|
||||||
|
|
||||||
writer.SceneObject(obj);
|
writer.SceneObject(obj);
|
||||||
}
|
}
|
||||||
writer.EndArray();
|
writer.EndArray();
|
||||||
@@ -809,7 +832,7 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr
|
|||||||
SceneObject* obj = targetObjects.Value->At(i);
|
SceneObject* obj = targetObjects.Value->At(i);
|
||||||
auto data = it->GetObject();
|
auto data = it->GetObject();
|
||||||
|
|
||||||
// Check if object is from that prefab
|
// Check if object is from this prefab
|
||||||
if (obj->GetPrefabID() == prefabId)
|
if (obj->GetPrefabID() == prefabId)
|
||||||
{
|
{
|
||||||
if (!obj->GetPrefabObjectID().IsValid())
|
if (!obj->GetPrefabObjectID().IsValid())
|
||||||
@@ -883,7 +906,7 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr
|
|||||||
{
|
{
|
||||||
const SceneObject* obj = targetObjects->At(i);
|
const SceneObject* obj = targetObjects->At(i);
|
||||||
|
|
||||||
// Check if object is from that prefab
|
// Check if object is from this prefab
|
||||||
if (obj->GetPrefabID() == prefabId)
|
if (obj->GetPrefabID() == prefabId)
|
||||||
{
|
{
|
||||||
// Map prefab instance to existing prefab object
|
// Map prefab instance to existing prefab object
|
||||||
|
|||||||
@@ -94,6 +94,24 @@ SceneObject* Prefab::GetDefaultInstance(const Guid& objectId)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Prefab::GetNestedObject(const Guid& objectId, Guid& outPrefabId, Guid& outObjectId) const
|
||||||
|
{
|
||||||
|
if (WaitForLoaded())
|
||||||
|
return false;
|
||||||
|
bool result = false;
|
||||||
|
Guid result1 = Guid::Empty, result2 = Guid::Empty;
|
||||||
|
const ISerializable::DeserializeStream** prefabObjectDataPtr = ObjectsDataCache.TryGet(objectId);
|
||||||
|
if (prefabObjectDataPtr)
|
||||||
|
{
|
||||||
|
const ISerializable::DeserializeStream& prefabObjectData = **prefabObjectDataPtr;
|
||||||
|
result = JsonTools::GetGuidIfValid(result1, prefabObjectData, "PrefabID") &&
|
||||||
|
JsonTools::GetGuidIfValid(result2, prefabObjectData, "PrefabObjectID");
|
||||||
|
}
|
||||||
|
outPrefabId = result1;
|
||||||
|
outObjectId = result2;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
void Prefab::DeleteDefaultInstance()
|
void Prefab::DeleteDefaultInstance()
|
||||||
{
|
{
|
||||||
ScopeLock lock(Locker);
|
ScopeLock lock(Locker);
|
||||||
|
|||||||
@@ -70,6 +70,15 @@ public:
|
|||||||
/// <returns>The object of the prefab loaded from the prefab. Contains the default values. It's not added to gameplay but deserialized with postLoad and init event fired.</returns>
|
/// <returns>The object of the prefab loaded from the prefab. Contains the default values. It's not added to gameplay but deserialized with postLoad and init event fired.</returns>
|
||||||
API_FUNCTION() SceneObject* GetDefaultInstance(API_PARAM(Ref) const Guid& objectId);
|
API_FUNCTION() SceneObject* GetDefaultInstance(API_PARAM(Ref) const Guid& objectId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the reference to the other nested prefab for a specific prefab object.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="objectId">The ID of the object in this prefab.</param>
|
||||||
|
/// <param name="outPrefabId">Result ID of the prefab asset referenced by the given object.</param>
|
||||||
|
/// <param name="outObjectId">Result ID of the prefab object referenced by the given object.</param>
|
||||||
|
/// <returns>True if got valid reference, otherwise false.</returns>
|
||||||
|
API_FUNCTION() bool GetNestedObject(API_PARAM(Ref) const Guid& objectId, API_PARAM(Out) Guid& outPrefabId, API_PARAM(Out) Guid& outObjectId) const;
|
||||||
|
|
||||||
#if USE_EDITOR
|
#if USE_EDITOR
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Applies the difference from the prefab object instance, saves the changes and synchronizes them with the active instances of the prefab asset.
|
/// Applies the difference from the prefab object instance, saves the changes and synchronizes them with the active instances of the prefab asset.
|
||||||
|
|||||||
@@ -559,7 +559,7 @@ TEST_CASE("Prefabs")
|
|||||||
Content::DeleteAsset(prefabNested1);
|
Content::DeleteAsset(prefabNested1);
|
||||||
Content::DeleteAsset(prefabBase);
|
Content::DeleteAsset(prefabBase);
|
||||||
}
|
}
|
||||||
SECTION("Test Applying Prefab ChangeTo Object References")
|
SECTION("Test Applying Prefab Change To Object References")
|
||||||
{
|
{
|
||||||
// https://github.com/FlaxEngine/FlaxEngine/issues/3136
|
// https://github.com/FlaxEngine/FlaxEngine/issues/3136
|
||||||
|
|
||||||
@@ -614,4 +614,78 @@ TEST_CASE("Prefabs")
|
|||||||
instanceB->DeleteObject();
|
instanceB->DeleteObject();
|
||||||
Content::DeleteAsset(prefab);
|
Content::DeleteAsset(prefab);
|
||||||
}
|
}
|
||||||
|
SECTION("Test Applying Prefab With Missing Nested Prefab")
|
||||||
|
{
|
||||||
|
// https://github.com/FlaxEngine/FlaxEngine/issues/3244
|
||||||
|
|
||||||
|
// Create Prefab B with just root object
|
||||||
|
AssetReference<Prefab> prefabB = Content::CreateVirtualAsset<Prefab>();
|
||||||
|
REQUIRE(prefabB);
|
||||||
|
Guid id;
|
||||||
|
Guid::Parse("25dbe4b0416be0777a6ce59e8788b10f", id);
|
||||||
|
prefabB->ChangeID(id);
|
||||||
|
auto prefabBInit = prefabB->Init(Prefab::TypeName,
|
||||||
|
"["
|
||||||
|
"{"
|
||||||
|
"\"ID\": \"aac6b9644492fbca1a6ab0a7904a557e\","
|
||||||
|
"\"TypeName\": \"FlaxEngine.ExponentialHeightFog\","
|
||||||
|
"\"Name\": \"Prefab B.Root\""
|
||||||
|
"}"
|
||||||
|
"]");
|
||||||
|
REQUIRE(!prefabBInit);
|
||||||
|
|
||||||
|
// Create Prefab A with nested Prefab B attached to the root
|
||||||
|
AssetReference<Prefab> prefabA = Content::CreateVirtualAsset<Prefab>();
|
||||||
|
REQUIRE(prefabA);
|
||||||
|
Guid::Parse("4cb744714f746e31855f41815612d14b", id);
|
||||||
|
prefabA->ChangeID(id);
|
||||||
|
auto prefabAInit = prefabA->Init(Prefab::TypeName,
|
||||||
|
"["
|
||||||
|
"{"
|
||||||
|
"\"ID\": \"244274a04cc60d56a2f024bfeef5772d\","
|
||||||
|
"\"TypeName\": \"FlaxEngine.SpotLight\","
|
||||||
|
"\"Name\": \"Prefab A.Root\""
|
||||||
|
"},"
|
||||||
|
"{"
|
||||||
|
"\"ID\": \"1e51f1094f430733333f8280e78dfcc3\","
|
||||||
|
"\"PrefabID\": \"25dbe4b0416be0777a6ce59e8788b10f\","
|
||||||
|
"\"PrefabObjectID\": \"aac6b9644492fbca1a6ab0a7904a557e\","
|
||||||
|
"\"ParentID\": \"244274a04cc60d56a2f024bfeef5772d\""
|
||||||
|
"}"
|
||||||
|
"]");
|
||||||
|
REQUIRE(!prefabAInit);
|
||||||
|
|
||||||
|
// Spawn test instances of both prefabs
|
||||||
|
ScriptingObjectReference<Actor> instanceA = PrefabManager::SpawnPrefab(prefabA);
|
||||||
|
ScriptingObjectReference<Actor> instanceB = PrefabManager::SpawnPrefab(prefabB);
|
||||||
|
|
||||||
|
// Delete nested prefab
|
||||||
|
Content::DeleteAsset(prefabB);
|
||||||
|
|
||||||
|
// Apply instance A and verify it's fine even tho prefab B doesn't exist anymore
|
||||||
|
bool applyResult = PrefabManager::ApplyAll(instanceA);
|
||||||
|
REQUIRE(!applyResult);
|
||||||
|
|
||||||
|
// Check state of objects
|
||||||
|
REQUIRE(instanceA);
|
||||||
|
REQUIRE(instanceA->Children.Count() == 1);
|
||||||
|
REQUIRE(instanceA->Children[0] != nullptr);
|
||||||
|
REQUIRE(instanceA->Children[0]->Is<ExponentialHeightFog>());
|
||||||
|
REQUIRE(instanceB);
|
||||||
|
REQUIRE(instanceB->Is<ExponentialHeightFog>());
|
||||||
|
|
||||||
|
// Verify if prefab has new data to properly spawn another prefab
|
||||||
|
ScriptingObjectReference<Actor> instanceC = PrefabManager::SpawnPrefab(prefabA);
|
||||||
|
REQUIRE(instanceC);
|
||||||
|
REQUIRE(instanceC->Children.Count() == 1);
|
||||||
|
REQUIRE(instanceC->Children[0] != nullptr);
|
||||||
|
REQUIRE(instanceC->Children[0]->Is<ExponentialHeightFog>());
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
instanceA->DeleteObject();
|
||||||
|
instanceB->DeleteObject();
|
||||||
|
instanceC->DeleteObject();
|
||||||
|
Content::DeleteAsset(prefabA);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user