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

View File

@ -25,9 +25,9 @@ public class ClusterReceptor : Cluster, IReceptor {
public override Nucleus ShallowCloneTo(Cluster parent) { public override Nucleus ShallowCloneTo(Cluster parent) {
ClusterReceptor clone = new(this.prefab, parent, this.name) { ClusterReceptor clone = new(this.prefab, parent, this.name) {
clusterPrefab = this.clusterPrefab, clusterPrefab = this.clusterPrefab,
array = this.array //array = this.array
}; };
//CloneFields(clone);
// This cloned the prefab with the clusternuclei, // This cloned the prefab with the clusternuclei,
// but did not clone the receivers outside the cluster // but did not clone the receivers outside the cluster
RestoreExternalReceivers(clone, this.clusterPrefab, parent); RestoreExternalReceivers(clone, this.clusterPrefab, parent);
@ -37,9 +37,10 @@ public class ClusterReceptor : Cluster, IReceptor {
public override Nucleus Clone(ClusterPrefab parent) { public override Nucleus Clone(ClusterPrefab parent) {
ClusterReceptor clone = new(prefab, parent, this.name) { ClusterReceptor clone = new(prefab, parent, this.name) {
array = this.array array = this._array
}; };
//CloneFields(clone);
foreach (Synapse synapse in this.synapses) { foreach (Synapse synapse in this.synapses) {
Synapse clonedSynapse = clone.AddSynapse(synapse.nucleus); Synapse clonedSynapse = clone.AddSynapse(synapse.nucleus);
clonedSynapse.weight = synapse.weight; clonedSynapse.weight = synapse.weight;
@ -61,10 +62,29 @@ public class ClusterReceptor : Cluster, IReceptor {
return clone; 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] [SerializeReference]
private NucleusArray _array; private NucleusArray _array;
public NucleusArray array { public NucleusArray array {
get { return _array; } //get { return _array; }
set { _array = value; } set { _array = value; }
} }
@ -76,11 +96,15 @@ public class ClusterReceptor : Cluster, IReceptor {
} }
public void AddReceptorElement(ClusterPrefab prefab) { public void AddReceptorElement(ClusterPrefab prefab) {
this.nucleiArray = IReceptorHelpers.AddReceptorElement(this.nucleiArray, prefab); IReceptorHelpers.AddReceptorElement(this, prefab);
} }
public void RemoveReceptorElement() { 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() { 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) { public override void ProcessStimulus(Vector3 inputValue, int thingId = 0, string thingName = null) {
this.array ??= new NucleusArray(this.parent); this._array ??= new NucleusArray(this.parent);
this.array.ProcessStimulus(thingId, inputValue, thingName); this._array.ProcessStimulus(thingId, inputValue, thingName);
} }
} }

View File

@ -16,25 +16,9 @@ public class ClusterInspector : Editor {
#region Start #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() { public override VisualElement CreateInspectorGUI() {
ClusterPrefab prefab = target as ClusterPrefab; 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) if (prefab != null)
prefab.EnsureInitialization(); prefab.EnsureInitialization();
@ -482,9 +466,9 @@ public class ClusterInspector : Editor {
fontStyle = FontStyle.Bold, fontStyle = FontStyle.Bold,
}; };
if (nucleus is Receptor receptor1) { if (nucleus is IReceptor receptor1) {
// draw the array size label
if (expandArray) { if (expandArray) {
// Put array indices above elements
style.alignment = TextAnchor.LowerCenter; style.alignment = TextAnchor.LowerCenter;
Vector3 labelPos1 = position + Vector3.down * (size + 5); // below disc Vector3 labelPos1 = position + Vector3.down * (size + 5); // below disc
int colonPos1 = nucleus.name.IndexOf(":"); int colonPos1 = nucleus.name.IndexOf(":");
@ -494,6 +478,7 @@ public class ClusterInspector : Editor {
} }
} }
else { else {
// draw the array size label
if (color.grayscale > 0.5f) if (color.grayscale > 0.5f)
style.normal.textColor = Color.black; style.normal.textColor = Color.black;
else else
@ -503,33 +488,14 @@ public class ClusterInspector : Editor {
} }
} }
if (nucleus is ClusterReceptor clusterReceptor) { if (expandArray == false) {
// draw the array size label // put name below nucleus
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) {
Vector3 labelPos = position - Vector3.down * (size + 5); // below neuron Vector3 labelPos = position - Vector3.down * (size + 5); // below neuron
style.alignment = TextAnchor.UpperCenter; style.alignment = TextAnchor.UpperCenter;
int colonPos = nucleus.name.IndexOf(":"); int colonPos = nucleus.name.IndexOf(":");
if (colonPos > 0 && colonPos < nucleus.name.Length - 2) { 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]; string baseName = nucleus.name[..colonPos];
Handles.Label(labelPos, baseName, style); Handles.Label(labelPos, baseName, style);
} }
@ -652,22 +618,8 @@ 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) {
// EditorGUILayout.BeginHorizontal(); if (this.currentNucleus is IReceptor receptor1) {
// 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) {
EditorGUILayout.BeginHorizontal(); EditorGUILayout.BeginHorizontal();
EditorGUILayout.IntField("Array size", receptor1.nucleiArray.Count()); EditorGUILayout.IntField("Array size", receptor1.nucleiArray.Count());
if (GUILayout.Button("Add")) { if (GUILayout.Button("Add")) {
@ -684,21 +636,21 @@ public class ClusterInspector : Editor {
} }
EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal();
} }
else if (this.currentNucleus is ClusterReceptor receptor2) { // else if (this.currentNucleus is ClusterReceptor receptor2) {
EditorGUILayout.BeginHorizontal(); // EditorGUILayout.BeginHorizontal();
EditorGUILayout.IntField("Array size", receptor2.array.nuclei.Count()); // EditorGUILayout.IntField("Array size", receptor2.array.nuclei.Count());
if (GUILayout.Button("Add")) { // if (GUILayout.Button("Add")) {
Undo.RecordObject(prefabAsset, "Array add " + prefabAsset.name); // Undo.RecordObject(prefabAsset, "Array add " + prefabAsset.name);
receptor2.array.AddNucleus(this.prefab); // receptor2.array.AddNucleus(this.prefab);
anythingChanged = true; // anythingChanged = true;
} // }
if (GUILayout.Button("Del")) { // if (GUILayout.Button("Del")) {
Undo.RecordObject(prefabAsset, "Array delete " + prefabAsset.name); // Undo.RecordObject(prefabAsset, "Array delete " + prefabAsset.name);
receptor2.array.RemoveNucleus(); // receptor2.array.RemoveNucleus();
anythingChanged = true; // anythingChanged = true;
} // }
EditorGUILayout.EndHorizontal(); // EditorGUILayout.EndHorizontal();
} // }
// Synapses // Synapses
@ -749,6 +701,7 @@ public class ClusterInspector : Editor {
EditorGUILayout.BeginHorizontal(); EditorGUILayout.BeginHorizontal();
if (synapse.nucleus.parent != null && synapse.nucleus.parent != this.currentNucleus) { if (synapse.nucleus.parent != null && synapse.nucleus.parent != this.currentNucleus) {
// If it is a cluster
GUIStyle labelStyle = new(GUI.skin.label); GUIStyle labelStyle = new(GUI.skin.label);
float labelWidth = 200; float labelWidth = 200;
if (synapse.nucleus.clusterPrefab != null) { if (synapse.nucleus.clusterPrefab != null) {
@ -997,7 +950,7 @@ public class ClusterInspector : Editor {
EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal();
if (connecting) { if (connecting) {
Nucleus nucleus = nuclei.ElementAt(selectedConnectNucleus); Nucleus nucleus = nuclei.ElementAt(selectedConnectNucleus);
if (nucleus is Receptor receptor) if (nucleus is IReceptor receptor)
receptor.AddArrayReceiver(this.currentNucleus); receptor.AddArrayReceiver(this.currentNucleus);
else if (nucleus is Neuron neuron) else if (nucleus is Neuron neuron)
neuron.AddReceiver(this.currentNucleus); neuron.AddReceiver(this.currentNucleus);
@ -1058,9 +1011,36 @@ public class ClusterInspector : Editor {
protected virtual void ChangeSynapse(Synapse synapse, Neuron newNucleus) { protected virtual void ChangeSynapse(Synapse synapse, Neuron newNucleus) {
Neuron synapseNeuron = synapse.nucleus as Neuron; Neuron synapseNeuron = synapse.nucleus as Neuron;
if (synapse.nucleus.parent is Cluster subCluster && subCluster.prefab != this.prefab) { if (synapse.nucleus.parent is Cluster subCluster && subCluster.prefab != this.prefab) {
// it is a neuron in a subcluster if (synapse.nucleus.parent is ClusterReceptor receptor) {
synapseNeuron.RemoveReceiver(this.currentNucleus); // the new nucleus is part of a (cluster) receptor,
newNucleus.AddReceiver(this.currentNucleus); // 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 { else {
synapseNeuron.RemoveReceiver(this.currentNucleus); synapseNeuron.RemoveReceiver(this.currentNucleus);

View File

@ -11,41 +11,66 @@ public interface IReceptor {
public void AddReceptorElement(ClusterPrefab prefab); public void AddReceptorElement(ClusterPrefab prefab);
public void RemoveReceptorElement(); public void RemoveReceptorElement();
public void AddArrayReceiver(Nucleus receiverToAdd, float weight = 1);
public void ProcessStimulus(Vector3 inputValue, int thingId = 0, string thingName = null); public void ProcessStimulus(Vector3 inputValue, int thingId = 0, string thingName = null);
} }
public static class IReceptorHelpers { 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"); 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]; Nucleus[] newArray = new Nucleus[newLength];
for (int i = 0; i < nucleiArray.Length; i++) string baseName = receptor.GetName();
newArray[i] = nucleiArray[i]; int colonPos = baseName.IndexOf(":");
if (nucleiArray[0] is Nucleus nucleus) { 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] = 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) { public static void RemoveReceptorElement(IReceptor receptor) {
int newLength = nucleiArray.Length - 1; int newLength = receptor.nucleiArray.Length - 1;
if (newLength == 0) { if (newLength == 0) {
Debug.LogWarning("Perceptoid array cannot be empty"); 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++) for (int i = 0; i < newLength; i++)
newPerceptei[i] = nucleiArray[i]; newArray[i] = receptor.nucleiArray[i];
// Delete the last perception // Delete the last perception
if (nucleiArray[newLength] is Nucleus nucleus) if (receptor.nucleiArray[newLength] is Nucleus nucleus)
Neuron.Delete(nucleus); //this._nuclei[newLength]); 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) { 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() { 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) { // 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) { public virtual void AddArrayReceiver(Nucleus receiverToAdd, float weight = 1) {
foreach (Nucleus element in this._array.nuclei) { IReceptorHelpers.AddArrayReceiver(this, receiverToAdd, weight);
if (element is Neuron neuron) { // foreach (Nucleus element in this._array.nuclei) {
neuron.AddReceiver(receiverToAdd, weight); // if (element is Neuron neuron) {
} // neuron.AddReceiver(receiverToAdd, weight);
} // }
// }
} }
public override void UpdateStateIsolated() { public override void UpdateStateIsolated() {