Improved receptor

This commit is contained in:
Pascal Serrarens 2026-02-12 17:02:27 +01:00
parent 537064d84b
commit 19398ade98
2 changed files with 143 additions and 71 deletions

View File

@ -263,35 +263,69 @@ public class ClusterInspector : Editor {
// Draw selected Nucleus // Draw selected Nucleus
if (expandArray) { if (expandArray) {
float maxValue = 0; if (this.currentNucleus is Receptor receptor) {
foreach (Nucleus nucleus in this.currentNucleus.array.nuclei) { float maxValue = 0;
float value = length(nucleus.outputValue); foreach (Nucleus nucleus in receptor.instances) {
if (value > maxValue) float value = length(nucleus.outputValue);
maxValue = value; if (value > maxValue)
} maxValue = value;
}
float spacing = 400f / this.currentNucleus.array.nuclei.Count(); float spacing = 400f / receptor.instances.Count();
float margin = 10 + spacing / 2; float margin = 10 + spacing / 2;
float xMin = 150 - size; float xMin = 150 - size;
float xMax = 150 + size; float xMax = 150 + size;
float yMin = 10 + margin - size / 2; float yMin = 10 + margin - size / 2;
float yMax = 400 - margin + size; float yMax = 400 - margin + size;
Vector3[] verts = new Vector3[4] { Vector3[] verts = new Vector3[4] {
new(xMin, yMin, 0), new(xMin, yMin, 0),
new(xMax, yMin, 0), new(xMax, yMin, 0),
new(xMax, yMax, 0), new(xMax, yMax, 0),
new(xMin, yMax, 0) new(xMin, yMax, 0)
}; };
Handles.color = Color.black; Handles.color = Color.black;
Handles.DrawAAConvexPolygon(verts); Handles.DrawAAConvexPolygon(verts);
int row = 0; int row = 0;
foreach (Nucleus nucleus in this.currentNucleus.array.nuclei) { foreach (Nucleus nucleus in receptor.instances) {
Vector3 pos = new(150, margin + row * spacing, 0.0f); Vector3 pos = new(150, margin + row * spacing, 0.0f);
Handles.color = Color.white; Handles.color = Color.white;
// The selected nucleus highlight ring // The selected nucleus highlight ring
Handles.DrawSolidDisc(pos, Vector3.forward, size + 2); Handles.DrawSolidDisc(pos, Vector3.forward, size + 2);
DrawNucleus(nucleus, pos, maxValue, size); DrawNucleus(nucleus, pos, maxValue, size);
row++; row++;
}
}
else {
float maxValue = 0;
foreach (Nucleus nucleus in this.currentNucleus.array.nuclei) {
float value = length(nucleus.outputValue);
if (value > maxValue)
maxValue = value;
}
float spacing = 400f / this.currentNucleus.array.nuclei.Count();
float margin = 10 + spacing / 2;
float xMin = 150 - size;
float xMax = 150 + size;
float yMin = 10 + margin - size / 2;
float yMax = 400 - margin + size;
Vector3[] verts = new Vector3[4] {
new(xMin, yMin, 0),
new(xMax, yMin, 0),
new(xMax, yMax, 0),
new(xMin, yMax, 0)
};
Handles.color = Color.black;
Handles.DrawAAConvexPolygon(verts);
int row = 0;
foreach (Nucleus nucleus in this.currentNucleus.array.nuclei) {
Vector3 pos = new(150, margin + row * spacing, 0.0f);
Handles.color = Color.white;
// The selected nucleus highlight ring
Handles.DrawSolidDisc(pos, Vector3.forward, size + 2);
DrawNucleus(nucleus, pos, maxValue, size);
row++;
}
} }
} }
else { else {
@ -431,9 +465,20 @@ public class ClusterInspector : Editor {
if ((!expandArray || nucleus.array.nuclei.First() != this.currentNucleus) && nucleus.array.nuclei.Count() > 1) { if ((!expandArray || nucleus.array.nuclei.First() != this.currentNucleus) && nucleus.array.nuclei.Count() > 1) {
Handles.Label(labelPosition, nucleus.array.nuclei.Count().ToString(), style); Handles.Label(labelPosition, nucleus.array.nuclei.Count().ToString(), style);
} }
if (!expandArray && nucleus is ReceptorArray receptor) { if (nucleus is Receptor receptor) {
Handles.Label(labelPosition, receptor.receptors.Count().ToString(), style); Handles.Label(labelPosition, receptor.instances.Count().ToString(), style);
} }
// else if (nucleus is ReceptorInstance receptorI) {
// if (expandArray) {
// int arrayIx = 0;
// foreach (ReceptorInstance n in receptorI.receptorArray.receptors) {
// if (n == receptorI)
// break;
// arrayIx++;
// }
// Handles.Label(labelPosition, $"[{arrayIx}]", style);
// }
// }
if (expandArray && nucleus.array.nuclei.First() == this.currentNucleus) { if (expandArray && nucleus.array.nuclei.First() == this.currentNucleus) {
int arrayIx = 0; int arrayIx = 0;
foreach (Nucleus n in nucleus.array.nuclei) { foreach (Nucleus n in nucleus.array.nuclei) {
@ -494,10 +539,12 @@ public class ClusterInspector : Editor {
private void HandleClicked(Nucleus nucleus) { private void HandleClicked(Nucleus nucleus) {
if (nucleus == this.currentNucleus) { if (nucleus == this.currentNucleus) {
if (nucleus is Nucleus n) { expandArray = !expandArray;
expandArray = !expandArray; }
return; else if (nucleus is ReceptorInstance receptor) {
} expandArray = false;
this.currentNucleus = receptor.receptor;
BuildLayers();
} }
else if (nucleus is Nucleus n) { else if (nucleus is Nucleus n) {
this.currentNucleus = n; this.currentNucleus = n;
@ -562,9 +609,9 @@ public class ClusterInspector : Editor {
if (this.currentNucleus is MemoryCell memory) { if (this.currentNucleus is MemoryCell memory) {
memory.staticMemory = EditorGUILayout.Toggle("Static Memory", memory.staticMemory); memory.staticMemory = EditorGUILayout.Toggle("Static Memory", memory.staticMemory);
} }
if (this.currentNucleus is ReceptorArray receptor) { if (this.currentNucleus is Receptor receptor) {
EditorGUILayout.BeginHorizontal(); EditorGUILayout.BeginHorizontal();
EditorGUILayout.IntField("Receptor size", receptor.receptors.Count()); EditorGUILayout.IntField("Receptor size", receptor.instances.Count());
if (GUILayout.Button("Add")) { if (GUILayout.Button("Add")) {
Undo.RecordObject(prefabAsset, "Receptor add " + prefabAsset.name); Undo.RecordObject(prefabAsset, "Receptor add " + prefabAsset.name);
receptor.AddReceptor(this.prefab); receptor.AddReceptor(this.prefab);
@ -621,8 +668,11 @@ public class ClusterInspector : Editor {
else { else {
EditorGUILayout.BeginHorizontal(); EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(synapse.nucleus.name); EditorGUILayout.LabelField(synapse.nucleus.name);
if (GUILayout.Button("Disconnect")) if (GUILayout.Button("Disconnect")) {
synapse.nucleus.RemoveReceiver(this.currentNucleus); synapse.nucleus.RemoveReceiver(this.currentNucleus);
this.prefab.GarbageCollection();
anythingChanged = true;
}
EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal();
} }
@ -701,8 +751,8 @@ public class ClusterInspector : Editor {
void OnSceneGUI(SceneView sceneView) { void OnSceneGUI(SceneView sceneView) {
if (this.gameObject != null) { if (this.gameObject != null) {
if (this.currentNucleus is ReceptorArray receptor) { if (this.currentNucleus is Receptor receptor && expandArray) {
foreach (Nucleus nucleus in receptor.receptors) { foreach (Nucleus nucleus in receptor.instances) {
Vector3 worldVector = this.gameObject.transform.TransformVector(nucleus.outputValue); Vector3 worldVector = this.gameObject.transform.TransformVector(nucleus.outputValue);
Handles.color = Color.yellow; Handles.color = Color.yellow;
Handles.DrawLine(this.gameObject.transform.position, this.gameObject.transform.position + worldVector); Handles.DrawLine(this.gameObject.transform.position, this.gameObject.transform.position + worldVector);
@ -819,7 +869,7 @@ public class ClusterInspector : Editor {
} }
protected virtual void AddReceptorInput(Nucleus nucleus) { protected virtual void AddReceptorInput(Nucleus nucleus) {
ReceptorArray newReceptor = new(this.prefab, "New Receptor"); Receptor newReceptor = new(this.prefab, "New Receptor");
newReceptor.AddReceiver(nucleus); newReceptor.AddReceiver(nucleus);
this.currentNucleus = newReceptor; this.currentNucleus = newReceptor;
BuildLayers(); BuildLayers();

View File

@ -18,31 +18,43 @@ public class ReceptorInstance : Nucleus {
// We explicitly do not add this to the prefab, as it is serialized in the ReceptorArray // We explicitly do not add this to the prefab, as it is serialized in the ReceptorArray
} }
public override Nucleus ShallowCloneTo(Cluster parent) { public override Nucleus ShallowCloneTo(Cluster parent) {
ReceptorInstance clone = new(parent, name + " +1"); ReceptorInstance clone = new(parent, name + " +1") {
receptor = this.receptor
};
return clone; return clone;
} }
public override Nucleus Clone(ClusterPrefab prefab) { public override Nucleus Clone(ClusterPrefab prefab) {
ReceptorInstance clone = new(prefab, name); ReceptorInstance clone = new(prefab, name) {
receptor = this.receptor
};
return clone; return clone;
} }
[SerializeReference]
public Receptor receptor;
public override void UpdateStateIsolated() { public override void UpdateStateIsolated() {
} }
} }
[Serializable] [Serializable]
public class ReceptorArray : Nucleus { public class Receptor : Nucleus {
public ReceptorArray(Cluster parent, string name) { public Receptor(Cluster parent, string name) {
this.parent = parent; this.parent = parent;
this.name = name; this.name = name;
this._receptors = new ReceptorInstance[1]; this._instances = new ReceptorInstance[1];
this._receptors[0] = new ReceptorInstance(parent, this.name + "[0]"); this._instances[0] = new ReceptorInstance(parent, this.name + "[0]") {
receptor = this
};
this.parent?.nuclei.Add(this); this.parent?.nuclei.Add(this);
} }
public ReceptorArray(ClusterPrefab prefab, string name) { public Receptor(ClusterPrefab prefab, string name) {
this.cluster = prefab; this.cluster = prefab;
this.name = name; this.name = name;
this._receptors = new ReceptorInstance[1]; this._instances = new ReceptorInstance[1];
this._receptors[0] = new ReceptorInstance(prefab, this.name + "[0]"); this._instances[0] = new ReceptorInstance(prefab, this.name + "[0]") {
receptor = this
};
if (this.cluster != null) if (this.cluster != null)
this.cluster.nuclei.Add(this); this.cluster.nuclei.Add(this);
else else
@ -51,22 +63,26 @@ public class ReceptorArray : Nucleus {
} }
public override Nucleus ShallowCloneTo(Cluster parent) { public override Nucleus ShallowCloneTo(Cluster parent) {
ReceptorArray clone = new(parent, name) { Receptor clone = new(parent, name) {
_receptors = new ReceptorInstance[this.receptors.Length] _instances = new ReceptorInstance[this.instances.Length]
}; };
for (int ix = 0; ix < this.receptors.Length; ix++) { for (int ix = 0; ix < this.instances.Length; ix++) {
clone._receptors[ix] = new ReceptorInstance(parent, $"{this.name} [{ix}]"); clone._instances[ix] = new ReceptorInstance(parent, $"{this.name} [{ix}]") {
receptor = clone
};
} }
return clone; return clone;
} }
public override Nucleus Clone(ClusterPrefab prefab) { public override Nucleus Clone(ClusterPrefab prefab) {
ReceptorArray clone = new(prefab, this.name) { Receptor clone = new(prefab, this.name) {
_instances = new ReceptorInstance[this.instances.Length]
}; };
clone._receptors = new ReceptorInstance[this.receptors.Length]; for (int ix = 0; ix < this.instances.Length; ix++) {
for (int ix = 0; ix < this.receptors.Length; ix++) { clone._instances[ix] = new ReceptorInstance(prefab, this.name) {
clone._receptors[ix] = new ReceptorInstance(prefab, this.name); receptor = this
};
} }
foreach (Synapse synapse in this.synapses) { foreach (Synapse synapse in this.synapses) {
@ -80,42 +96,44 @@ public class ReceptorArray : Nucleus {
} }
[SerializeReference] [SerializeReference]
private ReceptorInstance[] _receptors; private ReceptorInstance[] _instances;
public ReceptorInstance[] receptors { public ReceptorInstance[] instances {
get { get {
return _receptors; return _instances;
} }
} }
public void AddReceptor(ClusterPrefab prefab) { public void AddReceptor(ClusterPrefab prefab) {
if (this._receptors.Length == 0) { if (this._instances.Length == 0) {
Debug.LogError("Empty receptor array, cannot add"); Debug.LogError("Empty receptor array, cannot add");
return; return;
} }
int newLength = this._receptors.Length + 1; int newLength = this._instances.Length + 1;
ReceptorInstance[] newArray = new ReceptorInstance[newLength]; ReceptorInstance[] newArray = new ReceptorInstance[newLength];
for (int i = 0; i < this._receptors.Length; i++) for (int i = 0; i < this._instances.Length; i++)
newArray[i] = this._receptors[i]; newArray[i] = this._instances[i];
newArray[newLength - 1] = (ReceptorInstance)this._receptors[0].Clone(prefab); ReceptorInstance newReceptor = (ReceptorInstance)this._instances[0].Clone(prefab);
newReceptor.name = $"{this.name} [{this._instances.Length}]";
newArray[newLength - 1] = newReceptor;
this._receptors = newArray; this._instances = newArray;
} }
public void RemoveReceptor() { public void RemoveReceptor() {
int newLength = this._receptors.Length - 1; int newLength = this._instances.Length - 1;
if (newLength == 0) { if (newLength == 0) {
Debug.LogWarning("Receptor array cannot be empty"); Debug.LogWarning("Receptor array cannot be empty");
return; return;
} }
ReceptorInstance[] newPerceptei = new ReceptorInstance[newLength]; ReceptorInstance[] newPerceptei = new ReceptorInstance[newLength];
for (int i = 0; i < newLength; i++) for (int i = 0; i < newLength; i++)
newPerceptei[i] = this._receptors[i]; newPerceptei[i] = this._instances[i];
// Delete the last perception // Delete the last perception
if (this._receptors[newLength] is Nucleus nucleus) if (this._instances[newLength] is Nucleus nucleus)
Neuron.Delete(nucleus); //this._nuclei[newLength]); Neuron.Delete(nucleus); //this._nuclei[newLength]);
this._receptors = newPerceptei; this._instances = newPerceptei;
} }
private Dictionary<int, Nucleus> thingReceivers = new(); private Dictionary<int, Nucleus> thingReceivers = new();
@ -152,6 +170,8 @@ public class ReceptorArray : Nucleus {
private void CleanupReceivers() { private void CleanupReceivers() {
// Remove a thing-receiver connection when the nucleus is inactive // Remove a thing-receiver connection when the nucleus is inactive
List<int> receiversToRemove = new(); List<int> receiversToRemove = new();
thingReceivers ??= new();
foreach (KeyValuePair<int, Nucleus> item in thingReceivers) { foreach (KeyValuePair<int, Nucleus> item in thingReceivers) {
if (item.Value.isSleeping) if (item.Value.isSleeping)
receiversToRemove.Add(item.Key); receiversToRemove.Add(item.Key);
@ -166,6 +186,7 @@ public class ReceptorArray : Nucleus {
if (colonPos > 0) if (colonPos > 0)
selectedReceiver.name = selectedReceiver.name[..colonPos]; selectedReceiver.name = selectedReceiver.name[..colonPos];
} }
} }
private Nucleus SelectReceptor(int thingId, float3 inputValue) { private Nucleus SelectReceptor(int thingId, float3 inputValue) {
@ -173,7 +194,7 @@ public class ReceptorArray : Nucleus {
float inputMagnitude = length(inputValue); float inputMagnitude = length(inputValue);
Nucleus selectedReceiver = null; Nucleus selectedReceiver = null;
float selectedMagnitude = 0; float selectedMagnitude = 0;
foreach (Nucleus receiver in this._receptors) { foreach (Nucleus receiver in this._instances) {
if (thingReceivers.ContainsValue(receiver) == false) { if (thingReceivers.ContainsValue(receiver) == false) {
// We found an unusued receiver // We found an unusued receiver
Debug.Log($"{thingId} -> [{receiver.name}]"); Debug.Log($"{thingId} -> [{receiver.name}]");
@ -217,21 +238,22 @@ public class ReceptorArray : Nucleus {
float3 sum = this.bias; float3 sum = this.bias;
// Receptors do not have inputs, so we ignore the synapses // Receptors do not have inputs, so we ignore the synapses
foreach (Nucleus nucleus in this._receptors) foreach (Nucleus nucleus in this._instances)
sum += nucleus.outputValue; sum += nucleus.outputValue;
this.outputValue = sum / _receptors.Length; this.outputValue = sum / _instances.Length;
this.stale = 0; this.stale = 0;
UpdateNuclei(); UpdateNuclei();
} }
public override void UpdateNuclei() { public override void UpdateNuclei() {
foreach (Nucleus nucleus in this.receptors) { foreach (Nucleus nucleus in this.instances) {
nucleus.stale++; nucleus.stale++;
if (nucleus.stale > staleValueForSleep && lengthsq(nucleus.outputValue) > 0) { if (nucleus.stale > staleValueForSleep && lengthsq(nucleus.outputValue) > 0) {
nucleus.outputValue = Vector3.zero; nucleus.outputValue = Vector3.zero;
this.UpdateStateIsolated(); //this.UpdateStateIsolated();
this.parent.UpdateFromNucleus(this);
} }
} }
} }