From 249e88850a2f24f52ac7c2cf3a1a455ab0edabe7 Mon Sep 17 00:00:00 2001 From: Pascal Serrarens Date: Mon, 20 Apr 2026 16:06:33 +0200 Subject: [PATCH] Improve full graph view --- Editor/BrainEditorWindow.cs | 242 ++---- Editor/ClusterInspector.cs | 1372 ++++---------------------------- Editor/ClusterViewer.cs | 17 +- Editor/DAGWindow.cs | 356 --------- Editor/DAGWindow.cs.meta | 2 - Runtime/Scripts/Core/Neuron.cs | 9 +- 6 files changed, 243 insertions(+), 1755 deletions(-) delete mode 100644 Editor/DAGWindow.cs delete mode 100644 Editor/DAGWindow.cs.meta diff --git a/Editor/BrainEditorWindow.cs b/Editor/BrainEditorWindow.cs index 195ac6a..2b16ffe 100644 --- a/Editor/BrainEditorWindow.cs +++ b/Editor/BrainEditorWindow.cs @@ -29,10 +29,6 @@ namespace NanoBrain { const float minZoom = 0.5f; const float maxZoom = 2.0f; - // Vector2 dragStart; - // bool draggingNode = false; - // int draggingNodeId = -1; - private readonly System.Type acceptedType = typeof(ClusterPrefab); [MenuItem("Window/Brain Viewer")] @@ -42,14 +38,10 @@ namespace NanoBrain { } void OnEnable() { - // if (nodes.Count == 0) - // CreateSampleGraph(); - - // Register callback so window updates when selection changes Selection.selectionChanged += OnSelectionChanged; RefreshSelection(); - ComputeLeftToRightLayout(); + ComputeLayout(); } private void OnDisable() { @@ -58,7 +50,7 @@ namespace NanoBrain { private void OnSelectionChanged() { RefreshSelection(); - ComputeLeftToRightLayout(); + ComputeLayout(); Repaint(); } @@ -86,27 +78,6 @@ namespace NanoBrain { } } - - // void CreateSampleGraph() { - // nodes.Clear(); - // edges.Clear(); - - // nodes.Add(new DagNode() { id = 0, title = "In1" }); - // nodes.Add(new DagNode() { id = 1, title = "In2" }); - // nodes.Add(new DagNode() { id = 2, title = "A" }); - // nodes.Add(new DagNode() { id = 3, title = "B" }); - // nodes.Add(new DagNode() { id = 4, title = "C" }); - // nodes.Add(new DagNode() { id = 5, title = "Out1" }); - // nodes.Add(new DagNode() { id = 6, title = "Out2" }); - - // edges.Add(new DagEdge() { fromId = 0, toId = 2 }); - // edges.Add(new DagEdge() { fromId = 1, toId = 2 }); - // edges.Add(new DagEdge() { fromId = 2, toId = 3 }); - // edges.Add(new DagEdge() { fromId = 2, toId = 4 }); - // edges.Add(new DagEdge() { fromId = 3, toId = 5 }); - // edges.Add(new DagEdge() { fromId = 4, toId = 6 }); - // } - void OnGUI() { HandleInput(); @@ -132,7 +103,8 @@ namespace NanoBrain { foreach (DagEdge e in edges) { DagNode from = GetNodeById(e.fromId); DagNode to = GetNodeById(e.toId); - if (from == null || to == null) continue; + if (from == null || to == null) + continue; DrawEdgeCircleNodes(from, to); } @@ -141,13 +113,6 @@ namespace NanoBrain { DrawNucleus(n); GUI.matrix = oldMatrix; - - // Footer toolbar - GUILayout.FlexibleSpace(); - EditorGUILayout.BeginHorizontal(EditorStyles.toolbar); - if (GUILayout.Button("Fit", EditorStyles.toolbarButton)) FitToView(); - if (GUILayout.Button("Layout LR", EditorStyles.toolbarButton)) ComputeLeftToRightLayout(); - EditorGUILayout.EndHorizontal(); } void HandleInput() { @@ -171,22 +136,6 @@ namespace NanoBrain { } DagNode GetNodeById(int id) => nodes.FirstOrDefault(x => x.id == id); - List GetIncomingEdges(DagNode node) { - List incoming = new(); - foreach (DagEdge e in edges) { - if (e.toId == node.id) - incoming.Add(e); - } - return incoming; - } - List GetOutgoingEdges(DagNode node) { - List outgoing = new(); - foreach (DagEdge e in edges) { - if (e.fromId == node.id) - outgoing.Add(e); - } - return outgoing; - } void DrawNucleus(DagNode n) { Vector3 position = n.position; @@ -194,11 +143,6 @@ namespace NanoBrain { Handles.color = Color.white * 0.9f; Handles.DrawSolidDisc(n.position, Vector3.forward, n.radius); - if (GetIncomingEdges(n).Count == 0) - DrawArrowHead(n.position - new Vector2(n.radius + 10, 0), n.position - new Vector2(n.radius + 5, 0), 10f / zoom, 12f / zoom, Color.white); - if (GetOutgoingEdges(n).Count == 0) - DrawArrowHead(n.position + new Vector2(n.radius + 10, 0), n.position + new Vector2(n.radius + 15, 0), 10f / zoom, 12f / zoom, Color.white); - Handles.color = Color.white; GUIStyle style = new(EditorStyles.label) { alignment = TextAnchor.UpperCenter, @@ -216,109 +160,114 @@ namespace NanoBrain { Handles.color = Color.white * 0.9f; Handles.DrawLine(from.position, to.position); - - // Vector2 dir = (b - a).normalized; - // Vector2 start = a + dir * from.radius; - // Vector2 end = b - dir * to.radius; - - //DrawArrowHead(end - dir * 2f, end, 10f / zoom, 12f / zoom, Color.white); - } - void DrawArrowHead(Vector2 from, Vector2 to, float headWidth, float headLength, Color color) { - Vector2 dir = (to - from).normalized; - if (dir == Vector2.zero) return; - Vector2 right = new Vector2(-dir.y, dir.x); - - Vector3 p1 = to; - Vector3 p2 = to - dir * headLength + right * headWidth * 0.5f; - Vector3 p3 = to - dir * headLength - right * headWidth * 0.5f; - - Handles.color = color; - Handles.DrawAAConvexPolygon(p1, p2, p3); - } - - // Left-to-right layered layout (sources on the left, sinks on the right) - void ComputeLeftToRightLayout() { + // Right-to-left layered layout (sources on the right, sinks on the left) + void ComputeLayout() { // build adjacency and indegree - var adj = nodes.ToDictionary(n => n.id, n => new List()); - var indeg = nodes.ToDictionary(n => n.id, n => 0); - foreach (var e in edges) { - if (!adj.ContainsKey(e.fromId) || !adj.ContainsKey(e.toId)) continue; - adj[e.fromId].Add(e.toId); - indeg[e.toId]++; + Dictionary> adjacency = nodes.ToDictionary(n => n.id, n => new List()); + Dictionary indegree = nodes.ToDictionary(n => n.id, n => 0); + foreach (DagEdge edge in edges) { + if (!adjacency.ContainsKey(edge.fromId) || !adjacency.ContainsKey(edge.toId)) + continue; + adjacency[edge.fromId].Add(edge.toId); + indegree[edge.toId]++; + } + Dictionary outdegree = nodes.ToDictionary(node => node.id, n => 0); + foreach (DagEdge edge in edges) { + if (!adjacency.ContainsKey(edge.fromId) || !adjacency.ContainsKey(edge.toId)) + continue; + adjacency[edge.fromId].Add(edge.toId); + outdegree[edge.fromId]++; } // Kahn's algorithm to compute topological layers (horizontal layers) - Dictionary layer = new(); - Queue q = new(indeg.Where(kv => kv.Value == 0).Select(kv => kv.Key)); - foreach (var id in q) layer[id] = 0; + // build parent list (reverse adjacency) and parentIndegree = number of children each parent has + Dictionary> parents = nodes.ToDictionary(n => n.id, _ => new List()); + Dictionary childCount = nodes.ToDictionary(n => n.id, _ => 0); + foreach (DagEdge edge in edges) { + if (!adjacency.ContainsKey(edge.fromId) || !adjacency.ContainsKey(edge.toId)) continue; + adjacency[edge.fromId].Add(edge.toId); + parents[edge.toId].Add(edge.fromId); // parent of 'to' is 'from' + childCount[edge.fromId]++; // outdegree + } - while (q.Count > 0) { - int u = q.Dequeue(); + Dictionary layer = new(); + Queue queue = new(outdegree.Where(kv => kv.Value == 0).Select(kv => kv.Key)); + foreach (int id in queue) + layer[id] = 0; + + // process parents (reverse traversal) + while (queue.Count > 0) { + int u = queue.Dequeue(); int l = layer[u]; - foreach (var v in adj[u]) { - // prefer placing v at least one layer after u - if (!layer.ContainsKey(v) || layer[v] < l + 1) layer[v] = l + 1; - indeg[v]--; - if (indeg[v] == 0) q.Enqueue(v); + foreach (int p in parents[u]) { + if (!layer.ContainsKey(p) || layer[p] < l + 1) + layer[p] = l + 1; + childCount[p]--; // decrement remaining unprocessed children + if (childCount[p] == 0) + queue.Enqueue(p); } } + // Any unreachable nodes -> assign next layers int maxLayer = layer.Count > 0 ? layer.Values.Max() : 0; - foreach (var n in nodes) { - if (!layer.ContainsKey(n.id)) { + foreach (DagNode node in nodes) { + if (!layer.ContainsKey(node.id)) { maxLayer++; - layer[n.id] = maxLayer; + layer[node.id] = maxLayer; } } // Group nodes by layer (left to right) - var layers = layer.GroupBy(kv => kv.Value).OrderBy(g => g.Key).Select(g => g.Select(x => x.Key).ToList()).ToList(); + List> layers = layer.GroupBy(kv => kv.Value).OrderBy(g => g.Key).Select(g => g.Select(x => x.Key).ToList()).ToList(); - // Layout parameters (horizontal spacing drives left->right) - float hSpacing = 150f; + // 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 vSpacing = 100f; // Place nodes: x increases with layer index, y spaced within layer - for (int li = 0; li < layers.Count; li++) { - var lst = layers[li]; - float totalHeight = (lst.Count - 1) * vSpacing; - for (int i = 0; i < lst.Count; i++) { - int id = lst[i]; - var n = GetNodeById(id); - if (n == null) continue; - float x = hSpacing + li * hSpacing; + for (int layerIx = 0; layerIx < layers.Count; layerIx++) { + List nodeList = layers[layerIx]; + float totalHeight = (nodeList.Count - 1) * vSpacing; + for (int i = 0; i < nodeList.Count; i++) { + int index = nodeList[i]; + DagNode node = GetNodeById(index); + if (node == null) + continue; + float x = hSpacing + layerIx * hSpacing; float y = 400 - totalHeight / 2f + i * vSpacing; // Debug.Log($"({li}, {i}) -> {x}, {y}"); - n.position = new Vector2(x, y); + node.position = new Vector2(x, y); } } Repaint(); } - void FitToView() { - if (nodes.Count == 0) return; - // compute bounds including radii - Rect bounds = new Rect(nodes[0].position - Vector2.one * nodes[0].radius, Vector2.one * nodes[0].radius * 2f); - foreach (var n in nodes) - bounds = RectUnion(bounds, new Rect(n.position - Vector2.one * n.radius, Vector2.one * n.radius * 2f)); - - // center graph at origin (0,0) then set pan so it appears centered in window - Vector2 graphCenter = bounds.center; - // move nodes so center is at origin - for (int i = 0; i < nodes.Count; i++) - nodes[i].position -= graphCenter; - - // reset pan/zoom so centered - pan = Vector2.zero; - zoom = 1.0f; - Repaint(); - } - - static Rect RectUnion(Rect a, Rect b) { float xMin = Mathf.Min(a.xMin, b.xMin); float xMax = Mathf.Max(a.xMax, b.xMax); @@ -327,21 +276,6 @@ namespace NanoBrain { return Rect.MinMaxRect(xMin, yMin, xMax, yMax); } - Vector2 ScreenToGraph_old(Vector2 screenPos) { - Vector2 origin = new Vector2(position.width / 2, position.height / 2); - // invert the GUI.matrix transform (approx for current simple transforms) - return (screenPos - (origin + pan)) / zoom + origin * (1 - 1 / zoom); - } - Vector2 ScreenToGraph(Vector2 screenPos) { - Vector2 windowCenter = new Vector2(position.width / 2f, position.height / 2f); - Rect bounds = GetGraphBounds(); - Vector2 graphCenter = bounds.center; - Vector2 autoPan = -graphCenter; - // inverse of: screen -> translate by -(windowCenter+autoPan+pan), scale by 1/zoom, translate by windowCenter - return (screenPos - (windowCenter + autoPan + pan)) / zoom + windowCenter; - } - - Rect GetGraphBounds() { if (nodes == null || nodes.Count == 0) return new Rect(Vector2.zero, Vector2.one); Rect bounds = new( @@ -352,18 +286,6 @@ namespace NanoBrain { new Rect(n.position - Vector2.one * n.radius, 2f * n.radius * Vector2.one)); return bounds; } - - - - int HitTestNode(Vector2 graphPos) { - // returns node id under point or -1 - for (int i = nodes.Count - 1; i >= 0; i--) { - var n = nodes[i]; - if ((graphPos - n.position).sqrMagnitude <= n.radius * n.radius) return n.id; - } - return -1; - } - } } \ No newline at end of file diff --git a/Editor/ClusterInspector.cs b/Editor/ClusterInspector.cs index 60ff610..c93e228 100644 --- a/Editor/ClusterInspector.cs +++ b/Editor/ClusterInspector.cs @@ -33,29 +33,31 @@ namespace NanoBrain { VisualElement mainContainer = new() { style = { - flexDirection = FlexDirection.Row + flexDirection = FlexDirection.Row, } }; - GraphEditor graph = new(cluster); - graph.style.flexGrow = 1; + GraphEditor graphContainer = new(cluster); + graphContainer.style.flexShrink = 0; + graphContainer.style.width = 300; + graphContainer.style.overflow = Overflow.Hidden; VisualElement inspectorContainer = new() { name = "inspector", style = { - alignSelf = Align.Stretch, minHeight = 450, width = 300, - flexGrow = 0 + flexGrow = 0, + flexDirection = FlexDirection.Row, } }; - mainContainer.Add(graph); + mainContainer.Add(graphContainer); mainContainer.Add(inspectorContainer); root.Add(mainContainer); - graph.SetGraph(gameObject, output, inspectorContainer); + graphContainer.SetGraph(gameObject, output, inspectorContainer); - return graph; + return graphContainer; } public class GraphEditor : GraphView { @@ -106,17 +108,7 @@ namespace NanoBrain { this.prefabAsset = CreateInstance(); //Debug.LogError("Cluster Prefab is not found on disk"); } - DrawInspector(inspectorContainer); - } - - #region Inspector - - private VisualElement inspectorIMGUIContainer; - private bool showSynapses = true; - private bool showActivation = true; - protected bool breakOnWake = false; - protected bool trace = false; - void DrawInspector(VisualElement inspectorContainer) { + // DrawInspector(inspectorContainer); if (inspectorContainer == null) return; @@ -131,6 +123,14 @@ namespace NanoBrain { inspectorContainer.Add(inspectorIMGUIContainer); } + #region Inspector + + private VisualElement inspectorIMGUIContainer; + private bool showSynapses = true; + private bool showActivation = true; + protected bool breakOnWake = false; + protected bool trace = false; + void InspectorHandler(SerializedObject serializedObject) { bool anythingChanged = false; @@ -150,13 +150,34 @@ namespace NanoBrain { fontStyle = FontStyle.Bold }; - GUILayout.Label(this.currentNucleus.GetType().ToString(), headerStyle); - string newName = EditorGUILayout.TextField(this.currentNucleus.name, boldTextFieldStyle); - if (newName != this.currentNucleus.name) { - this.currentNucleus.name = newName; - this.prefab.RefreshOutputs(); - outputsField.choices = this.prefab.outputs.Select(output => output.name).ToList(); - anythingChanged = true; + string nucleusType = this.currentNucleus.GetType().Name; + // if (this.currentNucleus.parent != null) { + // string clusterName = this.currentNucleus.parent.name; + // GUILayout.Label(clusterName + ": " + nucleusType, headerStyle); + // } + // else + GUILayout.Label(nucleusType, headerStyle); + + + if (this.currentNucleus.parent is Cluster parentCluster) { + EditorGUILayout.BeginHorizontal(); + if (GUILayout.Button(this.currentNucleus.parent.name)) + EditCluster(parentCluster); + EditorGUI.BeginDisabledGroup(true); + EditorGUILayout.TextField(this.currentNucleus.name, boldTextFieldStyle); + EditorGUI.EndDisabledGroup(); + if (GUILayout.Button("Reimport")) + ReimportCluster(parentCluster); + EditorGUILayout.EndHorizontal(); + } + else { + string newName = EditorGUILayout.TextField(this.currentNucleus.name, boldTextFieldStyle); + if (newName != this.currentNucleus.name) { + this.currentNucleus.name = newName; + this.prefab.RefreshOutputs(); + outputsField.choices = this.prefab.outputs.Select(output => output.name).ToList(); + anythingChanged = true; + } } if (Application.isPlaying) { @@ -174,7 +195,7 @@ namespace NanoBrain { memory.staticMemory = EditorGUILayout.Toggle("Static Memory", memory.staticMemory); } - if (this.currentNucleus is Cluster cluster) { + if (this.currentNucleus is Cluster cluster) { EditorGUILayout.BeginHorizontal(); if (cluster.siblingClusters != null && cluster.siblingClusters.Length > 1) EditorGUILayout.IntField("Array size", cluster.siblingClusters.Count()); @@ -195,115 +216,113 @@ namespace NanoBrain { // Synapses - //if (this.currentNucleus is not Receptor) { //} && this.currentNucleus is not ClusterReceptor) { - showSynapses = EditorGUILayout.BeginFoldoutHeaderGroup(showSynapses, "Synapses"); - if (showSynapses) { - if (this.currentNucleus is Neuron neuron2) { - Neuron.CombinatorType newCombinator = (Neuron.CombinatorType)EditorGUILayout.EnumPopup("Combinator", neuron2.combinator); - anythingChanged |= newCombinator != neuron2.combinator; - neuron2.combinator = newCombinator; - } + showSynapses = EditorGUILayout.BeginFoldoutHeaderGroup(showSynapses, "Synapses"); + if (showSynapses) { + if (this.currentNucleus is Neuron neuron2) { + Neuron.CombinatorType newCombinator = (Neuron.CombinatorType)EditorGUILayout.EnumPopup("Combinator", neuron2.combinator); + anythingChanged |= newCombinator != neuron2.combinator; + neuron2.combinator = newCombinator; + } - EditorGUIUtility.wideMode = true; - EditorGUIUtility.labelWidth = 100; - Vector3 newBias = EditorGUILayout.Vector3Field("Bias", this.currentNucleus.bias); - anythingChanged |= newBias != this.currentNucleus.bias; - this.currentNucleus.bias = newBias; + EditorGUIUtility.wideMode = true; + EditorGUIUtility.labelWidth = 100; + Vector3 newBias = EditorGUILayout.Vector3Field("Bias", this.currentNucleus.bias); + anythingChanged |= newBias != this.currentNucleus.bias; + this.currentNucleus.bias = newBias; - Nucleus[] array = null; - int elementIx = -1; - if (this.currentNucleus.synapses.Count > 0) { - Synapse[] synapses = this.currentNucleus.synapses.ToArray(); - foreach (Synapse synapse in synapses) { - if (synapse.neuron == null) - continue; + Nucleus[] array = null; + int elementIx = -1; + if (this.currentNucleus.synapses.Count > 0) { + Synapse[] synapses = this.currentNucleus.synapses.ToArray(); + foreach (Synapse synapse in synapses) { + if (synapse.neuron == null) + continue; - if (array != null) { - if (synapse.neuron.parent is Cluster iCluster && elementIx > 0) { - int thisElementIx = Cluster.GetNucleusIndex(iCluster.clusterNuclei, synapse.neuron); - if (thisElementIx == elementIx) - continue; - else - elementIx = thisElementIx; - } - // if (array.Contains(synapse.nucleus)) - // continue; - else if (array.Contains(synapse.neuron.parent)) + if (array != null) { + if (synapse.neuron.parent is Cluster iCluster && elementIx > 0) { + int thisElementIx = Cluster.GetNucleusIndex(iCluster.clusterNuclei, synapse.neuron); + if (thisElementIx == elementIx) continue; - } - else { - // if (synapse.neuron.parent is IReceptor iReceptor) { - // array = iReceptor.nucleiArray; - // if (iReceptor is Cluster iCluster) - // elementIx = Cluster.GetNucleusIndex(iCluster.clusterNuclei, synapse.neuron); - // } - // else if (synapse.nucleus is Receptor receptor2) // && receptor2.array != null && receptor2.array.nuclei.Length > 1) - // array = receptor2.nucleiArray; - } - - EditorGUILayout.Space(); - - if (Application.isPlaying) { - if (synapse.neuron is Neuron synapseNeuron) { - Vector3 value = synapseNeuron.outputValue * synapse.weight; - GUIContent synapseValueLabel = new(synapse.neuron.name, synapseNeuron.outputValue.ToString()); - EditorGUILayout.FloatField(synapseValueLabel, synapseNeuron.outputMagnitude); - } - } - else { - EditorGUILayout.BeginHorizontal(); - - if (synapse.neuron.parent != null && synapse.neuron.parent != this.currentNucleus) { - // If it is a cluster - GUIStyle labelStyle = new(GUI.skin.label); - float labelWidth = 200; - if (synapse.neuron.clusterPrefab != null) { - labelWidth = labelStyle.CalcSize(new GUIContent($"{synapse.neuron.parent.baseName}.")).x; - GUILayout.Label($"{synapse.neuron.parent.baseName}", GUILayout.Width(labelWidth)); - } - string[] options = synapse.neuron.parent.clusterNuclei.Select(n => n.name).ToArray(); - int selectedIndex = System.Array.IndexOf(options, synapse.neuron.name); - int newIndex = EditorGUILayout.Popup(selectedIndex, options); - if (newIndex != selectedIndex && synapse.neuron.parent.clusterNuclei[newIndex] is Neuron newNeuron) - ChangeSynapse(synapse, newNeuron); - } else - GUILayout.Label(synapse.neuron.name); - - bool disconnecting = GUILayout.Button("Disconnect", GUILayout.Width(80)); - if (disconnecting && synapse.neuron is Neuron synapseNeuron) { - synapseNeuron.RemoveReceiver(this.currentNucleus); - this.prefab.GarbageCollection(); - anythingChanged = true; - } - EditorGUILayout.EndHorizontal(); - + elementIx = thisElementIx; } + // if (array.Contains(synapse.nucleus)) + // continue; + else if (array.Contains(synapse.neuron.parent)) + continue; + } + else { + // if (synapse.neuron.parent is IReceptor iReceptor) { + // array = iReceptor.nucleiArray; + // if (iReceptor is Cluster iCluster) + // elementIx = Cluster.GetNucleusIndex(iCluster.clusterNuclei, synapse.neuron); + // } + // else if (synapse.nucleus is Receptor receptor2) // && receptor2.array != null && receptor2.array.nuclei.Length > 1) + // array = receptor2.nucleiArray; + } - EditorGUI.indentLevel++; - float newWeight = EditorGUILayout.FloatField("Weight", synapse.weight); - if (newWeight != synapse.weight) { - // if (synapse.neuron.parent is IReceptor receptor) { - // Nucleus[] receptorArray = receptor.nucleiArray; - // foreach (Synapse s in this.currentNucleus.synapses) { - // if (s.neuron.parent is IReceptor r && r.nucleiArray == receptorArray) - // s.weight = newWeight; - // } - // } - // else - synapse.weight = newWeight; + EditorGUILayout.Space(); + + if (Application.isPlaying) { + if (synapse.neuron is Neuron synapseNeuron) { + Vector3 value = synapseNeuron.outputValue * synapse.weight; + GUIContent synapseValueLabel = new(synapse.neuron.name, synapseNeuron.outputValue.ToString()); + EditorGUILayout.FloatField(synapseValueLabel, synapseNeuron.outputMagnitude); + } + } + else { + EditorGUILayout.BeginHorizontal(); + + if (synapse.neuron.parent != null && synapse.neuron.parent != this.currentNucleus) { + // If it is a cluster + GUIStyle labelStyle = new(GUI.skin.label); + float labelWidth = 200; + if (synapse.neuron.clusterPrefab != null) { + labelWidth = labelStyle.CalcSize(new GUIContent($"{synapse.neuron.parent.baseName}.")).x; + GUILayout.Label($"{synapse.neuron.parent.baseName}", GUILayout.Width(labelWidth)); + } + string[] options = synapse.neuron.parent.clusterNuclei.Select(n => n.name).ToArray(); + int selectedIndex = System.Array.IndexOf(options, synapse.neuron.name); + int newIndex = EditorGUILayout.Popup(selectedIndex, options); + if (newIndex != selectedIndex && synapse.neuron.parent.clusterNuclei[newIndex] is Neuron newNeuron) + ChangeSynapse(synapse, newNeuron); + } + else + GUILayout.Label(synapse.neuron.name); + + bool disconnecting = GUILayout.Button("Disconnect", GUILayout.Width(80)); + if (disconnecting && synapse.neuron is Neuron synapseNeuron) { + synapseNeuron.RemoveReceiver(this.currentNucleus); + this.prefab.GarbageCollection(); anythingChanged = true; } - EditorGUI.indentLevel--; - } - } + EditorGUILayout.EndHorizontal(); - EditorGUILayout.Space(); - anythingChanged |= ConnectNucleus(this.prefab, this.currentNucleus); - anythingChanged |= AddSynapse(this.prefab, this.currentNucleus); + } + + EditorGUI.indentLevel++; + float newWeight = EditorGUILayout.FloatField("Weight", synapse.weight); + if (newWeight != synapse.weight) { + // if (synapse.neuron.parent is IReceptor receptor) { + // Nucleus[] receptorArray = receptor.nucleiArray; + // foreach (Synapse s in this.currentNucleus.synapses) { + // if (s.neuron.parent is IReceptor r && r.nucleiArray == receptorArray) + // s.weight = newWeight; + // } + // } + // else + synapse.weight = newWeight; + anythingChanged = true; + } + EditorGUI.indentLevel--; + } } - EditorGUILayout.EndFoldoutHeaderGroup(); - //} + + EditorGUILayout.Space(); + anythingChanged |= ConnectNucleus(this.prefab, this.currentNucleus); + anythingChanged |= AddSynapse(this.prefab, this.currentNucleus); + } + EditorGUILayout.EndFoldoutHeaderGroup(); // Activation @@ -314,12 +333,12 @@ namespace NanoBrain { if (this.currentNucleus is Neuron neuron) { if (this.currentNucleus is not MemoryCell) { EditorGUILayout.BeginHorizontal(); - EditorGUILayout.LabelField("Activation Curve", GUILayout.Width(150)); + EditorGUILayout.LabelField("Activation Curve", GUILayout.MinWidth(60)); if (neuron.curveMax > 0) - EditorGUILayout.CurveField(neuron.curve, Color.cyan, new Rect(0, 0, 1, neuron.curveMax)); + EditorGUILayout.CurveField(neuron.curve, Color.cyan, new Rect(0, 0, 1, neuron.curveMax), GUILayout.Width(40)); else - EditorGUILayout.CurveField(neuron.curve, Color.cyan, new Rect(0, neuron.curveMax, 1, -neuron.curveMax)); - Neuron.ActivationType newPreset = (Neuron.ActivationType)EditorGUILayout.EnumPopup(neuron.curvePreset, GUILayout.Width(100)); + EditorGUILayout.CurveField(neuron.curve, Color.cyan, new Rect(0, neuron.curveMax, 1, -neuron.curveMax), GUILayout.Width(40)); + Neuron.ActivationType newPreset = (Neuron.ActivationType)EditorGUILayout.EnumPopup(neuron.curvePreset, GUILayout.MinWidth(50)); anythingChanged |= newPreset != neuron.curvePreset; neuron.curvePreset = newPreset; EditorGUILayout.EndHorizontal(); @@ -361,27 +380,6 @@ namespace NanoBrain { } } - // void OnSceneGUI(SceneView sceneView) { - // if (this.gameObject != null) { - // // if (this.currentNucleus is IReceptor receptor) { - // // foreach (Nucleus nucleus in receptor.nucleiArray) { - // // if (nucleus is Neuron neuron) { - // // Vector3 worldVector = this.gameObject.transform.TransformVector(neuron.outputValue); - // // Handles.color = Color.yellow; - // // Handles.DrawLine(this.gameObject.transform.position, this.gameObject.transform.position + worldVector); - // // } - // // } - // // } - // // else { - // if (this.currentNucleus is Neuron currentNeuron) { - // Vector3 worldVector = this.gameObject.transform.TransformVector(currentNeuron.outputValue); - // Handles.color = Color.yellow; - // Handles.DrawLine(this.gameObject.transform.position, this.gameObject.transform.position + worldVector); - // } - // // } - // } - // } - #region Synapses protected virtual void AddInput(Nucleus.Type selectedType, Nucleus nucleus) { @@ -473,7 +471,7 @@ namespace NanoBrain { // if (nucleus is IReceptor receptor) // receptor.AddArrayReceiver(this.currentNucleus); // else - if (nucleus is Neuron neuron) + if (nucleus is Neuron neuron) neuron.AddReceiver(this.currentNucleus); else if (nucleus is Cluster subCluster) subCluster.defaultOutput.AddReceiver(this.currentNucleus); @@ -559,9 +557,9 @@ namespace NanoBrain { // } // } // else { - // it is a neuron in a subcluster - synapseNeuron.RemoveReceiver(this.currentNucleus); - newNucleus.AddReceiver(this.currentNucleus); + // it is a neuron in a subcluster + synapseNeuron.RemoveReceiver(this.currentNucleus); + newNucleus.AddReceiver(this.currentNucleus); // } } else { @@ -588,1071 +586,5 @@ namespace NanoBrain { #endregion Inspector } } - /* - [CustomEditor(typeof(ClusterPrefab))] - public class ClusterInspector : Editor { - public override VisualElement CreateInspectorGUI() { - ClusterPrefab prefab = target as ClusterPrefab; - if (prefab != null) - prefab.EnsureInitialization(); - - serializedObject.Update(); - - VisualElement root = new(); - CreateInspector(root, prefab, prefab.output, null); - - serializedObject.ApplyModifiedProperties(); - return root; - } - - public static GraphView CreateInspector(VisualElement root, ClusterPrefab cluster, Nucleus output, GameObject gameObject) { - root.style.paddingLeft = 0; - root.style.paddingRight = 0; - root.style.paddingTop = 0; - root.style.paddingBottom = 0; - - root.styleSheets.Add(Resources.Load("GraphStyles")); - - VisualElement mainContainer = new() { - style = { - flexDirection = FlexDirection.Row - } - }; - GraphView graph = new(cluster); - graph.style.flexGrow = 1; - - VisualElement inspectorContainer = new VisualElement { - name = "inspector", - style = { - alignSelf = Align.Stretch, - minHeight = 450, - width = 300, - flexGrow = 0 - } - }; - - mainContainer.Add(graph); - mainContainer.Add(inspectorContainer); - root.Add(mainContainer); - - graph.SetGraph(gameObject, output, inspectorContainer); - - return graph; - } - - public class GraphView : VisualElement { - readonly ClusterPrefab prefab; - SerializedObject serializedBrain; - Nucleus currentNucleus; - GameObject gameObject; - private List layers = new(); - private readonly Dictionary neuroidPositions = new(); - private bool expandArray = false; - - ClusterPrefab prefabAsset; - readonly PopupField outputsField; - - public GraphView(ClusterPrefab prefab) { - this.prefab = prefab; - - name = "content"; - style.flexGrow = 1; - - IMGUIContainer graphContainer = new(OnIMGUI); - graphContainer.style.position = Position.Absolute; - graphContainer.style.left = 0; graphContainer.style.top = 0; - graphContainer.style.right = 0; graphContainer.style.bottom = 0; - graphContainer.pickingMode = PickingMode.Position; - graphContainer.focusable = true; - Add(graphContainer); - - VisualElement outputContainer = new() { - style = { - flexDirection = FlexDirection.Row, - alignItems = Align.Center, - } - }; - - List names = this.prefab.outputs.Select(output => output.name).ToList(); - if (names.Count > 0 && names.First() != null) { - outputsField = new(names, names.First()) { - style = { flexGrow = 1 } - }; - outputsField.RegisterValueChangedCallback(evt => OnOutputChanged(evt.newValue)); - outputContainer.Add(outputsField); - } - - Button addButton = new(() => OnAddClusterOutput()) { - text = "Add" - }; - outputContainer.Add(addButton); - - Add(outputContainer); - - // Subscribe when added to panel (editor UI ready) - RegisterCallback(evt => Subscribe()); - RegisterCallback(evt => Unsubscribe()); - } - - void OnOutputChanged(string outputName) { - if (this.currentNucleus.parent != null) - // Get nucleus in the parent instance - this.currentNucleus = this.currentNucleus.parent.GetNucleus(outputName); - else - // Get nucleus in the prefab - this.currentNucleus = this.prefab.GetNucleus(outputName); - } - - void OnAddClusterOutput() { - Nucleus newOutput = new Neuron(this.prefab, "New Output"); - this.prefab.RefreshOutputs(); - outputsField.choices = this.prefab.outputs.Select(output => output.name).ToList(); - outputsField.value = newOutput.name; - - this.currentNucleus = newOutput; - } - - bool subscribed = false; - void Subscribe() { - if (subscribed) return; - SceneView.duringSceneGui += OnSceneGUI; - subscribed = true; - SceneView.RepaintAll(); - } - - void Unsubscribe() { - if (!subscribed) return; - SceneView.duringSceneGui -= OnSceneGUI; - subscribed = false; - } - - public void SetGraph(GameObject gameObject, Nucleus nucleus, VisualElement inspectorContainer) { - this.gameObject = gameObject; - //this.cluster = brain; - if (Application.isPlaying == false) - this.serializedBrain = new SerializedObject(this.prefab); - this.currentNucleus = nucleus; - Rebuild(inspectorContainer); - } - - void Rebuild(VisualElement inspectorContainer) { - BuildLayers(); - - if (this.currentNucleus == null) { - inspectorContainer.Clear(); - return; - } - - string path = AssetDatabase.GetAssetPath(this.prefab); // or known path - this.prefabAsset = AssetDatabase.LoadAssetAtPath(path); - if (this.prefabAsset == null) { - // create in memory save if it doesn't exist - this.prefabAsset = CreateInstance(); - //Debug.LogError("Cluster Prefab is not found on disk"); - } - DrawInspector(inspectorContainer); - } - - private void BuildLayers() { - // A temporary list to track what's been added to layers - this.layers = new(); - int layerIx = 0; - - Nucleus selectedNucleus = this.currentNucleus; - if (selectedNucleus == null) - return; - NeuroidLayer currentLayer = new() { ix = layerIx }; - - if (selectedNucleus is Neuron selectedNeuron && selectedNeuron.receivers != null) { - foreach (Nucleus receiver in selectedNeuron.receivers) { - Nucleus outputNeuroid = receiver; - if (outputNeuroid != null) { - AddToLayer(currentLayer, outputNeuroid); - // Debug.Log($"layer {layerIx} nucleus {outputNeuroid.name}"); - } - } - } - if (currentLayer.neuroids.Count > 0) { - this.layers.Add(currentLayer); - layerIx++; - currentLayer = new() { ix = layerIx }; - } - - AddToLayer(currentLayer, selectedNucleus); - this.layers.Add(currentLayer); - // Debug.Log($"layer {layerIx} nucleus {selectedNucleus.name}"); - - layerIx++; - currentLayer = new() { ix = layerIx }; - - if (selectedNucleus.synapses != null) { - foreach (Synapse synapse in selectedNucleus.synapses) { - Nucleus input = synapse.neuron; - AddToLayer(currentLayer, input); - // Debug.Log($"layer {layerIx} nucleus {input.name}"); - } - } - if (currentLayer.neuroids.Count > 0) { - this.layers.Add(currentLayer); - } - } - - private void AddToLayer(NeuroidLayer layer, Nucleus nucleus) { - if (nucleus == null) - return; - layer.neuroids.Add(nucleus); - //nucleus.layerIx = layer.ix; - // Store its position - Vector2Int neuroidPosition = new(layer.ix, layer.neuroids.Count - 1); - neuroidPositions[nucleus] = neuroidPosition; - - } - - public void OnIMGUI() { - if (currentNucleus == null) - return; - - if (Application.isPlaying == false) - serializedBrain.Update(); - - Handles.BeginGUI(); - DrawGraph(); - Handles.EndGUI(); - - } - - private void DrawGraph() { - float size = 20; - Vector3 position = new(150, 210, 0); - - DrawReceivers(this.currentNucleus, position, size); - DrawSynapses(this.currentNucleus, position, size); - - // Draw selected Nucleus - if (expandArray) { - if (this.currentNucleus is IReceptor receptor1) { - float maxValue = 0; - foreach (Nucleus nucleus in receptor1.nucleiArray) { - if (nucleus is Neuron neuron) { - float value = neuron.outputMagnitude; - if (value > maxValue) - maxValue = value; - } - } - - float spacing = 400f / receptor1.nucleiArray.Count(); - float margin = 10 + spacing / 2; - float xMin = 150 - size; - float xMax = 150 + size; - float yMin = 10 + margin - size / 2; - float yMax = 400 - margin + size; - Vector3[] verts = new Vector3[4] { - new(xMin, yMin, 0), - new(xMax, yMin, 0), - new(xMax, yMax, 0), - new(xMin, yMax, 0) - }; - Handles.color = Color.black; - Handles.DrawAAConvexPolygon(verts); - int row = 0; - foreach (Nucleus nucleus in receptor1.nucleiArray) { - Vector3 pos = new(150, margin + row * spacing, 0.0f); - Handles.color = Color.white; - // The selected nucleus highlight ring - Handles.DrawSolidDisc(pos, Vector3.forward, size + 2); - DrawNucleus(nucleus, pos, maxValue, size); - row++; - } - GUIStyle style = new(EditorStyles.label) { - alignment = TextAnchor.UpperCenter, - normal = { textColor = Color.white }, - fontStyle = FontStyle.Bold, - }; - Vector3 labelPos = new(150, yMax + size + 5, 0); - string receptorName = receptor1.GetName(); - int colonPos = receptorName.IndexOf(":"); - if (colonPos > 0) { - string baseName = receptorName[..colonPos]; - Handles.Label(labelPos, baseName, style); - } - else - Handles.Label(labelPos, receptorName, style); - } - else { - Handles.color = Color.white; - // The selected nucleus highlight ring - Handles.DrawSolidDisc(position, Vector3.forward, size + 2); - float maxValue = 1; - if (this.currentNucleus is Neuron neuron) - maxValue = neuron.outputMagnitude; - else if (this.currentNucleus is Cluster cluster) - maxValue = cluster.defaultOutput.outputMagnitude; - - DrawNucleus(this.currentNucleus, position, maxValue, 20); - - } - } - else { - Handles.color = Color.white; - // The selected nucleus highlight ring - Handles.DrawSolidDisc(position, Vector3.forward, size + 2); - float maxValue = 1; - if (this.currentNucleus is Neuron neuron) - maxValue = neuron.outputMagnitude; - else if (this.currentNucleus is Cluster cluster) - maxValue = cluster.defaultOutput.outputMagnitude; - DrawNucleus(this.currentNucleus, position, maxValue, 20); - } - } - - private void DrawReceivers(Nucleus nucleus, Vector3 parentPos, float size) { - List receivers; - if (nucleus is Neuron neuron) - receivers = neuron.receivers; - else if (nucleus is Cluster cluster) - receivers = cluster.CollectReceivers(); - else - return; - - int nodeCount = receivers.Count(); //neuron != null ? neuron.receivers.Count() : 1; - - // Determine the maximum value in this layer - // This is used to 'scale' the output value colors of the nuclei - float maxValue = 0; - foreach (Nucleus receiver in receivers) { - if (receiver is Neuron neuroid) { - float value = neuroid.outputMagnitude; - if (value > maxValue) - maxValue = value; - } - } - - // Determine the spacing of the nuclei in the layer - float spacing = 400f / nodeCount; - float margin = 10 + spacing / 2; - - int row = 0; - List drawnArrays = new(); - foreach (Nucleus receiver in receivers) { - if (receiver is Receptor receptor) { - if (drawnArrays.Contains(receptor.nucleiArray)) - continue; - drawnArrays.Add(receptor.nucleiArray); - } - - Nucleus receiverNucleus = receiver; - if (receiverNucleus == null) - continue; - - Vector3 pos = new(50, margin + row * spacing, 0.0f); - Handles.color = Color.white; - Handles.DrawLine(parentPos, pos); - - DrawNucleus(receiverNucleus, pos, maxValue, size); - row++; - } - } - - private void DrawSynapses(Nucleus nucleus, Vector3 parentPos, float size) { - int nodeCount = nucleus.synapses.Count; - - // Determine the maximum value in this layer - // This is used to 'scale' the output value colors of the nuclei - float maxValue = 0; - int neuronCount = 0; - List drawnArrays = new(); - foreach (Synapse synapse in nucleus.synapses) { - if (synapse.neuron == null) - continue; - - if (synapse.neuron is Receptor receptor) { - if (drawnArrays.Contains(receptor.nucleiArray)) - continue; - drawnArrays.Add(receptor.nucleiArray); - } - else if (synapse.neuron.parent is ClusterReceptor clusterReceptor) { - if (drawnArrays.Contains(clusterReceptor.nucleiArray)) - continue; - drawnArrays.Add(clusterReceptor.nucleiArray); - } - if (synapse.neuron is Neuron synapseNeuron) { - float value = synapseNeuron.outputMagnitude * synapse.weight; - // Debug.Log($"{synapse.nucleus.name}: {value} {length(synapse.nucleus.outputValue)} {synapse.weight}"); - if (value > maxValue) - maxValue = value; - } - neuronCount++; - } - - // Determine the spacing of the nuclei in the layer - float spacing = 400f / neuronCount; - float margin = 10 + spacing / 2; - - int row = 0; - drawnArrays = new(); - foreach (Synapse synapse in nucleus.synapses) { - if (synapse.neuron is null) - continue; - - if (synapse.neuron is Receptor neuron) { - if (drawnArrays.Contains(neuron.nucleiArray)) - continue; - drawnArrays.Add(neuron.nucleiArray); - } - else if (synapse.neuron.parent is ClusterReceptor clusterReceptor) { - if (drawnArrays.Contains(clusterReceptor.nucleiArray)) - continue; - drawnArrays.Add(clusterReceptor.nucleiArray); - } - Vector3 pos = new(250, margin + row * spacing, 0.0f); - Handles.color = Color.white; - Handles.DrawLine(parentPos, pos); - Color color = Color.black; - if (Application.isPlaying) { - if (maxValue == 0 || !float.IsFinite(maxValue)) - maxValue = 1; - float brightness = 0; - if (synapse.neuron is Neuron synapseNeuron) - brightness = synapseNeuron.outputMagnitude * synapse.weight / maxValue; - color = new Color(brightness, brightness, brightness, 1f); - } - if (synapse.neuron.parent != null && synapse.neuron.parent != this.currentNucleus.parent) { - // the synapse nucleus is part of a subcluster - DrawNucleus(synapse.neuron.parent, pos, maxValue, size, color); - } - // else if (synapse.nucleus.cluster != null && synapse.nucleus.cluster != this.currentNucleus.cluster) { - // DrawNucleus(synapse.nucleus.parent, pos, maxValue, size, color); - // } - else { - DrawNucleus(synapse.neuron, pos, maxValue, size, color); - } - row++; - } - } - - private void DrawNucleus(Nucleus nucleus, Vector3 position, float maxValue, float size) { - Color color; - if (Application.isPlaying) { - float brightness = 0; - if (nucleus is Neuron neuron) - brightness = neuron.outputMagnitude / maxValue; - color = new Color(brightness, brightness, brightness, 1f); - } - else - color = Color.black; - DrawNucleus(nucleus, position, maxValue, size, color); - } - - private void DrawNucleus(Nucleus nucleus, Vector3 position, float maxValue, float size, Color color) { - if (nucleus is MemoryCell) { - Handles.color = Color.white; - Handles.DrawWireDisc(position + Vector3.right * 10, Vector3.forward, size); - } - - Handles.color = color; - Handles.DrawSolidDisc(position, Vector3.forward, size); - - Handles.color = Color.white; - // Position the label in front of the disc - Vector3 labelPosition = position + (Vector3.forward * 0.1f); - - GUIStyle style = new(EditorStyles.label) { - alignment = TextAnchor.MiddleCenter, - normal = { textColor = Color.white }, - fontStyle = FontStyle.Bold, - }; - - if (nucleus is IReceptor receptor1) { - if (expandArray) { - // Put array indices above elements - style.alignment = TextAnchor.LowerCenter; - Vector3 labelPos1 = position + Vector3.down * (size + 5); // below disc - int colonPos1 = nucleus.name.IndexOf(":"); - if (colonPos1 > 0) { - string extName = nucleus.name[(colonPos1 + 2)..]; - Handles.Label(labelPos1, extName, style); - } - } - else { - // draw the array size label - if (color.grayscale > 0.5f) - style.normal.textColor = Color.black; - else - style.normal.textColor = Color.white; - Handles.Label(labelPosition, receptor1.nucleiArray.Length.ToString(), style); - style.normal.textColor = Color.white; - } - } - - if (expandArray == false || nucleus is not IReceptor) { - // put name below nucleus - Vector3 labelPos = position - Vector3.down * (size + 5); // below neuron - style.alignment = TextAnchor.UpperCenter; - - int colonPos = nucleus.name.IndexOf(":"); - if (colonPos > 0 && colonPos < nucleus.name.Length - 2) { - // if it is an array, we should not show the :0 of the first element - string baseName = nucleus.name[..colonPos]; - Handles.Label(labelPos, baseName, style); - } - else - Handles.Label(labelPos, nucleus.name, style); - - } - - // Draw Cluster ring - if (nucleus is Cluster) { - Handles.color = Color.white; - Handles.DrawWireDisc(position, Vector3.forward, size + 5); - } - - // Tooltip - Rect neuronRect = new(position.x - size, position.y - size, size * 2, size * 2); - int id = GUIUtility.GetControlID(FocusType.Passive); - Event e = Event.current; - EventType et = e.GetTypeForControl(id); - if (e != null && neuronRect.Contains(e.mousePosition)) { - // Process Hover - HandleMouseHover(nucleus, neuronRect); - // Process click - if (e.type == EventType.MouseDown && e.button == 0) { - // Consume the event so the scene doesn't also handle it - e.Use(); - HandleClicked(nucleus); - } - } - } - - private void HandleMouseHover(Nucleus nucleus, Rect rect) { - GUIContent tooltip; - if (nucleus is Neuron neuron) { - tooltip = new( - $"{nucleus.name}" + - $"\nValue: {neuron.outputMagnitude}"); - } - else - tooltip = new($"{nucleus.name}"); - - Vector2 mousePosition = Event.current.mousePosition; - - // Display tooltip with some offset - Vector2 tooltipSize = GUI.skin.box.CalcSize(tooltip); - Rect tooltipRect = new Rect(mousePosition.x + 10, mousePosition.y + 10, tooltipSize.x, tooltipSize.y); - - GUI.Box(tooltipRect, tooltip); - } - - private void HandleClicked(Nucleus nucleus) { - if (nucleus == this.currentNucleus) { - if (nucleus is Receptor || nucleus is ClusterReceptor) - expandArray = !expandArray; - else - expandArray = false; - } - // else if (nucleus is ReceptorInstance receptor) { - // this.currentNucleus = receptor.receptor; - // expandArray = false; - // BuildLayers(); - // } - else { - this.currentNucleus = nucleus; - expandArray = false; - BuildLayers(); - } - } - - private VisualElement inspectorIMGUIContainer; - private bool showSynapses = true; - private bool showActivation = true; - protected bool breakOnWake = false; - protected bool trace = false; - void DrawInspector(VisualElement inspectorContainer) { - if (inspectorContainer == null) - return; - - inspectorContainer.Clear(); - if (this.currentNucleus == null) - return; - - // create a SerializedObject wrapper so Unity inspector controls work (and Undo) - SerializedObject so = new(prefabAsset); - this.inspectorIMGUIContainer = new IMGUIContainer(() => InspectorHandler(so)); - - inspectorContainer.Add(inspectorIMGUIContainer); - } - - void InspectorHandler(SerializedObject serializedObject) { - bool anythingChanged = false; - - if (serializedObject == null || serializedObject.targetObject == null) - return; - - if (this.currentNucleus == null) - return; - - serializedObject.Update(); - - GUIStyle headerStyle = new(EditorStyles.boldLabel) { - alignment = TextAnchor.MiddleLeft, - margin = new RectOffset(10, 0, 4, 4) - }; - GUIStyle boldTextFieldStyle = new(EditorStyles.textField) { - fontStyle = FontStyle.Bold - }; - - GUILayout.Label(this.currentNucleus.GetType().ToString(), headerStyle); - string newName = EditorGUILayout.TextField(this.currentNucleus.name, boldTextFieldStyle); - if (newName != this.currentNucleus.name) { - this.currentNucleus.name = newName; - this.prefab.RefreshOutputs(); - outputsField.choices = this.prefab.outputs.Select(output => output.name).ToList(); - anythingChanged = true; - } - - if (Application.isPlaying) { - if (currentNucleus is Neuron currentNeuron1) { - GUIContent nameLabel = new("Output", currentNeuron1.outputValue.ToString()); - EditorGUILayout.FloatField(nameLabel, currentNeuron1.outputMagnitude); - } - else - EditorGUILayout.LabelField(" "); - } - else - EditorGUILayout.LabelField(" "); - - if (this.currentNucleus is MemoryCell memory) { - memory.staticMemory = EditorGUILayout.Toggle("Static Memory", memory.staticMemory); - } - - if (this.currentNucleus is IReceptor receptor1) { - EditorGUILayout.BeginHorizontal(); - EditorGUILayout.IntField("Array size", receptor1.nucleiArray.Count()); - if (GUILayout.Button("Add")) { - Undo.RecordObject(prefabAsset, "Array add " + prefabAsset.name); - receptor1.AddReceptorElement(this.prefab); - anythingChanged = true; - } - if (GUILayout.Button("Del")) { - Undo.RecordObject(prefabAsset, "Array delete " + prefabAsset.name); - receptor1.RemoveReceptorElement(); - anythingChanged = true; - } - EditorGUILayout.EndHorizontal(); - } - - // Synapses - - if (this.currentNucleus is not Receptor && this.currentNucleus is not ClusterReceptor) { - showSynapses = EditorGUILayout.BeginFoldoutHeaderGroup(showSynapses, "Synapses"); - if (showSynapses) { - if (this.currentNucleus is Neuron neuron2) { - Neuron.CombinatorType newCombinator = (Neuron.CombinatorType)EditorGUILayout.EnumPopup("Combinator", neuron2.combinator); - anythingChanged |= newCombinator != neuron2.combinator; - neuron2.combinator = newCombinator; - } - - EditorGUIUtility.wideMode = true; - EditorGUIUtility.labelWidth = 100; - Vector3 newBias = EditorGUILayout.Vector3Field("Bias", this.currentNucleus.bias); - anythingChanged |= newBias != this.currentNucleus.bias; - this.currentNucleus.bias = newBias; - - Nucleus[] array = null; - int elementIx = -1; - if (this.currentNucleus.synapses.Count > 0) { - Synapse[] synapses = this.currentNucleus.synapses.ToArray(); - foreach (Synapse synapse in synapses) { - if (synapse.neuron == null) - continue; - - if (array != null) { - if (synapse.neuron.parent is Cluster iCluster && elementIx > 0) { - int thisElementIx = Cluster.GetNucleusIndex(iCluster.clusterNuclei, synapse.neuron); - if (thisElementIx == elementIx) - continue; - else - elementIx = thisElementIx; - } - // if (array.Contains(synapse.nucleus)) - // continue; - else if (array.Contains(synapse.neuron.parent)) - continue; - } - else { - if (synapse.neuron.parent is IReceptor iReceptor) { - array = iReceptor.nucleiArray; - if (iReceptor is Cluster iCluster) - elementIx = Cluster.GetNucleusIndex(iCluster.clusterNuclei, synapse.neuron); - } - // else if (synapse.nucleus is Receptor receptor2) // && receptor2.array != null && receptor2.array.nuclei.Length > 1) - // array = receptor2.nucleiArray; - } - - EditorGUILayout.Space(); - - if (Application.isPlaying) { - if (synapse.neuron is Neuron synapseNeuron) { - Vector3 value = synapseNeuron.outputValue * synapse.weight; - GUIContent synapseValueLabel = new(synapse.neuron.name, synapseNeuron.outputValue.ToString()); - EditorGUILayout.FloatField(synapseValueLabel, synapseNeuron.outputMagnitude); - } - } - else { - EditorGUILayout.BeginHorizontal(); - - if (synapse.neuron.parent != null && synapse.neuron.parent != this.currentNucleus) { - // If it is a cluster - GUIStyle labelStyle = new(GUI.skin.label); - float labelWidth = 200; - if (synapse.neuron.clusterPrefab != null) { - labelWidth = labelStyle.CalcSize(new GUIContent($"{synapse.neuron.parent.baseName}.")).x; - GUILayout.Label($"{synapse.neuron.parent.baseName}", GUILayout.Width(labelWidth)); - } - string[] options = synapse.neuron.parent.clusterNuclei.Select(n => n.name).ToArray(); - int selectedIndex = System.Array.IndexOf(options, synapse.neuron.name); - int newIndex = EditorGUILayout.Popup(selectedIndex, options); - if (newIndex != selectedIndex && synapse.neuron.parent.clusterNuclei[newIndex] is Neuron newNeuron) - ChangeSynapse(synapse, newNeuron); - } - else - GUILayout.Label(synapse.neuron.name); - - bool disconnecting = GUILayout.Button("Disconnect", GUILayout.Width(80)); - if (disconnecting && synapse.neuron is Neuron synapseNeuron) { - synapseNeuron.RemoveReceiver(this.currentNucleus); - this.prefab.GarbageCollection(); - anythingChanged = true; - } - EditorGUILayout.EndHorizontal(); - - } - - EditorGUI.indentLevel++; - float newWeight = EditorGUILayout.FloatField("Weight", synapse.weight); - if (newWeight != synapse.weight) { - if (synapse.neuron.parent is IReceptor receptor) { - Nucleus[] receptorArray = receptor.nucleiArray; - foreach (Synapse s in this.currentNucleus.synapses) { - if (s.neuron.parent is IReceptor r && r.nucleiArray == receptorArray) - s.weight = newWeight; - } - } - else - synapse.weight = newWeight; - anythingChanged = true; - } - EditorGUI.indentLevel--; - } - } - - EditorGUILayout.Space(); - anythingChanged |= ConnectNucleus(this.prefab, this.currentNucleus); - anythingChanged |= AddSynapse(this.prefab, this.currentNucleus); - } - EditorGUILayout.EndFoldoutHeaderGroup(); - } - - // Activation - - if (this.currentNucleus is not Cluster) { - EditorGUILayout.Space(); - showActivation = EditorGUILayout.BeginFoldoutHeaderGroup(showActivation, "Activation"); - if (showActivation) { - if (this.currentNucleus is Neuron neuron) { - if (this.currentNucleus is not MemoryCell) { - EditorGUILayout.BeginHorizontal(); - EditorGUILayout.LabelField("Activation Curve", GUILayout.Width(150)); - if (neuron.curveMax > 0) - EditorGUILayout.CurveField(neuron.curve, Color.cyan, new Rect(0, 0, 1, neuron.curveMax)); - else - EditorGUILayout.CurveField(neuron.curve, Color.cyan, new Rect(0, neuron.curveMax, 1, -neuron.curveMax)); - Neuron.CurvePresets newPreset = (Neuron.CurvePresets)EditorGUILayout.EnumPopup(neuron.curvePreset, GUILayout.Width(100)); - anythingChanged |= newPreset != neuron.curvePreset; - neuron.curvePreset = newPreset; - EditorGUILayout.EndHorizontal(); - } - if (neuron is Receptor receptor2) { - if (receptor2.nucleiArray == null || receptor2.nucleiArray.Count() == 0) - receptor2.array = new NucleusArray(neuron); - } - } - - EditorGUILayout.Space(); - } - EditorGUILayout.EndFoldoutHeaderGroup(); - } - - if (GUILayout.Button("Delete this neuron")) - DeleteNucleus(this.currentNucleus); - - if (this.currentNucleus is Cluster subCluster) { - if (GUILayout.Button("Edit Cluster")) - EditCluster(subCluster); - } - - EditorGUILayout.Space(); - breakOnWake = EditorGUILayout.Toggle("Break on wake", breakOnWake); - if (breakOnWake && this.currentNucleus is Neuron currentNeuron) { - if (currentNeuron.isSleeping == false) - Debug.Break(); - } - trace = EditorGUILayout.Toggle("Trace", trace); - this.currentNucleus.trace = trace; - - serializedObject.ApplyModifiedProperties(); - if (anythingChanged) { - EditorUtility.SetDirty(prefabAsset); - AssetDatabase.SaveAssets(); - } - } - - void OnSceneGUI(SceneView sceneView) { - if (this.gameObject != null) { - if (this.currentNucleus is IReceptor receptor) { - foreach (Nucleus nucleus in receptor.nucleiArray) { - if (nucleus is Neuron neuron) { - Vector3 worldVector = this.gameObject.transform.TransformVector(neuron.outputValue); - Handles.color = Color.yellow; - Handles.DrawLine(this.gameObject.transform.position, this.gameObject.transform.position + worldVector); - } - } - } - else { - if (this.currentNucleus is Neuron currentNeuron) { - Vector3 worldVector = this.gameObject.transform.TransformVector(currentNeuron.outputValue); - Handles.color = Color.yellow; - Handles.DrawLine(this.gameObject.transform.position, this.gameObject.transform.position + worldVector); - } - } - } - } - - #region Synapses - - protected virtual void AddInput(Nucleus.Type selectedType, Nucleus nucleus) { - switch (selectedType) { - case Nucleus.Type.Neuron: - AddNeuronInput(nucleus); - break; - case Nucleus.Type.MemoryCell: - AddMemoryCellInput(nucleus); - break; - // case Nucleus.Type.Selector: - // AddSelectorInput(nucleus); - // break; - case Nucleus.Type.Cluster: - AddClusterInput(nucleus); - break; - // case Nucleus.Type.Pulsar: - // AddPulsarInput(nucleus); - // break; - case Nucleus.Type.Receptor: - AddReceptorInput(nucleus); - break; - // case Nucleus.Type.ReceptorArray: - // AddReceptorArrayInput(nucleus); - // break; - case Nucleus.Type.ClusterReceptor: - AddClusterReceptorInput(nucleus); - break; - default: - break; - } - } - - protected virtual void AddNeuronInput(Nucleus nucleus) { - Neuron newNeuroid = new(this.prefab, "New neuron"); - newNeuroid.AddReceiver(nucleus); - this.currentNucleus = newNeuroid; - BuildLayers(); - } - - protected virtual void AddMemoryCellInput(Nucleus nucleus) { - MemoryCell newMemory = new(this.prefab, "New memory cell"); - newMemory.AddReceiver(nucleus); - this.currentNucleus = newMemory; - BuildLayers(); - } - - protected virtual void AddClusterInput(Nucleus nucleus) { - ClusterPickerWindow.ShowPicker(brain => OnClusterPicked(nucleus, brain), "Select Cluster"); - } - private void OnClusterPicked(Nucleus nucleus, ClusterPrefab prefab) { - Cluster subclusterInstance = new(prefab, this.prefab); - subclusterInstance.defaultOutput.AddReceiver(nucleus); - } - - protected virtual void AddReceptorInput(Nucleus nucleus) { - Receptor newReceptor = new(this.prefab, "New Receptor"); - newReceptor.AddReceiver(nucleus); - this.currentNucleus = newReceptor; - BuildLayers(); - } - - protected virtual void AddClusterReceptorInput(Nucleus nucleus) { - ClusterPickerWindow.ShowPicker(prefab => OnClusterReceptorPicked(nucleus, prefab), "Select Cluster"); - } - private void OnClusterReceptorPicked(Nucleus nucleus, ClusterPrefab selectedPrefab) { - ClusterReceptor clusterInstance = new(selectedPrefab, this.prefab, "New " + selectedPrefab.name); - clusterInstance.defaultOutput.AddReceiver(nucleus); - this.currentNucleus = clusterInstance; - BuildLayers(); - } - - private void EditCluster(Cluster subCluster) { - // May be used with storedPrefab... - Selection.activeObject = subCluster.prefab; - EditorGUIUtility.PingObject(subCluster.prefab); - var editor = Editor.CreateEditor(subCluster.prefab); - } - - int selectedConnectNucleus = -1; - // Connect to another nucleus in the same cluster - protected virtual bool ConnectNucleus(ClusterPrefab cluster, Nucleus nucleusToConnect) { - if (cluster == null) - return false; - - IEnumerable synapseNuclei = this.currentNucleus.synapses - .Where(synapse => synapse.neuron != null) - .Select(synapse => synapse.neuron); - - IEnumerable nuclei = cluster.nuclei - .Except(synapseNuclei); - IEnumerable nucleiNames = nuclei - .Select(n => { - int idx = n.name.IndexOf(':'); - return idx < 0 ? n.name : n.name[..idx]; - }) - .Distinct(); - - string[] names = nucleiNames.ToArray(); - EditorGUILayout.BeginHorizontal(); - selectedConnectNucleus = EditorGUILayout.Popup(selectedConnectNucleus, names); - bool connecting = GUILayout.Button("Connect", GUILayout.Width(80)); - EditorGUILayout.EndHorizontal(); - if (connecting) { - Nucleus nucleus = nuclei.ElementAt(selectedConnectNucleus); - if (nucleus is IReceptor receptor) - receptor.AddArrayReceiver(this.currentNucleus); - else if (nucleus is Neuron neuron) - neuron.AddReceiver(this.currentNucleus); - else if (nucleus is Cluster subCluster) - subCluster.defaultOutput.AddReceiver(this.currentNucleus); - - } - return connecting; - } - - protected virtual void DeleteNucleus(Nucleus nucleus) { - if (nucleus == null) - return; - - if (nucleus is Neuron neuron) { - foreach (Nucleus receiver in neuron.receivers) { - if (receiver != null) { - this.currentNucleus = receiver; - break; - } - } - } - this.prefab.nuclei.Remove(nucleus); - - if (outputsField.value == nucleus.name) { - this.prefab.RefreshOutputs(); - outputsField.choices = this.prefab.outputs.Select(output => output.name).ToList(); - outputsField.index = 0; - } - - Neuron.Delete(nucleus); - - this.currentNucleus = this.prefab.output; - BuildLayers(); - } - - Nucleus.Type selectedType = Nucleus.Type.None; - protected virtual bool AddSynapse(ClusterPrefab cluster, Nucleus nucleus) { - if (cluster == null) - return false; - - EditorGUILayout.BeginHorizontal(); - selectedType = (Nucleus.Type)EditorGUILayout.EnumPopup(selectedType); - bool connecting = GUILayout.Button("Add", GUILayout.Width(80)); - EditorGUILayout.EndHorizontal(); - - if (connecting) { - AddInput(selectedType, this.currentNucleus); - } - return connecting; - // if (selectedType == Nucleus.Type.None) - // return false; - - // AddInput(selectedType, this.currentNucleus); - // return true; - } - - protected virtual void ChangeSynapse(Synapse synapse, Neuron newNucleus) { - Neuron synapseNeuron = synapse.neuron as Neuron; - if (synapse.neuron.parent is Cluster subCluster && subCluster.prefab != this.prefab) { - if (synapse.neuron.parent is ClusterReceptor receptor) { - // the new nucleus is part of a (cluster) receptor, - // so we have to change all synapses to this nucleus array elements - int oldNucleusIx = Cluster.GetNucleusIndex(subCluster.clusterNuclei, synapse.neuron); - int newNucleusIx = Cluster.GetNucleusIndex(subCluster.clusterNuclei, newNucleus); - foreach (Nucleus element in receptor.nucleiArray) { - if (element is not ClusterReceptor clusterReceptor) - continue; - // Get the same neuron as the synapse.nucleus in a different element - // of the ClusterReceptor array - Nucleus oldElementNucleus = clusterReceptor.clusterNuclei[oldNucleusIx]; - if (oldElementNucleus is not Neuron oldElementNeuron) - continue; - // Get the same neuron as newNucleus in a different element - // of the ClusterReceptor array - Nucleus newElementNucleus = clusterReceptor.clusterNuclei[newNucleusIx]; - if (newElementNucleus is not Neuron newElementNeuron) - continue; - - oldElementNeuron.RemoveReceiver(this.currentNucleus); - newElementNeuron.AddReceiver(this.currentNucleus); - // Now find the synapse which pointed to the old Neuron - // Synapse synapseForUpdate = this.currentNucleus.GetSynapse(oldElementNeuron); - // synapseForUpdate.nucleus = newElementNeuron; - } - } - else { - // it is a neuron in a subcluster - synapseNeuron.RemoveReceiver(this.currentNucleus); - newNucleus.AddReceiver(this.currentNucleus); - } - } - else { - synapseNeuron.RemoveReceiver(this.currentNucleus); - newNucleus.AddReceiver(this.currentNucleus); - } - } - - protected virtual void DisconnectNucleus(Neuron nucleus) { - if (this.currentNucleus.clusterPrefab == null) - return; - string[] names = this.currentNucleus.synapses.Select(synapse => synapse.neuron.name).ToArray(); - int selectedIndex = -1; - selectedIndex = EditorGUILayout.Popup("Disconnect from", selectedIndex, names); - if (selectedIndex >= 0 && selectedIndex < this.currentNucleus.clusterPrefab.nuclei.Count) { - Synapse synapse = this.currentNucleus.synapses[selectedIndex]; - Neuron synapseNeuron = synapse.neuron as Neuron; - synapseNeuron.RemoveReceiver(this.currentNucleus); - } - } - - #endregion Synapses - } - - } - - public class NeuroidLayer { - public int ix = 0; - public List neuroids = new(); - } - */ } \ No newline at end of file diff --git a/Editor/ClusterViewer.cs b/Editor/ClusterViewer.cs index 8e55da5..00d6132 100644 --- a/Editor/ClusterViewer.cs +++ b/Editor/ClusterViewer.cs @@ -466,15 +466,14 @@ namespace NanoBrain { style.alignment = TextAnchor.UpperCenter; if (nucleus.parent != null && nucleus.parent is Cluster parentCluster1) { + // This neuron is part of another cluster parentCluster1.name ??= ""; string baseName = ""; - if (parentCluster1 != currentNucleus.parent) { int colonPos = parentCluster1.name.IndexOf(":"); if (colonPos > 0 && colonPos < parentCluster1.name.Length - 2) baseName = parentCluster1.name[..colonPos] + "."; else baseName = parentCluster1.name + "."; - } // if (colonPos > 0 && colonPos < parentCluster1.name.Length - 2) { // // if it is an array, we should not show the :0 of the first element // //baseName = baseName[..colonPos]; @@ -549,13 +548,13 @@ namespace NanoBrain { else expandArray = false; } - else if (nucleus.parent != this.currentNucleus.parent) { - // We go to a different cluster - // select the cluster, not the neuron in the cluster - this.currentNucleus = nucleus.parent; - expandArray = false; - BuildLayers(); - } + // else if (nucleus.parent != this.currentNucleus.parent) { + // // We go to a different cluster + // // select the cluster, not the neuron in the cluster + // this.currentNucleus = nucleus.parent; + // expandArray = false; + // BuildLayers(); + // } else { this.currentNucleus = nucleus; expandArray = false; diff --git a/Editor/DAGWindow.cs b/Editor/DAGWindow.cs deleted file mode 100644 index 1cb590e..0000000 --- a/Editor/DAGWindow.cs +++ /dev/null @@ -1,356 +0,0 @@ - -using UnityEngine; -using UnityEditor; -using System.Collections.Generic; -using System.Linq; - -namespace NanoBrain { - - // Simple DAG data model - // [System.Serializable] - // public class DagNode - // { - // public int id; - // public string title; - // public Vector2 position; - // public float radius = 36f; // circle radius - // } - - // [System.Serializable] - // public class DagEdge - // { - // public int fromId; - // public int toId; - // } - - public class DAGEditorWindow : EditorWindow { - List nodes = new List(); - List edges = new List(); - - Vector2 pan = Vector2.zero; - float zoom = 1.0f; - const float minZoom = 0.5f; - const float maxZoom = 2.0f; - - GUIStyle labelStyle; - int selectedNodeId = -1; - - Vector2 dragStart; - bool draggingNode = false; - int draggingNodeId = -1; - - [MenuItem("Window/DAG Viewer (LR, Circles)")] - public static void ShowWindow() { - var w = GetWindow("DAG Viewer (LR)"); - w.minSize = new Vector2(500, 300); - } - - void OnEnable() { - labelStyle = new GUIStyle(EditorStyles.label); - labelStyle.alignment = TextAnchor.MiddleCenter; - labelStyle.normal.textColor = Color.white; - labelStyle.fontStyle = FontStyle.Bold; - - if (nodes.Count == 0) - CreateSampleGraph(); - - ComputeLeftToRightLayout(); - } - - void CreateSampleGraph() { - nodes.Clear(); - edges.Clear(); - - nodes.Add(new DagNode() { id = 0, title = "In1" }); - nodes.Add(new DagNode() { id = 1, title = "In2" }); - nodes.Add(new DagNode() { id = 2, title = "A" }); - nodes.Add(new DagNode() { id = 3, title = "B" }); - nodes.Add(new DagNode() { id = 4, title = "C" }); - nodes.Add(new DagNode() { id = 5, title = "Out1" }); - nodes.Add(new DagNode() { id = 6, title = "Out2" }); - - edges.Add(new DagEdge() { fromId = 0, toId = 2 }); - edges.Add(new DagEdge() { fromId = 1, toId = 2 }); - edges.Add(new DagEdge() { fromId = 2, toId = 3 }); - edges.Add(new DagEdge() { fromId = 2, toId = 4 }); - edges.Add(new DagEdge() { fromId = 3, toId = 5 }); - edges.Add(new DagEdge() { fromId = 4, toId = 6 }); - } - - void OnGUI() { - HandleInput(); - - Rect rect = new Rect(0, 0, position.width, position.height); - EditorGUI.DrawRect(rect, new Color(0.11f, 0.11f, 0.11f)); - - Matrix4x4 oldMatrix = GUI.matrix; - Vector2 origin = new Vector2(position.width / 2, position.height / 2); - GUI.matrix = Matrix4x4.TRS(origin + pan, Quaternion.identity, Vector3.one * zoom) * - Matrix4x4.TRS(-origin, Quaternion.identity, Vector3.one); - - // Draw edges first - foreach (var e in edges) { - var from = GetNodeById(e.fromId); - var to = GetNodeById(e.toId); - if (from == null || to == null) continue; - DrawEdgeCircleNodes(from, to); - } - - // Draw nodes (circles) - foreach (var n in nodes) { - DrawNodeCircle(n); - } - - GUI.matrix = oldMatrix; - - // Footer toolbar - GUILayout.FlexibleSpace(); - EditorGUILayout.BeginHorizontal(EditorStyles.toolbar); - if (GUILayout.Button("Fit", EditorStyles.toolbarButton)) FitToView(); - if (GUILayout.Button("Layout LR", EditorStyles.toolbarButton)) ComputeLeftToRightLayout(); - if (GUILayout.Button("Add Node", EditorStyles.toolbarButton)) { - AddNode("N" + nodes.Count); - ComputeLeftToRightLayout(); - } - if (GUILayout.Button("Add Edge (selected->new)", EditorStyles.toolbarButton)) { - if (selectedNodeId != -1) { - var newNode = AddNode("N" + nodes.Count); - edges.Add(new DagEdge() { fromId = selectedNodeId, toId = newNode.id }); - ComputeLeftToRightLayout(); - } - } - EditorGUILayout.EndHorizontal(); - } - - void HandleInput() { - Event e = Event.current; - - // Zoom with scroll - if (e.type == EventType.ScrollWheel) { - float oldZoom = zoom; - float delta = -e.delta.y * 0.01f; - zoom = Mathf.Clamp(zoom + delta, minZoom, maxZoom); - Vector2 mouse = e.mousePosition; - pan += (mouse - new Vector2(position.width / 2, position.height / 2)) * (1 - zoom / oldZoom); - e.Use(); - } - - // Pan with middle or right+ctrl drag - if (e.type == EventType.MouseDrag && (e.button == 2 || (e.button == 1 && e.control))) { - pan += e.delta; - e.Use(); - } - - // Node dragging & selection (convert mouse to graph space) - Vector2 graphMouse = ScreenToGraph(e.mousePosition); - if (e.type == EventType.MouseDown && e.button == 0) { - int hit = HitTestNode(graphMouse); - if (hit != -1) { - selectedNodeId = hit; - draggingNode = true; - draggingNodeId = hit; - dragStart = graphMouse; - e.Use(); - } - else { - selectedNodeId = -1; - } - } - - if (draggingNode && draggingNodeId != -1) { - if (e.type == EventType.MouseDrag && e.button == 0) { - Vector2 graphDelta = e.delta / zoom; - var n = GetNodeById(draggingNodeId); - if (n != null) { - n.position += graphDelta; - Repaint(); - e.Use(); - } - } - if (e.type == EventType.MouseUp && e.button == 0) { - draggingNode = false; - draggingNodeId = -1; - e.Use(); - } - } - } - - DagNode AddNode(string title) { - int nextId = nodes.Count > 0 ? nodes.Max(n => n.id) + 1 : 0; - var n = new DagNode() { id = nextId, title = title, position = Vector2.zero }; - nodes.Add(n); - return n; - } - - DagNode GetNodeById(int id) => nodes.FirstOrDefault(x => x.id == id); - - void DrawNodeCircle(DagNode n) { - Vector2 center = n.position; - float r = n.radius; - Rect nodeRect = new Rect(center.x - r, center.y - r, r * 2, r * 2); - - // circle background - Color bg = (n.id == selectedNodeId) ? new Color(0.15f, 0.5f, 0.9f) : new Color(0.2f, 0.2f, 0.2f); - EditorGUI.DrawRect(nodeRect, bg); - - // anti-aliased circle outline - Handles.color = Color.white * 0.9f; - Handles.DrawAAPolyLine(3f / zoom, GetCircleOutlinePoints(center, r, 48).ToArray()); - - // label - Vector2 labelPos = center - new Vector2(0, 8); - GUI.Label(new Rect(labelPos.x - r, labelPos.y - 8, r * 2, 18), n.title, labelStyle); - } - - List GetCircleOutlinePoints(Vector2 center, float radius, int segments) { - var pts = new List(segments + 1); - for (int i = 0; i <= segments; i++) { - float a = (float)i / segments * Mathf.PI * 2f; - pts.Add(new Vector3(center.x + Mathf.Cos(a) * radius, center.y + Mathf.Sin(a) * radius, 0)); - } - return pts; - } - - void DrawEdgeCircleNodes(DagNode from, DagNode to) { - Vector2 a = from.position; - Vector2 b = to.position; - if (a == b) return; - - // Compute edge line that starts/ends at circle circumferences - Vector2 dir = (b - a).normalized; - Vector2 start = a + dir * from.radius; - Vector2 end = b - dir * to.radius; - - // Use a simple curved line: start -> control -> end (bezier) - Vector2 control = new Vector2((start.x + end.x) / 2f, (start.y + end.y) / 2f); - // Slight vertical offset to separate overlapping lines based on node ids - float offset = ((from.id * 7 + to.id * 11) % 7 - 3) * 6f / zoom; - control += new Vector2(0, offset); - - Handles.color = Color.white * 0.9f; - Handles.DrawAAPolyLine(3f / zoom, 20, GetBezierPoints(start, control, end, 24).ToArray()); - - // Arrow at end pointing towards 'b' - DrawArrowHead(end - dir * 2f, end, 10f / zoom, 12f / zoom, Color.white); - } - - List GetBezierPoints(Vector2 p0, Vector2 p1, Vector2 p2, int seg) { - var pts = new List(seg + 1); - for (int i = 0; i <= seg; i++) { - float t = (float)i / seg; - Vector2 p = (1 - t) * (1 - t) * p0 + 2 * (1 - t) * t * p1 + t * t * p2; - pts.Add(new Vector3(p.x, p.y, 0)); - } - return pts; - } - - void DrawArrowHead(Vector2 from, Vector2 to, float headWidth, float headLength, Color color) { - Vector2 dir = (to - from).normalized; - if (dir == Vector2.zero) return; - Vector2 right = new Vector2(-dir.y, dir.x); - - Vector3 p1 = to; - Vector3 p2 = to - dir * headLength + right * headWidth * 0.5f; - Vector3 p3 = to - dir * headLength - right * headWidth * 0.5f; - - Handles.color = color; - Handles.DrawAAConvexPolygon(p1, p2, p3); - } - - // Left-to-right layered layout (sources on the left, sinks on the right) - void ComputeLeftToRightLayout() { - // build adjacency and indegree - var adj = nodes.ToDictionary(n => n.id, n => new List()); - var indeg = nodes.ToDictionary(n => n.id, n => 0); - foreach (var e in edges) { - if (!adj.ContainsKey(e.fromId) || !adj.ContainsKey(e.toId)) continue; - adj[e.fromId].Add(e.toId); - indeg[e.toId]++; - } - - // Kahn's algorithm to compute topological layers (horizontal layers) - Dictionary layer = new Dictionary(); - Queue q = new Queue(indeg.Where(kv => kv.Value == 0).Select(kv => kv.Key)); - foreach (var id in q) layer[id] = 0; - - while (q.Count > 0) { - int u = q.Dequeue(); - int l = layer[u]; - foreach (var v in adj[u]) { - // prefer placing v at least one layer after u - if (!layer.ContainsKey(v) || layer[v] < l + 1) layer[v] = l + 1; - indeg[v]--; - if (indeg[v] == 0) q.Enqueue(v); - } - } - - // Any unreachable nodes -> assign next layers - int maxLayer = layer.Count > 0 ? layer.Values.Max() : 0; - foreach (var n in nodes) { - if (!layer.ContainsKey(n.id)) { - maxLayer++; - layer[n.id] = maxLayer; - } - } - - // Group nodes by layer (left to right) - var layers = layer.GroupBy(kv => kv.Value).OrderBy(g => g.Key).Select(g => g.Select(x => x.Key).ToList()).ToList(); - - // Layout parameters (horizontal spacing drives left->right) - float hSpacing = 220f; - float vSpacing = 120f; - - // Place nodes: x increases with layer index, y spaced within layer - for (int li = 0; li < layers.Count; li++) { - var lst = layers[li]; - float totalHeight = (lst.Count - 1) * vSpacing; - for (int i = 0; i < lst.Count; i++) { - int id = lst[i]; - var n = GetNodeById(id); - if (n == null) continue; - float x = li * hSpacing; - float y = -totalHeight / 2f + i * vSpacing; - n.position = new Vector2(x, y); - } - } - - Repaint(); - } - - void FitToView() { - if (nodes.Count == 0) return; - Rect bounds = new Rect(nodes[0].position - Vector2.one * nodes[0].radius, Vector2.one * nodes[0].radius * 2f); - foreach (var n in nodes) - bounds = RectUnion(bounds, new Rect(n.position - Vector2.one * n.radius, Vector2.one * n.radius * 2f)); - - Vector2 center = bounds.center; - pan = -center; - zoom = 1.0f; - Repaint(); - } - - static Rect RectUnion(Rect a, Rect b) { - float xMin = Mathf.Min(a.xMin, b.xMin); - float xMax = Mathf.Max(a.xMax, b.xMax); - float yMin = Mathf.Min(a.yMin, b.yMin); - float yMax = Mathf.Max(a.yMax, b.yMax); - return Rect.MinMaxRect(xMin, yMin, xMax, yMax); - } - - Vector2 ScreenToGraph(Vector2 screenPos) { - Vector2 origin = new Vector2(position.width / 2, position.height / 2); - // invert the GUI.matrix transform (approx for current simple transforms) - return (screenPos - (origin + pan)) / zoom + origin * (1 - 1 / zoom); - } - - int HitTestNode(Vector2 graphPos) { - // returns node id under point or -1 - for (int i = nodes.Count - 1; i >= 0; i--) { - var n = nodes[i]; - if ((graphPos - n.position).sqrMagnitude <= n.radius * n.radius) return n.id; - } - return -1; - } - } - -} \ No newline at end of file diff --git a/Editor/DAGWindow.cs.meta b/Editor/DAGWindow.cs.meta deleted file mode 100644 index ea0ee9e..0000000 --- a/Editor/DAGWindow.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 95393aed582b8b30d965400672aec4d8 \ No newline at end of file diff --git a/Runtime/Scripts/Core/Neuron.cs b/Runtime/Scripts/Core/Neuron.cs index 530f44f..8e47dc8 100644 --- a/Runtime/Scripts/Core/Neuron.cs +++ b/Runtime/Scripts/Core/Neuron.cs @@ -524,16 +524,9 @@ namespace NanoBrain { } public virtual void RemoveReceiver(Nucleus receiverToRemove) { - int n1 = _receivers.Count; this._receivers.RemoveAll(receiver => receiver == receiverToRemove); - int n2 = _receivers.Count; - Debug.Log($" Removed {n1} - {n2} receivers"); - - n1 = receiverToRemove.synapses.Count; receiverToRemove.synapses.RemoveAll(synapse => synapse.neuron == this); - n2 = receiverToRemove.synapses.Count; - Debug.Log($" Removed {n1} - {n2} synapses"); - } + } #endregion Receivers