using Ghost.Editor.Core.Contracts; namespace Ghost.Editor.Core; /// /// The base class for all objects that can be tracked and recorded by the Undo system. /// public abstract class GhostObject : IDisposable { /// /// A persistent unique identifier used to track this object across Undo/Redo operations, /// even if the underlying object is destroyed and resurrected. /// public Guid InstanceID { get; protected set; } // Use WeakReference so we don't prevent Garbage Collection of dead objects private static readonly Dictionary> s_objectRegistry = new(); public static event Action? OnObjectModified; protected GhostObject() { InstanceID = Guid.NewGuid(); s_objectRegistry[InstanceID] = new WeakReference(this); } protected GhostObject(Guid instanceID) { InstanceID = instanceID; s_objectRegistry[InstanceID] = new WeakReference(this); } /// /// Resolves a GhostObject by its InstanceID in O(1) time. /// public static GhostObject? Find(Guid id) { if (s_objectRegistry.TryGetValue(id, out var weakRef)) { if (weakRef.TryGetTarget(out var obj)) { return obj; } else { // Dead object, GC has collected it s_objectRegistry.Remove(id); } } return null; } /// /// Called before mutating state. /// Hooks into the Undo and Dirty Tracking systems. /// public virtual void Modify() { OnObjectModified?.Invoke(this); // TODO: Unify RecordObject in future sessions. For now, we skip IUndoService.RecordObject here // since specialized methods are still required in UndoService. // Mark dirty for persistence directly EditorApplication.GetService().MarkDirty(this); } /// /// Serializes the state of this object into a binary format. /// public virtual void SerializeState(BinaryWriter writer) { } /// /// Deserializes the state of this object from a binary format. /// public virtual void DeserializeState(BinaryReader reader) { } protected virtual void Dispose(bool disposing) { if (disposing) { s_objectRegistry.Remove(InstanceID); } } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } ~GhostObject() { Dispose(false); } }