using System; using System.Collections.Generic; using System.Linq; using UnityEngine; using Newtonsoft.Json; namespace SynapticPro { /// /// History management and Undo/Redo functionality for Unity operations /// public class NexusOperationHistory { private static NexusOperationHistory instance; public static NexusOperationHistory Instance { get { if (instance == null) { instance = new NexusOperationHistory(); } return instance; } } private Stack undoStack = new Stack(); private Stack redoStack = new Stack(); private const int maxHistorySize = 50; public event Action OnHistoryChanged; [Serializable] public class OperationRecord { public string id; public string operationType; public string description; public DateTime timestamp; public Dictionary parameters; public OperationState beforeState; public OperationState afterState; public bool canUndo; public string error; } [Serializable] public class OperationState { public List gameObjects = new List(); public Dictionary globalState = new Dictionary(); } [Serializable] public class GameObjectState { public string name; public string path; public bool active; public Vector3 position; public Quaternion rotation; public Vector3 scale; public List components = new List(); } [Serializable] public class ComponentState { public string type; public Dictionary properties = new Dictionary(); } /// /// Record operation /// public void RecordOperation(string operationType, string description, Dictionary parameters, Action operation) { var record = new OperationRecord { id = Guid.NewGuid().ToString(), operationType = operationType, description = description, timestamp = DateTime.Now, parameters = parameters, canUndo = true }; // Capture state before operation record.beforeState = CaptureState(parameters); try { // Execute operation operation?.Invoke(); // Capture state after operation record.afterState = CaptureState(parameters); // Add to history AddToHistory(record); Debug.Log($"[Operation History] Recorded: {description}"); } catch (Exception e) { record.error = e.Message; record.canUndo = false; Debug.LogError($"[Operation History] Operation failed: {e.Message}"); throw; } } /// /// Capture current state /// private OperationState CaptureState(Dictionary parameters) { var state = new OperationState(); // Identify target GameObject if (parameters != null && parameters.ContainsKey("target")) { var targetName = parameters["target"].ToString(); var target = GameObject.Find(targetName); if (target != null) { state.gameObjects.Add(CaptureGameObjectState(target)); } } // For newly created objects if (parameters != null && parameters.ContainsKey("name")) { var name = parameters["name"].ToString(); var obj = GameObject.Find(name); if (obj != null) { state.gameObjects.Add(CaptureGameObjectState(obj)); } } // Global state (scene settings, etc.) state.globalState["activeScene"] = UnityEngine.SceneManagement.SceneManager.GetActiveScene().name; return state; } /// /// Capture GameObject state /// private GameObjectState CaptureGameObjectState(GameObject obj) { var state = new GameObjectState { name = obj.name, path = GetFullPath(obj), active = obj.activeSelf, position = obj.transform.position, rotation = obj.transform.rotation, scale = obj.transform.localScale }; // Component state foreach (var component in obj.GetComponents()) { if (component == null || component is Transform) continue; var compState = new ComponentState { type = component.GetType().FullName }; // Save properties of major components if (component is Rigidbody rb) { compState.properties["mass"] = rb.mass; compState.properties["useGravity"] = rb.useGravity; compState.properties["isKinematic"] = rb.isKinematic; } else if (component is Collider col) { compState.properties["isTrigger"] = col.isTrigger; compState.properties["enabled"] = col.enabled; } else if (component is Renderer rend) { compState.properties["enabled"] = rend.enabled; compState.properties["materialCount"] = rend.sharedMaterials.Length; } state.components.Add(compState); } return state; } /// /// Add to history /// private void AddToHistory(OperationRecord record) { undoStack.Push(record); redoStack.Clear(); // Clear the Redo stack when a new operation is added // Delete old records if maximum history size is exceeded while (undoStack.Count > maxHistorySize) { var oldRecords = undoStack.ToArray(); undoStack.Clear(); for (int i = 1; i < oldRecords.Length; i++) { undoStack.Push(oldRecords[i]); } } OnHistoryChanged?.Invoke(); } /// /// Execute Undo /// public bool Undo() { if (undoStack.Count == 0) return false; var record = undoStack.Pop(); if (!record.canUndo) { Debug.LogWarning($"[Operation History] Cannot undo: {record.description}"); return false; } try { // Restore state RestoreState(record.beforeState); // Add to Redo stack redoStack.Push(record); Debug.Log($"[Operation History] Undo: {record.description}"); OnHistoryChanged?.Invoke(); return true; } catch (Exception e) { Debug.LogError($"[Operation History] Undo failed: {e.Message}"); return false; } } /// /// Execute Redo /// public bool Redo() { if (redoStack.Count == 0) return false; var record = redoStack.Pop(); try { // Restore state RestoreState(record.afterState); // Add to Undo stack undoStack.Push(record); Debug.Log($"[Operation History] Redo: {record.description}"); OnHistoryChanged?.Invoke(); return true; } catch (Exception e) { Debug.LogError($"[Operation History] Redo failed: {e.Message}"); return false; } } /// /// Restore state /// private void RestoreState(OperationState state) { if (state == null) return; foreach (var objState in state.gameObjects) { var obj = GameObject.Find(objState.path); if (obj != null) { // Restore Transform obj.transform.position = objState.position; obj.transform.rotation = objState.rotation; obj.transform.localScale = objState.scale; obj.SetActive(objState.active); // Restore component state foreach (var compState in objState.components) { var component = obj.GetComponent(compState.type); if (component != null) { RestoreComponentState(component, compState.properties); } } } } } /// /// Restore component state /// private void RestoreComponentState(Component component, Dictionary properties) { if (component is Rigidbody rb && properties != null) { if (properties.ContainsKey("mass")) rb.mass = Convert.ToSingle(properties["mass"]); if (properties.ContainsKey("useGravity")) rb.useGravity = Convert.ToBoolean(properties["useGravity"]); if (properties.ContainsKey("isKinematic")) rb.isKinematic = Convert.ToBoolean(properties["isKinematic"]); } else if (component is Collider col && properties != null) { if (properties.ContainsKey("isTrigger")) col.isTrigger = Convert.ToBoolean(properties["isTrigger"]); if (properties.ContainsKey("enabled")) col.enabled = Convert.ToBoolean(properties["enabled"]); } else if (component is Renderer rend && properties != null) { if (properties.ContainsKey("enabled")) rend.enabled = Convert.ToBoolean(properties["enabled"]); } } /// /// Clear history /// public void ClearHistory() { undoStack.Clear(); redoStack.Clear(); OnHistoryChanged?.Invoke(); } /// /// Get history information /// public string GetHistoryInfo() { var info = new System.Text.StringBuilder(); info.AppendLine($"📝 Operation History"); info.AppendLine($"Undo available: {undoStack.Count} operations"); info.AppendLine($"Redo available: {redoStack.Count} operations"); if (undoStack.Count > 0) { info.AppendLine("\nRecent operations:"); var recent = undoStack.ToArray(); for (int i = 0; i < Math.Min(5, recent.Length); i++) { var record = recent[i]; info.AppendLine($" - {record.description} ({record.timestamp:HH:mm:ss})"); } } return info.ToString(); } /// /// Export history in JSON format /// public string ExportHistory() { var history = new { undoStack = undoStack.ToArray(), redoStack = redoStack.ToArray(), exportTime = DateTime.Now }; return JsonConvert.SerializeObject(history, Formatting.Indented); } private string GetFullPath(GameObject obj) { var path = obj.name; var parent = obj.transform.parent; while (parent != null) { path = parent.name + "/" + path; parent = parent.parent; } return path; } public bool CanUndo => undoStack.Count > 0; public bool CanRedo => redoStack.Count > 0; // Checkpoint functionality private Dictionary checkpoints = new Dictionary(); [Serializable] public class CheckpointData { public string name; public string description; public DateTime timestamp; public Stack undoStackSnapshot; public Stack redoStackSnapshot; } /// /// Create checkpoint /// public bool CreateCheckpoint(string name, string description = "") { try { var checkpoint = new CheckpointData { name = name, description = description, timestamp = DateTime.Now, undoStackSnapshot = new Stack(undoStack.Reverse()), redoStackSnapshot = new Stack(redoStack.Reverse()) }; checkpoints[name] = checkpoint; Debug.Log($"[Operation History] Checkpoint '{name}' created"); return true; } catch (Exception e) { Debug.LogError($"[Operation History] Failed to create checkpoint: {e.Message}"); return false; } } /// /// Restore from checkpoint /// public bool RestoreCheckpoint(string name) { try { if (!checkpoints.ContainsKey(name)) { Debug.LogWarning($"[Operation History] Checkpoint '{name}' not found"); return false; } var checkpoint = checkpoints[name]; // Restore stacks undoStack = new Stack(checkpoint.undoStackSnapshot.Reverse()); redoStack = new Stack(checkpoint.redoStackSnapshot.Reverse()); OnHistoryChanged?.Invoke(); Debug.Log($"[Operation History] Restored to checkpoint '{name}' ({checkpoint.timestamp})"); return true; } catch (Exception e) { Debug.LogError($"[Operation History] Failed to restore checkpoint: {e.Message}"); return false; } } /// /// Get list of available checkpoints /// public string GetCheckpoints() { if (checkpoints.Count == 0) return "No checkpoints available"; var result = new System.Text.StringBuilder(); result.AppendLine("📍 Available Checkpoints:"); foreach (var kvp in checkpoints.OrderByDescending(x => x.Value.timestamp)) { var cp = kvp.Value; result.AppendLine($" - {cp.name} ({cp.timestamp:yyyy-MM-dd HH:mm:ss})"); if (!string.IsNullOrEmpty(cp.description)) result.AppendLine($" {cp.description}"); } return result.ToString(); } } }