From bab236c081d76ad96bc3762e3f97422fdf8b78e5 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 1 Feb 2021 13:51:32 +0100 Subject: [PATCH] Add support for custom SceneGraphNode to handle delete with undo --- Source/Editor/SceneGraph/SceneGraphNode.cs | 40 ++++++ .../Editor/Undo/Actions/DeleteActorsAction.cs | 119 ++++++++++++++---- .../Windows/Assets/PrefabWindow.Actions.cs | 7 +- 3 files changed, 136 insertions(+), 30 deletions(-) diff --git a/Source/Editor/SceneGraph/SceneGraphNode.cs b/Source/Editor/SceneGraph/SceneGraphNode.cs index 755aea139..c06c0d1c4 100644 --- a/Source/Editor/SceneGraph/SceneGraphNode.cs +++ b/Source/Editor/SceneGraph/SceneGraphNode.cs @@ -312,6 +312,46 @@ namespace FlaxEditor.SceneGraph { } + /// + /// The scene graph node state container. Used for Editor undo actions (eg. restoring deleted node). + /// + public struct StateData + { + /// + /// The name of the scene graph node type (full). + /// + public string TypeName; + + /// + /// The name of the method (in ) that takes this state as a parameter and returns the created scene graph node. Used by the undo actions to restore deleted objects. + /// + public string CreateMethodName; + + /// + /// The custom state data (as string). + /// + public string State; + + /// + /// The custom state data (as raw bytes). + /// + public byte[] StateRaw; + } + + /// + /// Gets a value indicating whether this node can use property for editor undo operations. + /// + public virtual bool CanUseState => false; + + /// + /// Gets or sets the node state. + /// + public virtual StateData State + { + get => throw new NotImplementedException(); + set => throw new NotImplementedException(); + } + /// /// Deletes object represented by this node eg. actor. /// diff --git a/Source/Editor/Undo/Actions/DeleteActorsAction.cs b/Source/Editor/Undo/Actions/DeleteActorsAction.cs index fa319e6f3..41dac3ccc 100644 --- a/Source/Editor/Undo/Actions/DeleteActorsAction.cs +++ b/Source/Editor/Undo/Actions/DeleteActorsAction.cs @@ -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 _nodesData; + + [Serialize] + private Guid[] _nodeParentsIDs; [Serialize] private Guid[] _prefabIds; @@ -30,31 +37,51 @@ namespace FlaxEditor.Actions /// The node parents. /// [Serialize] - protected List _nodeParents; + protected List _nodeParents; /// /// Initializes a new instance of the class. /// - /// The objects. + /// The objects. /// If set to true action will be inverted - instead of delete it will be create actors. - internal DeleteActorsAction(List objects, bool isInverted = false) + internal DeleteActorsAction(List nodes, bool isInverted = false) { _isInverted = isInverted; - _nodeParents = new List(objects.Count); - var actorNodes = new List(objects.Count); - var actors = new List(objects.Count); - for (int i = 0; i < objects.Count; i++) + + // Collect nodes to delete + var deleteNodes = new List(nodes.Count); + var actors = new List(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(); + _nodesData.Add(node.State); + } } } - actorNodes.BuildNodesParents(_nodeParents); - _data = Actor.ToBytes(actors.ToArray()); + // Collect parent nodes to delete + _nodeParents = new List(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 /// public void Dispose() { - _data = null; + _actorsData = null; + _nodeParentsIDs = null; _prefabIds = null; _prefabObjectIds = null; + _nodeParents.Clear(); } /// @@ -123,28 +152,64 @@ namespace FlaxEditor.Actions /// 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(); + + // 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(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); diff --git a/Source/Editor/Windows/Assets/PrefabWindow.Actions.cs b/Source/Editor/Windows/Assets/PrefabWindow.Actions.cs index 00a96910f..fca4909f0 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.Actions.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.Actions.cs @@ -194,8 +194,8 @@ namespace FlaxEditor.Windows.Assets private class CustomDeleteActorsAction : DeleteActorsAction { - public CustomDeleteActorsAction(List objects, bool isInverted = false) - : base(objects, isInverted) + public CustomDeleteActorsAction(List 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();