From f1b133bd6008b0c7e3acf4cf37e2458ec826fb33 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 27 Feb 2024 10:55:58 +0100 Subject: [PATCH] Add prefab link breaking to preserve nested prefabs links #1752 --- Source/Editor/Editor.cs | 3 + .../Editor/Managed/ManagedEditor.Internal.cpp | 15 +++ .../Undo/Actions/BreakPrefabLinkAction.cs | 111 ++++++++++-------- 3 files changed, 78 insertions(+), 51 deletions(-) diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index b37f0468c..4cee6d971 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -1676,6 +1676,9 @@ namespace FlaxEditor [return: MarshalAs(UnmanagedType.U1)] internal static partial bool Internal_CanSetToRoot(IntPtr prefab, IntPtr newRoot); + [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetPrefabNestedObject", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] + internal static partial void Internal_GetPrefabNestedObject(IntPtr prefabId, IntPtr prefabObjectId, IntPtr outPrefabId, IntPtr outPrefabObjectId); + [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetAnimationTime", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] internal static partial float Internal_GetAnimationTime(IntPtr animatedModel); diff --git a/Source/Editor/Managed/ManagedEditor.Internal.cpp b/Source/Editor/Managed/ManagedEditor.Internal.cpp index 9c6c73dfd..43c805bf4 100644 --- a/Source/Editor/Managed/ManagedEditor.Internal.cpp +++ b/Source/Editor/Managed/ManagedEditor.Internal.cpp @@ -509,6 +509,21 @@ DEFINE_INTERNAL_CALL(bool) EditorInternal_CanSetToRoot(Prefab* prefab, Actor* ta return true; } +DEFINE_INTERNAL_CALL(void) EditorInternal_GetPrefabNestedObject(Guid* prefabId, Guid* prefabObjectId, Guid* outPrefabId, Guid* outPrefabObjectId) +{ + *outPrefabId = Guid::Empty; + *outPrefabObjectId = Guid::Empty; + const auto prefab = Content::Load(*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"); +} + DEFINE_INTERNAL_CALL(float) EditorInternal_GetAnimationTime(AnimatedModel* animatedModel) { return animatedModel && animatedModel->GraphInstance.State.Count() == 1 ? animatedModel->GraphInstance.State[0].Animation.TimePosition : 0.0f; diff --git a/Source/Editor/Undo/Actions/BreakPrefabLinkAction.cs b/Source/Editor/Undo/Actions/BreakPrefabLinkAction.cs index 08efc28d0..55553de25 100644 --- a/Source/Editor/Undo/Actions/BreakPrefabLinkAction.cs +++ b/Source/Editor/Undo/Actions/BreakPrefabLinkAction.cs @@ -17,6 +17,30 @@ namespace FlaxEditor.Actions [Serializable] sealed class BreakPrefabLinkAction : IUndoAction { + private struct Item + { + public Guid ID; + public Guid PrefabID; + public Guid PrefabObjectID; + + public unsafe Item(SceneObject obj, List nestedPrefabLinks) + { + ID = obj.ID; + PrefabID = obj.PrefabID; + PrefabObjectID = obj.PrefabObjectID; + if (nestedPrefabLinks != null) + { + // Check if this object comes from another nested prefab (to break link only from the top-level prefab) + Item nested; + nested.ID = ID; + fixed (Item* i = &this) + Editor.Internal_GetPrefabNestedObject(new IntPtr(&i->PrefabID), new IntPtr(&i->PrefabObjectID), new IntPtr(&nested.PrefabID), new IntPtr(&nested.PrefabObjectID)); + if (nested.PrefabID != Guid.Empty && nested.PrefabObjectID != Guid.Empty) + nestedPrefabLinks.Add(nested); + } + } + } + [Serialize] private readonly bool _isBreak; @@ -24,25 +48,18 @@ namespace FlaxEditor.Actions private Guid _actorId; [Serialize] - private Guid _prefabId; + private List _items = new(); - [Serialize] - private Dictionary _prefabObjectIds; - - private BreakPrefabLinkAction(bool isBreak, Guid actorId, Guid prefabId) + private BreakPrefabLinkAction(bool isBreak, Guid actorId) { _isBreak = isBreak; _actorId = actorId; - _prefabId = prefabId; } private BreakPrefabLinkAction(bool isBreak, Actor actor) { _isBreak = isBreak; _actorId = actor.ID; - _prefabId = actor.PrefabID; - - _prefabObjectIds = new Dictionary(1024); CollectIds(actor); } @@ -55,7 +72,7 @@ namespace FlaxEditor.Actions { if (actor == null) throw new ArgumentNullException(nameof(actor)); - return new BreakPrefabLinkAction(true, actor.ID, Guid.Empty); + return new BreakPrefabLinkAction(true, actor.ID); } /// @@ -96,53 +113,45 @@ namespace FlaxEditor.Actions /// public void Dispose() { - _prefabObjectIds.Clear(); + _items.Clear(); } private void DoLink() { - if (_prefabObjectIds == null) - throw new Exception("Cannot link prefab. Missing objects Ids mapping."); - var actor = Object.Find(ref _actorId); if (actor == null) throw new Exception("Cannot link prefab. Missing actor."); - // Restore cached links - foreach (var e in _prefabObjectIds) - { - var objId = e.Key; - var prefabObjId = e.Value; - - var obj = Object.Find(ref objId); - if (obj is Actor) - { - Actor.Internal_LinkPrefab(Object.GetUnmanagedPtr(obj), ref _prefabId, ref prefabObjId); - } - else if (obj is Script) - { - Script.Internal_LinkPrefab(Object.GetUnmanagedPtr(obj), ref _prefabId, ref prefabObjId); - } - } - - Editor.Instance.Scene.MarkSceneEdited(actor.Scene); - Editor.Instance.Windows.PropertiesWin.Presenter.BuildLayout(); + Link(_items); + Refresh(actor); } - private void CollectIds(Actor actor) + private void Link(List items) { - _prefabObjectIds.Add(actor.ID, actor.PrefabObjectID); + for (int i = 0; i < items.Count; i++) + { + var item = items[i]; + var obj = Object.Find(ref item.ID); + if (obj != null) + SceneObject.Internal_LinkPrefab(Object.GetUnmanagedPtr(obj), ref item.PrefabID, ref item.PrefabObjectID); + } + } + + private void CollectIds(Actor actor, List nestedPrefabLinks = null) + { + _items.Add(new Item(actor, nestedPrefabLinks)); for (int i = 0; i < actor.ChildrenCount; i++) - { - CollectIds(actor.GetChild(i)); - } + CollectIds(actor.GetChild(i), nestedPrefabLinks); for (int i = 0; i < actor.ScriptsCount; i++) - { - var script = actor.GetScript(i); - _prefabObjectIds.Add(script.ID, script.PrefabObjectID); - } + _items.Add(new Item(actor.GetScript(i), nestedPrefabLinks)); + } + + private void Refresh(Actor actor) + { + Editor.Instance.Scene.MarkSceneEdited(actor.Scene); + Editor.Instance.Windows.PropertiesWin.Presenter.BuildLayout(); } private void DoBreak() @@ -153,18 +162,18 @@ namespace FlaxEditor.Actions if (!actor.HasPrefabLink) throw new Exception("Cannot break missing prefab link."); - if (_prefabObjectIds == null) - _prefabObjectIds = new Dictionary(1024); - else - _prefabObjectIds.Clear(); - CollectIds(actor); - - _prefabId = actor.PrefabID; + // Cache 'prev' state and extract any nested prefab instances to remain + _items.Clear(); + var nestedPrefabLinks = new List(); + CollectIds(actor, nestedPrefabLinks); + // Break prefab linkage actor.BreakPrefabLink(); - Editor.Instance.Scene.MarkSceneEdited(actor.Scene); - Editor.Instance.Windows.PropertiesWin.Presenter.BuildLayout(); + // Restore prefab link for nested instances + Link(nestedPrefabLinks); + + Refresh(actor); } } }