Files
FlaxEngine/Source/Editor/Undo/Actions/BreakPrefabLinkAction.cs

184 lines
5.8 KiB
C#

// Copyright (c) Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using FlaxEngine;
using Object = FlaxEngine.Object;
namespace FlaxEditor.Actions
{
/// <summary>
/// Implementation of <see cref="IUndoAction"/> used to break/restore <see cref="Prefab"/> connection for the collection of <see cref="Actor"/> and <see cref="Script"/> objects.
/// </summary>
/// <remarks>
/// This action assumes that all objects in the given actor hierarchy are using the same prefab asset.
/// </remarks>
/// <seealso cref="IUndoAction" />
[Serializable]
sealed class BreakPrefabLinkAction : IUndoAction
{
private struct Item
{
public Guid ID;
public Guid PrefabID;
public Guid PrefabObjectID;
public 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;
var prefab = FlaxEngine.Content.Load<Prefab>(PrefabID);
if (prefab != null &&
prefab.GetNestedObject(PrefabObjectID, out nested.PrefabID, out nested.PrefabObjectID) &&
nested.PrefabID != Guid.Empty &&
nested.PrefabObjectID != Guid.Empty)
{
nestedPrefabLinks.Add(nested);
}
}
}
}
[Serialize]
private readonly bool _isBreak;
[Serialize]
private Guid _actorId;
[Serialize]
private List<Item> _items = new();
private BreakPrefabLinkAction(bool isBreak, Guid actorId)
{
_isBreak = isBreak;
_actorId = actorId;
}
private BreakPrefabLinkAction(bool isBreak, Actor actor)
{
_isBreak = isBreak;
_actorId = actor.ID;
CollectIds(actor);
}
/// <summary>
/// Creates a new undo action that in state for breaking prefab connection.
/// </summary>
/// <param name="actor">The target actor.</param>
/// <returns>The action.</returns>
public static BreakPrefabLinkAction Break(Actor actor)
{
if (actor == null)
throw new ArgumentNullException(nameof(actor));
return new BreakPrefabLinkAction(true, actor.ID);
}
/// <summary>
/// Creates a new undo action that in state for linked prefab connection. Action on perform will undo that.
/// </summary>
/// <param name="actor">The target actor.</param>
/// <returns>The action.</returns>
public static BreakPrefabLinkAction Linked(Actor actor)
{
if (actor == null)
throw new ArgumentNullException(nameof(actor));
if (!actor.HasPrefabLink)
throw new Exception("Cannot register missing prefab link.");
return new BreakPrefabLinkAction(false, actor);
}
/// <inheritdoc />
public string ActionString => _isBreak ? "Break prefab link" : "Link prefab";
/// <inheritdoc />
public void Do()
{
if (_isBreak)
DoBreak();
else
DoLink();
}
/// <inheritdoc />
public void Undo()
{
if (_isBreak)
DoLink();
else
DoBreak();
}
/// <inheritdoc />
public void Dispose()
{
_items.Clear();
}
private void DoLink()
{
var actor = Object.Find<Actor>(ref _actorId);
if (actor == null)
throw new Exception("Cannot link prefab. Missing actor.");
Link(_items);
Refresh(actor);
}
private void Link(List<Item> items)
{
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), item.PrefabID, 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), nestedPrefabLinks);
for (int i = 0; i < actor.ScriptsCount; i++)
_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()
{
var actor = Object.Find<Actor>(ref _actorId);
if (actor == null)
throw new Exception("Cannot break prefab link. Missing actor.");
if (!actor.HasPrefabLink)
throw new Exception("Cannot break missing prefab link.");
// 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();
// Restore prefab link for nested instances
Link(nestedPrefabLinks);
Refresh(actor);
}
}
}