// 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 { /// /// Undo action object. /// /// [Serializable] public class UndoActionObject : UndoActionBase { /// /// The data value storage to solve issue for flax objects and editor scene tree nodes which are serialized by ref id. /// [Serializable] public struct DataValue { /// /// The generic value (anything). /// public object Generic; /// /// The flax object. /// public FlaxEngine.Object FlaxObject; /// /// The editor node object. /// public SceneGraph.SceneGraphNode EditorNode; /// /// The id. /// public Guid? Id; /// /// Gets the proper value. /// [NoSerialize] public object Value => EditorNode ?? FlaxObject ?? Generic ?? Id; /// /// Initializes a new instance of the struct. /// /// The value. 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; } } } /// /// The data. /// [Serializable] public struct DataStorage { /// /// The values 1. /// public DataValue[] Values1; /// /// The values 2. /// public DataValue[] Values2; /// /// The instance. /// public DataValue Instance; } /// /// Prepared undo data container object. /// public struct DataPrepared { /// /// The difference data. /// public MemberComparison[] Diff; /// /// The target object instance. /// 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; /// /// Gets the target. /// public object Target => TargetInstance; /// /// Initializes a new instance of the class. /// /// The difference. /// The action string. /// The target instance. public UndoActionObject(List 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) }; } /// /// Prepares the data for the undo. /// /// The prepared undo action data. 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, }; } /// 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); } } /// public override string ActionString { get; } /// 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); } } /// 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); } } } }