First steps to using instanceCount for clusters

This commit is contained in:
Pascal Serrarens 2026-04-21 17:13:56 +02:00
parent 8801fa2ff2
commit b6630ad84e
5 changed files with 167 additions and 61 deletions

View File

@ -196,13 +196,16 @@ namespace NanoBrain {
if (this.currentNucleus is Cluster cluster) { if (this.currentNucleus is Cluster cluster) {
EditorGUILayout.BeginHorizontal(); EditorGUILayout.BeginHorizontal();
if (cluster.siblingClusters != null && cluster.siblingClusters.Length > 1) if (cluster.instanceCount > 1)
EditorGUILayout.IntField("Array size", cluster.instanceCount, GUILayout.MinWidth(150));
else if (cluster.siblingClusters != null && cluster.siblingClusters.Length > 1)
EditorGUILayout.IntField("Array size", cluster.siblingClusters.Count(), GUILayout.MinWidth(150)); EditorGUILayout.IntField("Array size", cluster.siblingClusters.Count(), GUILayout.MinWidth(150));
else else
EditorGUILayout.IntField("Array size", 1, GUILayout.MinWidth(150)); EditorGUILayout.IntField("Array size", 1, GUILayout.MinWidth(150));
if (GUILayout.Button("Add")) { if (GUILayout.Button("Add")) {
Undo.RecordObject(prefabAsset, "Array add " + prefabAsset.name); Undo.RecordObject(prefabAsset, "Array add " + prefabAsset.name);
cluster.AddInstance(this.prefab); //cluster.AddInstance(this.prefab);
cluster.AddInstance();
anythingChanged = true; anythingChanged = true;
} }
if (GUILayout.Button("Del")) { if (GUILayout.Button("Del")) {
@ -213,8 +216,36 @@ namespace NanoBrain {
EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal();
} }
// Synapses SynapsesInspector(ref anythingChanged);
ActivationInspector(ref anythingChanged);
if (GUILayout.Button("Delete this neuron"))
DeleteNucleus(this.currentNucleus);
if (this.currentNucleus is Cluster subCluster) {
if (GUILayout.Button("Reimport Cluster"))
ReimportCluster(subCluster);
if (GUILayout.Button("Edit Cluster"))
EditCluster(subCluster);
}
EditorGUILayout.Space();
breakOnWake = EditorGUILayout.Toggle("Break on wake", breakOnWake);
if (breakOnWake && this.currentNucleus is Neuron currentNeuron) {
if (currentNeuron.isSleeping == false)
Debug.Break();
}
trace = EditorGUILayout.Toggle("Trace", trace);
this.currentNucleus.trace = trace;
serializedObject.ApplyModifiedProperties();
if (anythingChanged) {
EditorUtility.SetDirty(prefabAsset);
AssetDatabase.SaveAssets();
}
}
protected void SynapsesInspector(ref bool anythingChanged) {
showSynapses = EditorGUILayout.BeginFoldoutHeaderGroup(showSynapses, "Synapses"); showSynapses = EditorGUILayout.BeginFoldoutHeaderGroup(showSynapses, "Synapses");
if (showSynapses) { if (showSynapses) {
if (this.currentNucleus is Neuron neuron2) { if (this.currentNucleus is Neuron neuron2) {
@ -248,19 +279,17 @@ namespace NanoBrain {
else else
elementIx = thisElementIx; elementIx = thisElementIx;
} }
// if (array.Contains(synapse.nucleus)) if (array.Contains(synapse.neuron))
// continue; continue;
else if (array.Contains(synapse.neuron.parent)) else if (array.Contains(synapse.neuron.parent))
continue; continue;
} }
else { else {
// if (synapse.neuron.parent is IReceptor iReceptor) { if (synapse.neuron.parent is Cluster iReceptor) {
// array = iReceptor.nucleiArray; array = iReceptor.siblingClusters;
// if (iReceptor is Cluster iCluster) if (iReceptor is Cluster iCluster)
// elementIx = Cluster.GetNucleusIndex(iCluster.clusterNuclei, synapse.neuron); elementIx = Cluster.GetNucleusIndex(iCluster.clusterNuclei, synapse.neuron);
// } }
// else if (synapse.nucleus is Receptor receptor2) // && receptor2.array != null && receptor2.array.nuclei.Length > 1)
// array = receptor2.nucleiArray;
} }
EditorGUILayout.Space(); EditorGUILayout.Space();
@ -325,8 +354,9 @@ namespace NanoBrain {
anythingChanged |= AddSynapse(this.prefab, this.currentNucleus); anythingChanged |= AddSynapse(this.prefab, this.currentNucleus);
} }
EditorGUILayout.EndFoldoutHeaderGroup(); EditorGUILayout.EndFoldoutHeaderGroup();
}
// Activation protected void ActivationInspector(ref bool anythingChanged) {
if (this.currentNucleus is not Cluster) { if (this.currentNucleus is not Cluster) {
EditorGUILayout.Space(); EditorGUILayout.Space();
@ -356,30 +386,6 @@ namespace NanoBrain {
EditorGUILayout.EndFoldoutHeaderGroup(); EditorGUILayout.EndFoldoutHeaderGroup();
} }
if (GUILayout.Button("Delete this neuron"))
DeleteNucleus(this.currentNucleus);
if (this.currentNucleus is Cluster subCluster) {
if (GUILayout.Button("Reimport Cluster"))
ReimportCluster(subCluster);
if (GUILayout.Button("Edit Cluster"))
EditCluster(subCluster);
}
EditorGUILayout.Space();
breakOnWake = EditorGUILayout.Toggle("Break on wake", breakOnWake);
if (breakOnWake && this.currentNucleus is Neuron currentNeuron) {
if (currentNeuron.isSleeping == false)
Debug.Break();
}
trace = EditorGUILayout.Toggle("Trace", trace);
this.currentNucleus.trace = trace;
serializedObject.ApplyModifiedProperties();
if (anythingChanged) {
EditorUtility.SetDirty(prefabAsset);
AssetDatabase.SaveAssets();
}
} }
#region Synapses #region Synapses
@ -426,8 +432,8 @@ namespace NanoBrain {
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 prefab) { private void OnClusterPicked(Nucleus nucleus, ClusterPrefab selectedPrefab) {
Cluster subclusterInstance = new(prefab, this.prefab); Cluster subclusterInstance = new(selectedPrefab, this.prefab);
subclusterInstance.defaultOutput.AddReceiver(nucleus); subclusterInstance.defaultOutput.AddReceiver(nucleus);
} }
@ -443,8 +449,10 @@ namespace NanoBrain {
Cluster reimportedCluster = new(subCluster.prefab, this.prefab); Cluster reimportedCluster = new(subCluster.prefab, this.prefab);
subCluster.MoveReceivers(reimportedCluster); subCluster.MoveReceivers(reimportedCluster);
// subcluster should be garbage now... // subcluster should be garbage now...
this.currentNucleus = reimportedCluster;
} }
else { else {
this.currentNucleus = null;
List<Cluster> newSiblingsList = new(); List<Cluster> newSiblingsList = new();
foreach (Cluster sibling in subCluster.siblingClusters) { foreach (Cluster sibling in subCluster.siblingClusters) {
Cluster reimportedCluster = new(sibling.prefab, this.prefab) { Cluster reimportedCluster = new(sibling.prefab, this.prefab) {
@ -452,6 +460,8 @@ namespace NanoBrain {
}; };
sibling.MoveReceivers(reimportedCluster); sibling.MoveReceivers(reimportedCluster);
newSiblingsList.Add(reimportedCluster); newSiblingsList.Add(reimportedCluster);
// make the first reimportedCluster the new current nucleus
this.currentNucleus ??= reimportedCluster;
} }
Cluster[] newSiblings = newSiblingsList.ToArray(); Cluster[] newSiblings = newSiblingsList.ToArray();
foreach (Cluster sibling in newSiblings) foreach (Cluster sibling in newSiblings)
@ -460,7 +470,7 @@ namespace NanoBrain {
} }
int selectedConnectNucleus = -1; int selectedConnectNucleus = -1;
// Connect to another nucleus in the same cluster // 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;
@ -485,14 +495,10 @@ namespace NanoBrain {
EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal();
if (connecting) { if (connecting) {
Nucleus nucleus = nuclei.ElementAt(selectedConnectNucleus); Nucleus nucleus = nuclei.ElementAt(selectedConnectNucleus);
// if (nucleus is IReceptor receptor) if (nucleus is Cluster subCluster)
// receptor.AddArrayReceiver(this.currentNucleus); subCluster.AddArrayReceiver(this.currentNucleus);
// else else if (nucleus is Neuron neuron)
if (nucleus is Neuron neuron)
neuron.AddReceiver(this.currentNucleus); neuron.AddReceiver(this.currentNucleus);
else if (nucleus is Cluster subCluster)
subCluster.defaultOutput.AddReceiver(this.currentNucleus);
} }
return connecting; return connecting;
} }

View File

@ -557,8 +557,16 @@ namespace NanoBrain {
Handles.Label(labelPos1, "0", style); Handles.Label(labelPos1, "0", style);
} }
else { else {
if (parentCluster.siblingClusters != null && parentCluster.siblingClusters.Length > 1) { // draw the array size label
// draw the array size label if (parentCluster.instanceCount > 1) {
if (color.grayscale > 0.5f)
style.normal.textColor = Color.black;
else
style.normal.textColor = Color.white;
Handles.Label(labelPosition, parentCluster.instanceCount.ToString(), style);
style.normal.textColor = Color.white;
}
else if (parentCluster.siblingClusters != null && parentCluster.siblingClusters.Length > 1) {
if (color.grayscale > 0.5f) if (color.grayscale > 0.5f)
style.normal.textColor = Color.black; style.normal.textColor = Color.black;
else else
@ -582,8 +590,16 @@ namespace NanoBrain {
Handles.Label(labelPos1, "0", style); Handles.Label(labelPos1, "0", style);
} }
else { else {
if (cluster.siblingClusters != null && cluster.siblingClusters.Length > 1) { // draw the array size label
// draw the array size label if (cluster.instanceCount > 1) {
if (color.grayscale > 0.5f)
style.normal.textColor = Color.black;
else
style.normal.textColor = Color.white;
Handles.Label(labelPosition, cluster.instanceCount.ToString(), style);
style.normal.textColor = Color.white;
}
else if (cluster.siblingClusters != null && cluster.siblingClusters.Length > 1) {
if (color.grayscale > 0.5f) if (color.grayscale > 0.5f)
style.normal.textColor = Color.black; style.normal.textColor = Color.black;
else else

View File

@ -15,6 +15,7 @@ namespace NanoBrain {
/// Clusters can be nested inside other clusters. /// Clusters can be nested inside other clusters.
[Serializable] [Serializable]
public class Cluster : Nucleus { public class Cluster : Nucleus {
// It may be that clusters will not be nuclei anymore in the future....
/// <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
@ -28,8 +29,11 @@ namespace NanoBrain {
} }
} }
// This should not be serialized
[SerializeReference] [SerializeReference]
public Cluster[] siblingClusters; public Cluster[] siblingClusters;
// This serialization should be enough
public int instanceCount = 1;
public Dictionary<int, Cluster> thingClusters = new(); public Dictionary<int, Cluster> thingClusters = new();
#region Init #region Init
@ -141,7 +145,6 @@ namespace NanoBrain {
} }
arrayIx++; arrayIx++;
} }
//clonedNucleus.array = clonedArray;
clonedNucleus.siblingClusters = clonedArray; clonedNucleus.siblingClusters = clonedArray;
} }
else { else {
@ -152,6 +155,50 @@ namespace NanoBrain {
} }
} }
// 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;
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)
@ -245,6 +292,7 @@ namespace NanoBrain {
} }
public override Nucleus ShallowCloneTo(Cluster parent) { public override Nucleus ShallowCloneTo(Cluster parent) {
// Clusters should not be cloned, but instantiated from the prefab....
Cluster clone = new(this.prefab, parent) { Cluster clone = new(this.prefab, parent) {
name = this.name, name = this.name,
clusterPrefab = this.clusterPrefab, clusterPrefab = this.clusterPrefab,
@ -314,6 +362,10 @@ namespace NanoBrain {
#region Cluster Array #region Cluster Array
public void AddInstance() {
this.instanceCount++;
}
public void AddInstance(ClusterPrefab prefab) { public void AddInstance(ClusterPrefab prefab) {
// Ensure siblingClusters exists // Ensure siblingClusters exists
if (this.siblingClusters == null || this.siblingClusters.Length == 0) if (this.siblingClusters == null || this.siblingClusters.Length == 0)
@ -340,18 +392,22 @@ namespace NanoBrain {
} }
public void RemoveInstance() { public void RemoveInstance() {
if (this.siblingClusters == null || this.siblingClusters.Length <= 1) if (instanceCount > 1)
return; instanceCount--;
else {
if (this.siblingClusters == null || this.siblingClusters.Length <= 1)
return;
// Prepare the new array // Prepare the new array
int newLength = this.siblingClusters.Length - 1; int newLength = this.siblingClusters.Length - 1;
Cluster[] newClusters = new Cluster[newLength]; Cluster[] newClusters = new Cluster[newLength];
for (int i = 0; i < newLength; i++) for (int i = 0; i < newLength; i++)
newClusters[i] = this.siblingClusters[i]; newClusters[i] = this.siblingClusters[i];
Neuron.Delete(this.siblingClusters[^1]); Neuron.Delete(this.siblingClusters[^1]);
this.siblingClusters = newClusters; this.siblingClusters = newClusters;
}
} }
public virtual Cluster GetThingCluster() { public virtual Cluster GetThingCluster() {
@ -411,6 +467,13 @@ namespace NanoBrain {
return true; return true;
} }
public void AddArrayReceiver(Nucleus receiverToAdd, float weight = 1) {
foreach (Cluster cluster in this.siblingClusters) {
cluster.defaultOutput.AddReceiver(receiverToAdd, weight);
}
}
#endregion ClusterArray #endregion ClusterArray
public ClusterPrefab prefab; public ClusterPrefab prefab;
@ -593,6 +656,22 @@ namespace NanoBrain {
return receivers; return receivers;
} }
public List<(Neuron, Nucleus)> CollectConnections() {
List<(Neuron, Nucleus)> connections = new();
foreach (Nucleus outputNucleus in this.clusterNuclei) {
if (outputNucleus is not Neuron output)
continue;
foreach (Nucleus receiver in output.receivers) {
// Only add receivers outside this cluster
if (receiver.clusterPrefab != this.prefab)
connections.Add((output, receiver));
}
}
return connections;
}
public void MoveReceivers(Cluster newCluster) { public void MoveReceivers(Cluster newCluster) {
Debug.Log($"Move receivers for {this.name} to {newCluster.name}"); Debug.Log($"Move receivers for {this.name} to {newCluster.name}");
foreach (Nucleus outputNucleus in this.clusterNuclei) { foreach (Nucleus outputNucleus in this.clusterNuclei) {

View File

@ -88,6 +88,10 @@ public abstract class Nucleus {
return synapse; return synapse;
} }
// public Synapse AddSynapse(ClusterPrefab clusterPrefab, string neuronName, float weight = 1) {
// }
/// <summary> /// <summary>
/// Find a synapse /// Find a synapse
/// </summary> /// </summary>

View File

@ -10,6 +10,7 @@ namespace NanoBrain {
public class ClusterPrefab : ScriptableObject { public class ClusterPrefab : ScriptableObject {
/// The nuclei in this cluster /// The nuclei in this cluster
[SerializeReference] [SerializeReference]
// This list should not include any clusters...
public List<Nucleus> nuclei = new(); public List<Nucleus> nuclei = new();
/// <summary> /// <summary>