From b6630ad84eed6c7cfa8df9ff89e6bd626183a718 Mon Sep 17 00:00:00 2001 From: Pascal Serrarens Date: Tue, 21 Apr 2026 17:13:56 +0200 Subject: [PATCH] First steps to using instanceCount for clusters --- Editor/ClusterInspector.cs | 100 ++++++++++-------- Editor/ClusterViewer.cs | 24 ++++- Runtime/Scripts/Core/Cluster.cs | 99 +++++++++++++++-- Runtime/Scripts/Core/Nucleus.cs | 4 + .../ScriptableObjects/ClusterPrefab.cs | 1 + 5 files changed, 167 insertions(+), 61 deletions(-) diff --git a/Editor/ClusterInspector.cs b/Editor/ClusterInspector.cs index f32357e..a6668b9 100644 --- a/Editor/ClusterInspector.cs +++ b/Editor/ClusterInspector.cs @@ -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 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; } diff --git a/Editor/ClusterViewer.cs b/Editor/ClusterViewer.cs index 0940fc3..34a9c11 100644 --- a/Editor/ClusterViewer.cs +++ b/Editor/ClusterViewer.cs @@ -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 diff --git a/Runtime/Scripts/Core/Cluster.cs b/Runtime/Scripts/Core/Cluster.cs index d6ef274..f58f539 100644 --- a/Runtime/Scripts/Core/Cluster.cs +++ b/Runtime/Scripts/Core/Cluster.cs @@ -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.... /// /// 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 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 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) { diff --git a/Runtime/Scripts/Core/Nucleus.cs b/Runtime/Scripts/Core/Nucleus.cs index 60352b2..f983a2d 100644 --- a/Runtime/Scripts/Core/Nucleus.cs +++ b/Runtime/Scripts/Core/Nucleus.cs @@ -88,6 +88,10 @@ public abstract class Nucleus { return synapse; } + // public Synapse AddSynapse(ClusterPrefab clusterPrefab, string neuronName, float weight = 1) { + + // } + /// /// Find a synapse /// diff --git a/Runtime/Scripts/ScriptableObjects/ClusterPrefab.cs b/Runtime/Scripts/ScriptableObjects/ClusterPrefab.cs index e50093f..2920733 100644 --- a/Runtime/Scripts/ScriptableObjects/ClusterPrefab.cs +++ b/Runtime/Scripts/ScriptableObjects/ClusterPrefab.cs @@ -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 nuclei = new(); ///