WIP cluster references/instance

This commit is contained in:
Pascal Serrarens 2026-04-22 17:21:25 +02:00
parent 04bab9264f
commit d583e67e39
7 changed files with 196 additions and 138 deletions

View File

@ -7,7 +7,7 @@ using UnityEngine.UIElements;
namespace NanoBrain { namespace NanoBrain {
[CustomEditor(typeof(Brain))] [CustomEditor(typeof(Brain))]
public class NanoBrainComponent_Editor : Editor { public class Brain_Editor : Editor {
protected static VisualElement mainContainer; protected static VisualElement mainContainer;
protected static VisualElement inspectorContainer; protected static VisualElement inspectorContainer;
@ -24,7 +24,7 @@ namespace NanoBrain {
} }
public override VisualElement CreateInspectorGUI() { public override VisualElement CreateInspectorGUI() {
Cluster brain = component.brain; Cluster brain = component.InitializeBrain();
if (Application.isPlaying == false) if (Application.isPlaying == false)
serializedObject.Update(); serializedObject.Update();
@ -47,6 +47,7 @@ namespace NanoBrain {
root.Add(brainField); root.Add(brainField);
//} //}
if (brain != null) if (brain != null)
CreateViewer(root, brain.prefab, brain.defaultOutput, component.gameObject); CreateViewer(root, brain.prefab, brain.defaultOutput, component.gameObject);

View File

@ -8,7 +8,7 @@ using UnityEngine.UIElements;
namespace NanoBrain { namespace NanoBrain {
[CustomEditor(typeof(ClusterPrefab))] [CustomEditor(typeof(ClusterPrefab))]
public class ClusterInspector : ClusterViewer { public class ClusterEditor : ClusterViewer {
public override VisualElement CreateInspectorGUI() { public override VisualElement CreateInspectorGUI() {
ClusterPrefab prefab = target as ClusterPrefab; ClusterPrefab prefab = target as ClusterPrefab;
@ -18,13 +18,13 @@ namespace NanoBrain {
serializedObject.Update(); serializedObject.Update();
VisualElement root = new(); VisualElement root = new();
CreateInspector(root, prefab, prefab.output, null); CreateEditor(root, prefab, prefab.output, null);
serializedObject.ApplyModifiedProperties(); serializedObject.ApplyModifiedProperties();
return root; return root;
} }
public GraphView CreateInspector(VisualElement root, ClusterPrefab cluster, Nucleus output, GameObject gameObject) { public GraphView CreateEditor(VisualElement root, ClusterPrefab cluster, Nucleus output, GameObject gameObject) {
root.style.paddingLeft = 0; root.style.paddingLeft = 0;
root.style.paddingRight = 0; root.style.paddingRight = 0;
root.style.paddingTop = 0; root.style.paddingTop = 0;
@ -42,7 +42,6 @@ namespace NanoBrain {
graphContainer.style.width = 300; graphContainer.style.width = 300;
graphContainer.style.overflow = Overflow.Hidden; graphContainer.style.overflow = Overflow.Hidden;
VisualElement inspectorContainer = new() { VisualElement inspectorContainer = new() {
name = "inspector", name = "inspector",
style = { style = {
@ -57,7 +56,7 @@ namespace NanoBrain {
mainContainer.Add(inspectorContainer); mainContainer.Add(inspectorContainer);
root.Add(mainContainer); root.Add(mainContainer);
graphContainer.SetGraph(gameObject, output, inspectorContainer, this); graphContainer.SetGraph(gameObject, output, inspectorContainer);
return graphContainer; return graphContainer;
} }
@ -83,11 +82,9 @@ namespace NanoBrain {
this.currentNucleus = newOutput; this.currentNucleus = newOutput;
} }
public void SetGraph(GameObject gameObject, Nucleus nucleus, VisualElement inspectorContainer, ClusterInspector editor) { public void SetGraph(GameObject gameObject, Nucleus nucleus, VisualElement inspectorContainer) {
this.gameObject = gameObject; this.gameObject = gameObject;
this.currentEditor = editor;
//this.cluster = brain;
if (Application.isPlaying == false) if (Application.isPlaying == false)
this.serializedBrain = new SerializedObject(this.prefab); this.serializedBrain = new SerializedObject(this.prefab);
this.currentNucleus = nucleus; this.currentNucleus = nucleus;
@ -96,7 +93,7 @@ namespace NanoBrain {
OnOutputChanged(outputsPopup.choices[0]); OnOutputChanged(outputsPopup.choices[0]);
} }
void Rebuild(VisualElement inspectorContainer) { private void Rebuild(VisualElement inspectorContainer) {
BuildLayers(); BuildLayers();
if (this.currentNucleus == null) { if (this.currentNucleus == null) {
@ -121,6 +118,11 @@ namespace NanoBrain {
// create a SerializedObject wrapper so Unity inspector controls work (and Undo) // create a SerializedObject wrapper so Unity inspector controls work (and Undo)
SerializedObject so = new(prefabAsset); SerializedObject so = new(prefabAsset);
foreach (Nucleus nucleus in this.prefab.nuclei) {
nucleus.Initialize();
}
this.inspectorIMGUIContainer = new IMGUIContainer(() => InspectorHandler(so)); this.inspectorIMGUIContainer = new IMGUIContainer(() => InspectorHandler(so));
inspectorContainer.Add(inspectorIMGUIContainer); inspectorContainer.Add(inspectorIMGUIContainer);
@ -257,7 +259,6 @@ namespace NanoBrain {
} }
trace = EditorGUILayout.Toggle("Trace", trace); trace = EditorGUILayout.Toggle("Trace", trace);
this.currentNucleus.trace = trace; this.currentNucleus.trace = trace;
} }
protected void SynapsesInspector(ref bool anythingChanged) { protected void SynapsesInspector(ref bool anythingChanged) {
@ -319,19 +320,27 @@ namespace NanoBrain {
else { else {
EditorGUILayout.BeginHorizontal(); EditorGUILayout.BeginHorizontal();
if (synapse.neuron.parent != null && synapse.neuron.parent != this.currentNucleus) { if (synapse.neuron.clusterPrefab != this.currentNucleus.clusterPrefab) {
// If it is a cluster // If it is a cluster
GUIStyle labelStyle = new(GUI.skin.label); GUIStyle labelStyle = new(GUI.skin.label);
float labelWidth = 200; float labelWidth = 200;
if (synapse.neuron.clusterPrefab != null) { if (synapse.neuron.clusterPrefab != null) {
labelWidth = labelStyle.CalcSize(new GUIContent($"{synapse.neuron.parent.baseName}.")).x; labelWidth = labelStyle.CalcSize(new GUIContent($"{synapse.neuron.clusterPrefab.name}.")).x;
GUILayout.Label($"{synapse.neuron.parent.baseName}", GUILayout.Width(labelWidth)); GUILayout.Label($"{synapse.neuron.clusterPrefab.name}", GUILayout.Width(labelWidth));
} }
string[] options = synapse.neuron.parent.clusterNuclei.Select(n => n.name).ToArray(); //string[] options = synapse.neuron.parent.clusterNuclei.Select(n => n.name).ToArray();
string[] options = synapse.neuron.clusterPrefab.nuclei.Select(n => n.name).ToArray();
int selectedIndex = System.Array.IndexOf(options, synapse.neuron.name); int selectedIndex = System.Array.IndexOf(options, synapse.neuron.name);
int newIndex = EditorGUILayout.Popup(selectedIndex, options); int newIndex = EditorGUILayout.Popup(selectedIndex, options);
if (newIndex != selectedIndex && synapse.neuron.parent.clusterNuclei[newIndex] is Neuron newNeuron) // if (newIndex != selectedIndex && synapse.neuron.clusterPrefab.nuclei[newIndex] is Neuron newNeuron)
// ChangeSynapse(synapse, newNeuron);
if (newIndex != selectedIndex) {
// It shall be ensured that the parent.clusterNuclei and
// clusterPrefab.nuclei contain the same neurons in the same order....
Nucleus selectedNucleus = synapse.neuron.parent.clusterNuclei[newIndex];
Neuron newNeuron = selectedNucleus as Neuron;
ChangeSynapse(synapse, newNeuron); ChangeSynapse(synapse, newNeuron);
}
} }
else else
GUILayout.Label(synapse.neuron.name); GUILayout.Label(synapse.neuron.name);

View File

@ -9,7 +9,6 @@ namespace NanoBrain {
public class ClusterViewer : Editor { public class ClusterViewer : Editor {
//public static ClusterViewer previousEditor;
public static ClusterPrefab previousPrefab; public static ClusterPrefab previousPrefab;
public class GraphView : VisualElement { public class GraphView : VisualElement {
@ -29,9 +28,6 @@ namespace NanoBrain {
protected IMGUIContainer graphContainer; protected IMGUIContainer graphContainer;
protected readonly PopupField<string> outputsPopup; protected readonly PopupField<string> outputsPopup;
public ClusterInspector currentEditor;
//public ClusterViewer previousEditor;
public enum Mode { public enum Mode {
Focus, Focus,
Full Full
@ -93,8 +89,8 @@ namespace NanoBrain {
RegisterCallback<DetachFromPanelEvent>(evt => Unsubscribe()); RegisterCallback<DetachFromPanelEvent>(evt => Unsubscribe());
} }
protected virtual void OnModeChange(ChangeEvent<System.Enum> evt) { protected virtual void OnModeChange(ChangeEvent<System.Enum> changeEvent) {
mode = (Mode)evt.newValue; this.mode = (Mode)changeEvent.newValue;
} }
protected virtual void OnOutputChanged(string outputName) { protected virtual void OnOutputChanged(string outputName) {
@ -441,8 +437,6 @@ namespace NanoBrain {
} }
private void DrawSynapses(Nucleus nucleus, Vector3 parentPos, float size) { private void DrawSynapses(Nucleus nucleus, Vector3 parentPos, float size) {
int nodeCount = nucleus.synapses.Count;
// Determine the maximum value in this layer // Determine the maximum value in this layer
// This is used to 'scale' the output value colors of the nuclei // This is used to 'scale' the output value colors of the nuclei
float maxValue = 0; float maxValue = 0;
@ -452,7 +446,7 @@ namespace NanoBrain {
if (synapse.neuron == null) if (synapse.neuron == null)
continue; continue;
// Draw multiple synapses to the same neuron only once // Count multiple synapses to the same neuron only once
if (drawnNeurons.Contains(synapse.neuron)) if (drawnNeurons.Contains(synapse.neuron))
continue; continue;
drawnNeurons.Add(synapse.neuron); drawnNeurons.Add(synapse.neuron);
@ -474,6 +468,7 @@ namespace NanoBrain {
if (synapse.neuron is null) if (synapse.neuron is null)
continue; continue;
// Draw multiple synapses to the same neuron only once
if (drawnNeurons.Contains(synapse.neuron)) if (drawnNeurons.Contains(synapse.neuron))
continue; continue;
drawnNeurons.Add(synapse.neuron); drawnNeurons.Add(synapse.neuron);
@ -485,8 +480,7 @@ namespace NanoBrain {
if (Application.isPlaying) { if (Application.isPlaying) {
if (maxValue == 0 || !float.IsFinite(maxValue)) if (maxValue == 0 || !float.IsFinite(maxValue))
maxValue = 1; maxValue = 1;
float brightness = 0; float brightness = synapse.neuron.outputMagnitude * synapse.weight / maxValue;
brightness = synapse.neuron.outputMagnitude * synapse.weight / maxValue;
color = new Color(brightness, brightness, brightness, 1f); color = new Color(brightness, brightness, brightness, 1f);
} }
DrawNucleus(synapse.neuron, pos, size, color); DrawNucleus(synapse.neuron, pos, size, color);
@ -731,7 +725,7 @@ namespace NanoBrain {
Selection.activeObject = subCluster.prefab; Selection.activeObject = subCluster.prefab;
EditorGUIUtility.PingObject(subCluster.prefab); EditorGUIUtility.PingObject(subCluster.prefab);
ClusterViewer.previousPrefab = this.prefab; ClusterViewer.previousPrefab = this.prefab;
ClusterInspector newEditor = CreateEditor(subCluster.prefab) as ClusterInspector; ClusterEditor newEditor = CreateEditor(subCluster.prefab) as ClusterEditor;
} }
#endregion Graph #endregion Graph

View File

@ -20,17 +20,24 @@ namespace NanoBrain {
/// </summary> /// </summary>
public Cluster brain { public Cluster brain {
get { get {
if (brainInstance == null && brainPrefab != null) { // if (brainInstance == null && brainPrefab != null) {
brainInstance = new Cluster(brainPrefab) { // brainInstance = new Cluster(brainPrefab) {
name = brainPrefab.name // name = brainPrefab.name
}; // };
} else if (brainInstance != null && brainPrefab == null) { // } else if (brainInstance != null && brainPrefab == null) {
brainInstance = null; // brainInstance = null;
} // }
return brainInstance; return brainInstance;
} }
} }
public Cluster InitializeBrain() {
brainInstance = new Cluster(brainPrefab) {
name = brainPrefab.name
};
return brainInstance;
}
/// <summary> /// <summary>
/// Update the weight for all Synapses coming from the Neuron with the given name /// Update the weight for all Synapses coming from the Neuron with the given name
/// </summary> /// </summary>

View File

@ -17,6 +17,8 @@ namespace NanoBrain {
public class Cluster : Nucleus { public class Cluster : Nucleus {
// It may be that clusters will not be nuclei anymore in the future.... // It may be that clusters will not be nuclei anymore in the future....
public ClusterPrefab prefab;
/// <summary> /// <summary>
/// The base name of the cluster. I don't think this is actively used at this moment /// The base name of the cluster. I don't think this is actively used at this moment
/// </summary> /// </summary>
@ -36,6 +38,12 @@ namespace NanoBrain {
public int instanceCount = 1; public int instanceCount = 1;
public Dictionary<int, Cluster> thingClusters = new(); public Dictionary<int, Cluster> thingClusters = new();
[SerializeReference]
public List<Nucleus> clusterNuclei = new();
// the nuclei sorted using topological sorting
// to ensure that the cluster is computer in the right order
public List<Nucleus> sortedNuclei;
#region Init #region Init
/// <summary> /// <summary>
@ -79,8 +87,9 @@ namespace NanoBrain {
/// Where which the clone be found??? /// Where which the clone be found???
private void ClonePrefab() { private void ClonePrefab() {
Nucleus[] prefabNuclei = this.prefab.nuclei.ToArray(); Nucleus[] prefabNuclei = this.prefab.nuclei.ToArray();
// first clone the nuclei without their connections // first clone the nuclei without their connections
foreach (Nucleus nucleus in this.prefab.nuclei) { foreach (Nucleus nucleus in prefabNuclei) {
nucleus.ShallowCloneTo(this); nucleus.ShallowCloneTo(this);
} }
Nucleus[] clonedNuclei = this.clusterNuclei.ToArray(); Nucleus[] clonedNuclei = this.clusterNuclei.ToArray();
@ -95,110 +104,152 @@ namespace NanoBrain {
if (clonedNucleus == null || clonedNucleus is not Neuron clonedNeuron) if (clonedNucleus == null || clonedNucleus is not Neuron clonedNeuron)
continue; continue;
// Copy the receivers, which will also create the synapses foreach (Synapse prefabSynapse in prefabNeuron.synapses) {
// Clusters do not have receivers... Neuron synapseNeuron = prefabSynapse.neuron;
foreach (Nucleus receiver in prefabNeuron.receivers.ToArray()) { if (synapseNeuron.parent is not null && synapseNeuron.clusterPrefab != this.clusterPrefab) {
int ix = GetNucleusIndex(prefabNuclei, receiver); // Neuron is in another cluster, find the cloned cluster first
if (ix < 0) Cluster prefabCluster = synapseNeuron.parent;
continue; int clusterIx = GetNucleusIndex(prefabNuclei, prefabCluster);
if (clusterIx < 0)
if (clonedNuclei[ix] is not Nucleus clonedReceiver) // Could not find the cluster in the prefab
continue; continue;
if (clonedNuclei[clusterIx] is not Cluster clonedCluster)
// Find the synapse for the weight // Could not find the cloned cluster
float weight = 1;
foreach (Synapse synapse in receiver.synapses) {
// Find the weight for this synapse
if (synapse.neuron == prefabNucleus) {
weight = synapse.weight;
break;
}
}
clonedNeuron.AddReceiver(clonedReceiver, weight);
}
}
// Copy the siblings for clusters
for (int nucleusIx = 0; nucleusIx < prefabNuclei.Length; nucleusIx++) {
Nucleus prefabNucleus = prefabNuclei[nucleusIx];
if (prefabNucleus is not Cluster prefabCluster)
continue;
if (prefabCluster.siblingClusters == null || prefabCluster.siblingClusters.Length == 0)
continue;
Cluster clonedNucleus = clonedNuclei[nucleusIx] as Cluster;
if (prefabCluster == prefabCluster.siblingClusters[0]) {
// We clone the array only for the first entry
//NucleusArray clonedArray = new(prefabReceptor.nucleiArray.Length);
Cluster[] clonedArray = new Cluster[prefabCluster.siblingClusters.Length];
int arrayIx = 0;
foreach (Cluster prefabArrayNucleus in prefabCluster.siblingClusters) {
int arrayNucleusIx = GetNucleusIndex(prefabNuclei, prefabArrayNucleus);
if (arrayNucleusIx >= 0) {
Cluster clonedArrayNucleus = clonedNuclei[arrayNucleusIx] as Cluster;
clonedArray[arrayIx] = clonedArrayNucleus;
}
else {
Debug.LogError($" Could not find prefab nucleus {prefabNucleus.name} in the clones");
}
arrayIx++;
}
clonedNucleus.siblingClusters = clonedArray;
}
else {
// The others will refer to the array created for the first nucleus in the array
int firstNucleusIx = GetNucleusIndex(prefabNuclei, prefabCluster.siblingClusters[0]);
Cluster clonedFirstNucleus = clonedNuclei[firstNucleusIx] as Cluster;
clonedNucleus.siblingClusters = clonedFirstNucleus.siblingClusters;
}
}
// Collect the subclusters
List<Cluster> subClusters = new();
foreach (Nucleus nucleus in prefabNuclei) {
foreach (Synapse synapse in nucleus.synapses) {
Nucleus synapseNucleus = synapse.neuron;
if (synapseNucleus is not Cluster subCluster)
continue;
if (subClusters.Contains(subCluster))
continue;
subClusters.Add(subCluster);
}
}
// Create the subcluster instances
foreach (Cluster subCluster in subClusters) {
for (int ix = 0; ix < subCluster.instanceCount; ix++) {
// create the new instance
Cluster clusterInstance = new(subCluster.prefab);
// connect it
foreach ((Neuron sender, Nucleus receiver) in subCluster.CollectConnections()) {
int receiverIx = GetNucleusIndex(prefabNuclei, receiver);
if (receiverIx < 0)
continue; continue;
if (clonedNuclei[receiverIx] is not Nucleus clonedReceiver) // Now find the neuron in that cloned cluster
int neuronIx = GetNucleusIndex(prefabCluster.prefab.nuclei, prefabSynapse.neuron);
if (neuronIx < 0)
// Could not find the neuron in the prefab cluster
continue;
if (clonedCluster.clusterNuclei[neuronIx] is not Neuron clonedSender)
// Could not find the neuron in the cloned cluster
continue; continue;
// Find the synapse for the weight clonedSender.AddReceiver(clonedNeuron, prefabSynapse.weight);
float weight = 1; }
foreach (Synapse synapse in receiver.synapses) { else {
// Find the weight for this synapse int ix = GetNucleusIndex(prefabNuclei, prefabSynapse.neuron);
if (synapse.neuron == sender) { if (ix < 0)
weight = synapse.weight; continue;
break; if (clonedNuclei[ix] is not Neuron clonedSender)
continue;
// Copy the receivers which will also create the synapse
clonedSender.AddReceiver(clonedNeuron, prefabSynapse.weight);
}
}
// // Copy the receivers, which will also create the synapses
// foreach (Nucleus receiver in prefabNeuron.receivers.ToArray()) {
// int ix = GetNucleusIndex(prefabNuclei, receiver);
// if (ix < 0)
// continue;
// if (clonedNuclei[ix] is not Nucleus clonedReceiver)
// continue;
// // Find the synapse for the weight
// float weight = 1;
// foreach (Synapse synapse in receiver.synapses) {
// // Find the weight for this synapse
// if (synapse.neuron == prefabNucleus) {
// weight = synapse.weight;
// break;
// }
// }
// clonedNeuron.AddReceiver(clonedReceiver, weight);
// }
}
/*
// Copy the siblings for clusters
for (int nucleusIx = 0; nucleusIx < prefabNuclei.Length; nucleusIx++) {
Nucleus prefabNucleus = prefabNuclei[nucleusIx];
if (prefabNucleus is not Cluster prefabCluster)
continue;
if (prefabCluster.siblingClusters == null || prefabCluster.siblingClusters.Length == 0)
continue;
Cluster clonedNucleus = clonedNuclei[nucleusIx] as Cluster;
if (prefabCluster == prefabCluster.siblingClusters[0]) {
// We clone the array only for the first entry
//NucleusArray clonedArray = new(prefabReceptor.nucleiArray.Length);
Cluster[] clonedArray = new Cluster[prefabCluster.siblingClusters.Length];
int arrayIx = 0;
foreach (Cluster prefabArrayNucleus in prefabCluster.siblingClusters) {
int arrayNucleusIx = GetNucleusIndex(prefabNuclei, prefabArrayNucleus);
if (arrayNucleusIx >= 0) {
Cluster clonedArrayNucleus = clonedNuclei[arrayNucleusIx] as Cluster;
clonedArray[arrayIx] = clonedArrayNucleus;
}
else {
Debug.LogError($" Could not find prefab nucleus {prefabNucleus.name} in the clones");
}
arrayIx++;
}
clonedNucleus.siblingClusters = clonedArray;
}
else {
// The others will refer to the array created for the first nucleus in the array
int firstNucleusIx = GetNucleusIndex(prefabNuclei, prefabCluster.siblingClusters[0]);
Cluster clonedFirstNucleus = clonedNuclei[firstNucleusIx] as Cluster;
clonedNucleus.siblingClusters = clonedFirstNucleus.siblingClusters;
} }
} }
if (clusterInstance.GetNucleus(sender.name) is not Neuron clonedSender) /*
continue; // Collect the subclusters
List<Cluster> subClusters = new();
foreach (Nucleus nucleus in prefabNuclei) {
foreach (Synapse synapse in nucleus.synapses) {
Nucleus synapseNucleus = synapse.neuron;
Cluster subCluster = synapseNucleus.parent;
if (subCluster is null ||
synapseNucleus.clusterPrefab == this.clusterPrefab) {
clonedSender.AddReceiver(clonedReceiver, weight); continue;
} }
} // if (synapseNucleus is not Cluster subCluster)
} // continue;
if (subClusters.Contains(subCluster))
continue;
subClusters.Add(subCluster);
}
}
// Create the subcluster instances
foreach (Cluster subCluster in subClusters) {
for (int ix = 0; ix < subCluster.instanceCount; ix++) {
// create the new instance
Cluster clusterInstance = new(subCluster.prefab);
// connect it
foreach ((Neuron sender, Nucleus receiver) in subCluster.CollectConnections()) {
int receiverIx = GetNucleusIndex(prefabNuclei, receiver);
if (receiverIx < 0)
continue;
if (clonedNuclei[receiverIx] is not Nucleus clonedReceiver)
continue;
// Find the synapse for the weight
float weight = 1;
foreach (Synapse synapse in receiver.synapses) {
// Find the weight for this synapse
if (synapse.neuron == sender) {
weight = synapse.weight;
break;
}
}
if (clusterInstance.GetNucleus(sender.name) is not Neuron clonedSender)
continue;
clonedSender.AddReceiver(clonedReceiver, weight);
}
}
}
*/
foreach (Nucleus nucleus in this.clusterNuclei) { foreach (Nucleus nucleus in this.clusterNuclei) {
if (nucleus is Cluster clonedSubCluster) if (nucleus is Cluster clonedSubCluster)
@ -477,14 +528,8 @@ namespace NanoBrain {
#endregion ClusterArray #endregion ClusterArray
public ClusterPrefab prefab;
[SerializeReference]
public List<Nucleus> clusterNuclei = new();
// the nuclei sorted using topological sorting
// to ensure that the cluster is computer in the right order
public List<Nucleus> sortedNuclei;
//public Dictionary<string, Nucleus> nucleiDict = new(); //public Dictionary<string, Nucleus> nucleiDict = new();
public List<Nucleus> _inputs = null; public List<Nucleus> _inputs = null;

View File

@ -59,6 +59,8 @@ public abstract class Nucleus {
//ClusterArray, //ClusterArray,
} }
public virtual void Initialize() {}
#region Synapses #region Synapses
/// <summary> /// <summary>