Files
FlaxEngine/Source/Editor/History/UndoActionObject.cs
2023-01-10 15:29:37 +01:00

228 lines
7.0 KiB
C#

// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using FlaxEditor.Utilities;
using FlaxEngine;
using Newtonsoft.Json;
using JsonSerializer = FlaxEngine.Json.JsonSerializer;
namespace FlaxEditor.History
{
/// <summary>
/// Undo action object.
/// </summary>
/// <seealso cref="IUndoAction" />
[Serializable]
public class UndoActionObject : UndoActionBase<UndoActionObject.DataStorage>
{
/// <summary>
/// The data value storage to solve issue for flax objects and editor scene tree nodes which are serialized by ref id.
/// </summary>
[Serializable]
public struct DataValue
{
/// <summary>
/// The generic value (anything).
/// </summary>
public object Generic;
/// <summary>
/// The flax object.
/// </summary>
public FlaxEngine.Object FlaxObject;
/// <summary>
/// The editor node object.
/// </summary>
public SceneGraph.SceneGraphNode EditorNode;
/// <summary>
/// The id.
/// </summary>
public Guid? Id;
/// <summary>
/// Gets the proper value.
/// </summary>
[NoSerialize]
public object Value => EditorNode ?? FlaxObject ?? Generic ?? Id;
/// <summary>
/// Initializes a new instance of the <see cref="DataValue"/> struct.
/// </summary>
/// <param name="value">The value.</param>
public DataValue(object value)
{
Generic = null;
FlaxObject = null;
EditorNode = null;
Id = null;
if (value is SceneGraph.SceneGraphNode editorNode)
{
EditorNode = editorNode;
}
else if (value is FlaxEngine.Object flaxObject)
{
FlaxObject = flaxObject;
}
else if (value is Guid guid)
{
Id = guid;
}
else
{
Generic = value;
}
}
}
/// <summary>
/// The data.
/// </summary>
[Serializable]
public struct DataStorage
{
/// <summary>
/// The values 1.
/// </summary>
public DataValue[] Values1;
/// <summary>
/// The values 2.
/// </summary>
public DataValue[] Values2;
/// <summary>
/// The instance.
/// </summary>
public DataValue Instance;
}
/// <summary>
/// Prepared undo data container object.
/// </summary>
public struct DataPrepared
{
/// <summary>
/// The difference data.
/// </summary>
public MemberComparison[] Diff;
/// <summary>
/// The target object instance.
/// </summary>
public object TargetInstance;
}
internal static JsonSerializerSettings JsonSettings;
// For objects that cannot be referenced in undo action like: FlaxEngine.Object or SceneGraphNode we store them in DataStorage,
// otherwise here:
private readonly object TargetInstance;
private readonly MemberInfoPath[] Members;
/// <summary>
/// Gets the target.
/// </summary>
public object Target => TargetInstance;
/// <summary>
/// Initializes a new instance of the <see cref="UndoActionObject"/> class.
/// </summary>
/// <param name="diff">The difference.</param>
/// <param name="actionString">The action string.</param>
/// <param name="targetInstance">The target instance.</param>
public UndoActionObject(List<MemberComparison> diff, string actionString, object targetInstance)
{
bool useDataStorageForInstance = targetInstance is FlaxEngine.Object || targetInstance is SceneGraph.SceneGraphNode;
ActionString = actionString;
TargetInstance = useDataStorageForInstance ? null : targetInstance;
int count = diff.Count;
var values1 = new DataValue[count];
var values2 = new DataValue[count];
Members = new MemberInfoPath[count];
for (int i = 0; i < count; i++)
{
values1[i] = new DataValue(diff[i].Value1);
values2[i] = new DataValue(diff[i].Value2);
Members[i] = diff[i].MemberPath;
}
Data = new DataStorage
{
Values1 = values1,
Values2 = values2,
Instance = new DataValue(useDataStorageForInstance ? targetInstance : null)
};
}
/// <summary>
/// Prepares the data for the undo.
/// </summary>
/// <returns>The prepared undo action data.</returns>
public DataPrepared PrepareData()
{
var data = Data;
var count = data.Values1.Length;
var diff = new MemberComparison[count];
for (int i = 0; i < count; i++)
{
diff[i] = new MemberComparison(Members[i], data.Values1[i].Value, data.Values2[i].Value);
}
return new DataPrepared
{
Diff = diff,
TargetInstance = data.Instance.Value ?? TargetInstance,
};
}
/// <inheritdoc />
public override DataStorage Data
{
protected set
{
// Inject objects typename serialization to prevent data type mismatch when loading from saved state
var settings = JsonSettings;
if (settings == null)
{
settings = JsonSerializer.CreateDefaultSettings(false);
settings.TypeNameHandling = TypeNameHandling.All;
JsonSettings = settings;
}
_data = JsonConvert.SerializeObject(value, Formatting.Indented, settings);
//Editor.Log(_data);
}
}
/// <inheritdoc />
public override string ActionString { get; }
/// <inheritdoc />
public override void Do()
{
var data = PrepareData();
for (var i = 0; i < data.Diff.Length; i++)
{
var diff = data.Diff[i];
diff.SetMemberValue(data.TargetInstance, diff.Value2);
}
}
/// <inheritdoc />
public override void Undo()
{
var data = PrepareData();
for (var i = data.Diff.Length - 1; i >= 0; i--)
{
var diff = data.Diff[i];
diff.SetMemberValue(data.TargetInstance, diff.Value1);
}
}
}
}