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

View File

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

View File

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

View File

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

View File

@ -17,6 +17,8 @@ namespace NanoBrain {
public class Cluster : Nucleus {
// It may be that clusters will not be nuclei anymore in the future....
public ClusterPrefab prefab;
/// <summary>
/// The base name of the cluster. I don't think this is actively used at this moment
/// </summary>
@ -36,6 +38,12 @@ namespace NanoBrain {
public int instanceCount = 1;
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
/// <summary>
@ -79,8 +87,9 @@ namespace NanoBrain {
/// Where which the clone be found???
private void ClonePrefab() {
Nucleus[] prefabNuclei = this.prefab.nuclei.ToArray();
// first clone the nuclei without their connections
foreach (Nucleus nucleus in this.prefab.nuclei) {
foreach (Nucleus nucleus in prefabNuclei) {
nucleus.ShallowCloneTo(this);
}
Nucleus[] clonedNuclei = this.clusterNuclei.ToArray();
@ -95,110 +104,152 @@ namespace NanoBrain {
if (clonedNucleus == null || clonedNucleus is not Neuron clonedNeuron)
continue;
// Copy the receivers, which will also create the synapses
// Clusters do not have receivers...
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;
}
}
// 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)
foreach (Synapse prefabSynapse in prefabNeuron.synapses) {
Neuron synapseNeuron = prefabSynapse.neuron;
if (synapseNeuron.parent is not null && synapseNeuron.clusterPrefab != this.clusterPrefab) {
// Neuron is in another cluster, find the cloned cluster first
Cluster prefabCluster = synapseNeuron.parent;
int clusterIx = GetNucleusIndex(prefabNuclei, prefabCluster);
if (clusterIx < 0)
// Could not find the cluster in the prefab
continue;
if (clonedNuclei[clusterIx] is not Cluster clonedCluster)
// Could not find the cloned cluster
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;
// 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;
clonedSender.AddReceiver(clonedNeuron, prefabSynapse.weight);
}
else {
int ix = GetNucleusIndex(prefabNuclei, prefabSynapse.neuron);
if (ix < 0)
continue;
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) {
if (nucleus is Cluster clonedSubCluster)
@ -477,14 +528,8 @@ namespace NanoBrain {
#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 List<Nucleus> _inputs = null;

View File

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