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>
/// Deletes object represented by this node eg. actor.
/// </summary>

View File

@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using FlaxEditor.SceneGraph;
using FlaxEditor.Scripting;
using FlaxEngine;
namespace FlaxEditor.Actions
@@ -15,7 +16,13 @@ namespace FlaxEditor.Actions
class DeleteActorsAction : IUndoAction
{
[Serialize]
private byte[] _data;
private byte[] _actorsData;
[Serialize]
private List<SceneGraphNode.StateData> _nodesData;
[Serialize]
private Guid[] _nodeParentsIDs;
[Serialize]
private Guid[] _prefabIds;
@@ -30,31 +37,51 @@ namespace FlaxEditor.Actions
/// The node parents.
/// </summary>
[Serialize]
protected List<ActorNode> _nodeParents;
protected List<SceneGraphNode> _nodeParents;
/// <summary>
/// Initializes a new instance of the <see cref="DeleteActorsAction"/> class.
/// </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>
internal DeleteActorsAction(List<SceneGraphNode> objects, bool isInverted = false)
internal DeleteActorsAction(List<SceneGraphNode> nodes, bool isInverted = false)
{
_isInverted = isInverted;
_nodeParents = new List<ActorNode>(objects.Count);
var actorNodes = new List<ActorNode>(objects.Count);
var actors = new List<Actor>(objects.Count);
for (int i = 0; i < objects.Count; i++)
// Collect nodes to delete
var deleteNodes = new List<SceneGraphNode>(nodes.Count);
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);
actors.Add(node.Actor);
deleteNodes.Add(actorNode);
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];
_prefabObjectIds = new Guid[actors.Count];
for (int i = 0; i < actors.Count; i++)
@@ -88,9 +115,11 @@ namespace FlaxEditor.Actions
/// <inheritdoc />
public void Dispose()
{
_data = null;
_actorsData = null;
_nodeParentsIDs = null;
_prefabIds = null;
_prefabObjectIds = null;
_nodeParents.Clear();
}
/// <summary>
@@ -123,28 +152,64 @@ namespace FlaxEditor.Actions
/// </summary>
protected virtual void Create()
{
// Restore objects
var actors = Actor.FromBytes(_data);
if (actors == null)
return;
for (int i = 0; i < actors.Length; i++)
var nodes = new List<SceneGraphNode>();
// Restore actors
var actors = Actor.FromBytes(_actorsData);
if (actors != null)
{
Guid prefabId = _prefabIds[i];
if (prefabId != Guid.Empty)
nodes.Capacity = Math.Max(nodes.Capacity, actors.Length);
// 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)
{
actorNodes.Add(node);
nodes.Add(node);
}
}
actorNodes.BuildNodesParents(_nodeParents);
nodes.BuildNodesParents(_nodeParents);
// Mark scenes as modified
for (int i = 0; i < _nodeParents.Count; i++)
{
Editor.Instance.Scene.MarkSceneEdited(_nodeParents[i].ParentScene);

View File

@@ -194,8 +194,8 @@ namespace FlaxEditor.Windows.Assets
private class CustomDeleteActorsAction : DeleteActorsAction
{
public CustomDeleteActorsAction(List<SceneGraphNode> objects, bool isInverted = false)
: base(objects, isInverted)
public CustomDeleteActorsAction(List<SceneGraphNode> nodes, bool isInverted = false)
: 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)
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();