clusterview improvements
This commit is contained in:
parent
2a9693acca
commit
2a88689179
@ -8,22 +8,17 @@ namespace NanoBrain.Unity {
|
|||||||
|
|
||||||
[CustomEditor(typeof(ClusterPrefab))]
|
[CustomEditor(typeof(ClusterPrefab))]
|
||||||
public class ClusterEditor : Editor {
|
public class ClusterEditor : Editor {
|
||||||
const float drawAreaWidth = 300f; // adjust as needed
|
const float drawAreaWidth = 320f;
|
||||||
const float padding = 6f;
|
const float padding = 6f;
|
||||||
ClusterPrefab clusterPrefab;
|
ClusterPrefab clusterPrefab;
|
||||||
Nucleus currentNucleus {
|
ClusterView view;
|
||||||
get { return clusterView.currentNucleus; }
|
|
||||||
set { clusterView.currentNucleus = value; }
|
|
||||||
}
|
|
||||||
Cluster currentCluster => clusterView.currentCluster;
|
|
||||||
protected Nucleus selectedOutput;
|
|
||||||
ClusterView clusterView;
|
|
||||||
|
|
||||||
void OnEnable() {
|
void OnEnable() {
|
||||||
clusterPrefab = (ClusterPrefab)target;
|
clusterPrefab = (ClusterPrefab)target;
|
||||||
clusterView = ClusterView.GetClusterView(serializedObject);
|
view = ClusterView.GetClusterView(serializedObject);
|
||||||
clusterView.currentCluster ??= clusterPrefab.cluster;
|
view.currentCluster ??= clusterPrefab.cluster;
|
||||||
clusterView.currentNucleus = clusterPrefab.cluster.defaultOutput;
|
view.currentNucleus = clusterPrefab.cluster.defaultOutput;
|
||||||
|
view.selectedOutput = view.currentNucleus;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnInspectorGUI() {
|
public override void OnInspectorGUI() {
|
||||||
@ -38,7 +33,7 @@ namespace NanoBrain.Unity {
|
|||||||
Rect innerRect = new(drawRect.x + padding, drawRect.y + padding,
|
Rect innerRect = new(drawRect.x + padding, drawRect.y + padding,
|
||||||
drawRect.width - padding * 2, drawRect.height - padding * 2);
|
drawRect.width - padding * 2, drawRect.height - padding * 2);
|
||||||
|
|
||||||
clusterView.Render(innerRect);
|
view.Render(innerRect);
|
||||||
|
|
||||||
// Right: info panel (takes remaining width)
|
// Right: info panel (takes remaining width)
|
||||||
EditorGUILayout.BeginVertical(GUILayout.ExpandWidth(true));
|
EditorGUILayout.BeginVertical(GUILayout.ExpandWidth(true));
|
||||||
@ -54,7 +49,6 @@ namespace NanoBrain.Unity {
|
|||||||
|
|
||||||
#region Inspector
|
#region Inspector
|
||||||
|
|
||||||
//private VisualElement inspectorIMGUIContainer;
|
|
||||||
private bool showSynapses = true;
|
private bool showSynapses = true;
|
||||||
private bool showActivation = true;
|
private bool showActivation = true;
|
||||||
protected bool breakOnWake = false;
|
protected bool breakOnWake = false;
|
||||||
@ -72,7 +66,7 @@ namespace NanoBrain.Unity {
|
|||||||
fontStyle = FontStyle.Bold
|
fontStyle = FontStyle.Bold
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.currentNucleus == null) {
|
if (this.view.currentNucleus == null) {
|
||||||
OutputsInspector(ref anythingChanged);
|
OutputsInspector(ref anythingChanged);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -82,19 +76,19 @@ namespace NanoBrain.Unity {
|
|||||||
margin = new RectOffset(10, 0, 4, 4)
|
margin = new RectOffset(10, 0, 4, 4)
|
||||||
};
|
};
|
||||||
// Nucleus type
|
// Nucleus type
|
||||||
string nucleusType = this.currentNucleus.GetType().Name;
|
string nucleusType = this.view.currentNucleus.GetType().Name;
|
||||||
GUILayout.Label(nucleusType, headerStyle);
|
GUILayout.Label(nucleusType, headerStyle);
|
||||||
|
|
||||||
// Nucleus name
|
// Nucleus name
|
||||||
string newName = EditorGUILayout.TextField(this.currentNucleus.name, boldTextFieldStyle);
|
string newName = EditorGUILayout.TextField(this.view.currentNucleus.name, boldTextFieldStyle);
|
||||||
if (newName != this.currentNucleus.name) {
|
if (newName != this.view.currentNucleus.name) {
|
||||||
this.currentNucleus.name = newName;
|
this.view.currentNucleus.name = newName;
|
||||||
anythingChanged = true;
|
anythingChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Current output value
|
// Current output value
|
||||||
if (Application.isPlaying) {
|
if (Application.isPlaying) {
|
||||||
if (currentNucleus is Neuron currentNeuron1) {
|
if (this.view.currentNucleus is Neuron currentNeuron1) {
|
||||||
GUIContent nameLabel = new("Output", currentNeuron1.outputValue.ToString());
|
GUIContent nameLabel = new("Output", currentNeuron1.outputValue.ToString());
|
||||||
EditorGUILayout.FloatField(nameLabel, currentNeuron1.outputMagnitude);
|
EditorGUILayout.FloatField(nameLabel, currentNeuron1.outputMagnitude);
|
||||||
}
|
}
|
||||||
@ -105,17 +99,17 @@ namespace NanoBrain.Unity {
|
|||||||
EditorGUILayout.LabelField(" ");
|
EditorGUILayout.LabelField(" ");
|
||||||
|
|
||||||
// Memory cell
|
// Memory cell
|
||||||
if (this.currentNucleus is MemoryCell memory)
|
if (this.view.currentNucleus is MemoryCell memory)
|
||||||
MemoryCellInspector(memory, ref anythingChanged);
|
MemoryCellInspector(memory, ref anythingChanged);
|
||||||
// Cluster
|
// Cluster
|
||||||
else if (this.currentNucleus is Cluster cluster)
|
else if (this.view.currentNucleus is Cluster cluster)
|
||||||
ClusterInspector(cluster, ref anythingChanged);
|
ClusterInspector(cluster, ref anythingChanged);
|
||||||
// Other
|
// Other
|
||||||
else
|
else
|
||||||
NucleusInspector(this.currentNucleus, ref anythingChanged);
|
NucleusInspector(this.view.currentNucleus, ref anythingChanged);
|
||||||
|
|
||||||
if (GUILayout.Button("Delete"))
|
if (GUILayout.Button("Delete"))
|
||||||
DeleteNucleus(this.currentNucleus);
|
DeleteNucleus(this.view.currentNucleus);
|
||||||
}
|
}
|
||||||
|
|
||||||
serializedObject.ApplyModifiedProperties();
|
serializedObject.ApplyModifiedProperties();
|
||||||
@ -134,10 +128,10 @@ namespace NanoBrain.Unity {
|
|||||||
|
|
||||||
bool connecting = GUILayout.Button("Add Output Neuron");
|
bool connecting = GUILayout.Button("Add Output Neuron");
|
||||||
if (connecting) {
|
if (connecting) {
|
||||||
Nucleus newOutput = new Neuron(this.currentCluster, "New Output");
|
Nucleus newOutput = new Neuron(this.view.currentCluster, "New Output");
|
||||||
this.currentCluster.Refresh();
|
this.view.currentCluster.Refresh();
|
||||||
this.currentNucleus = newOutput;
|
this.view.currentNucleus = newOutput;
|
||||||
this.selectedOutput = this.currentNucleus;
|
view.selectedOutput = this.view.currentNucleus;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,7 +174,7 @@ namespace NanoBrain.Unity {
|
|||||||
|
|
||||||
EditorGUILayout.Space();
|
EditorGUILayout.Space();
|
||||||
breakOnWake = EditorGUILayout.Toggle("Break on wake", breakOnWake);
|
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)
|
if (currentNeuron.isSleeping == false)
|
||||||
Debug.Break();
|
Debug.Break();
|
||||||
// trace = EditorGUILayout.Toggle("Trace", trace);
|
// trace = EditorGUILayout.Toggle("Trace", trace);
|
||||||
@ -193,7 +187,7 @@ namespace NanoBrain.Unity {
|
|||||||
showSynapses = EditorGUILayout.Foldout(showSynapses, "Synapses", true);
|
showSynapses = EditorGUILayout.Foldout(showSynapses, "Synapses", true);
|
||||||
if (showSynapses) {
|
if (showSynapses) {
|
||||||
EditorGUI.indentLevel--;
|
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);
|
Neuron.CombinatorType newCombinator = (Neuron.CombinatorType)EditorGUILayout.EnumPopup("Combinator", neuron2.combinator);
|
||||||
anythingChanged |= newCombinator != neuron2.combinator;
|
anythingChanged |= newCombinator != neuron2.combinator;
|
||||||
neuron2.combinator = newCombinator;
|
neuron2.combinator = newCombinator;
|
||||||
@ -212,7 +206,7 @@ namespace NanoBrain.Unity {
|
|||||||
|
|
||||||
Nucleus[] array = null;
|
Nucleus[] array = null;
|
||||||
int elementIx = -1;
|
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();
|
Synapse[] synapses = currentNeuron.synapses.ToArray();
|
||||||
foreach (Synapse synapse in synapses) {
|
foreach (Synapse synapse in synapses) {
|
||||||
if (synapse.neuron == null)
|
if (synapse.neuron == null)
|
||||||
@ -253,7 +247,7 @@ namespace NanoBrain.Unity {
|
|||||||
EditorGUILayout.BeginHorizontal();
|
EditorGUILayout.BeginHorizontal();
|
||||||
GUILayout.Space(indentPx);
|
GUILayout.Space(indentPx);
|
||||||
|
|
||||||
if (synapse.neuron.parent != this.currentNucleus.parent) {
|
if (synapse.neuron.parent != this.view.currentNucleus.parent) {
|
||||||
// If it is a different cluster
|
// If it is a different cluster
|
||||||
GUIStyle labelStyle = new(GUI.skin.label);
|
GUIStyle labelStyle = new(GUI.skin.label);
|
||||||
float labelWidth = 200;
|
float labelWidth = 200;
|
||||||
@ -274,8 +268,8 @@ namespace NanoBrain.Unity {
|
|||||||
|
|
||||||
bool disconnecting = GUILayout.Button("Disconnect", GUILayout.Width(80));
|
bool disconnecting = GUILayout.Button("Disconnect", GUILayout.Width(80));
|
||||||
if (disconnecting) {
|
if (disconnecting) {
|
||||||
synapse.neuron.RemoveReceiver(this.currentNucleus);
|
synapse.neuron.RemoveReceiver(this.view.currentNucleus);
|
||||||
this.currentCluster.Refresh();
|
this.view.currentCluster.Refresh();
|
||||||
anythingChanged = true;
|
anythingChanged = true;
|
||||||
}
|
}
|
||||||
EditorGUILayout.EndHorizontal();
|
EditorGUILayout.EndHorizontal();
|
||||||
@ -292,8 +286,8 @@ namespace NanoBrain.Unity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
EditorGUILayout.Space();
|
EditorGUILayout.Space();
|
||||||
anythingChanged |= ConnectNucleus(this.clusterPrefab, this.currentNucleus);
|
anythingChanged |= ConnectNucleus(this.clusterPrefab, this.view.currentNucleus);
|
||||||
anythingChanged |= AddSynapse(this.clusterPrefab, this.currentNucleus);
|
anythingChanged |= AddSynapse(this.clusterPrefab, this.view.currentNucleus);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
EditorGUI.indentLevel--;
|
EditorGUI.indentLevel--;
|
||||||
@ -306,8 +300,8 @@ namespace NanoBrain.Unity {
|
|||||||
showActivation = EditorGUILayout.Foldout(showActivation, "Activation");
|
showActivation = EditorGUILayout.Foldout(showActivation, "Activation");
|
||||||
if (showActivation) {
|
if (showActivation) {
|
||||||
EditorGUI.indentLevel--;
|
EditorGUI.indentLevel--;
|
||||||
if (this.currentNucleus is Neuron neuron) {
|
if (this.view.currentNucleus is Neuron neuron) {
|
||||||
if (this.currentNucleus is not MemoryCell) {
|
if (this.view.currentNucleus is not MemoryCell) {
|
||||||
EditorGUILayout.BeginHorizontal();
|
EditorGUILayout.BeginHorizontal();
|
||||||
EditorGUILayout.LabelField("Activation Curve", GUILayout.MinWidth(60));
|
EditorGUILayout.LabelField("Activation Curve", GUILayout.MinWidth(60));
|
||||||
if (neuron.curveMax > 0)
|
if (neuron.curveMax > 0)
|
||||||
@ -350,58 +344,33 @@ namespace NanoBrain.Unity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void AddNeuronInput(Nucleus nucleus) {
|
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");
|
//Neuron newNeuroid = new(this.prefab.cluster, "New neuron");
|
||||||
newNeuron.AddReceiver(nucleus);
|
newNeuron.AddReceiver(nucleus);
|
||||||
this.currentNucleus = newNeuron;
|
this.view.currentNucleus = newNeuron;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void AddMemoryCellInput(Nucleus nucleus) {
|
protected virtual void AddMemoryCellInput(Nucleus nucleus) {
|
||||||
MemoryCell newMemory = new(this.clusterPrefab.cluster, "New memory cell");
|
MemoryCell newMemory = new(this.clusterPrefab.cluster, "New memory cell");
|
||||||
newMemory.AddReceiver(nucleus);
|
newMemory.AddReceiver(nucleus);
|
||||||
this.currentNucleus = newMemory;
|
this.view.currentNucleus = newMemory;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void AddClusterInput(Nucleus nucleus) {
|
protected virtual void AddClusterInput(Nucleus nucleus) {
|
||||||
ClusterPickerWindow.ShowPicker(brain => OnClusterPicked(nucleus, brain), "Select Cluster");
|
ClusterPickerWindow.ShowPicker(brain => OnClusterPicked(nucleus, brain), "Select Cluster");
|
||||||
}
|
}
|
||||||
private void OnClusterPicked(Nucleus nucleus, ClusterPrefab selectedPrefab) {
|
private void OnClusterPicked(Nucleus nucleus, ClusterPrefab selectedPrefab) {
|
||||||
Cluster subclusterInstance = new(selectedPrefab, this.currentCluster);
|
Cluster subclusterInstance = new(selectedPrefab, this.view.currentCluster);
|
||||||
subclusterInstance.defaultOutput.AddReceiver(nucleus);
|
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;
|
int selectedConnectNucleus = -1;
|
||||||
// Connect to another nucleus
|
// Connect to another nucleus
|
||||||
protected virtual bool ConnectNucleus(ClusterPrefab cluster, Nucleus nucleusToConnect) {
|
protected virtual bool ConnectNucleus(ClusterPrefab cluster, Nucleus nucleusToConnect) {
|
||||||
if (cluster == null)
|
if (cluster == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
Neuron currentNeuron = this.currentNucleus as Neuron;
|
Neuron currentNeuron = this.view.currentNucleus as Neuron;
|
||||||
IEnumerable<Nucleus> synapseNuclei = currentNeuron.synapses
|
IEnumerable<Nucleus> synapseNuclei = currentNeuron.synapses
|
||||||
.Where(synapse => synapse.neuron != null)
|
.Where(synapse => synapse.neuron != null)
|
||||||
.Select(synapse => synapse.neuron);
|
.Select(synapse => synapse.neuron);
|
||||||
@ -422,13 +391,9 @@ namespace NanoBrain.Unity {
|
|||||||
EditorGUILayout.EndHorizontal();
|
EditorGUILayout.EndHorizontal();
|
||||||
if (connecting) {
|
if (connecting) {
|
||||||
Nucleus nucleus = nuclei.ElementAt(selectedConnectNucleus);
|
Nucleus nucleus = nuclei.ElementAt(selectedConnectNucleus);
|
||||||
// if (nucleus is Cluster subCluster) {
|
|
||||||
// subCluster.AddArrayReceiver(this.currentNucleus);
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
if (nucleus is Neuron neuron)
|
if (nucleus is Neuron neuron)
|
||||||
neuron.AddReceiver(this.currentNucleus);
|
neuron.AddReceiver(this.view.currentNucleus);
|
||||||
this.currentCluster.Refresh();
|
this.view.currentCluster.Refresh();
|
||||||
}
|
}
|
||||||
return connecting;
|
return connecting;
|
||||||
}
|
}
|
||||||
@ -440,20 +405,18 @@ namespace NanoBrain.Unity {
|
|||||||
if (nucleus is Neuron neuron) {
|
if (nucleus is Neuron neuron) {
|
||||||
foreach (Nucleus receiver in neuron.receivers) {
|
foreach (Nucleus receiver in neuron.receivers) {
|
||||||
if (receiver != null) {
|
if (receiver != null) {
|
||||||
this.currentNucleus = receiver;
|
this.view.currentNucleus = receiver;
|
||||||
break;
|
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.clusterPrefab.cluster.RefreshOutputs();
|
||||||
|
|
||||||
|
|
||||||
this.currentNucleus = this.clusterPrefab.cluster.defaultOutput;
|
this.view.currentNucleus = this.clusterPrefab.cluster.defaultOutput;
|
||||||
this.selectedOutput = this.currentNucleus;
|
this.view.selectedOutput = this.view.currentNucleus;
|
||||||
}
|
}
|
||||||
|
|
||||||
Nucleus.Type selectedType = Nucleus.Type.None;
|
Nucleus.Type selectedType = Nucleus.Type.None;
|
||||||
@ -467,7 +430,7 @@ namespace NanoBrain.Unity {
|
|||||||
EditorGUILayout.EndHorizontal();
|
EditorGUILayout.EndHorizontal();
|
||||||
|
|
||||||
if (connecting) {
|
if (connecting) {
|
||||||
AddInput(selectedType, this.currentNucleus);
|
AddInput(selectedType, this.view.currentNucleus);
|
||||||
}
|
}
|
||||||
return connecting;
|
return connecting;
|
||||||
}
|
}
|
||||||
@ -494,31 +457,28 @@ namespace NanoBrain.Unity {
|
|||||||
// if (newElementNucleus is not Neuron newElementNeuron)
|
// if (newElementNucleus is not Neuron newElementNeuron)
|
||||||
// continue;
|
// continue;
|
||||||
|
|
||||||
// oldElementNeuron.RemoveReceiver(this.currentNucleus);
|
// oldElementNeuron.RemoveReceiver(this.clusterView.currentNucleus);
|
||||||
// newElementNeuron.AddReceiver(this.currentNucleus);
|
// newElementNeuron.AddReceiver(this.clusterView.currentNucleus);
|
||||||
// // Now find the synapse which pointed to the old Neuron
|
// // 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;
|
// // synapseForUpdate.nucleus = newElementNeuron;
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// else {
|
// else {
|
||||||
// it is a neuron in a subcluster
|
// it is a neuron in a subcluster
|
||||||
synapseNeuron.RemoveReceiver(this.currentNucleus);
|
synapseNeuron.RemoveReceiver(this.view.currentNucleus);
|
||||||
newNucleus.AddReceiver(this.currentNucleus);
|
newNucleus.AddReceiver(this.view.currentNucleus);
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
synapseNeuron.RemoveReceiver(this.currentNucleus);
|
synapseNeuron.RemoveReceiver(this.view.currentNucleus);
|
||||||
newNucleus.AddReceiver(this.currentNucleus);
|
newNucleus.AddReceiver(this.view.currentNucleus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion Synapses
|
#endregion Synapses
|
||||||
|
|
||||||
#endregion Inspector
|
#endregion Inspector
|
||||||
/*
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -14,9 +14,6 @@ namespace NanoBrain.Unity {
|
|||||||
EditorGUILayout.PropertyField(serializedObject.FindProperty(propertyName));
|
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 padding = 4f;
|
||||||
const float elementHeight = 64f; // height reserved for the VisualElement
|
const float elementHeight = 64f; // height reserved for the VisualElement
|
||||||
|
|
||||||
@ -33,7 +30,7 @@ namespace NanoBrain.Unity {
|
|||||||
return height;
|
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) {
|
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
|
||||||
label = EditorGUI.BeginProperty(position, label, property);
|
label = EditorGUI.BeginProperty(position, label, property);
|
||||||
@ -44,8 +41,8 @@ namespace NanoBrain.Unity {
|
|||||||
|
|
||||||
// Draw the object field on the top line
|
// Draw the object field on the top line
|
||||||
Rect fieldRect = new(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight);
|
Rect fieldRect = new(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight);
|
||||||
|
|
||||||
EditorGUI.PropertyField(fieldRect, property, label);
|
EditorGUI.PropertyField(fieldRect, property, label);
|
||||||
|
|
||||||
if (property.objectReferenceValue is ClusterPrefab prefab) {
|
if (property.objectReferenceValue is ClusterPrefab prefab) {
|
||||||
// key per field instance
|
// key per field instance
|
||||||
string key = property.propertyPath + "_" + property.serializedObject.targetObject.GetEntityId();
|
string key = property.propertyPath + "_" + property.serializedObject.targetObject.GetEntityId();
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
|
|
||||||
@ -7,49 +8,67 @@ namespace NanoBrain.Unity {
|
|||||||
public class ClusterView {
|
public class ClusterView {
|
||||||
|
|
||||||
private static readonly float discRadius = 20;
|
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) {
|
public static ClusterView GetClusterView(SerializedProperty property) {
|
||||||
string key = property.propertyPath + "_" + property.serializedObject.targetObject.GetEntityId();
|
string key = property.propertyPath + "_" + property.serializedObject.targetObject.GetEntityId();
|
||||||
if (!viewStates.TryGetValue(key, out ClusterView state))
|
if (!clusterViews.TryGetValue(key, out ClusterView clusterView))
|
||||||
state = new() { key = key };
|
clusterView = new() { key = key };
|
||||||
return state;
|
return clusterView;
|
||||||
}
|
}
|
||||||
public static ClusterView GetClusterView(SerializedObject serializedObject) {
|
public static ClusterView GetClusterView(SerializedObject serializedObject) {
|
||||||
string key = serializedObject.targetObject.GetEntityId().ToString();
|
string key = serializedObject.targetObject.GetEntityId().ToString();
|
||||||
if (!viewStates.TryGetValue(key, out ClusterView state))
|
if (!clusterViews.TryGetValue(key, out ClusterView clusterView))
|
||||||
state = new() { key = key };
|
clusterView = new() { key = key };
|
||||||
return state;
|
return clusterView;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateViewState() {
|
private void UpdateViewState() {
|
||||||
viewStates[this.key] = this;
|
clusterViews[this.key] = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Render(Rect drawRect, Cluster cluster, SerializedProperty property) {
|
public static void Render(Rect drawRect, Cluster cluster, SerializedProperty property) {
|
||||||
ClusterView clusterView = GetClusterView(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);
|
clusterView.Render(drawRect);
|
||||||
}
|
}
|
||||||
public static void Render(Rect drawRect, Cluster cluster, SerializedObject obj) {
|
public static void Render(Rect drawRect, Cluster cluster, SerializedObject obj) {
|
||||||
ClusterView clusterView = GetClusterView(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);
|
clusterView.Render(drawRect);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Render(Rect drawRect) {
|
public void Render(Rect drawRect) {
|
||||||
// background
|
// 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);
|
Rect contentRect = new(0f, 0f, contentWidth, drawRect.height - 20);
|
||||||
|
|
||||||
// Begin horizontal-only scroll view
|
|
||||||
this.scrollPos = GUI.BeginScrollView(drawRect, this.scrollPos, contentRect, false, false);
|
this.scrollPos = GUI.BeginScrollView(drawRect, this.scrollPos, contentRect, false, false);
|
||||||
|
|
||||||
// Local content group: draw GUI content using content-local coords (0..contentWidth)
|
// Local content group: draw GUI content using content-local coords (0..contentWidth)
|
||||||
GUI.BeginGroup(new Rect(-this.scrollPos.x, 0f, contentWidth, drawRect.height));
|
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.EndGroup();
|
||||||
GUI.EndScrollView();
|
GUI.EndScrollView();
|
||||||
|
|
||||||
@ -60,12 +79,18 @@ namespace NanoBrain.Unity {
|
|||||||
GUI.BeginGroup(new Rect(-this.scrollPos.x, 0f, contentWidth, drawRect.height));
|
GUI.BeginGroup(new Rect(-this.scrollPos.x, 0f, contentWidth, drawRect.height));
|
||||||
|
|
||||||
Handles.BeginGUI();
|
Handles.BeginGUI();
|
||||||
this.DrawFocusGraph();
|
if (mode == Mode.Focus)
|
||||||
|
this.DrawFocusGraph();
|
||||||
|
else
|
||||||
|
this.DrawFullGraph();
|
||||||
Handles.EndGUI();
|
Handles.EndGUI();
|
||||||
|
|
||||||
GUI.EndGroup(); // end inner group
|
GUI.EndGroup(); // end inner group
|
||||||
GUI.EndGroup(); // end clipping 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();
|
UpdateViewState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,6 +102,8 @@ namespace NanoBrain.Unity {
|
|||||||
public Nucleus selectedSynapseNeuron = null;
|
public Nucleus selectedSynapseNeuron = null;
|
||||||
public Nucleus selectedOutput;
|
public Nucleus selectedOutput;
|
||||||
|
|
||||||
|
#region Focus Graph
|
||||||
|
|
||||||
protected void DrawFocusGraph() {
|
protected void DrawFocusGraph() {
|
||||||
float size = 20;
|
float size = 20;
|
||||||
Vector3 position = new(150, 210, 0);
|
Vector3 position = new(150, 210, 0);
|
||||||
@ -152,7 +179,7 @@ namespace NanoBrain.Unity {
|
|||||||
maxValue = neuron.outputMagnitude;
|
maxValue = neuron.outputMagnitude;
|
||||||
else if (this.currentNucleus is Cluster cluster)
|
else if (this.currentNucleus is Cluster cluster)
|
||||||
maxValue = cluster.defaultOutput.outputMagnitude;
|
maxValue = cluster.defaultOutput.outputMagnitude;
|
||||||
Debug.Log($"Neuron {maxValue} {currentCluster.defaultOutput.outputMagnitude}");
|
// Debug.Log($"Neuron {maxValue} {currentCluster.defaultOutput.outputMagnitude}");
|
||||||
DrawNucleus(this.currentNucleus, position, maxValue);
|
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) {
|
protected void DrawReceivers(Nucleus nucleus, Vector3 parentPos) {
|
||||||
List<Nucleus> receivers;
|
List<Nucleus> receivers;
|
||||||
if (nucleus is Neuron neuron)
|
if (nucleus is Neuron neuron)
|
||||||
@ -283,8 +404,8 @@ namespace NanoBrain.Unity {
|
|||||||
// Handles.DrawLine(parentPos, pos);
|
// Handles.DrawLine(parentPos, pos);
|
||||||
Color color = Color.black;
|
Color color = Color.black;
|
||||||
if (Application.isPlaying) {
|
if (Application.isPlaying) {
|
||||||
if (maxValue == 0 || !float.IsFinite(maxValue))
|
//if (maxValue == 0 || !float.IsFinite(maxValue))
|
||||||
maxValue = 1;
|
maxValue = 1 * synapse.weight;
|
||||||
float brightness = synapse.neuron.outputMagnitude * synapse.weight / maxValue;
|
float brightness = synapse.neuron.outputMagnitude * synapse.weight / maxValue;
|
||||||
color = new Color(brightness, brightness, brightness, 1f);
|
color = new Color(brightness, brightness, brightness, 1f);
|
||||||
}
|
}
|
||||||
@ -394,6 +515,7 @@ namespace NanoBrain.Unity {
|
|||||||
|
|
||||||
|
|
||||||
protected void DrawNucleus(Nucleus nucleus, Vector3 position, float maxValue) {
|
protected void DrawNucleus(Nucleus nucleus, Vector3 position, float maxValue) {
|
||||||
|
maxValue = 1;
|
||||||
Color color;
|
Color color;
|
||||||
if (Application.isPlaying) {
|
if (Application.isPlaying) {
|
||||||
float brightness = 0;
|
float brightness = 0;
|
||||||
@ -622,7 +744,7 @@ namespace NanoBrain.Unity {
|
|||||||
|
|
||||||
// Display tooltip with some offset
|
// Display tooltip with some offset
|
||||||
Vector2 tooltipSize = GUI.skin.box.CalcSize(tooltip);
|
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);
|
GUI.Box(tooltipRect, tooltip);
|
||||||
}
|
}
|
||||||
@ -688,4 +810,146 @@ namespace NanoBrain.Unity {
|
|||||||
#endregion Interaction
|
#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 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