using ImageProcessingGraph.Editor.Windows; using UnityEngine; using UnityEditor; using UnityEditor.Callbacks; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using UnityEditor.Experimental.GraphView; using Debug = UnityEngine.Debug; // ReSharper disable once CheckNamespace namespace ImageProcessingGraph.Editor { [CreateAssetMenu(menuName = "Image Processing Graph/New Graph")] public class ImageProcessingGraphAsset : ScriptableObject { [SerializeReference] private List nodes; [SerializeField] private List connections; [SerializeField] public List runOrder; [SerializeField] public List stickyNotes; public List Nodes => nodes; public List Connections => connections; public ImageProcessingGraphAsset() { nodes = new List(); connections = new List(); } [OnOpenAsset(1)] public static bool OpenGraphAsset(int instanceID, int line) { var asset = EditorUtility.InstanceIDToObject(instanceID) as ImageProcessingGraphAsset; if (asset != null) { ImageProcessingGraphEditorWindow.Open(asset); return true; } return false; } public void RunGraph() { // Create and start the stopwatch to measure time Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); if (runOrder == null) runOrder = GetExecutionOrder(this.nodes, this.connections); foreach (var VARIABLE in runOrder) { VARIABLE.RunNode(); } // Stop the stopwatch after running the nodes stopwatch.Stop(); // Log the elapsed time UnityEngine.Debug.Log($"Graph execution took {stopwatch.ElapsedMilliseconds} milliseconds."); } /// /// Computes a topological execution order of nodes based on their dependencies. /// Throws if a cycle is detected or nodes are missing from the input list. /// public List GetExecutionOrder(List nodes, List connections, bool debug = false) { // === Initialization === var nodeMap = new Dictionary(nodes.Count); var adjList = new Dictionary>(nodes.Count); var inDegree = new Dictionary(nodes.Count); foreach (var node in nodes) { string id = node.ID; nodeMap[id] = node; adjList[id] = new List(); inDegree[id] = 0; } // === Build Graph === foreach (var conn in connections) { string from = conn.outputPort.nodeID; string to = conn.inputPort.nodeID; if (!nodeMap.ContainsKey(from) || !nodeMap.ContainsKey(to)) throw new System.Exception($"Invalid connection: '{from}' → '{to}' (One or both node IDs not found)"); adjList[from].Add(to); inDegree[to]++; } if (debug) { Debug.Log("=== Dependency Graph ==="); foreach (var from in adjList.Keys) { var outputs = string.Join(", ", adjList[from].Select(t => $"{nodeMap[t].typeName} ({t})")); Debug.Log($"[{nodeMap[from].typeName}] ({from}) → [{outputs}]"); } Debug.Log("========================="); } // === Topological Sort (Kahn's Algorithm) === var executionOrder = new List(nodes.Count); var queue = new Queue(nodes.Count); foreach (var kvp in inDegree) { if (kvp.Value == 0) queue.Enqueue(kvp.Key); } while (queue.Count > 0) { string currentID = queue.Dequeue(); executionOrder.Add(nodeMap[currentID]); foreach (var neighbor in adjList[currentID]) { inDegree[neighbor]--; if (inDegree[neighbor] == 0) queue.Enqueue(neighbor); } } if (executionOrder.Count != nodes.Count) { var remaining = string.Join(", ", inDegree.Where(p => p.Value > 0).Select(p => $"{nodeMap[p.Key].typeName} ({p.Key})")); throw new System.Exception($"Cycle detected in graph. Remaining nodes: {remaining}"); } if (debug) { Debug.Log("=== Execution Order ==="); foreach (var n in executionOrder) Debug.Log($"→ {n.typeName} ({n.ID})"); } return executionOrder; } } }