Add prefab link breaking to preserve nested prefabs links

#1752
This commit is contained in:
Wojtek Figat
2024-02-27 10:55:58 +01:00
parent 7428ecfe63
commit f1b133bd60
3 changed files with 78 additions and 51 deletions

View File

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

View File

@@ -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<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");
}
DEFINE_INTERNAL_CALL(float) EditorInternal_GetAnimationTime(AnimatedModel* animatedModel)
{
return animatedModel && animatedModel->GraphInstance.State.Count() == 1 ? animatedModel->GraphInstance.State[0].Animation.TimePosition : 0.0f;

View File

@@ -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<Item> 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<Item> _items = new();
[Serialize]
private Dictionary<Guid, Guid> _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<Guid, Guid>(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);
}
/// <summary>
@@ -96,53 +113,45 @@ namespace FlaxEditor.Actions
/// <inheritdoc />
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<Actor>(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<Object>(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<Item> items)
{
_prefabObjectIds.Add(actor.ID, actor.PrefabObjectID);
for (int i = 0; i < items.Count; i++)
{
var item = items[i];
var obj = Object.Find<Object>(ref item.ID);
if (obj != null)
SceneObject.Internal_LinkPrefab(Object.GetUnmanagedPtr(obj), ref item.PrefabID, ref item.PrefabObjectID);
}
}
private void CollectIds(Actor actor, List<Item> 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<Guid, Guid>(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<Item>();
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);
}
}
}