Improved clusterreceptor

This commit is contained in:
Pascal Serrarens 2026-02-16 17:23:52 +01:00
parent 5fea9880de
commit a394f582cf
6 changed files with 179 additions and 120 deletions

View File

@ -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;
}

View File

@ -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<Nucleus> _clusterReceivers = null;
public override List<Nucleus> 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;

View File

@ -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

View File

@ -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();
}
}

View File

@ -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<Nucleus> _receivers = new();
public List<Nucleus> receivers {
public virtual List<Nucleus> receivers {
get { return _receivers; }
set { _receivers = value; }
}

View File

@ -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");