diff --git a/Cluster.cs b/Cluster.cs index 22c52db..84ff4b2 100644 --- a/Cluster.cs +++ b/Cluster.cs @@ -20,23 +20,19 @@ public class Cluster : Nucleus { ClonePrefab(); _ = this.inputs; this.sortedNuclei = TopologicalSort(this.nuclei); - // Does not work because we have nuclei with the same names in an nucleusArray - // 'Pheromone steering' - //this.nucleiDict = nuclei.ToDictionary(nucleus => nucleus.name); } public Cluster(ClusterPrefab prefab, ClusterPrefab parent = null) { this.prefab = prefab; this.name = prefab.name; - this.cluster = parent; + this.clusterPrefab = parent; - if (this.cluster != null) - this.cluster.nuclei.Add(this); + if (this.clusterPrefab != null) + this.clusterPrefab.nuclei.Add(this); ClonePrefab(); _ = this.inputs; this.sortedNuclei = TopologicalSort(this.nuclei); - //this.nucleiDict = nuclei.ToDictionary(nucleus => nucleus.name); } private void ClonePrefab() { @@ -151,10 +147,7 @@ public class Cluster : Nucleus { } public override Nucleus Clone(ClusterPrefab prefab) { - //Neuron clone = new(this.cluster, this.name) { - Neuron clone = new(prefab, this.name) { - // array = this.array, - }; + Neuron clone = new(prefab, this.name); foreach (Synapse synapse in this.synapses) { Synapse clonedSynapse = clone.AddSynapse(synapse.nucleus); @@ -169,6 +162,7 @@ public class Cluster : Nucleus { public override Nucleus ShallowCloneTo(Cluster parent) { Cluster clone = new(this.prefab, parent) { name = this.name, + clusterPrefab = this.clusterPrefab, }; return clone; } diff --git a/ClusterReceptor.cs b/ClusterReceptor.cs index 38ecb0d..d6aca1b 100644 --- a/ClusterReceptor.cs +++ b/ClusterReceptor.cs @@ -1,30 +1,32 @@ using System; +using System.Collections.Generic; using UnityEngine; using Unity.Mathematics; using static Unity.Mathematics.math; - [Serializable] public class ClusterReceptor : Cluster { public ClusterReceptor(ClusterPrefab prefab, Cluster parent, string name) : base(prefab, parent) { this.name = name; this.array ??= new NucleusArray(this); } - public ClusterReceptor(ClusterPrefab prefab, ClusterPrefab parent, string name) : base(prefab, parent) { + public ClusterReceptor(ClusterPrefab prefabToInstantiate, ClusterPrefab parent, string name) : base(prefabToInstantiate, parent) { this.name = name; this.array = new NucleusArray(this); } public override Nucleus ShallowCloneTo(Cluster parent) { ClusterReceptor clone = new(this.prefab, parent, this.name) { + clusterPrefab = this.clusterPrefab, array = this.array }; return clone; } - public override Nucleus Clone(ClusterPrefab prefab) { - ClusterReceptor clone = new(prefab, parent, this.name); - clone.array = this.array; + public override Nucleus Clone(ClusterPrefab parent) { + ClusterReceptor clone = new(prefab, parent, this.name) { + array = this.array + }; foreach (Synapse synapse in this.synapses) { Synapse clonedSynapse = clone.AddSynapse(synapse.nucleus); @@ -36,7 +38,33 @@ public class ClusterReceptor : Cluster { return clone; } - public NucleusArray array { get; set; } + [SerializeReference] + private NucleusArray _array; + public NucleusArray array { + get { return _array; } + set { _array = value; } + } + + #region Receivers + + private List _clusterReceivers = null; + public override List receivers { + get { + if (_clusterReceivers == null || _clusterReceivers.Count == 0) { + _clusterReceivers = new(); + foreach (Nucleus output in this.nuclei) { + _clusterReceivers.AddRange(output.receivers); + } + } + return _clusterReceivers; + } + } + public override void AddReceiver(Nucleus receivingNucleus, float weight = 1) { + this.output.receivers.Add(receivingNucleus); + receivingNucleus.AddSynapse(this.output, weight); + } + + #endregion Receivers public override void UpdateStateIsolated() { float3 sum = this.bias; diff --git a/Editor/ClusterInspector.cs b/Editor/ClusterInspector.cs index b8b8e30..bafeee7 100644 --- a/Editor/ClusterInspector.cs +++ b/Editor/ClusterInspector.cs @@ -409,7 +409,10 @@ public class ClusterInspector : Editor { if (drawnArrays.Contains(receptor.array)) continue; drawnArrays.Add(receptor.array); - + } else if (synapse.nucleus.parent is ClusterReceptor clusterReceptor) { + if (drawnArrays.Contains(clusterReceptor.array)) + continue; + drawnArrays.Add(clusterReceptor.array); } float value = length(synapse.nucleus.outputValue) * synapse.weight; // Debug.Log($"{synapse.nucleus.name}: {value} {length(synapse.nucleus.outputValue)} {synapse.weight}"); @@ -429,16 +432,27 @@ public class ClusterInspector : Editor { if (drawnArrays.Contains(neuron.array)) continue; drawnArrays.Add(neuron.array); + } else if (synapse.nucleus.parent is ClusterReceptor clusterReceptor) { + if (drawnArrays.Contains(clusterReceptor.array)) + continue; + drawnArrays.Add(clusterReceptor.array); } Vector3 pos = new(250, margin + row * spacing, 0.0f); Handles.color = Color.white; Handles.DrawLine(parentPos, pos); - if (synapse.nucleus != null) { - Color color = Color.black; - if (Application.isPlaying) { - float brightness = length(synapse.nucleus.outputValue) * synapse.weight / maxValue; - color = new Color(brightness, brightness, brightness, 1f); - } + Color color = Color.black; + if (Application.isPlaying) { + float brightness = length(synapse.nucleus.outputValue) * synapse.weight / maxValue; + color = new Color(brightness, brightness, brightness, 1f); + } + if (synapse.nucleus.parent != null && synapse.nucleus.parent != this.currentNucleus) { + // the synapse nucleus is part of a subcluster + DrawNucleus(synapse.nucleus.parent, pos, maxValue, size, color); + } + // else if (synapse.nucleus.cluster != null && synapse.nucleus.cluster != this.currentNucleus.cluster) { + // DrawNucleus(synapse.nucleus.parent, pos, maxValue, size, color); + // } + else { DrawNucleus(synapse.nucleus, pos, maxValue, size, color); } row++; @@ -495,6 +509,7 @@ public class ClusterInspector : Editor { // style.normal.textColor = Color.white; // Handles.Label(labelPosition, receptor.instances.Count().ToString(), style); // } + if (nucleus is ClusterReceptor clusterReceptor) { if (expandArray && clusterReceptor.array.nuclei.First() == this.currentNucleus) { style.alignment = TextAnchor.LowerCenter; @@ -575,7 +590,8 @@ public class ClusterInspector : Editor { private void HandleClicked(Nucleus nucleus) { if (nucleus == this.currentNucleus) { - expandArray = !expandArray; + if (nucleus is Receptor || nucleus is ClusterReceptor) + expandArray = !expandArray; } else if (nucleus is ReceptorInstance receptor) { expandArray = false; @@ -693,64 +709,78 @@ public class ClusterInspector : Editor { // Synapses - showSynapses = EditorGUILayout.BeginFoldoutHeaderGroup(showSynapses, "Synapses"); - if (showSynapses) { + if (this.currentNucleus is not Receptor && this.currentNucleus is not ClusterReceptor) { + showSynapses = EditorGUILayout.BeginFoldoutHeaderGroup(showSynapses, "Synapses"); + if (showSynapses) { - anythingChanged = ConnectNucleus(this.prefab, this.currentNucleus); - anythingChanged = AddSynapse(this.prefab, this.currentNucleus); - EditorGUILayout.Space(); + anythingChanged = ConnectNucleus(this.prefab, this.currentNucleus); + anythingChanged = AddSynapse(this.prefab, this.currentNucleus); + EditorGUILayout.Space(); - if (this.currentNucleus is Neuron neuron2) { - Neuron.CombinatorType newCombinator = (Neuron.CombinatorType)EditorGUILayout.EnumPopup("Combinator", neuron2.combinator); - anythingChanged |= newCombinator != neuron2.combinator; - neuron2.combinator = newCombinator; - } + if (this.currentNucleus is Neuron neuron2) { + Neuron.CombinatorType newCombinator = (Neuron.CombinatorType)EditorGUILayout.EnumPopup("Combinator", neuron2.combinator); + anythingChanged |= newCombinator != neuron2.combinator; + neuron2.combinator = newCombinator; + } - Vector3 newBias = EditorGUILayout.Vector3Field("Bias", this.currentNucleus.bias); - anythingChanged |= newBias != this.currentNucleus.bias; - this.currentNucleus.bias = newBias; + Vector3 newBias = EditorGUILayout.Vector3Field("Bias", this.currentNucleus.bias); + anythingChanged |= newBias != this.currentNucleus.bias; + this.currentNucleus.bias = newBias; - NucleusArray array = null; - if (this.currentNucleus.synapses.Count > 0) { - Synapse[] synapses = this.currentNucleus.synapses.ToArray(); - foreach (Synapse synapse in synapses) { - if (synapse.nucleus != null) { - if (array != null) { - if (array.nuclei.Contains(synapse.nucleus)) - continue; - } - else { - if (synapse.nucleus is Receptor receptor2 && receptor2.array != null && receptor2.array.nuclei.Length > 1) - array = receptor2.array; - } - - EditorGUILayout.Space(); - - if (Application.isPlaying) { - Vector3 value = synapse.nucleus.outputValue * synapse.weight; - GUIContent synapseValueLabel = new(synapse.nucleus.name, synapse.nucleus.outputValue.ToString()); - EditorGUILayout.FloatField(synapseValueLabel, length(synapse.nucleus.outputValue)); - } - else { - EditorGUILayout.BeginHorizontal(); - EditorGUILayout.LabelField(synapse.nucleus.name); - if (GUILayout.Button("Disconnect")) { - synapse.nucleus.RemoveReceiver(this.currentNucleus); - this.prefab.GarbageCollection(); - anythingChanged = true; + NucleusArray array = null; + if (this.currentNucleus.synapses.Count > 0) { + Synapse[] synapses = this.currentNucleus.synapses.ToArray(); + foreach (Synapse synapse in synapses) { + if (synapse.nucleus != null) { + if (array != null) { + if (array.nuclei.Contains(synapse.nucleus)) + continue; + } + else { + if (synapse.nucleus is Receptor receptor2 && receptor2.array != null && receptor2.array.nuclei.Length > 1) + array = receptor2.array; } - EditorGUILayout.EndHorizontal(); - } - EditorGUI.indentLevel++; - synapse.weight = EditorGUILayout.FloatField("Weight", synapse.weight); - EditorGUI.indentLevel--; + EditorGUILayout.Space(); + + if (Application.isPlaying) { + Vector3 value = synapse.nucleus.outputValue * synapse.weight; + GUIContent synapseValueLabel = new(synapse.nucleus.name, synapse.nucleus.outputValue.ToString()); + EditorGUILayout.FloatField(synapseValueLabel, length(synapse.nucleus.outputValue)); + } + else { + EditorGUILayout.BeginHorizontal(); + + if (synapse.nucleus.parent != null && synapse.nucleus.parent != this.currentNucleus) { + GUIStyle labelStyle = new(GUI.skin.label); + float labelWidth = labelStyle.CalcSize(new GUIContent($"{synapse.nucleus.clusterPrefab.name}.")).x; + EditorGUILayout.LabelField($"{synapse.nucleus.clusterPrefab.name}", GUILayout.Width(labelWidth)); + string[] options = synapse.nucleus.parent.nuclei.Select(n => n.name).ToArray(); + int selectedIndex = System.Array.IndexOf(options, synapse.nucleus.name); + int newIndex = EditorGUILayout.Popup(selectedIndex, options); + if (newIndex != selectedIndex) { + ChangeSynapse(synapse, synapse.nucleus.parent.nuclei[newIndex]); + } + } + else + EditorGUILayout.LabelField(synapse.nucleus.name); + if (GUILayout.Button("Disconnect")) { + synapse.nucleus.RemoveReceiver(this.currentNucleus); + this.prefab.GarbageCollection(); + anythingChanged = true; + } + EditorGUILayout.EndHorizontal(); + } + + EditorGUI.indentLevel++; + synapse.weight = EditorGUILayout.FloatField("Weight", synapse.weight); + EditorGUI.indentLevel--; + } } } } + EditorGUILayout.EndFoldoutHeaderGroup(); } - EditorGUILayout.EndFoldoutHeaderGroup(); - // Activation EditorGUILayout.Space(); @@ -831,6 +861,8 @@ public class ClusterInspector : Editor { } } + #region Synapses + protected virtual void AddInput(Nucleus.Type selectedType, Nucleus nucleus) { switch (selectedType) { case Nucleus.Type.Neuron: @@ -869,30 +901,6 @@ public class ClusterInspector : Editor { BuildLayers(); } - protected virtual void DeleteNeuron(Nucleus nucleus) { - if (nucleus == null) - return; - - foreach (Nucleus receiver in nucleus.receivers) { - if (receiver != null) { - this.currentNucleus = receiver; - break; - } - } - this.prefab.nuclei.Remove(nucleus); - - if (outputsField.value == nucleus.name) { - this.prefab.RefreshOutputs(); - outputsField.choices = this.prefab.outputs.Select(output => output.name).ToList(); - outputsField.index = 0; - } - - Neuron.Delete(nucleus); - - this.currentNucleus = this.prefab.output; - BuildLayers(); - } - protected void AddSelectorInput(Nucleus nucleus) { Selector newSelector = new(this.prefab, "New Selector"); newSelector.AddReceiver(nucleus); @@ -933,19 +941,16 @@ public class ClusterInspector : Editor { } protected virtual void AddClusterReceptorInput(Nucleus nucleus) { - ClusterPickerWindow.ShowPicker(brain => OnClusterReceptorPicked(nucleus, brain), "Select Cluster"); + ClusterPickerWindow.ShowPicker(prefab => OnClusterReceptorPicked(nucleus, prefab), "Select Cluster"); } private void OnClusterPicked(Nucleus nucleus, ClusterPrefab prefab) { Cluster subclusterInstance = new(prefab, this.prefab); subclusterInstance.AddReceiver(nucleus); - // This does not work somehow - // this.currentNucleus = subclusterInstance; - // BuildLayers(); } - private void OnClusterReceptorPicked(Nucleus nucleus, ClusterPrefab prefab) { - ClusterReceptor clusterInstance = new(prefab, this.prefab, "New " + prefab.name); + private void OnClusterReceptorPicked(Nucleus nucleus, ClusterPrefab selectedPrefab) { + ClusterReceptor clusterInstance = new(selectedPrefab, this.prefab, "New " + selectedPrefab.name); clusterInstance.AddReceiver(nucleus); } @@ -980,6 +985,30 @@ public class ClusterInspector : Editor { return true; } + protected virtual void DeleteNeuron(Nucleus nucleus) { + if (nucleus == null) + return; + + foreach (Nucleus receiver in nucleus.receivers) { + if (receiver != null) { + this.currentNucleus = receiver; + break; + } + } + this.prefab.nuclei.Remove(nucleus); + + if (outputsField.value == nucleus.name) { + this.prefab.RefreshOutputs(); + outputsField.choices = this.prefab.outputs.Select(output => output.name).ToList(); + outputsField.index = 0; + } + + Neuron.Delete(nucleus); + + this.currentNucleus = this.prefab.output; + BuildLayers(); + } + protected virtual bool AddSynapse(ClusterPrefab cluster, Nucleus nucleus) { if (cluster == null) return false; @@ -992,18 +1021,25 @@ public class ClusterInspector : Editor { return true; } + protected virtual void ChangeSynapse(Synapse synapse, Nucleus newNucleus) { + synapse.nucleus.RemoveReceiver(this.currentNucleus); + newNucleus.AddReceiver(this.currentNucleus); + } + protected virtual void DisconnectNucleus(Neuron nucleus) { - if (this.currentNucleus.cluster == null) + if (this.currentNucleus.clusterPrefab == null) return; string[] names = this.currentNucleus.synapses.Select(synapse => synapse.nucleus.name).ToArray(); int selectedIndex = -1; selectedIndex = EditorGUILayout.Popup("Disconnect from", selectedIndex, names); //if (selectedIndex >= 0 && selectedIndex < this.currentNucleus.brain.perceptei.Count) { - if (selectedIndex >= 0 && selectedIndex < this.currentNucleus.cluster.nuclei.Count) { + if (selectedIndex >= 0 && selectedIndex < this.currentNucleus.clusterPrefab.nuclei.Count) { Synapse synapse = this.currentNucleus.synapses[selectedIndex]; synapse.nucleus.RemoveReceiver(this.currentNucleus); } } + + #endregion Synapses } #endregion Start diff --git a/Neuron.cs b/Neuron.cs index 315fc21..6fa9d4b 100644 --- a/Neuron.cs +++ b/Neuron.cs @@ -15,10 +15,10 @@ public class Neuron : Nucleus { this.parent?.nuclei.Add(this); } public Neuron(ClusterPrefab prefab, string name) { - this.cluster = prefab; + this.clusterPrefab = prefab; this.name = name; - if (this.cluster != null) - this.cluster.nuclei.Add(this); + if (this.clusterPrefab != null) + this.clusterPrefab.nuclei.Add(this); else Debug.LogError("No prefab when adding neuron to prefab"); } @@ -139,7 +139,7 @@ public class Neuron : Nucleus { } protected virtual void CloneFields(Neuron clone) { - // clone.array = this.array; + clone.clusterPrefab = this.clusterPrefab; clone.bias = this.bias; clone.combinator = this.combinator; clone.curve = this.curve; @@ -165,9 +165,9 @@ public class Neuron : Nucleus { receiver.synapses.RemoveAll(s => s.nucleus == nucleus); } - if (nucleus.cluster != null) { - nucleus.cluster.nuclei.RemoveAll(n => n == nucleus); - nucleus.cluster.GarbageCollection(); + if (nucleus.clusterPrefab != null) { + nucleus.clusterPrefab.nuclei.RemoveAll(n => n == nucleus); + nucleus.clusterPrefab.GarbageCollection(); } } diff --git a/Nucleus.cs b/Nucleus.cs index 1a014b3..e659008 100644 --- a/Nucleus.cs +++ b/Nucleus.cs @@ -8,9 +8,10 @@ using static Unity.Mathematics.math; public abstract class Nucleus { public string name; - //[Obsolete] - public ClusterPrefab cluster { get; set; } - public Cluster parent { get; set; } + [SerializeReference] + public ClusterPrefab clusterPrefab; + [SerializeReference] + public Cluster parent; protected float3 _outputValue; public virtual float3 outputValue { @@ -73,7 +74,7 @@ public abstract class Nucleus { [SerializeReference] private List _receivers = new(); - public List receivers { + public virtual List receivers { get { return _receivers; } set { _receivers = value; } } diff --git a/ReceptorArray.cs b/ReceptorArray.cs index 3ac96aa..c8441db 100644 --- a/ReceptorArray.cs +++ b/ReceptorArray.cs @@ -13,7 +13,7 @@ public class ReceptorInstance : Nucleus { // We explicitly do not add this to the parent, as it is serialized in the ReceptorArray } public ReceptorInstance(ClusterPrefab prefab, string name) { - this.cluster = prefab; + this.clusterPrefab = prefab; this.name = name; // We explicitly do not add this to the prefab, as it is serialized in the ReceptorArray } @@ -49,14 +49,14 @@ public class ReceptorArray : Nucleus { this.parent?.nuclei.Add(this); } public ReceptorArray(ClusterPrefab prefab, string name) { - this.cluster = prefab; + this.clusterPrefab = prefab; this.name = name; this._instances = new ReceptorInstance[1]; this._instances[0] = new ReceptorInstance(prefab, this.name + "[0]") { receptor = this }; - if (this.cluster != null) - this.cluster.nuclei.Add(this); + if (this.clusterPrefab != null) + this.clusterPrefab.nuclei.Add(this); else Debug.LogError("No prefab when adding receptor to prefab");