diff --git a/Editor/Brain_Editor.cs b/Editor/Brain_Editor.cs index 8037d8c..449a004 100644 --- a/Editor/Brain_Editor.cs +++ b/Editor/Brain_Editor.cs @@ -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); diff --git a/Editor/ClusterInspector.cs b/Editor/ClusterEditor.cs similarity index 92% rename from Editor/ClusterInspector.cs rename to Editor/ClusterEditor.cs index a7dc1f2..4dfc4e1 100644 --- a/Editor/ClusterInspector.cs +++ b/Editor/ClusterEditor.cs @@ -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); diff --git a/Editor/ClusterInspector.cs.meta b/Editor/ClusterEditor.cs.meta similarity index 100% rename from Editor/ClusterInspector.cs.meta rename to Editor/ClusterEditor.cs.meta diff --git a/Editor/ClusterViewer.cs b/Editor/ClusterViewer.cs index b5727a2..ba9b47a 100644 --- a/Editor/ClusterViewer.cs +++ b/Editor/ClusterViewer.cs @@ -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 outputsPopup; - public ClusterInspector currentEditor; - //public ClusterViewer previousEditor; - public enum Mode { Focus, Full @@ -93,8 +89,8 @@ namespace NanoBrain { RegisterCallback(evt => Unsubscribe()); } - protected virtual void OnModeChange(ChangeEvent evt) { - mode = (Mode)evt.newValue; + protected virtual void OnModeChange(ChangeEvent 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 diff --git a/Runtime/Scripts/Brain.cs b/Runtime/Scripts/Brain.cs index 81f2b4c..36fd6e7 100644 --- a/Runtime/Scripts/Brain.cs +++ b/Runtime/Scripts/Brain.cs @@ -20,17 +20,24 @@ namespace NanoBrain { /// 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; + } + /// /// Update the weight for all Synapses coming from the Neuron with the given name /// diff --git a/Runtime/Scripts/Core/Cluster.cs b/Runtime/Scripts/Core/Cluster.cs index acb312a..525e296 100644 --- a/Runtime/Scripts/Core/Cluster.cs +++ b/Runtime/Scripts/Core/Cluster.cs @@ -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; + /// /// The base name of the cluster. I don't think this is actively used at this moment /// @@ -36,6 +38,12 @@ namespace NanoBrain { public int instanceCount = 1; public Dictionary thingClusters = new(); + [SerializeReference] + public List clusterNuclei = new(); + // the nuclei sorted using topological sorting + // to ensure that the cluster is computer in the right order + public List sortedNuclei; + #region Init /// @@ -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 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 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 clusterNuclei = new(); - // the nuclei sorted using topological sorting - // to ensure that the cluster is computer in the right order - public List sortedNuclei; //public Dictionary nucleiDict = new(); public List _inputs = null; diff --git a/Runtime/Scripts/Core/Nucleus.cs b/Runtime/Scripts/Core/Nucleus.cs index f983a2d..314fee3 100644 --- a/Runtime/Scripts/Core/Nucleus.cs +++ b/Runtime/Scripts/Core/Nucleus.cs @@ -59,6 +59,8 @@ public abstract class Nucleus { //ClusterArray, } + public virtual void Initialize() {} + #region Synapses ///