diff --git a/Editor/ClusterView.cs b/Editor/ClusterView.cs index 7d75894..355b6e0 100644 --- a/Editor/ClusterView.cs +++ b/Editor/ClusterView.cs @@ -233,7 +233,7 @@ namespace NanoBrain.Unity { Dag.Node to = dag.nodes.FirstOrDefault(x => x.id == e.toId); if (from == null || to == null) continue; - + Vector2 fromPosition = from.position; Vector2 toPosition = to.position; DrawEdge(fromPosition, toPosition); diff --git a/Editor/Dag.cs b/Editor/Dag.cs index 2435fed..7b12f52 100644 --- a/Editor/Dag.cs +++ b/Editor/Dag.cs @@ -43,6 +43,144 @@ namespace NanoBrain.Unity { public static Node GetNodeById(Dag dag, int id) => dag.nodes.FirstOrDefault(x => x.id == id); public static void ComputeLayout(Dag dag) { + BuildAdjacencyAndPredecessors(dag, out Dictionary> adjacency, out Dictionary> predecessors); + List order = TopologicalOrder(adjacency, dag.nodes.Select(n => n.id)); + Dictionary column = ComputeLongestPathColumns(adjacency, order, dag.nodes.Select(n => n.id)); + + CreateDummiesAndEdges(dag, column, out List allNodes, out Dictionary allColumns, out List newEdges); + + List> columns = GroupColumns(allColumns); + PlaceNodes(allNodes, columns); + + // update dag with new lists (dummies included) + dag.edges = newEdges; + dag.nodes = allNodes; + } + + // Helper: build adjacency and predecessor lists + private static void BuildAdjacencyAndPredecessors(Dag dag, out Dictionary> adjacency, out Dictionary> predecessors) { + adjacency = dag.nodes.ToDictionary(n => n.id, n => new List()); + predecessors = dag.nodes.ToDictionary(n => n.id, n => new List()); + foreach (Edge edge in dag.edges) { + if (!adjacency.ContainsKey(edge.fromId) || !adjacency.ContainsKey(edge.toId)) + continue; + adjacency[edge.fromId].Add(edge.toId); + predecessors[edge.toId].Add(edge.fromId); + } + } + + // Helper: compute topological order (returns empty list if cycle present) + private static List TopologicalOrder(Dictionary> adjacency, IEnumerable nodeIds) { + Dictionary inDegree = nodeIds.ToDictionary(id => id, _ => 0); + foreach (KeyValuePair> keyValue in adjacency) + foreach (int to in keyValue.Value) if (inDegree.ContainsKey(to)) + inDegree[to]++; + + Queue queue = new(inDegree.Where(keyValue => keyValue.Value == 0).Select(kv => kv.Key)); + List topo = new(); + while (queue.Count > 0) { + int nodeId = queue.Dequeue(); + topo.Add(nodeId); + foreach (int adjacentNodeId in adjacency[nodeId]) { + if (!inDegree.ContainsKey(adjacentNodeId)) + continue; + inDegree[adjacentNodeId]--; + if (inDegree[adjacentNodeId] == 0) + queue.Enqueue(adjacentNodeId); + } + } + return topo; + } + + // Helper: longest-path-from-sinks column assignment (deterministic) + private static Dictionary ComputeLongestPathColumns(Dictionary> adjacency, List order, IEnumerable nodeIds) { + Dictionary column = nodeIds.ToDictionary(id => id, _ => 0); + foreach (int nodeId in Enumerable.Reverse(order)) { + foreach (int child in adjacency[nodeId]) { + int cand = column[child] + 1; + if (cand > column[nodeId]) + column[nodeId] = cand; + } + } + return column; + } + + // Helper: replace long edges with dummy node chains and return augmented node/column/edge lists + private static void CreateDummiesAndEdges(Dag dag, Dictionary column, out List allNodes, out Dictionary allColumns, out List newEdges) { + allColumns = new Dictionary(column); + allNodes = new List(dag.nodes); + newEdges = new List(); + int nextDummyId = -1; + + foreach (Edge edge in dag.edges) { + if (!column.ContainsKey(edge.fromId) || !column.ContainsKey(edge.toId)) { + newEdges.Add(edge); + continue; + } + + int columnFrom = column[edge.fromId]; + int columnTo = column[edge.toId]; + int span = Mathf.Abs(columnTo - columnFrom); + if (span <= 1) { + newEdges.Add(edge); + continue; + } + + int prev = edge.fromId; + int direction = columnTo > columnFrom ? 1 : -1; + for (int step = 1; step < span; step++) { + int dummyCol = columnFrom + step * direction; + int dummyId = nextDummyId--; + Node dummy = new() { + id = dummyId, + position = Vector2.zero + }; + // System.Reflection.FieldInfo field = typeof(Node).GetField("isDummy"); + // if (field != null) field.SetValue(dummy, true); + allNodes.Add(dummy); + allColumns[dummyId] = dummyCol; + + Edge newDummyEdge = new() { + fromId = prev, + toId = dummyId + }; + newEdges.Add(newDummyEdge); + prev = dummyId; + } + Edge newEdge = new() { + fromId = prev, + toId = edge.toId + }; + newEdges.Add(newEdge); + } + } + + // Helper: group columns into ordered list of lists + private static List> GroupColumns(Dictionary allColumns) { + return allColumns.GroupBy(kv => kv.Value).OrderBy(g => g.Key).Select(g => g.Select(x => x.Key).ToList()).ToList(); + } + + // Helper: place nodes vertically within each column + private static void PlaceNodes(List allNodes, List> columns) { + float hSpacing = 100f; + float totalHeight = 400f; + for (int columnIx = 0; columnIx < columns.Count; columnIx++) { + List nodeList = columns[columnIx]; + float spacing = totalHeight / Mathf.Max(1, nodeList.Count); + float margin = 10 + spacing / 2; + for (int i = 0; i < nodeList.Count; i++) { + int id = nodeList[i]; + Node node = allNodes.FirstOrDefault(n => n.id == id); + if (node == null) + continue; + float x = (hSpacing * 1.5f) + columnIx * hSpacing; + float y = margin + i * spacing; + node.position = new Vector2(x, y); + } + } + } + + public static void ComputeLayoutKahn(Dag dag) { Dictionary> adjacency = dag.nodes.ToDictionary(n => n.id, n => new List()); Dictionary outdegree = dag.nodes.ToDictionary(node => node.id, n => 0); foreach (Edge edge in dag.edges) { @@ -99,29 +237,6 @@ namespace NanoBrain.Unity { Select(g => g.Select(x => x.Key).ToList()). ToList(); - // Same code without using Linq - // Build layers dictionary: layerIndex -> List nodeIds - // Dictionary> layersDict = new(); - // foreach (KeyValuePair kv in layer) { - // int nodeId = kv.Key; - // int layerIndex = kv.Value; - // if (!layersDict.TryGetValue(layerIndex, out List list)) { - // list = new List(); - // layersDict[layerIndex] = list; - // } - // list.Add(nodeId); - // } - - // // Determine sorted layer indices - // List layerIndices = new(layersDict.Keys); - // layerIndices.Sort(); // ascending order - - // // Build final List> in sorted order - // List> layers = new(); - // foreach (int idx in layerIndices) { - // layers.Add(layersDict[idx]); - // } - float hSpacing = 100f; float totalHeight = 400f; @@ -136,14 +251,11 @@ namespace NanoBrain.Unity { if (node == null) continue; float x = (hSpacing * 1.5f) + columnIx * hSpacing; - //float y = 400 - totalHeight / 2f + i * vSpacing; float y = margin + i * spacing; - // Debug.Log($"({li}, {i}) -> {x}, {y}"); node.position = new Vector2(x, y); } } - //Repaint(); } } } \ No newline at end of file