Further improve receptor / nucleusarray combination

This commit is contained in:
Pascal Serrarens 2026-02-13 14:52:17 +01:00
parent 9b948fdd07
commit f295da9c55
5 changed files with 91 additions and 172 deletions

View File

@ -263,7 +263,7 @@ public class ClusterInspector : Editor {
// Draw selected Nucleus // Draw selected Nucleus
if (expandArray) { if (expandArray) {
if (this.currentNucleus is Receptor receptor) { if (this.currentNucleus is ReceptorArray receptor) {
float maxValue = 0; float maxValue = 0;
foreach (Nucleus nucleus in receptor.instances) { foreach (Nucleus nucleus in receptor.instances) {
float value = length(nucleus.outputValue); float value = length(nucleus.outputValue);
@ -467,7 +467,7 @@ 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 (nucleus is Receptor receptor) { if (nucleus is ReceptorArray receptor) {
Handles.Label(labelPosition, receptor.instances.Count().ToString(), style); Handles.Label(labelPosition, receptor.instances.Count().ToString(), style);
} }
// else if (nucleus is ReceptorInstance receptorI) { // else if (nucleus is ReceptorInstance receptorI) {
@ -611,7 +611,7 @@ 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 Receptor receptor) { if (this.currentNucleus is ReceptorArray receptor) {
EditorGUILayout.BeginHorizontal(); EditorGUILayout.BeginHorizontal();
EditorGUILayout.IntField("Receptor size", receptor.instances.Count()); EditorGUILayout.IntField("Receptor size", receptor.instances.Count());
if (GUILayout.Button("Add")) { if (GUILayout.Button("Add")) {
@ -626,6 +626,22 @@ public class ClusterInspector : Editor {
} }
EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal();
} }
if (this.currentNucleus is Receptor receptor1) {
EditorGUILayout.BeginHorizontal();
EditorGUILayout.IntField("Array size", receptor1.array.nuclei.Count());
if (GUILayout.Button("Add")) {
Undo.RecordObject(prefabAsset, "Array add " + prefabAsset.name);
receptor1.array.AddNucleus(this.prefab);
anythingChanged = true;
}
if (GUILayout.Button("Del")) {
Undo.RecordObject(prefabAsset, "Array delete " + prefabAsset.name);
receptor1.array.RemoveNucleus();
anythingChanged = true;
}
EditorGUILayout.EndHorizontal();
}
// Synapses // Synapses
@ -707,19 +723,22 @@ public class ClusterInspector : Editor {
} }
if (neuron.array == null || neuron.array.nuclei == null || neuron.array.nuclei.Count() == 0) if (neuron.array == null || neuron.array.nuclei == null || neuron.array.nuclei.Count() == 0)
neuron.array = new NucleusArray(neuron); neuron.array = new NucleusArray(neuron);
EditorGUILayout.BeginHorizontal();
EditorGUILayout.IntField("Array size", neuron.array.nuclei.Count()); // if (this.currentNucleus is Receptor receptor1) {
if (GUILayout.Button("Add")) { // EditorGUILayout.BeginHorizontal();
Undo.RecordObject(prefabAsset, "Array add " + prefabAsset.name); // EditorGUILayout.IntField("Array size", receptor1.array.nuclei.Count());
neuron.array.AddNucleus(this.prefab); // if (GUILayout.Button("Add")) {
anythingChanged = true; // Undo.RecordObject(prefabAsset, "Array add " + prefabAsset.name);
} // receptor1.array.AddNucleus(this.prefab);
if (GUILayout.Button("Del")) { // anythingChanged = true;
Undo.RecordObject(prefabAsset, "Array delete " + prefabAsset.name); // }
neuron.array.RemoveNucleus(); // if (GUILayout.Button("Del")) {
anythingChanged = true; // Undo.RecordObject(prefabAsset, "Array delete " + prefabAsset.name);
} // receptor1.array.RemoveNucleus();
EditorGUILayout.EndHorizontal(); // anythingChanged = true;
// }
// EditorGUILayout.EndHorizontal();
// }
} }
EditorGUILayout.Space(); EditorGUILayout.Space();
@ -753,7 +772,7 @@ public class ClusterInspector : Editor {
void OnSceneGUI(SceneView sceneView) { void OnSceneGUI(SceneView sceneView) {
if (this.gameObject != null) { if (this.gameObject != null) {
if (this.currentNucleus is Receptor receptor && expandArray) { if (this.currentNucleus is ReceptorArray receptor && expandArray) {
foreach (Nucleus nucleus in receptor.instances) { 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;
@ -791,6 +810,9 @@ public class ClusterInspector : Editor {
case Nucleus.Type.Receptor: case Nucleus.Type.Receptor:
AddReceptorInput(nucleus); AddReceptorInput(nucleus);
break; break;
case Nucleus.Type.ReceptorArray:
AddReceptorArrayInput(nucleus);
break;
default: default:
break; break;
} }
@ -877,6 +899,13 @@ public class ClusterInspector : Editor {
BuildLayers(); BuildLayers();
} }
protected virtual void AddReceptorArrayInput(Nucleus nucleus) {
ReceptorArray newReceptor = new(this.prefab, "New Receptor");
newReceptor.AddReceiver(nucleus);
this.currentNucleus = newReceptor;
BuildLayers();
}
private void OnClusterPicked(Nucleus nucleus, ClusterPrefab prefab) { private void OnClusterPicked(Nucleus nucleus, ClusterPrefab prefab) {
Cluster subclusterInstance = new(prefab, this.prefab); Cluster subclusterInstance = new(prefab, this.prefab);
subclusterInstance.AddReceiver(nucleus); subclusterInstance.AddReceiver(nucleus);

View File

@ -14,8 +14,8 @@ public class Neuron : Nucleus {
this.name = name; this.name = name;
this.parent?.nuclei.Add(this); this.parent?.nuclei.Add(this);
} }
public Neuron(ClusterPrefab parent, string name) { public Neuron(ClusterPrefab prefab, string name) {
this.cluster = parent; this.cluster = prefab;
this.name = name; this.name = name;
if (this.cluster != null) if (this.cluster != null)
this.cluster.nuclei.Add(this); this.cluster.nuclei.Add(this);

View File

@ -41,7 +41,8 @@ public abstract class Nucleus {
Selector, Selector,
Cluster, Cluster,
Pulsar, Pulsar,
Receptor Receptor,
ReceptorArray
} }
#region Synapses #region Synapses
@ -111,6 +112,10 @@ public abstract class Nucleus {
} }
} }
public virtual void ProcessStimulus(Vector3 inputValue, int thingId = 0, string thingName = null) {
this.array.ProcessStimulus(thingId, inputValue, thingName);
}
public virtual void ProcessStimulus(int thingId, Vector3 inputValue, string thingName = null) { public virtual void ProcessStimulus(int thingId, Vector3 inputValue, string thingName = null) {
this.array.ProcessStimulus(thingId, inputValue, thingName); this.array.ProcessStimulus(thingId, inputValue, thingName);
} }

View File

@ -1,159 +1,43 @@
/*
using System;
using System.Collections.Generic;
using UnityEngine; using UnityEngine;
using Unity.Mathematics; using Unity.Mathematics;
using static Unity.Mathematics.math; using static Unity.Mathematics.math;
public class Receptor : IReceptor { [System.Serializable]
public class Receptor : Neuron {
public Receptor(Cluster parent, string name) : base(parent, name) { }
public Receptor(ClusterPrefab prefab, string name) : base(prefab, name) { }
[SerializeField] public override Nucleus ShallowCloneTo(Cluster parent) {
protected string _name; Receptor clone = new(parent, name);
public virtual string name {
get => _name;
set => _name = value;
}
public Receptor(Cluster parent) {
this.parent = parent;
parent?.nuclei.Add(this);
}
public Receptor(Cluster parent, string name, string nucleusName) {
this.parent = parent ?? throw new ArgumentNullException(nameof(parent), "Parent cannot be null.");
this.name = name;
this.parent.nuclei.Add(this);
foreach (INucleus nucleus in parent.inputs) {
if (nucleus != null && nucleus.name == nucleusName) {
this.AddReceiver(nucleus);
}
}
}
private readonly Cluster parent;
public virtual IReceptor ShallowCloneTo(Cluster parent) {
Receptor clone = new(parent);
return clone; return clone;
} }
public override Nucleus Clone(ClusterPrefab prefab) {
public virtual IReceptor Clone() { Receptor clone = new(prefab, name);
Receptor clone = new(this.parent); CloneFields(clone);
// Adding receivers will also add synapses to the receivers
foreach (INucleus receiver in this.receivers) { foreach (Nucleus receiver in this.receivers)
clone.AddReceiver(receiver); clone.AddReceiver(receiver);
}
return clone; return clone;
} }
class Receiver { // [SerializeReference]
public INucleus nucleus; // private NucleusArray _array;
public int thingId; // public NucleusArray array {
public string thingName; // get { return _array; }
public Receiver(INucleus nucleus, int thingId, string thingName) { // set { _array = value; }
this.nucleus = nucleus; // }
this.thingId = thingId;
this.thingName = thingName;
} public override void UpdateStateIsolated() {
this.outputValue = this.bias;
} }
[SerializeReference] public override void UpdateNuclei() {
private List<INucleus> _receivers = new();
public List<INucleus> receivers {
get { return _receivers; }
set { _receivers = value; }
}
protected int[] thingIds; // every receiver can handle a thing with this id
public virtual void AddReceiver(INucleus receivingNucleus, float weight = 1) {
this._receivers.Add(receivingNucleus);
receivingNucleus.AddSynapse(this, weight);
}
public void RemoveReceiver(INucleus receiverNucleus) {
this._receivers.RemoveAll(receiver => receiver == receiverNucleus);
receiverNucleus.synapses.RemoveAll(synapse => synapse.nucleus == this);
}
private int stale = 1000;
private bool _isSleeping = false;
public bool isSleeping => _isSleeping;
private float3 _outputValue;
public float3 outputValue {
get { return this._outputValue; }
set {
this.stale = 0;
this._isSleeping = false;
this._outputValue = value;
}
}
public virtual void ProcessStimulus(int thingId, Vector3 newLocalPositionVector, string thingName = null) {
this.outputValue = newLocalPositionVector;
if (this._receivers == null)
return;
thingIds ??= new int[this._receivers.Count];
int receiverIx = 0;
INucleus selectedReceiver = null;
int selectedReceiverIx = 0;
foreach (INucleus receiver in this.receivers) {
if (thingIds[receiverIx] == thingId) {
// We found an existing receiver for this thing
selectedReceiver = receiver;
selectedReceiverIx = receiverIx;
// Do not look any further
break;
}
else if (receiver.isSleeping) {
// A sleeping receiver is not active and can therefore always be used
selectedReceiver = receiver;
selectedReceiverIx = receiverIx;
// Look further because we may find an existing receiver for this thing
}
else if (selectedReceiver == null) {
// If we haven't found a receiver yet, just start by taking the first
selectedReceiver = receiver;
selectedReceiverIx = receiverIx;
}
else if (selectedReceiver.isSleeping == false) {
// If no existing or sleeping receiver is found, we look for
// the receiver with the furthest/least interesting stimulus
if (length(receiver.outputValue) < length(selectedReceiver.outputValue)) {
// Debug.Log($"{selectedReceiver.name}[{selectedReceiverIx}] {length(selectedReceiver.outputValue)}" +
// $" {receiver.name}[{receiverIx}] {length(receiver.outputValue)} ");
selectedReceiver = receiver;
selectedReceiverIx = receiverIx;
}
}
receiverIx++;
}
if (selectedReceiverIx >= thingIds.Length)
return;
thingIds[selectedReceiverIx] = thingId;
if (thingName != null) {
string baseName = selectedReceiver.name;
int colonPos = selectedReceiver.name.IndexOf(":");
if (colonPos > 0)
baseName = selectedReceiver.name.Substring(0, colonPos);
selectedReceiver.name = baseName + ": " + thingName;
}
Debug.Log($"Receiver {selectedReceiver.name}[{selectedReceiverIx}] for thing {thingId}");
selectedReceiver.parent.UpdateStateIsolated();
}
public void UpdateNuclei() {
this.stale++; this.stale++;
this._isSleeping = this.stale > 2; if (this.stale > staleValueForSleep && lengthsq(this.bias) > 0) {
if (isSleeping) this.bias = new float3(0, 0, 0);
this._outputValue = Vector3.zero; this.parent.UpdateFromNucleus(this);
}
} }
}
*/ }

View File

@ -31,15 +31,15 @@ public class ReceptorInstance : Nucleus {
} }
[SerializeReference] [SerializeReference]
public Receptor receptor; public ReceptorArray receptor;
public override void UpdateStateIsolated() { public override void UpdateStateIsolated() {
} }
} }
[Serializable] [Serializable]
public class Receptor : Nucleus { public class ReceptorArray : Nucleus {
public Receptor(Cluster parent, string name) { public ReceptorArray(Cluster parent, string name) {
this.parent = parent; this.parent = parent;
this.name = name; this.name = name;
this._instances = new ReceptorInstance[1]; this._instances = new ReceptorInstance[1];
@ -48,7 +48,7 @@ public class Receptor : Nucleus {
}; };
this.parent?.nuclei.Add(this); this.parent?.nuclei.Add(this);
} }
public Receptor(ClusterPrefab prefab, string name) { public ReceptorArray(ClusterPrefab prefab, string name) {
this.cluster = prefab; this.cluster = prefab;
this.name = name; this.name = name;
this._instances = new ReceptorInstance[1]; this._instances = new ReceptorInstance[1];
@ -63,7 +63,7 @@ public class Receptor : Nucleus {
} }
public override Nucleus ShallowCloneTo(Cluster parent) { public override Nucleus ShallowCloneTo(Cluster parent) {
Receptor clone = new(parent, name) { ReceptorArray clone = new(parent, name) {
_instances = new ReceptorInstance[this.instances.Length] _instances = new ReceptorInstance[this.instances.Length]
}; };
for (int ix = 0; ix < this.instances.Length; ix++) { for (int ix = 0; ix < this.instances.Length; ix++) {
@ -76,7 +76,7 @@ public class Receptor : Nucleus {
} }
public override Nucleus Clone(ClusterPrefab prefab) { public override Nucleus Clone(ClusterPrefab prefab) {
Receptor clone = new(prefab, this.name) { ReceptorArray clone = new(prefab, this.name) {
_instances = new ReceptorInstance[this.instances.Length] _instances = new ReceptorInstance[this.instances.Length]
}; };
for (int ix = 0; ix < this.instances.Length; ix++) { for (int ix = 0; ix < this.instances.Length; ix++) {
@ -96,7 +96,7 @@ public class Receptor : Nucleus {
} }
[SerializeReference] [SerializeReference]
private ReceptorInstance[] _instances; private ReceptorInstance[] _instances = new ReceptorInstance[0];
public ReceptorInstance[] instances { public ReceptorInstance[] instances {
get { get {
return _instances; return _instances;
@ -141,7 +141,7 @@ public class Receptor : Nucleus {
public override void ProcessStimulus(int thingId, Vector3 inputValue, string thingName = null) { public override void ProcessStimulus(int thingId, Vector3 inputValue, string thingName = null) {
ProcessStimulus(inputValue, thingId, thingName); ProcessStimulus(inputValue, thingId, thingName);
} }
public void ProcessStimulus(Vector3 inputValue, int thingId = 0, string thingName = null) { public override void ProcessStimulus(Vector3 inputValue, int thingId = 0, string thingName = null) {
CleanupReceivers(); CleanupReceivers();
if (!thingReceivers.TryGetValue(thingId, out Nucleus selectedReceiver)) { if (!thingReceivers.TryGetValue(thingId, out Nucleus selectedReceiver)) {
//Debug.Log($" no receiver found for {thingId}"); //Debug.Log($" no receiver found for {thingId}");
@ -194,6 +194,7 @@ public class Receptor : Nucleus {
float inputMagnitude = length(inputValue); float inputMagnitude = length(inputValue);
Nucleus selectedReceiver = null; Nucleus selectedReceiver = null;
float selectedMagnitude = 0; float selectedMagnitude = 0;
this._instances ??= new ReceptorInstance[0];
foreach (Nucleus receiver in this._instances) { 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