From af0ba68bd81c7a2c8ab3614cd8c8450ebdb01b3e Mon Sep 17 00:00:00 2001 From: Pascal Serrarens Date: Mon, 4 May 2026 14:08:49 +0200 Subject: [PATCH] Viewer for multi-clusters --- Editor/ClusterViewer.cs | 93 ++++++++++++++++++++++++++------- Runtime/Scripts/Core/Cluster.cs | 75 +++++++++++++------------- 2 files changed, 109 insertions(+), 59 deletions(-) diff --git a/Editor/ClusterViewer.cs b/Editor/ClusterViewer.cs index 41dee0c..d6c4c9f 100644 --- a/Editor/ClusterViewer.cs +++ b/Editor/ClusterViewer.cs @@ -22,6 +22,8 @@ namespace NanoBrain { } //protected Nucleus currentNucleus; protected Nucleus selectedOutput; + // Only used when selecting a synapse to a multi-cluster + protected Nucleus selectedSynapseNeuron; protected GameObject gameObject; private bool expandArray = false; @@ -382,6 +384,10 @@ namespace NanoBrain { } protected void DrawSynapses(Nucleus nucleus, Vector3 parentPos, float size) { + if (this.selectedSynapseNeuron != null) { + DrawClusterSynapses(this.selectedSynapseNeuron, parentPos, size); + return; + } if (nucleus == null) return; @@ -389,15 +395,19 @@ namespace NanoBrain { // This is used to 'scale' the output value colors of the nuclei float maxValue = 0; int neuronCount = 0; - List drawnNeurons = new(); + List drawnNeuronNames = new(); foreach (Synapse synapse in nucleus.synapses) { if (synapse.neuron == null) continue; // Count multiple synapses to the same neuron only once - if (drawnNeurons.Contains(synapse.neuron)) + string neuronName = synapse.neuron.name; + if (synapse.neuron.parent != null) + neuronName = synapse.neuron.parent.baseName + "." + neuronName; + + if (drawnNeuronNames.Contains(neuronName)) continue; - drawnNeurons.Add(synapse.neuron); + drawnNeuronNames.Add(neuronName); float value = synapse.neuron.outputMagnitude * synapse.weight; if (value > maxValue) @@ -411,15 +421,20 @@ namespace NanoBrain { float margin = 10 + spacing / 2; int row = 0; - drawnNeurons = new(); + //List drawnNeurons = new(); + drawnNeuronNames = new(); foreach (Synapse synapse in nucleus.synapses) { if (synapse.neuron is null) continue; // Draw multiple synapses to the same neuron only once - if (drawnNeurons.Contains(synapse.neuron)) + string neuronName = synapse.neuron.name; + if (synapse.neuron.parent != null) + neuronName = synapse.neuron.parent.baseName + "." + neuronName; + + if (drawnNeuronNames.Contains(neuronName)) continue; - drawnNeurons.Add(synapse.neuron); + drawnNeuronNames.Add(neuronName); Vector3 pos = new(250, margin + row * spacing, 0.0f); DrawEdge(parentPos, pos); @@ -437,6 +452,37 @@ namespace NanoBrain { } } + protected void DrawClusterSynapses(Nucleus nucleus, Vector3 parentPos, float size) { + if (nucleus == null || nucleus.parent == null || nucleus.parent.siblingClusters == null) + return; + + // Hack to disable showing labels + expandArray = true; + + // Determine the spacing of the nuclei in the layer + float spacing = 400f / nucleus.parent.instanceCount; + float margin = 10 + spacing / 2; + + int row = 0; + foreach (Cluster sibling in nucleus.parent.siblingClusters) { + Nucleus siblingNucleus = sibling.GetNucleus(nucleus.name); + Vector3 position = new(250, margin + row * spacing, 0.0f); + DrawEdge(parentPos, position); + Color color = Color.black; + DrawNucleus(siblingNucleus, position, size, color); + GUIStyle style = new(EditorStyles.label) { + alignment = TextAnchor.UpperCenter, + normal = { textColor = Color.white }, + fontStyle = FontStyle.Bold, + }; + Vector3 labelPos = position - Vector3.down * (size + 5); // below neuron + string name = $"{sibling.baseName}.{nucleus.name}"; + Handles.Label(labelPos, name, style); + row++; + } + expandArray = false; + } + protected void DrawOutputs(Vector2 parentPos, float size) { // Determine the maximum value in this layer // This is used to 'scale' the output value colors of the nuclei @@ -536,7 +582,7 @@ namespace NanoBrain { else if (nucleus is Cluster cluster) DrawCluster(cluster, position, color, size); - if (expandArray == false || nucleus != currentNucleus) { + if (expandArray == false) {// || nucleus != currentNucleus) { // put name below nucleus Vector3 labelPos = position - Vector3.down * (size + 5); // below neuron style.alignment = TextAnchor.UpperCenter; @@ -730,24 +776,33 @@ namespace NanoBrain { protected void OnNeuronClick(Nucleus nucleus) { if (nucleus == this.currentNucleus) { - if (Application.isPlaying) { - if (nucleus is Cluster) - expandArray = !expandArray; - else - expandArray = false; - } - else { + this.selectedSynapseNeuron = null; + // if (Application.isPlaying) { + // if (nucleus is Cluster) + // expandArray = !expandArray; + // else + // expandArray = false; + // } + // else { if (nucleus is Cluster cluster) OnClusterClick(cluster); - } + // } } else if (nucleus.parent != null && this.currentNucleus != null && nucleus.parent != this.currentNucleus.parent) { // We go to a different cluster if (Application.isPlaying) { - this.currentNucleus = nucleus; - if (this.currentNucleus is Neuron neuron && neuron.receivers.Count == 0) - this.selectedOutput = this.currentNucleus; - expandArray = false; + if (this.selectedSynapseNeuron == null && nucleus.parent.instanceCount > 1) { + this.selectedSynapseNeuron = nucleus; + expandArray = false; + } + else { + this.currentNucleus = nucleus; + if (this.currentNucleus is Neuron neuron && neuron.receivers.Count == 0) + this.selectedOutput = this.currentNucleus; + this.selectedSynapseNeuron = null; + expandArray = false; + } + } else { // select the cluster, not the neuron in the cluster diff --git a/Runtime/Scripts/Core/Cluster.cs b/Runtime/Scripts/Core/Cluster.cs index c027f90..def7228 100644 --- a/Runtime/Scripts/Core/Cluster.cs +++ b/Runtime/Scripts/Core/Cluster.cs @@ -171,20 +171,23 @@ namespace NanoBrain { if (clonedNucleus is not Cluster clonedCluster) continue; - // if (clonedCluster.instanceCount <= 1) - // continue; - + List siblings = new() { + clonedCluster + }; for (int instanceIx = 1; instanceIx < clonedCluster.instanceCount; instanceIx++) { - //ClusterPrefab prefabCluster = clonedCluster.prefab; // Create another sibling Debug.Log($"create {clonedCluster.prefab.name} sibling"); Cluster sibling = new(clonedCluster.prefab, this) { - name = this.name, + name = $"{clonedCluster.baseName}: {instanceIx}", clusterPrefab = this.clusterPrefab, instanceCount = this.instanceCount, }; - RestoreAllExternalReceivers(clonedCluster, clonedCluster.prefab, this); + siblings.Add(sibling); + CopyAllExternalReceivers(clonedCluster, sibling, clonedCluster.prefab, this); } + Cluster[] siblingClusters = siblings.ToArray(); + foreach (Cluster sibling in siblings) + sibling.siblingClusters = siblingClusters; } } /* @@ -411,45 +414,37 @@ namespace NanoBrain { return clone; } - private static void RestoreAllExternalReceivers(Cluster clonedCluster, ClusterPrefab prefabParent, Cluster clonedParent) { + private static void CopyAllExternalReceivers(Cluster sourceCluster, Cluster sibling, ClusterPrefab prefabParent, Cluster clonedParent) { + for (int nucleusIx = 0; nucleusIx < sourceCluster.clusterNuclei.Count; nucleusIx++) { + Nucleus sourceNucleus = sourceCluster.clusterNuclei[nucleusIx]; + if (sourceNucleus is not Neuron sourceNeuron) + continue; - // int clonedClusterIx = GetNucleusIndex(clonedParent.clusterNuclei, clonedCluster); - // if (prefabParent.nuclei[clonedClusterIx] is not Cluster sourceCluster) - // return; + if (sibling.clusterNuclei[nucleusIx] is not Neuron clonedNeuron) + continue; - // for (int nucleusIx = 0; nucleusIx < sourceCluster.clusterNuclei.Count; nucleusIx++) { - // Nucleus sourceNucleus = sourceCluster.clusterNuclei[nucleusIx]; - // if (sourceNucleus is not Neuron sourceNeuron) - // continue; + // copy the receivers (and thus synapses) from the source to the sibling + foreach (Nucleus receiver in sourceNeuron.receivers) { + int ix = GetNucleusIndex(clonedParent.clusterNuclei, receiver); + if (ix < 0 || ix >= clonedParent.clusterNuclei.Count) + continue; - // if (clonedCluster.clusterNuclei[nucleusIx] is not Neuron clonedNeuron) - // 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 == sourceNucleus) { + weight = synapse.weight; + break; + } + } - // // copy the receivers (and thus synapses) from the source to the clone - // foreach (Nucleus receiver in sourceNeuron.receivers) { - // int ix = GetNucleusIndex(prefabParent.nuclei, receiver); - // if (ix < 0 || ix >= clonedParent.clusterNuclei.Count) - // continue; - - // Nucleus clonedReceiver = clonedParent.clusterNuclei[ix]; - - // // Find the synapse for the weight - // float weight = 1; - // foreach (Synapse synapse in receiver.synapses) { - // // Find the weight for this synapse - // if (synapse.neuron == sourceNucleus) { - // weight = synapse.weight; - // break; - // } - // } - - // clonedNeuron.AddReceiver(clonedReceiver, weight); - // Debug.Log($"external: {clonedReceiver.name} receives from {clonedNeuron.name} {clonedNeuron.GetHashCode()}"); - // } - // } - - + clonedNeuron.AddReceiver(receiver, weight); + Debug.Log($"external: {receiver.name} receives from {clonedNeuron.name} {clonedNeuron.GetHashCode()}"); + } + } + } protected int GetNucleusIndex(Nucleus[] nuclei, Nucleus nucleus) {