Files
FlaxEngine/Source/Editor/Undo/Actions/PasteActorsAction.cs
Ari Vuollet 3e33c63957 Optimize actor pasting performance with huge amount of actors
- Name lookups are cached to avoid checking the name from same Actor multiple times
- Avoid getting actor children multiple times for same parent
2023-08-16 20:24:19 +03:00

221 lines
7.4 KiB
C#

// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using FlaxEditor.SceneGraph;
using FlaxEngine;
namespace FlaxEditor.Actions
{
/// <summary>
/// Implementation of <see cref="IUndoAction"/> used to paste a set of <see cref="ActorNode"/>.
/// </summary>
/// <seealso cref="FlaxEditor.IUndoAction" />
[Serializable]
class PasteActorsAction : IUndoAction
{
[Serialize]
private Dictionary<Guid, Guid> _idsMapping;
[Serialize]
private byte[] _data;
[Serialize]
private Guid _pasteParent;
/// <summary>
/// The node parents.
/// </summary>
[Serialize]
protected List<Guid> _nodeParents;
/// <summary>
/// Initializes a new instance of the <see cref="PasteActorsAction"/> class.
/// </summary>
/// <param name="data">The data.</param>
/// <param name="objectIds">The object ids.</param>
/// <param name="pasteParent">The paste parent object id.</param>
/// <param name="name">The action name.</param>
protected PasteActorsAction(byte[] data, Guid[] objectIds, ref Guid pasteParent, string name)
{
ActionString = name;
_pasteParent = pasteParent;
_idsMapping = new Dictionary<Guid, Guid>(objectIds.Length * 4);
for (int i = 0; i < objectIds.Length; i++)
{
_idsMapping[objectIds[i]] = Guid.NewGuid();
}
_nodeParents = new List<Guid>(objectIds.Length);
_data = data;
}
internal static PasteActorsAction Paste(byte[] data, Guid pasteParent)
{
var objectIds = Actor.TryGetSerializedObjectsIds(data);
if (objectIds == null)
return null;
return new PasteActorsAction(data, objectIds, ref pasteParent, "Paste actors");
}
internal static PasteActorsAction Duplicate(byte[] data, Guid pasteParent)
{
var objectIds = Actor.TryGetSerializedObjectsIds(data);
if (objectIds == null)
return null;
return new PasteActorsAction(data, objectIds, ref pasteParent, "Duplicate actors");
}
/// <summary>
/// Links the broken parent reference (missing parent). By default links the actor to the first scene.
/// </summary>
/// <param name="actor">The actor.</param>
protected virtual void LinkBrokenParentReference(Actor actor)
{
// Link to the first scene root
if (Level.ScenesCount == 0)
throw new Exception("Failed to paste actor with a broken reference. No loaded scenes.");
actor.SetParent(Level.GetScene(0), false);
}
/// <inheritdoc />
public string ActionString { get; }
/// <summary>
/// Performs the paste/duplicate action and outputs created objects nodes.
/// </summary>
/// <param name="nodes">The nodes.</param>
/// <param name="nodeParents">The node parents.</param>
public virtual void Do(out List<ActorNode> nodes, out List<ActorNode> nodeParents)
{
// Restore objects
var actors = Actor.FromBytes(_data, _idsMapping);
if (actors == null)
{
nodes = null;
nodeParents = null;
return;
}
nodes = new List<ActorNode>(actors.Length);
for (int i = 0; i < actors.Length; i++)
{
var actor = actors[i];
// Check if has no parent linked (broken reference eg. old parent not existing)
if (actor.Parent == null)
{
LinkBrokenParentReference(actor);
}
var node = GetNode(actor.ID);
if (node is ActorNode actorNode)
{
nodes.Add(actorNode);
}
}
nodeParents = nodes.BuildNodesParents();
// Cache pasted nodes ids (parents only)
_nodeParents.Clear();
_nodeParents.Capacity = Mathf.Max(_nodeParents.Capacity, nodeParents.Count);
for (int i = 0; i < nodeParents.Count; i++)
{
_nodeParents.Add(nodeParents[i].ID);
}
var pasteParentNode = Editor.Instance.Scene.GetActorNode(_pasteParent);
if (pasteParentNode != null)
{
// Move pasted actors to the parent target (if specified and valid)
for (int i = 0; i < nodeParents.Count; i++)
{
nodeParents[i].Actor.SetParent(pasteParentNode.Actor, false);
}
}
// Store previously looked up names and the results
Dictionary<string, bool> foundNamesResults = new();
for (int i = 0; i < nodeParents.Count; i++)
{
var node = nodeParents[i];
var actor = node.Actor;
var parent = actor?.Parent;
if (parent != null)
{
bool IsNameValid(string name)
{
if (!foundNamesResults.TryGetValue(name, out bool found))
{
found = parent.GetChild(name) != null;
foundNamesResults.Add(name, found);
}
return !found;
}
// Fix name collisions
var name = actor.Name;
var children = parent.Children;
for (int j = 0; j < children.Length; j++)
{
var child = children[j];
if (child != actor && child.Name == name)
{
string newName = Utilities.Utils.IncrementNameNumber(name, x => IsNameValid(x));
foundNamesResults[newName] = true;
actor.Name = newName;
// Multiple actors may have the same name, continue
}
}
}
Editor.Instance.Scene.MarkSceneEdited(node.ParentScene);
}
for (int i = 0; i < nodeParents.Count; i++)
nodeParents[i].PostPaste();
}
/// <summary>
/// Gets the node.
/// </summary>
/// <param name="id">The actor id.</param>
/// <returns>The scene graph node.</returns>
protected virtual SceneGraphNode GetNode(Guid id)
{
return SceneGraphFactory.FindNode(id);
}
/// <inheritdoc />
public void Do()
{
Do(out _, out _);
}
/// <inheritdoc />
public virtual void Undo()
{
// Remove objects
for (int i = 0; i < _nodeParents.Count; i++)
{
var node = GetNode(_nodeParents[i]);
Editor.Instance.Scene.MarkSceneEdited(node.ParentScene);
node.Delete();
}
_nodeParents.Clear();
}
/// <inheritdoc />
public virtual void Dispose()
{
_nodeParents?.Clear();
_idsMapping?.Clear();
_data = null;
}
}
}