Files
FlaxEngine/Source/Editor/Undo/Actions/PasteActorsAction.cs
Ari Vuollet 58445f04c4 Fix potential incorrect null checks in FlaxEngine.Objects
The null-conditional operator checks for reference equality of the
Object, but doesn't check the validity of the unmanaged pointer. This
check is corrected in cases where the object was not immediately
returned from the bindings layer and may have been destroyed earlier.
2023-09-28 22:05:58 +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 != null ? actor.Parent : null;
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;
}
}
}