Improved ClusterReceptor

This commit is contained in:
Pascal Serrarens 2026-03-02 15:46:05 +01:00
parent b0ee3add3a
commit 463bef0868
5 changed files with 152 additions and 118 deletions

View File

@ -78,21 +78,21 @@ public class Cluster : Nucleus {
}
}
// Copy nucleus arrays
// Copy nucleus arrays for receptors
for (int nucleusIx = 0; nucleusIx < prefabNuclei.Length; nucleusIx++) {
Nucleus prefabReceptor = prefabNuclei[nucleusIx];
if (prefabReceptor is not Receptor prefabNucleus)
Nucleus prefabNucleus = prefabNuclei[nucleusIx];
if (prefabNucleus is not IReceptor prefabReceptor)
continue;
if (prefabNucleus.nucleiArray == null || prefabNucleus.nucleiArray.Length == 0)
if (prefabReceptor.nucleiArray == null || prefabReceptor.nucleiArray.Length == 0)
continue;
Receptor clonedNucleus = clonedNuclei[nucleusIx] as Receptor;
if (prefabNucleus == prefabNucleus.nucleiArray[0]) {
IReceptor clonedNucleus = clonedNuclei[nucleusIx] as IReceptor;
if (prefabReceptor == prefabReceptor.nucleiArray[0]) {
// We clone the array only for the first entry
NucleusArray clonedArray = new(prefabNucleus.nucleiArray.Length, "array");
NucleusArray clonedArray = new(prefabReceptor.nucleiArray.Length, "array");
int arrayIx = 0;
foreach (Nucleus prefabArrayNucleus in prefabNucleus.nucleiArray) {
foreach (Nucleus prefabArrayNucleus in prefabReceptor.nucleiArray) {
int arrayNucleusIx = GetNucleusIndex(prefabNuclei, prefabArrayNucleus);
if (arrayNucleusIx >= 0) {
Nucleus clonedArrayNucleus = clonedNuclei[arrayNucleusIx];
@ -103,12 +103,13 @@ public class Cluster : Nucleus {
}
arrayIx++;
}
clonedNucleus.array = clonedArray;
//clonedNucleus.array = clonedArray;
clonedNucleus.nucleiArray = clonedArray.nuclei;
}
else {
// The others will refer to the array created for the first nucleus in the array
int firstNucleusIx = GetNucleusIndex(prefabNuclei, prefabNucleus.nucleiArray[0]);
Receptor clonedFirstNucleus = clonedNuclei[firstNucleusIx] as Receptor;
int firstNucleusIx = GetNucleusIndex(prefabNuclei, prefabReceptor.nucleiArray[0]);
IReceptor clonedFirstNucleus = clonedNuclei[firstNucleusIx] as IReceptor;
clonedNucleus.nucleiArray = clonedFirstNucleus.nucleiArray;
}
}
@ -237,12 +238,13 @@ public class Cluster : Nucleus {
return -1;
}
protected int GetNucleusIndex(List<Nucleus> nuclei, Nucleus nucleus) {
public int GetNucleusIndex(List<Nucleus> nuclei, Nucleus nucleus) {
int i = 0;
foreach (Nucleus nucleiElement in nuclei) {
//for (int i = 0; i < nuclei.Length; i++) {
if (nucleus == nucleiElement)
return i;
i++;
}
return -1;
}

View File

@ -25,9 +25,9 @@ public class ClusterReceptor : Cluster, IReceptor {
public override Nucleus ShallowCloneTo(Cluster parent) {
ClusterReceptor clone = new(this.prefab, parent, this.name) {
clusterPrefab = this.clusterPrefab,
array = this.array
//array = this.array
};
//CloneFields(clone);
// This cloned the prefab with the clusternuclei,
// but did not clone the receivers outside the cluster
RestoreExternalReceivers(clone, this.clusterPrefab, parent);
@ -37,9 +37,10 @@ public class ClusterReceptor : Cluster, IReceptor {
public override Nucleus Clone(ClusterPrefab parent) {
ClusterReceptor clone = new(prefab, parent, this.name) {
array = this.array
array = this._array
};
//CloneFields(clone);
foreach (Synapse synapse in this.synapses) {
Synapse clonedSynapse = clone.AddSynapse(synapse.nucleus);
clonedSynapse.weight = synapse.weight;
@ -61,10 +62,29 @@ public class ClusterReceptor : Cluster, IReceptor {
return clone;
}
public override List<Nucleus> CollectReceivers() {
List<Nucleus> receivers = new();
foreach (Nucleus element in this.nucleiArray) {
if (element is not Cluster clusterElement)
continue;
//receivers.AddRange(clusterElement.CollectReceivers());
foreach (Neuron output in clusterElement.outputs) {
foreach (Nucleus receiver in output.receivers) {
// Only add receivers outside clusterElement cluster
if (receiver.clusterPrefab != clusterElement.prefab &&
receivers.Contains(receiver) == false)
receivers.Add(receiver);
}
}
}
return receivers;
}
[SerializeReference]
private NucleusArray _array;
public NucleusArray array {
get { return _array; }
//get { return _array; }
set { _array = value; }
}
@ -76,11 +96,15 @@ public class ClusterReceptor : Cluster, IReceptor {
}
public void AddReceptorElement(ClusterPrefab prefab) {
this.nucleiArray = IReceptorHelpers.AddReceptorElement(this.nucleiArray, prefab);
IReceptorHelpers.AddReceptorElement(this, prefab);
}
public void RemoveReceptorElement() {
this.nucleiArray = IReceptorHelpers.RemoveReceptorElement(this.nucleiArray);
IReceptorHelpers.RemoveReceptorElement(this);
}
public void AddArrayReceiver(Nucleus receiverToAdd, float weight = 1) {
IReceptorHelpers.AddArrayReceiver(this, receiverToAdd, weight);
}
public override void UpdateStateIsolated() {
@ -107,7 +131,7 @@ public class ClusterReceptor : Cluster, IReceptor {
}
public override void ProcessStimulus(Vector3 inputValue, int thingId = 0, string thingName = null) {
this.array ??= new NucleusArray(this.parent);
this.array.ProcessStimulus(thingId, inputValue, thingName);
this._array ??= new NucleusArray(this.parent);
this._array.ProcessStimulus(thingId, inputValue, thingName);
}
}

View File

@ -16,25 +16,9 @@ public class ClusterInspector : Editor {
#region Start
private void OnEnable() {
// Load an icon from resources or assets
Texture2D icon = Resources.Load<Texture2D>("ClusterIcon.png");
// Ensure the texture is valid; set the icon for the ScriptableObject
if (icon != null) {
EditorGUIUtility.SetIconForObject(target, icon);
}
}
public override VisualElement CreateInspectorGUI() {
ClusterPrefab prefab = target as ClusterPrefab;
// string path = AssetDatabase.GetAssetPath(prefab); // or known path
// Debug.Log($"{path}");
// ClusterPrefab currentWrapper = AssetDatabase.LoadAssetAtPath<ClusterPrefab>(path);
// if (currentWrapper == null)
// Debug.LogError("CreateInspectorGUI: Cluster Prefab is not found on disk");
if (prefab != null)
prefab.EnsureInitialization();
@ -482,9 +466,9 @@ public class ClusterInspector : Editor {
fontStyle = FontStyle.Bold,
};
if (nucleus is Receptor receptor1) {
// draw the array size label
if (nucleus is IReceptor receptor1) {
if (expandArray) {
// Put array indices above elements
style.alignment = TextAnchor.LowerCenter;
Vector3 labelPos1 = position + Vector3.down * (size + 5); // below disc
int colonPos1 = nucleus.name.IndexOf(":");
@ -494,6 +478,7 @@ public class ClusterInspector : Editor {
}
}
else {
// draw the array size label
if (color.grayscale > 0.5f)
style.normal.textColor = Color.black;
else
@ -503,33 +488,14 @@ public class ClusterInspector : Editor {
}
}
if (nucleus is ClusterReceptor clusterReceptor) {
// draw the array size label
if (expandArray && clusterReceptor.array.nuclei.First() == this.currentNucleus) {
style.alignment = TextAnchor.LowerCenter;
Vector3 labelPos2 = position + Vector3.down * (size + 5); // below disc
int colonPos2 = nucleus.name.IndexOf(":");
if (colonPos2 > 0) {
string extName = nucleus.name[(colonPos2 + 2)..];
Handles.Label(labelPos2, extName, style);
}
}
else {
if (color.grayscale > 0.5f)
style.normal.textColor = Color.black;
else
style.normal.textColor = Color.white;
Handles.Label(labelPosition, clusterReceptor.array.nuclei.Length.ToString(), style);
style.normal.textColor = Color.white;
}
}
if (!expandArray || nucleus is not Receptor) {
if (expandArray == false) {
// put name below nucleus
Vector3 labelPos = position - Vector3.down * (size + 5); // below neuron
style.alignment = TextAnchor.UpperCenter;
int colonPos = nucleus.name.IndexOf(":");
if (colonPos > 0 && colonPos < nucleus.name.Length - 2) {
// if it is an array, we should not show the :0 of the first element
string baseName = nucleus.name[..colonPos];
Handles.Label(labelPos, baseName, style);
}
@ -652,22 +618,8 @@ public class ClusterInspector : Editor {
if (this.currentNucleus is MemoryCell memory) {
memory.staticMemory = EditorGUILayout.Toggle("Static Memory", memory.staticMemory);
}
// if (this.currentNucleus is ReceptorArray receptor) {
// EditorGUILayout.BeginHorizontal();
// EditorGUILayout.IntField("Receptor size", receptor.instances.Count());
// if (GUILayout.Button("Add")) {
// Undo.RecordObject(prefabAsset, "Receptor add " + prefabAsset.name);
// receptor.AddReceptor(this.prefab);
// anythingChanged = true;
// }
// if (GUILayout.Button("Del")) {
// Undo.RecordObject(prefabAsset, "Receptor delete " + prefabAsset.name);
// receptor.RemoveReceptor();
// anythingChanged = true;
// }
// EditorGUILayout.EndHorizontal();
// }
if (this.currentNucleus is Receptor receptor1) {
if (this.currentNucleus is IReceptor receptor1) {
EditorGUILayout.BeginHorizontal();
EditorGUILayout.IntField("Array size", receptor1.nucleiArray.Count());
if (GUILayout.Button("Add")) {
@ -684,21 +636,21 @@ public class ClusterInspector : Editor {
}
EditorGUILayout.EndHorizontal();
}
else if (this.currentNucleus is ClusterReceptor receptor2) {
EditorGUILayout.BeginHorizontal();
EditorGUILayout.IntField("Array size", receptor2.array.nuclei.Count());
if (GUILayout.Button("Add")) {
Undo.RecordObject(prefabAsset, "Array add " + prefabAsset.name);
receptor2.array.AddNucleus(this.prefab);
anythingChanged = true;
}
if (GUILayout.Button("Del")) {
Undo.RecordObject(prefabAsset, "Array delete " + prefabAsset.name);
receptor2.array.RemoveNucleus();
anythingChanged = true;
}
EditorGUILayout.EndHorizontal();
}
// else if (this.currentNucleus is ClusterReceptor receptor2) {
// EditorGUILayout.BeginHorizontal();
// EditorGUILayout.IntField("Array size", receptor2.array.nuclei.Count());
// if (GUILayout.Button("Add")) {
// Undo.RecordObject(prefabAsset, "Array add " + prefabAsset.name);
// receptor2.array.AddNucleus(this.prefab);
// anythingChanged = true;
// }
// if (GUILayout.Button("Del")) {
// Undo.RecordObject(prefabAsset, "Array delete " + prefabAsset.name);
// receptor2.array.RemoveNucleus();
// anythingChanged = true;
// }
// EditorGUILayout.EndHorizontal();
// }
// Synapses
@ -749,6 +701,7 @@ public class ClusterInspector : Editor {
EditorGUILayout.BeginHorizontal();
if (synapse.nucleus.parent != null && synapse.nucleus.parent != this.currentNucleus) {
// If it is a cluster
GUIStyle labelStyle = new(GUI.skin.label);
float labelWidth = 200;
if (synapse.nucleus.clusterPrefab != null) {
@ -997,7 +950,7 @@ public class ClusterInspector : Editor {
EditorGUILayout.EndHorizontal();
if (connecting) {
Nucleus nucleus = nuclei.ElementAt(selectedConnectNucleus);
if (nucleus is Receptor receptor)
if (nucleus is IReceptor receptor)
receptor.AddArrayReceiver(this.currentNucleus);
else if (nucleus is Neuron neuron)
neuron.AddReceiver(this.currentNucleus);
@ -1058,9 +1011,36 @@ public class ClusterInspector : Editor {
protected virtual void ChangeSynapse(Synapse synapse, Neuron newNucleus) {
Neuron synapseNeuron = synapse.nucleus as Neuron;
if (synapse.nucleus.parent is Cluster subCluster && subCluster.prefab != this.prefab) {
// it is a neuron in a subcluster
synapseNeuron.RemoveReceiver(this.currentNucleus);
newNucleus.AddReceiver(this.currentNucleus);
if (synapse.nucleus.parent is ClusterReceptor receptor) {
// the new nucleus is part of a (cluster) receptor,
// so we have to change all synapses to this nucleus array elements
int oldNucleusIx = subCluster.GetNucleusIndex(subCluster.clusterNuclei, synapse.nucleus);
int newNucleusIx = subCluster.GetNucleusIndex(subCluster.clusterNuclei, newNucleus);
foreach(Nucleus element in receptor.nucleiArray) {
if (element is not ClusterReceptor clusterReceptor)
continue;
// Get the same neuron as the synapse.nucleus in a different element
// of the ClusterReceptor array
Nucleus oldElementNucleus = clusterReceptor.clusterNuclei[oldNucleusIx];
if (oldElementNucleus is not Neuron oldElementNeuron)
continue;
// Get the same neuron as newNucleus in a different element
// of the ClusterReceptor array
Nucleus newElementNucleus = clusterReceptor.clusterNuclei[newNucleusIx];
if (newElementNucleus is not Neuron newElementNeuron)
continue;
oldElementNeuron.RemoveReceiver(this.currentNucleus);
newElementNeuron.AddReceiver(this.currentNucleus);
// Now find the synapse which pointed to the old Neuron
// Synapse synapseForUpdate = this.currentNucleus.GetSynapse(oldElementNeuron);
// synapseForUpdate.nucleus = newElementNeuron;
}
} else {
// it is a neuron in a subcluster
synapseNeuron.RemoveReceiver(this.currentNucleus);
newNucleus.AddReceiver(this.currentNucleus);
}
}
else {
synapseNeuron.RemoveReceiver(this.currentNucleus);

View File

@ -11,41 +11,66 @@ public interface IReceptor {
public void AddReceptorElement(ClusterPrefab prefab);
public void RemoveReceptorElement();
public void AddArrayReceiver(Nucleus receiverToAdd, float weight = 1);
public void ProcessStimulus(Vector3 inputValue, int thingId = 0, string thingName = null);
}
public static class IReceptorHelpers {
public static Nucleus[] AddReceptorElement(Nucleus[] nucleiArray, ClusterPrefab prefab) {
if (nucleiArray.Length == 0) {
public static void AddReceptorElement(IReceptor receptor, ClusterPrefab prefab) {
if (receptor.nucleiArray.Length == 0) {
Debug.LogError("Empty perceptoid array, cannot add");
return null;
}
int newLength = nucleiArray.Length + 1;
int newLength = receptor.nucleiArray.Length + 1;
Nucleus[] newArray = new Nucleus[newLength];
for (int i = 0; i < nucleiArray.Length; i++)
newArray[i] = nucleiArray[i];
if (nucleiArray[0] is Nucleus nucleus) {
string baseName = receptor.GetName();
int colonPos = baseName.IndexOf(":");
if (colonPos > 0)
baseName = baseName[..colonPos];
for (int i = 0; i < receptor.nucleiArray.Length; i++)
newArray[i] = receptor.nucleiArray[i];
if (receptor.nucleiArray[0] is Nucleus nucleus) {
newArray[newLength - 1] = nucleus.Clone(prefab);
newArray[newLength - 1].name += $": {newLength - 1}";
newArray[newLength - 1].name = $"{baseName}: {newLength - 1}";
}
return newArray;
foreach (Nucleus element in receptor.nucleiArray) {
if (element is IReceptor receptorElement) {
receptorElement.nucleiArray = newArray;
}
}
}
public static Nucleus[] RemoveReceptorElement(Nucleus[] nucleiArray) {
int newLength = nucleiArray.Length - 1;
public static void RemoveReceptorElement(IReceptor receptor) {
int newLength = receptor.nucleiArray.Length - 1;
if (newLength == 0) {
Debug.LogWarning("Perceptoid array cannot be empty");
return null;
}
Nucleus[] newPerceptei = new Nucleus[newLength];
Nucleus[] newArray = new Nucleus[newLength];
for (int i = 0; i < newLength; i++)
newPerceptei[i] = nucleiArray[i];
newArray[i] = receptor.nucleiArray[i];
// Delete the last perception
if (nucleiArray[newLength] is Nucleus nucleus)
if (receptor.nucleiArray[newLength] is Nucleus nucleus)
Neuron.Delete(nucleus); //this._nuclei[newLength]);
return newPerceptei;
foreach (Nucleus element in receptor.nucleiArray) {
if (element is IReceptor receptorElement) {
receptorElement.nucleiArray = newArray;
}
}
}
public static void AddArrayReceiver(IReceptor receptor, Nucleus receiverToAdd, float weight = 1) {
foreach (Nucleus element in receptor.nucleiArray) {
if (element is Cluster cluster)
cluster.defaultOutput.AddReceiver(receiverToAdd, weight);
if (element is Neuron neuron)
neuron.AddReceiver(receiverToAdd, weight);
}
}
}

View File

@ -48,11 +48,13 @@ public class Receptor : Neuron, IReceptor {
}
public void AddReceptorElement(ClusterPrefab prefab) {
this.nucleiArray = IReceptorHelpers.AddReceptorElement(this.nucleiArray, prefab);
//this.nucleiArray = IReceptorHelpers.AddReceptorElement(this.nucleiArray, prefab);
IReceptorHelpers.AddReceptorElement(this, prefab);
}
public void RemoveReceptorElement() {
this.nucleiArray = IReceptorHelpers.RemoveReceptorElement(this.nucleiArray);
// this.nucleiArray = IReceptorHelpers.RemoveReceptorElement(this.nucleiArray);
IReceptorHelpers.RemoveReceptorElement(this);
}
// public override void AddReceiver(Nucleus receiverToAdd, float weight = 1) {
@ -65,11 +67,12 @@ public class Receptor : Neuron, IReceptor {
// }
public virtual void AddArrayReceiver(Nucleus receiverToAdd, float weight = 1) {
foreach (Nucleus element in this._array.nuclei) {
if (element is Neuron neuron) {
neuron.AddReceiver(receiverToAdd, weight);
}
}
IReceptorHelpers.AddArrayReceiver(this, receiverToAdd, weight);
// foreach (Nucleus element in this._array.nuclei) {
// if (element is Neuron neuron) {
// neuron.AddReceiver(receiverToAdd, weight);
// }
// }
}
public override void UpdateStateIsolated() {