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) {
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));
else
EditorGUILayout.IntField("Array size", 1, GUILayout.MinWidth(150));
if (GUILayout.Button("Add")) {
Undo.RecordObject(prefabAsset, "Array add " + prefabAsset.name);
cluster.AddInstance(this.prefab);
//cluster.AddInstance(this.prefab);
cluster.AddInstance();
anythingChanged = true;
}
if (GUILayout.Button("Del")) {
@ -213,8 +216,36 @@ namespace NanoBrain {
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");
if (showSynapses) {
if (this.currentNucleus is Neuron neuron2) {
@ -248,19 +279,17 @@ namespace NanoBrain {
else
elementIx = thisElementIx;
}
// if (array.Contains(synapse.nucleus))
// continue;
if (array.Contains(synapse.neuron))
continue;
else if (array.Contains(synapse.neuron.parent))
continue;
}
else {
// if (synapse.neuron.parent is IReceptor iReceptor) {
// array = iReceptor.nucleiArray;
// if (iReceptor is Cluster iCluster)
// 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;
if (synapse.neuron.parent is Cluster iReceptor) {
array = iReceptor.siblingClusters;
if (iReceptor is Cluster iCluster)
elementIx = Cluster.GetNucleusIndex(iCluster.clusterNuclei, synapse.neuron);
}
}
EditorGUILayout.Space();
@ -325,8 +354,9 @@ namespace NanoBrain {
anythingChanged |= AddSynapse(this.prefab, this.currentNucleus);
}
EditorGUILayout.EndFoldoutHeaderGroup();
}
// Activation
protected void ActivationInspector(ref bool anythingChanged) {
if (this.currentNucleus is not Cluster) {
EditorGUILayout.Space();
@ -356,30 +386,6 @@ namespace NanoBrain {
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
@ -426,8 +432,8 @@ namespace NanoBrain {
protected virtual void AddClusterInput(Nucleus nucleus) {
ClusterPickerWindow.ShowPicker(brain => OnClusterPicked(nucleus, brain), "Select Cluster");
}
private void OnClusterPicked(Nucleus nucleus, ClusterPrefab prefab) {
Cluster subclusterInstance = new(prefab, this.prefab);
private void OnClusterPicked(Nucleus nucleus, ClusterPrefab selectedPrefab) {
Cluster subclusterInstance = new(selectedPrefab, this.prefab);
subclusterInstance.defaultOutput.AddReceiver(nucleus);
}
@ -443,8 +449,10 @@ namespace NanoBrain {
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) {
@ -452,6 +460,8 @@ namespace NanoBrain {
};
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)
@ -460,7 +470,7 @@ namespace NanoBrain {
}
int selectedConnectNucleus = -1;
// Connect to another nucleus in the same cluster
// Connect to another nucleus
protected virtual bool ConnectNucleus(ClusterPrefab cluster, Nucleus nucleusToConnect) {
if (cluster == null)
return false;
@ -485,14 +495,10 @@ namespace NanoBrain {
EditorGUILayout.EndHorizontal();
if (connecting) {
Nucleus nucleus = nuclei.ElementAt(selectedConnectNucleus);
// if (nucleus is IReceptor receptor)
// receptor.AddArrayReceiver(this.currentNucleus);
// else
if (nucleus is Neuron neuron)
if (nucleus is Cluster subCluster)
subCluster.AddArrayReceiver(this.currentNucleus);
else if (nucleus is Neuron neuron)
neuron.AddReceiver(this.currentNucleus);
else if (nucleus is Cluster subCluster)
subCluster.defaultOutput.AddReceiver(this.currentNucleus);
}
return connecting;
}

View File

@ -557,8 +557,16 @@ namespace NanoBrain {
Handles.Label(labelPos1, "0", style);
}
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)
style.normal.textColor = Color.black;
else
@ -582,8 +590,16 @@ namespace NanoBrain {
Handles.Label(labelPos1, "0", style);
}
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)
style.normal.textColor = Color.black;
else

View File

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

View File

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

View File

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