clusterview improvements
This commit is contained in:
parent
2a9693acca
commit
2a88689179
@ -8,22 +8,17 @@ namespace NanoBrain.Unity {
|
||||
|
||||
[CustomEditor(typeof(ClusterPrefab))]
|
||||
public class ClusterEditor : Editor {
|
||||
const float drawAreaWidth = 300f; // adjust as needed
|
||||
const float drawAreaWidth = 320f;
|
||||
const float padding = 6f;
|
||||
ClusterPrefab clusterPrefab;
|
||||
Nucleus currentNucleus {
|
||||
get { return clusterView.currentNucleus; }
|
||||
set { clusterView.currentNucleus = value; }
|
||||
}
|
||||
Cluster currentCluster => clusterView.currentCluster;
|
||||
protected Nucleus selectedOutput;
|
||||
ClusterView clusterView;
|
||||
ClusterView view;
|
||||
|
||||
void OnEnable() {
|
||||
clusterPrefab = (ClusterPrefab)target;
|
||||
clusterView = ClusterView.GetClusterView(serializedObject);
|
||||
clusterView.currentCluster ??= clusterPrefab.cluster;
|
||||
clusterView.currentNucleus = clusterPrefab.cluster.defaultOutput;
|
||||
view = ClusterView.GetClusterView(serializedObject);
|
||||
view.currentCluster ??= clusterPrefab.cluster;
|
||||
view.currentNucleus = clusterPrefab.cluster.defaultOutput;
|
||||
view.selectedOutput = view.currentNucleus;
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI() {
|
||||
@ -38,7 +33,7 @@ namespace NanoBrain.Unity {
|
||||
Rect innerRect = new(drawRect.x + padding, drawRect.y + padding,
|
||||
drawRect.width - padding * 2, drawRect.height - padding * 2);
|
||||
|
||||
clusterView.Render(innerRect);
|
||||
view.Render(innerRect);
|
||||
|
||||
// Right: info panel (takes remaining width)
|
||||
EditorGUILayout.BeginVertical(GUILayout.ExpandWidth(true));
|
||||
@ -54,7 +49,6 @@ namespace NanoBrain.Unity {
|
||||
|
||||
#region Inspector
|
||||
|
||||
//private VisualElement inspectorIMGUIContainer;
|
||||
private bool showSynapses = true;
|
||||
private bool showActivation = true;
|
||||
protected bool breakOnWake = false;
|
||||
@ -72,7 +66,7 @@ namespace NanoBrain.Unity {
|
||||
fontStyle = FontStyle.Bold
|
||||
};
|
||||
|
||||
if (this.currentNucleus == null) {
|
||||
if (this.view.currentNucleus == null) {
|
||||
OutputsInspector(ref anythingChanged);
|
||||
return;
|
||||
}
|
||||
@ -82,19 +76,19 @@ namespace NanoBrain.Unity {
|
||||
margin = new RectOffset(10, 0, 4, 4)
|
||||
};
|
||||
// Nucleus type
|
||||
string nucleusType = this.currentNucleus.GetType().Name;
|
||||
string nucleusType = this.view.currentNucleus.GetType().Name;
|
||||
GUILayout.Label(nucleusType, headerStyle);
|
||||
|
||||
// Nucleus name
|
||||
string newName = EditorGUILayout.TextField(this.currentNucleus.name, boldTextFieldStyle);
|
||||
if (newName != this.currentNucleus.name) {
|
||||
this.currentNucleus.name = newName;
|
||||
string newName = EditorGUILayout.TextField(this.view.currentNucleus.name, boldTextFieldStyle);
|
||||
if (newName != this.view.currentNucleus.name) {
|
||||
this.view.currentNucleus.name = newName;
|
||||
anythingChanged = true;
|
||||
}
|
||||
|
||||
// Current output value
|
||||
if (Application.isPlaying) {
|
||||
if (currentNucleus is Neuron currentNeuron1) {
|
||||
if (this.view.currentNucleus is Neuron currentNeuron1) {
|
||||
GUIContent nameLabel = new("Output", currentNeuron1.outputValue.ToString());
|
||||
EditorGUILayout.FloatField(nameLabel, currentNeuron1.outputMagnitude);
|
||||
}
|
||||
@ -105,17 +99,17 @@ namespace NanoBrain.Unity {
|
||||
EditorGUILayout.LabelField(" ");
|
||||
|
||||
// Memory cell
|
||||
if (this.currentNucleus is MemoryCell memory)
|
||||
if (this.view.currentNucleus is MemoryCell memory)
|
||||
MemoryCellInspector(memory, ref anythingChanged);
|
||||
// Cluster
|
||||
else if (this.currentNucleus is Cluster cluster)
|
||||
else if (this.view.currentNucleus is Cluster cluster)
|
||||
ClusterInspector(cluster, ref anythingChanged);
|
||||
// Other
|
||||
else
|
||||
NucleusInspector(this.currentNucleus, ref anythingChanged);
|
||||
NucleusInspector(this.view.currentNucleus, ref anythingChanged);
|
||||
|
||||
if (GUILayout.Button("Delete"))
|
||||
DeleteNucleus(this.currentNucleus);
|
||||
DeleteNucleus(this.view.currentNucleus);
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
@ -134,10 +128,10 @@ namespace NanoBrain.Unity {
|
||||
|
||||
bool connecting = GUILayout.Button("Add Output Neuron");
|
||||
if (connecting) {
|
||||
Nucleus newOutput = new Neuron(this.currentCluster, "New Output");
|
||||
this.currentCluster.Refresh();
|
||||
this.currentNucleus = newOutput;
|
||||
this.selectedOutput = this.currentNucleus;
|
||||
Nucleus newOutput = new Neuron(this.view.currentCluster, "New Output");
|
||||
this.view.currentCluster.Refresh();
|
||||
this.view.currentNucleus = newOutput;
|
||||
view.selectedOutput = this.view.currentNucleus;
|
||||
}
|
||||
}
|
||||
|
||||
@ -180,7 +174,7 @@ namespace NanoBrain.Unity {
|
||||
|
||||
EditorGUILayout.Space();
|
||||
breakOnWake = EditorGUILayout.Toggle("Break on wake", breakOnWake);
|
||||
if (breakOnWake && this.currentNucleus is Neuron currentNeuron) {
|
||||
if (breakOnWake && this.view.currentNucleus is Neuron currentNeuron) {
|
||||
if (currentNeuron.isSleeping == false)
|
||||
Debug.Break();
|
||||
// trace = EditorGUILayout.Toggle("Trace", trace);
|
||||
@ -193,7 +187,7 @@ namespace NanoBrain.Unity {
|
||||
showSynapses = EditorGUILayout.Foldout(showSynapses, "Synapses", true);
|
||||
if (showSynapses) {
|
||||
EditorGUI.indentLevel--;
|
||||
if (this.currentNucleus is Neuron neuron2) {
|
||||
if (this.view.currentNucleus is Neuron neuron2) {
|
||||
Neuron.CombinatorType newCombinator = (Neuron.CombinatorType)EditorGUILayout.EnumPopup("Combinator", neuron2.combinator);
|
||||
anythingChanged |= newCombinator != neuron2.combinator;
|
||||
neuron2.combinator = newCombinator;
|
||||
@ -212,7 +206,7 @@ namespace NanoBrain.Unity {
|
||||
|
||||
Nucleus[] array = null;
|
||||
int elementIx = -1;
|
||||
if (this.currentNucleus is Neuron currentNeuron && currentNeuron.synapses.Count > 0) {
|
||||
if (this.view.currentNucleus is Neuron currentNeuron && currentNeuron.synapses.Count > 0) {
|
||||
Synapse[] synapses = currentNeuron.synapses.ToArray();
|
||||
foreach (Synapse synapse in synapses) {
|
||||
if (synapse.neuron == null)
|
||||
@ -253,7 +247,7 @@ namespace NanoBrain.Unity {
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
GUILayout.Space(indentPx);
|
||||
|
||||
if (synapse.neuron.parent != this.currentNucleus.parent) {
|
||||
if (synapse.neuron.parent != this.view.currentNucleus.parent) {
|
||||
// If it is a different cluster
|
||||
GUIStyle labelStyle = new(GUI.skin.label);
|
||||
float labelWidth = 200;
|
||||
@ -274,8 +268,8 @@ namespace NanoBrain.Unity {
|
||||
|
||||
bool disconnecting = GUILayout.Button("Disconnect", GUILayout.Width(80));
|
||||
if (disconnecting) {
|
||||
synapse.neuron.RemoveReceiver(this.currentNucleus);
|
||||
this.currentCluster.Refresh();
|
||||
synapse.neuron.RemoveReceiver(this.view.currentNucleus);
|
||||
this.view.currentCluster.Refresh();
|
||||
anythingChanged = true;
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
@ -292,8 +286,8 @@ namespace NanoBrain.Unity {
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
anythingChanged |= ConnectNucleus(this.clusterPrefab, this.currentNucleus);
|
||||
anythingChanged |= AddSynapse(this.clusterPrefab, this.currentNucleus);
|
||||
anythingChanged |= ConnectNucleus(this.clusterPrefab, this.view.currentNucleus);
|
||||
anythingChanged |= AddSynapse(this.clusterPrefab, this.view.currentNucleus);
|
||||
}
|
||||
else
|
||||
EditorGUI.indentLevel--;
|
||||
@ -306,8 +300,8 @@ namespace NanoBrain.Unity {
|
||||
showActivation = EditorGUILayout.Foldout(showActivation, "Activation");
|
||||
if (showActivation) {
|
||||
EditorGUI.indentLevel--;
|
||||
if (this.currentNucleus is Neuron neuron) {
|
||||
if (this.currentNucleus is not MemoryCell) {
|
||||
if (this.view.currentNucleus is Neuron neuron) {
|
||||
if (this.view.currentNucleus is not MemoryCell) {
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.LabelField("Activation Curve", GUILayout.MinWidth(60));
|
||||
if (neuron.curveMax > 0)
|
||||
@ -350,58 +344,33 @@ namespace NanoBrain.Unity {
|
||||
}
|
||||
|
||||
protected virtual void AddNeuronInput(Nucleus nucleus) {
|
||||
Neuron newNeuron = new(this.currentCluster, "New Neuron");
|
||||
Neuron newNeuron = new(this.view.currentCluster, "New Neuron");
|
||||
//Neuron newNeuroid = new(this.prefab.cluster, "New neuron");
|
||||
newNeuron.AddReceiver(nucleus);
|
||||
this.currentNucleus = newNeuron;
|
||||
this.view.currentNucleus = newNeuron;
|
||||
}
|
||||
|
||||
protected virtual void AddMemoryCellInput(Nucleus nucleus) {
|
||||
MemoryCell newMemory = new(this.clusterPrefab.cluster, "New memory cell");
|
||||
newMemory.AddReceiver(nucleus);
|
||||
this.currentNucleus = newMemory;
|
||||
this.view.currentNucleus = newMemory;
|
||||
}
|
||||
|
||||
protected virtual void AddClusterInput(Nucleus nucleus) {
|
||||
ClusterPickerWindow.ShowPicker(brain => OnClusterPicked(nucleus, brain), "Select Cluster");
|
||||
}
|
||||
private void OnClusterPicked(Nucleus nucleus, ClusterPrefab selectedPrefab) {
|
||||
Cluster subclusterInstance = new(selectedPrefab, this.currentCluster);
|
||||
Cluster subclusterInstance = new(selectedPrefab, this.view.currentCluster);
|
||||
subclusterInstance.defaultOutput.AddReceiver(nucleus);
|
||||
}
|
||||
|
||||
// private void ReimportCluster(Cluster subCluster) {
|
||||
// if (subCluster.siblingClusters == null || subCluster.siblingClusters.Length <= 0) {
|
||||
// Cluster reimportedCluster = new(subCluster.prefab, this.prefab);
|
||||
// subCluster.MoveReceivers(reimportedCluster);
|
||||
// // subcluster should be garbage now...
|
||||
// this.currentNucleus = reimportedCluster;
|
||||
// }
|
||||
// else {
|
||||
// this.currentNucleus = null;
|
||||
// List<Cluster> newSiblingsList = new();
|
||||
// foreach (Cluster sibling in subCluster.siblingClusters) {
|
||||
// Cluster reimportedCluster = new(sibling.prefab, this.prefab) {
|
||||
// name = sibling.name
|
||||
// };
|
||||
// sibling.MoveReceivers(reimportedCluster);
|
||||
// newSiblingsList.Add(reimportedCluster);
|
||||
// // make the first reimportedCluster the new current nucleus
|
||||
// this.currentNucleus ??= reimportedCluster;
|
||||
// }
|
||||
// Cluster[] newSiblings = newSiblingsList.ToArray();
|
||||
// foreach (Cluster sibling in newSiblings)
|
||||
// sibling.siblingClusters = newSiblings;
|
||||
// }
|
||||
// }
|
||||
|
||||
int selectedConnectNucleus = -1;
|
||||
// Connect to another nucleus
|
||||
protected virtual bool ConnectNucleus(ClusterPrefab cluster, Nucleus nucleusToConnect) {
|
||||
if (cluster == null)
|
||||
return false;
|
||||
|
||||
Neuron currentNeuron = this.currentNucleus as Neuron;
|
||||
Neuron currentNeuron = this.view.currentNucleus as Neuron;
|
||||
IEnumerable<Nucleus> synapseNuclei = currentNeuron.synapses
|
||||
.Where(synapse => synapse.neuron != null)
|
||||
.Select(synapse => synapse.neuron);
|
||||
@ -422,13 +391,9 @@ namespace NanoBrain.Unity {
|
||||
EditorGUILayout.EndHorizontal();
|
||||
if (connecting) {
|
||||
Nucleus nucleus = nuclei.ElementAt(selectedConnectNucleus);
|
||||
// if (nucleus is Cluster subCluster) {
|
||||
// subCluster.AddArrayReceiver(this.currentNucleus);
|
||||
// }
|
||||
// else
|
||||
if (nucleus is Neuron neuron)
|
||||
neuron.AddReceiver(this.currentNucleus);
|
||||
this.currentCluster.Refresh();
|
||||
neuron.AddReceiver(this.view.currentNucleus);
|
||||
this.view.currentCluster.Refresh();
|
||||
}
|
||||
return connecting;
|
||||
}
|
||||
@ -440,20 +405,18 @@ namespace NanoBrain.Unity {
|
||||
if (nucleus is Neuron neuron) {
|
||||
foreach (Nucleus receiver in neuron.receivers) {
|
||||
if (receiver != null) {
|
||||
this.currentNucleus = receiver;
|
||||
this.view.currentNucleus = receiver;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.currentCluster.DeleteNucleus(nucleus);//clusterNuclei.Remove(nucleus);
|
||||
this.view.currentCluster.DeleteNucleus(nucleus);//clusterNuclei.Remove(nucleus);
|
||||
|
||||
// this.prefab.nuclei.Remove(nucleus);
|
||||
// Neuron.Delete(nucleus);
|
||||
this.clusterPrefab.cluster.RefreshOutputs();
|
||||
|
||||
|
||||
this.currentNucleus = this.clusterPrefab.cluster.defaultOutput;
|
||||
this.selectedOutput = this.currentNucleus;
|
||||
this.view.currentNucleus = this.clusterPrefab.cluster.defaultOutput;
|
||||
this.view.selectedOutput = this.view.currentNucleus;
|
||||
}
|
||||
|
||||
Nucleus.Type selectedType = Nucleus.Type.None;
|
||||
@ -467,7 +430,7 @@ namespace NanoBrain.Unity {
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
if (connecting) {
|
||||
AddInput(selectedType, this.currentNucleus);
|
||||
AddInput(selectedType, this.view.currentNucleus);
|
||||
}
|
||||
return connecting;
|
||||
}
|
||||
@ -494,31 +457,28 @@ namespace NanoBrain.Unity {
|
||||
// if (newElementNucleus is not Neuron newElementNeuron)
|
||||
// continue;
|
||||
|
||||
// oldElementNeuron.RemoveReceiver(this.currentNucleus);
|
||||
// newElementNeuron.AddReceiver(this.currentNucleus);
|
||||
// oldElementNeuron.RemoveReceiver(this.clusterView.currentNucleus);
|
||||
// newElementNeuron.AddReceiver(this.clusterView.currentNucleus);
|
||||
// // Now find the synapse which pointed to the old Neuron
|
||||
// // Synapse synapseForUpdate = this.currentNucleus.GetSynapse(oldElementNeuron);
|
||||
// // Synapse synapseForUpdate = this.clusterView.currentNucleus.GetSynapse(oldElementNeuron);
|
||||
// // synapseForUpdate.nucleus = newElementNeuron;
|
||||
// }
|
||||
// }
|
||||
// else {
|
||||
// it is a neuron in a subcluster
|
||||
synapseNeuron.RemoveReceiver(this.currentNucleus);
|
||||
newNucleus.AddReceiver(this.currentNucleus);
|
||||
synapseNeuron.RemoveReceiver(this.view.currentNucleus);
|
||||
newNucleus.AddReceiver(this.view.currentNucleus);
|
||||
// }
|
||||
}
|
||||
else {
|
||||
synapseNeuron.RemoveReceiver(this.currentNucleus);
|
||||
newNucleus.AddReceiver(this.currentNucleus);
|
||||
synapseNeuron.RemoveReceiver(this.view.currentNucleus);
|
||||
newNucleus.AddReceiver(this.view.currentNucleus);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Synapses
|
||||
|
||||
#endregion Inspector
|
||||
/*
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -14,9 +14,6 @@ namespace NanoBrain.Unity {
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty(propertyName));
|
||||
}
|
||||
|
||||
// Cache VisualElement per property path to avoid recreating every frame
|
||||
static Dictionary<string, VisualElement> s_cache = new Dictionary<string, VisualElement>();
|
||||
|
||||
const float padding = 4f;
|
||||
const float elementHeight = 64f; // height reserved for the VisualElement
|
||||
|
||||
@ -33,7 +30,7 @@ namespace NanoBrain.Unity {
|
||||
return height;
|
||||
}
|
||||
|
||||
static Dictionary<string, bool> s_foldouts = new Dictionary<string, bool>();
|
||||
static readonly Dictionary<string, bool> s_foldouts = new();
|
||||
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
|
||||
label = EditorGUI.BeginProperty(position, label, property);
|
||||
@ -44,8 +41,8 @@ namespace NanoBrain.Unity {
|
||||
|
||||
// Draw the object field on the top line
|
||||
Rect fieldRect = new(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight);
|
||||
|
||||
EditorGUI.PropertyField(fieldRect, property, label);
|
||||
|
||||
if (property.objectReferenceValue is ClusterPrefab prefab) {
|
||||
// key per field instance
|
||||
string key = property.propertyPath + "_" + property.serializedObject.targetObject.GetEntityId();
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
@ -7,49 +8,67 @@ namespace NanoBrain.Unity {
|
||||
public class ClusterView {
|
||||
|
||||
private static readonly float discRadius = 20;
|
||||
private float viewWidth;
|
||||
private float contentWidth = 1000;
|
||||
|
||||
static readonly Dictionary<string, ClusterView> viewStates = new();
|
||||
public enum Mode {
|
||||
Focus,
|
||||
Full
|
||||
}
|
||||
public Mode mode = Mode.Focus;
|
||||
|
||||
static readonly Dictionary<string, ClusterView> clusterViews = new();
|
||||
public static ClusterView GetClusterView(SerializedProperty property) {
|
||||
string key = property.propertyPath + "_" + property.serializedObject.targetObject.GetEntityId();
|
||||
if (!viewStates.TryGetValue(key, out ClusterView state))
|
||||
state = new() { key = key };
|
||||
return state;
|
||||
if (!clusterViews.TryGetValue(key, out ClusterView clusterView))
|
||||
clusterView = new() { key = key };
|
||||
return clusterView;
|
||||
}
|
||||
public static ClusterView GetClusterView(SerializedObject serializedObject) {
|
||||
string key = serializedObject.targetObject.GetEntityId().ToString();
|
||||
if (!viewStates.TryGetValue(key, out ClusterView state))
|
||||
state = new() { key = key };
|
||||
return state;
|
||||
if (!clusterViews.TryGetValue(key, out ClusterView clusterView))
|
||||
clusterView = new() { key = key };
|
||||
return clusterView;
|
||||
}
|
||||
|
||||
private void UpdateViewState() {
|
||||
viewStates[this.key] = this;
|
||||
clusterViews[this.key] = this;
|
||||
}
|
||||
|
||||
public static void Render(Rect drawRect, Cluster cluster, SerializedProperty property) {
|
||||
ClusterView clusterView = GetClusterView(property);
|
||||
clusterView.currentCluster ??= cluster;
|
||||
if (clusterView.currentCluster == null) {
|
||||
clusterView.currentCluster = cluster;
|
||||
clusterView.currentNucleus = cluster.defaultOutput;
|
||||
clusterView.selectedOutput = clusterView.currentNucleus;
|
||||
}
|
||||
clusterView.Render(drawRect);
|
||||
}
|
||||
public static void Render(Rect drawRect, Cluster cluster, SerializedObject obj) {
|
||||
ClusterView clusterView = GetClusterView(obj);
|
||||
clusterView.currentCluster ??= cluster;
|
||||
if (clusterView.currentCluster == null) {
|
||||
clusterView.currentCluster = cluster;
|
||||
clusterView.currentNucleus = cluster.defaultOutput;
|
||||
clusterView.selectedOutput = clusterView.currentNucleus;
|
||||
}
|
||||
clusterView.Render(drawRect);
|
||||
}
|
||||
|
||||
public void Render(Rect drawRect) {
|
||||
// background
|
||||
EditorGUI.DrawRect(drawRect, Color.black);
|
||||
Color backgroundColor = new(0.08f, 0.08f, 0.08f, 1f);
|
||||
EditorGUI.DrawRect(drawRect, backgroundColor);
|
||||
|
||||
this.viewWidth = drawRect.width;
|
||||
if (mode == Mode.Focus)
|
||||
this.contentWidth = drawRect.width;
|
||||
|
||||
const float contentWidth = 1000f;
|
||||
Rect contentRect = new(0f, 0f, contentWidth, drawRect.height - 20);
|
||||
|
||||
// Begin horizontal-only scroll view
|
||||
this.scrollPos = GUI.BeginScrollView(drawRect, this.scrollPos, contentRect, false, false);
|
||||
|
||||
// Local content group: draw GUI content using content-local coords (0..contentWidth)
|
||||
GUI.BeginGroup(new Rect(-this.scrollPos.x, 0f, contentWidth, drawRect.height));
|
||||
EditorGUI.DrawRect(new Rect(0f, 0f, contentWidth, drawRect.height), new Color(0.08f, 0.08f, 0.08f, 1f));
|
||||
EditorGUI.DrawRect(new Rect(0f, 0f, contentWidth, drawRect.height), backgroundColor);
|
||||
GUI.EndGroup();
|
||||
GUI.EndScrollView();
|
||||
|
||||
@ -60,12 +79,18 @@ namespace NanoBrain.Unity {
|
||||
GUI.BeginGroup(new Rect(-this.scrollPos.x, 0f, contentWidth, drawRect.height));
|
||||
|
||||
Handles.BeginGUI();
|
||||
if (mode == Mode.Focus)
|
||||
this.DrawFocusGraph();
|
||||
else
|
||||
this.DrawFullGraph();
|
||||
Handles.EndGUI();
|
||||
|
||||
GUI.EndGroup(); // end inner group
|
||||
GUI.EndGroup(); // end clipping group
|
||||
|
||||
Rect popupRect = new(drawRect.x + 4, drawRect.y + 4, 100, EditorGUIUtility.singleLineHeight);
|
||||
mode = (Mode)EditorGUI.EnumPopup(popupRect, mode);
|
||||
|
||||
UpdateViewState();
|
||||
}
|
||||
|
||||
@ -77,6 +102,8 @@ namespace NanoBrain.Unity {
|
||||
public Nucleus selectedSynapseNeuron = null;
|
||||
public Nucleus selectedOutput;
|
||||
|
||||
#region Focus Graph
|
||||
|
||||
protected void DrawFocusGraph() {
|
||||
float size = 20;
|
||||
Vector3 position = new(150, 210, 0);
|
||||
@ -152,7 +179,7 @@ namespace NanoBrain.Unity {
|
||||
maxValue = neuron.outputMagnitude;
|
||||
else if (this.currentNucleus is Cluster cluster)
|
||||
maxValue = cluster.defaultOutput.outputMagnitude;
|
||||
Debug.Log($"Neuron {maxValue} {currentCluster.defaultOutput.outputMagnitude}");
|
||||
// Debug.Log($"Neuron {maxValue} {currentCluster.defaultOutput.outputMagnitude}");
|
||||
DrawNucleus(this.currentNucleus, position, maxValue);
|
||||
}
|
||||
}
|
||||
@ -162,6 +189,100 @@ namespace NanoBrain.Unity {
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Focus Graph
|
||||
|
||||
#region Full Graph
|
||||
|
||||
protected void DrawFullGraph() {
|
||||
Dag dag = GenerateGraph(this.selectedOutput);
|
||||
Dag.ComputeLayout(dag);
|
||||
// Draw edges
|
||||
foreach (Dag.Edge e in dag.edges) {
|
||||
Dag.Node from = dag.nodes.FirstOrDefault(x => x.id == e.fromId);
|
||||
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);
|
||||
}
|
||||
|
||||
// Draw nodes
|
||||
foreach (Dag.Node n in dag.nodes)
|
||||
DrawNucleus(n.nucleus, n.position, 1);
|
||||
|
||||
// Determine graph width
|
||||
float width = 0;
|
||||
float currentNucleusPosition = 0;
|
||||
foreach (Dag.Node node in dag.nodes) {
|
||||
if (node.position.x > width)
|
||||
width = node.position.x;
|
||||
if (node.nucleus == currentNucleus)
|
||||
currentNucleusPosition = node.position.x;
|
||||
}
|
||||
|
||||
// Resize the graph container to the full graph width
|
||||
float margin = 50f;
|
||||
this.contentWidth = Mathf.Max(width + 2 * margin, this.viewWidth);
|
||||
|
||||
// // Scroll to the current nucleus
|
||||
// float viewportWidth = this.viewWidth;
|
||||
// // center currentNucleus in viewport
|
||||
// float desiredScrollX = currentNucleusPosition - viewportWidth * 0.5f;
|
||||
// // clamp between 0 and maximum scrollable range
|
||||
// float maxScrollX = Mathf.Max(0f, this.contentWidth - viewportWidth);
|
||||
// desiredScrollX = Mathf.Clamp(desiredScrollX, 0f, maxScrollX);
|
||||
|
||||
// Vector2 current = this.scrollPos; //scrollView.scrollOffset;
|
||||
// this.scrollPos = new Vector2(desiredScrollX, current.y);
|
||||
}
|
||||
|
||||
public Dag GenerateGraph(Nucleus rootNucleus) {
|
||||
Dag dag = new();
|
||||
if (rootNucleus == null)
|
||||
return dag;
|
||||
|
||||
int ix = 0;
|
||||
Dag.Node receiver = new() {
|
||||
id = ix,
|
||||
//title = nucleus.name,
|
||||
nucleus = rootNucleus
|
||||
};
|
||||
dag.nodes.Add(receiver);
|
||||
ix++;
|
||||
DescendGraph(receiver, ref ix, dag);
|
||||
return dag;
|
||||
}
|
||||
|
||||
private void DescendGraph(Dag.Node receiver, ref int ix, Dag dag) {
|
||||
Neuron receiverNeuron = receiver.nucleus as Neuron;
|
||||
foreach (Synapse synapse in receiverNeuron.synapses) {
|
||||
Nucleus nucleus = synapse.neuron;
|
||||
if (nucleus.parent != null && nucleus.parent != currentNucleus.parent) {
|
||||
nucleus = nucleus.parent;
|
||||
}
|
||||
string nucleusName = nucleus.name;
|
||||
Dag.Node synapseNode = dag.FindNode(nucleusName);
|
||||
if (synapseNode == null) {
|
||||
synapseNode = new() {
|
||||
id = ix,
|
||||
nucleus = nucleus
|
||||
};
|
||||
dag.nodes.Add(synapseNode);
|
||||
}
|
||||
Dag.Edge edge = new() {
|
||||
fromId = synapseNode.id,
|
||||
toId = receiver.id
|
||||
};
|
||||
dag.edges.Add(edge);
|
||||
ix++;
|
||||
DescendGraph(synapseNode, ref ix, dag);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Full Graph
|
||||
|
||||
protected void DrawReceivers(Nucleus nucleus, Vector3 parentPos) {
|
||||
List<Nucleus> receivers;
|
||||
if (nucleus is Neuron neuron)
|
||||
@ -283,8 +404,8 @@ namespace NanoBrain.Unity {
|
||||
// Handles.DrawLine(parentPos, pos);
|
||||
Color color = Color.black;
|
||||
if (Application.isPlaying) {
|
||||
if (maxValue == 0 || !float.IsFinite(maxValue))
|
||||
maxValue = 1;
|
||||
//if (maxValue == 0 || !float.IsFinite(maxValue))
|
||||
maxValue = 1 * synapse.weight;
|
||||
float brightness = synapse.neuron.outputMagnitude * synapse.weight / maxValue;
|
||||
color = new Color(brightness, brightness, brightness, 1f);
|
||||
}
|
||||
@ -394,6 +515,7 @@ namespace NanoBrain.Unity {
|
||||
|
||||
|
||||
protected void DrawNucleus(Nucleus nucleus, Vector3 position, float maxValue) {
|
||||
maxValue = 1;
|
||||
Color color;
|
||||
if (Application.isPlaying) {
|
||||
float brightness = 0;
|
||||
@ -622,7 +744,7 @@ namespace NanoBrain.Unity {
|
||||
|
||||
// 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);
|
||||
Rect tooltipRect = new(mousePosition.x + 10, mousePosition.y + 10, tooltipSize.x, tooltipSize.y);
|
||||
|
||||
GUI.Box(tooltipRect, tooltip);
|
||||
}
|
||||
@ -688,4 +810,146 @@ namespace NanoBrain.Unity {
|
||||
#endregion Interaction
|
||||
}
|
||||
|
||||
public class Dag {
|
||||
|
||||
public class Node {
|
||||
public int id;
|
||||
public Vector2 position;
|
||||
public float radius = 20f; // circle radius
|
||||
public Nucleus nucleus;
|
||||
}
|
||||
|
||||
public class Edge {
|
||||
public int fromId;
|
||||
public int toId;
|
||||
}
|
||||
|
||||
public List<Node> nodes = new();
|
||||
public List<Edge> edges = new();
|
||||
|
||||
public Node FindNode(string name, bool justBaseName = true) {
|
||||
if (justBaseName) {
|
||||
int colonPos = name.IndexOf(":");
|
||||
if (colonPos > 0)
|
||||
name = name[..colonPos];
|
||||
}
|
||||
foreach (Node node in this.nodes) {
|
||||
string nodeName = node.nucleus.name;
|
||||
if (justBaseName) {
|
||||
int colonPos = nodeName.IndexOf(":");
|
||||
if (colonPos > 0)
|
||||
nodeName = nodeName[..colonPos];
|
||||
}
|
||||
if (nodeName == name)
|
||||
return node;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Node GetNodeById(Dag dag, int id) => dag.nodes.FirstOrDefault(x => x.id == id);
|
||||
|
||||
public static void ComputeLayout(Dag dag) {
|
||||
Dictionary<int, List<int>> adjacency = dag.nodes.ToDictionary(n => n.id, n => new List<int>());
|
||||
Dictionary<int, int> outdegree = dag.nodes.ToDictionary(node => node.id, n => 0);
|
||||
foreach (Edge edge in dag.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)
|
||||
// build parent list (reverse adjacency) and parentIndegree = number of children each parent has
|
||||
Dictionary<int, List<int>> parents = dag.nodes.ToDictionary(n => n.id, _ => new List<int>());
|
||||
Dictionary<int, int> childCount = dag.nodes.ToDictionary(n => n.id, _ => 0);
|
||||
|
||||
foreach (Edge edge in dag.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
|
||||
}
|
||||
|
||||
Dictionary<int, int> column = new();
|
||||
Queue<int> queue = new(outdegree.Where(keyValue => keyValue.Value == 0).Select(keyValue => keyValue.Key));
|
||||
foreach (int id in queue)
|
||||
column[id] = 0;
|
||||
|
||||
// process parents (reverse traversal)
|
||||
while (queue.Count > 0) {
|
||||
int nodeId = queue.Dequeue();
|
||||
int col = column[nodeId];
|
||||
foreach (int parentIx in parents[nodeId]) {
|
||||
if (!column.ContainsKey(parentIx) || column[parentIx] < col + 1)
|
||||
column[parentIx] = col + 1;
|
||||
childCount[parentIx]--; // decrement remaining unprocessed children
|
||||
if (childCount[parentIx] == 0)
|
||||
queue.Enqueue(parentIx);
|
||||
}
|
||||
}
|
||||
|
||||
// Any unreachable nodes -> assign next layers
|
||||
int maxColumn = column.Count > 0 ? column.Values.Max() : 0;
|
||||
foreach (Node node in dag.nodes) {
|
||||
if (!column.ContainsKey(node.id)) {
|
||||
maxColumn++;
|
||||
column[node.id] = maxColumn;
|
||||
}
|
||||
}
|
||||
|
||||
// Group nodes by column (left to right)
|
||||
List<List<int>> columns =
|
||||
column.
|
||||
GroupBy(kv => kv.Value).
|
||||
OrderBy(g => g.Key).
|
||||
Select(g => g.Select(x => x.Key).ToList()).
|
||||
ToList();
|
||||
|
||||
// Same code without using Linq
|
||||
// Build layers dictionary: layerIndex -> List<int> nodeIds
|
||||
// Dictionary<int, List<int>> layersDict = new();
|
||||
// foreach (KeyValuePair<int, int> kv in layer) {
|
||||
// int nodeId = kv.Key;
|
||||
// int layerIndex = kv.Value;
|
||||
// if (!layersDict.TryGetValue(layerIndex, out List<int> list)) {
|
||||
// list = new List<int>();
|
||||
// layersDict[layerIndex] = list;
|
||||
// }
|
||||
// list.Add(nodeId);
|
||||
// }
|
||||
|
||||
// // Determine sorted layer indices
|
||||
// List<int> layerIndices = new(layersDict.Keys);
|
||||
// layerIndices.Sort(); // ascending order
|
||||
|
||||
// // Build final List<List<int>> in sorted order
|
||||
// List<List<int>> layers = new();
|
||||
// foreach (int idx in layerIndices) {
|
||||
// layers.Add(layersDict[idx]);
|
||||
// }
|
||||
|
||||
float hSpacing = 100f;
|
||||
float totalHeight = 400f;
|
||||
|
||||
// Place nodes: x increases with column index, y spaced within column
|
||||
for (int columnIx = 0; columnIx < columns.Count; columnIx++) {
|
||||
List<int> nodeList = columns[columnIx];
|
||||
float spacing = totalHeight / nodeList.Count;
|
||||
float margin = 10 + spacing / 2;
|
||||
for (int i = 0; i < nodeList.Count; i++) {
|
||||
int index = nodeList[i];
|
||||
Node node = GetNodeById(dag, index);
|
||||
if (node == null)
|
||||
continue;
|
||||
float x = hSpacing + 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -897,147 +897,6 @@ namespace NanoBrain.Unity {
|
||||
public List<Nucleus> neuroids = new();
|
||||
}
|
||||
|
||||
public class Dag {
|
||||
|
||||
public class Node {
|
||||
public int id;
|
||||
public Vector2 position;
|
||||
public float radius = 20f; // circle radius
|
||||
public Nucleus nucleus;
|
||||
}
|
||||
|
||||
public class Edge {
|
||||
public int fromId;
|
||||
public int toId;
|
||||
}
|
||||
|
||||
public List<Node> nodes = new();
|
||||
public List<Edge> edges = new();
|
||||
|
||||
public Node FindNode(string name, bool justBaseName = true) {
|
||||
if (justBaseName) {
|
||||
int colonPos = name.IndexOf(":");
|
||||
if (colonPos > 0)
|
||||
name = name[..colonPos];
|
||||
}
|
||||
foreach (Node node in this.nodes) {
|
||||
string nodeName = node.nucleus.name;
|
||||
if (justBaseName) {
|
||||
int colonPos = nodeName.IndexOf(":");
|
||||
if (colonPos > 0)
|
||||
nodeName = nodeName[..colonPos];
|
||||
}
|
||||
if (nodeName == name)
|
||||
return node;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Node GetNodeById(Dag dag, int id) => dag.nodes.FirstOrDefault(x => x.id == id);
|
||||
|
||||
public static void ComputeLayout(Dag dag) {
|
||||
Dictionary<int, List<int>> adjacency = dag.nodes.ToDictionary(n => n.id, n => new List<int>());
|
||||
Dictionary<int, int> outdegree = dag.nodes.ToDictionary(node => node.id, n => 0);
|
||||
foreach (Edge edge in dag.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)
|
||||
// build parent list (reverse adjacency) and parentIndegree = number of children each parent has
|
||||
Dictionary<int, List<int>> parents = dag.nodes.ToDictionary(n => n.id, _ => new List<int>());
|
||||
Dictionary<int, int> childCount = dag.nodes.ToDictionary(n => n.id, _ => 0);
|
||||
|
||||
foreach (Edge edge in dag.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
|
||||
}
|
||||
|
||||
Dictionary<int, int> layer = new();
|
||||
Queue<int> 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 (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 (Node node in dag.nodes) {
|
||||
if (!layer.ContainsKey(node.id)) {
|
||||
maxLayer++;
|
||||
layer[node.id] = maxLayer;
|
||||
}
|
||||
}
|
||||
|
||||
// Group nodes by layer (left to right)
|
||||
List<List<int>> layers =
|
||||
layer.
|
||||
GroupBy(kv => kv.Value).
|
||||
OrderBy(g => g.Key).
|
||||
Select(g => g.Select(x => x.Key).ToList()).
|
||||
ToList();
|
||||
|
||||
// Same code without using Linq
|
||||
// Build layers dictionary: layerIndex -> List<int> nodeIds
|
||||
// Dictionary<int, List<int>> layersDict = new();
|
||||
// foreach (KeyValuePair<int, int> kv in layer) {
|
||||
// int nodeId = kv.Key;
|
||||
// int layerIndex = kv.Value;
|
||||
// if (!layersDict.TryGetValue(layerIndex, out List<int> list)) {
|
||||
// list = new List<int>();
|
||||
// layersDict[layerIndex] = list;
|
||||
// }
|
||||
// list.Add(nodeId);
|
||||
// }
|
||||
|
||||
// // Determine sorted layer indices
|
||||
// List<int> layerIndices = new(layersDict.Keys);
|
||||
// layerIndices.Sort(); // ascending order
|
||||
|
||||
// // Build final List<List<int>> in sorted order
|
||||
// List<List<int>> layers = new();
|
||||
// foreach (int idx in layerIndices) {
|
||||
// layers.Add(layersDict[idx]);
|
||||
// }
|
||||
|
||||
float hSpacing = 100f;
|
||||
float totalHeight = 400f;
|
||||
|
||||
// Place nodes: x increases with layer index, y spaced within layer
|
||||
for (int layerIx = 0; layerIx < layers.Count; layerIx++) {
|
||||
List<int> nodeList = layers[layerIx];
|
||||
float spacing = totalHeight / nodeList.Count;
|
||||
float margin = 10 + spacing / 2;
|
||||
for (int i = 0; i < nodeList.Count; i++) {
|
||||
int index = nodeList[i];
|
||||
Node node = GetNodeById(dag, index);
|
||||
if (node == null)
|
||||
continue;
|
||||
float x = hSpacing + layerIx * 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user