Add support for custom SceneGraphNode to handle delete with undo

This commit is contained in:
Wojtek Figat
2021-02-01 13:51:32 +01:00
parent bf80827bfd
commit bab236c081
3 changed files with 136 additions and 30 deletions

View File

@@ -312,6 +312,46 @@ namespace FlaxEditor.SceneGraph
{ {
} }
/// <summary>
/// The scene graph node state container. Used for Editor undo actions (eg. restoring deleted node).
/// </summary>
public struct StateData
{
/// <summary>
/// The name of the scene graph node type (full).
/// </summary>
public string TypeName;
/// <summary>
/// The name of the method (in <see cref="TypeName"/>) that takes this state as a parameter and returns the created scene graph node. Used by the undo actions to restore deleted objects.
/// </summary>
public string CreateMethodName;
/// <summary>
/// The custom state data (as string).
/// </summary>
public string State;
/// <summary>
/// The custom state data (as raw bytes).
/// </summary>
public byte[] StateRaw;
}
/// <summary>
/// Gets a value indicating whether this node can use <see cref="State"/> property for editor undo operations.
/// </summary>
public virtual bool CanUseState => false;
/// <summary>
/// Gets or sets the node state.
/// </summary>
public virtual StateData State
{
get => throw new NotImplementedException();
set => throw new NotImplementedException();
}
/// <summary> /// <summary>
/// Deletes object represented by this node eg. actor. /// Deletes object represented by this node eg. actor.
/// </summary> /// </summary>

View File

@@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using FlaxEditor.SceneGraph; using FlaxEditor.SceneGraph;
using FlaxEditor.Scripting;
using FlaxEngine; using FlaxEngine;
namespace FlaxEditor.Actions namespace FlaxEditor.Actions
@@ -15,7 +16,13 @@ namespace FlaxEditor.Actions
class DeleteActorsAction : IUndoAction class DeleteActorsAction : IUndoAction
{ {
[Serialize] [Serialize]
private byte[] _data; private byte[] _actorsData;
[Serialize]
private List<SceneGraphNode.StateData> _nodesData;
[Serialize]
private Guid[] _nodeParentsIDs;
[Serialize] [Serialize]
private Guid[] _prefabIds; private Guid[] _prefabIds;
@@ -30,31 +37,51 @@ namespace FlaxEditor.Actions
/// The node parents. /// The node parents.
/// </summary> /// </summary>
[Serialize] [Serialize]
protected List<ActorNode> _nodeParents; protected List<SceneGraphNode> _nodeParents;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="DeleteActorsAction"/> class. /// Initializes a new instance of the <see cref="DeleteActorsAction"/> class.
/// </summary> /// </summary>
/// <param name="objects">The objects.</param> /// <param name="nodes">The objects.</param>
/// <param name="isInverted">If set to <c>true</c> action will be inverted - instead of delete it will be create actors.</param> /// <param name="isInverted">If set to <c>true</c> action will be inverted - instead of delete it will be create actors.</param>
internal DeleteActorsAction(List<SceneGraphNode> objects, bool isInverted = false) internal DeleteActorsAction(List<SceneGraphNode> nodes, bool isInverted = false)
{ {
_isInverted = isInverted; _isInverted = isInverted;
_nodeParents = new List<ActorNode>(objects.Count);
var actorNodes = new List<ActorNode>(objects.Count); // Collect nodes to delete
var actors = new List<Actor>(objects.Count); var deleteNodes = new List<SceneGraphNode>(nodes.Count);
for (int i = 0; i < objects.Count; i++) var actors = new List<Actor>(nodes.Count);
for (int i = 0; i < nodes.Count; i++)
{ {
if (objects[i] is ActorNode node) var node = nodes[i];
if (node is ActorNode actorNode)
{ {
actorNodes.Add(node); deleteNodes.Add(actorNode);
actors.Add(node.Actor); actors.Add(actorNode.Actor);
}
else
{
deleteNodes.Add(node);
if (node.CanUseState)
{
if (_nodesData == null)
_nodesData = new List<SceneGraphNode.StateData>();
_nodesData.Add(node.State);
}
} }
} }
actorNodes.BuildNodesParents(_nodeParents);
_data = Actor.ToBytes(actors.ToArray()); // Collect parent nodes to delete
_nodeParents = new List<SceneGraphNode>(nodes.Count);
deleteNodes.BuildNodesParents(_nodeParents);
_nodeParentsIDs = new Guid[_nodeParents.Count];
for (int i = 0; i < _nodeParentsIDs.Length; i++)
_nodeParentsIDs[i] = _nodeParents[i].ID;
// Serialize actors
_actorsData = Actor.ToBytes(actors.ToArray());
// Cache actors linkage to prefab objects
_prefabIds = new Guid[actors.Count]; _prefabIds = new Guid[actors.Count];
_prefabObjectIds = new Guid[actors.Count]; _prefabObjectIds = new Guid[actors.Count];
for (int i = 0; i < actors.Count; i++) for (int i = 0; i < actors.Count; i++)
@@ -88,9 +115,11 @@ namespace FlaxEditor.Actions
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose()
{ {
_data = null; _actorsData = null;
_nodeParentsIDs = null;
_prefabIds = null; _prefabIds = null;
_prefabObjectIds = null; _prefabObjectIds = null;
_nodeParents.Clear();
} }
/// <summary> /// <summary>
@@ -123,28 +152,64 @@ namespace FlaxEditor.Actions
/// </summary> /// </summary>
protected virtual void Create() protected virtual void Create()
{ {
// Restore objects var nodes = new List<SceneGraphNode>();
var actors = Actor.FromBytes(_data);
if (actors == null) // Restore actors
return; var actors = Actor.FromBytes(_actorsData);
for (int i = 0; i < actors.Length; i++) if (actors != null)
{ {
Guid prefabId = _prefabIds[i]; nodes.Capacity = Math.Max(nodes.Capacity, actors.Length);
if (prefabId != Guid.Empty)
// Preserve prefab objects linkage
for (int i = 0; i < actors.Length; i++)
{ {
Actor.Internal_LinkPrefab(FlaxEngine.Object.GetUnmanagedPtr(actors[i]), ref prefabId, ref _prefabObjectIds[i]); Guid prefabId = _prefabIds[i];
if (prefabId != Guid.Empty)
{
Actor.Internal_LinkPrefab(FlaxEngine.Object.GetUnmanagedPtr(actors[i]), ref prefabId, ref _prefabObjectIds[i]);
}
} }
} }
var actorNodes = new List<ActorNode>(actors.Length);
for (int i = 0; i < actors.Length; i++) // Restore nodes state
if (_nodesData != null)
{ {
var foundNode = GetNode(actors[i].ID); for (int i = 0; i < _nodesData.Count; i++)
{
var state = _nodesData[i];
var type = TypeUtils.GetManagedType(state.TypeName);
if (type == null)
{
Editor.LogError($"Missing type {state.TypeName} for scene graph node undo state restore.");
continue;
}
var method = type.GetMethod(state.CreateMethodName);
if (method == null)
{
Editor.LogError($"Missing method {state.CreateMethodName} from type {state.TypeName} for scene graph node undo state restore.");
continue;
}
var node = method.Invoke(null, new object[] { state });
if (node == null)
{
Editor.LogError($"Failed to restore scene graph node state via method {state.CreateMethodName} from type {state.TypeName}.");
continue;
}
}
}
// Cache parent nodes ids
for (int i = 0; i < _nodeParentsIDs.Length; i++)
{
var foundNode = GetNode(_nodeParentsIDs[i]);
if (foundNode is ActorNode node) if (foundNode is ActorNode node)
{ {
actorNodes.Add(node); nodes.Add(node);
} }
} }
actorNodes.BuildNodesParents(_nodeParents); nodes.BuildNodesParents(_nodeParents);
// Mark scenes as modified
for (int i = 0; i < _nodeParents.Count; i++) for (int i = 0; i < _nodeParents.Count; i++)
{ {
Editor.Instance.Scene.MarkSceneEdited(_nodeParents[i].ParentScene); Editor.Instance.Scene.MarkSceneEdited(_nodeParents[i].ParentScene);

View File

@@ -194,8 +194,8 @@ namespace FlaxEditor.Windows.Assets
private class CustomDeleteActorsAction : DeleteActorsAction private class CustomDeleteActorsAction : DeleteActorsAction
{ {
public CustomDeleteActorsAction(List<SceneGraphNode> objects, bool isInverted = false) public CustomDeleteActorsAction(List<SceneGraphNode> nodes, bool isInverted = false)
: base(objects, isInverted) : base(nodes, isInverted)
{ {
} }
@@ -207,7 +207,8 @@ namespace FlaxEditor.Windows.Assets
// Unlink nodes from parents (actors spawned for prefab editing are not in a gameplay and may not send some important events) // Unlink nodes from parents (actors spawned for prefab editing are not in a gameplay and may not send some important events)
for (int i = 0; i < nodes.Length; i++) for (int i = 0; i < nodes.Length; i++)
{ {
nodes[i].Actor.Parent = null; if (nodes[i] is ActorNode actorNode)
actorNode.Actor.Parent = null;
} }
base.Delete(); base.Delete();