UnityImageProcessing_Package/Scripts/Editor/ImageProcessingGraphAsset.cs
2025-04-26 19:40:12 +01:00

150 lines
5.1 KiB
C#

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<BaseImageNode> nodes;
[SerializeField] private List<GraphConnection> connections;
[SerializeField] public List<BaseImageNode> runOrder;
[SerializeField] public List<StickyNote> stickyNotes;
public List<BaseImageNode> Nodes => nodes;
public List<GraphConnection> Connections => connections;
public ImageProcessingGraphAsset()
{
nodes = new List<BaseImageNode>();
connections = new List<GraphConnection>();
}
[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.");
}
/// <summary>
/// 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.
/// </summary>
public List<BaseImageNode> GetExecutionOrder(List<BaseImageNode> nodes, List<GraphConnection> connections, bool debug = false)
{
// === Initialization ===
var nodeMap = new Dictionary<string, BaseImageNode>(nodes.Count);
var adjList = new Dictionary<string, List<string>>(nodes.Count);
var inDegree = new Dictionary<string, int>(nodes.Count);
foreach (var node in nodes)
{
string id = node.ID;
nodeMap[id] = node;
adjList[id] = new List<string>();
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<BaseImageNode>(nodes.Count);
var queue = new Queue<string>(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;
}
}
}