Cluster no longer clones ClusterPrefab

This commit is contained in:
Pascal Serrarens 2026-01-29 17:41:55 +01:00
parent b60fc19d96
commit 5cb9e788a4
9 changed files with 262 additions and 116 deletions

View File

@ -7,38 +7,31 @@ using static Unity.Mathematics.math;
[System.Serializable] [System.Serializable]
public class Cluster : INucleus { public class Cluster : INucleus {
// The ScriptableObject asset from which the runtime object has been created // The ScriptableObject asset from which the runtime object has been created
[SerializeField] public ClusterPrefab storedPrefab;
public ClusterPrefab prefab; //public ClusterPrefab prefab;
public ClusterPrefab cluster { get; set; } public Cluster() {
}
[SerializeField] public Cluster(Cluster parent) {
protected string _name; this.parent = parent;
public virtual string name {
get => _name;
set => _name = value;
} }
public Cluster(ClusterPrefab prefab) { public Cluster(ClusterPrefab prefab) {
this.prefab = UnityEngine.Object.Instantiate(prefab); this.storedPrefab = prefab;
//this.prefab = prefab.Clone();
this.name = prefab.name; this.name = prefab.name;
this.cluster = null; this.cluster = null;
if (this.cluster != null) if (this.cluster != null)
this.cluster.nuclei.Add(this); this.cluster.nuclei.Add(this);
} }
// Hmm, a cluster instance can never be part of a scriptable object...(Cluster) public Cluster(ClusterPrefab parent, ClusterPrefab realPrefab) {
public Cluster(ClusterPrefab parent, ClusterPrefab prefab) { this.storedPrefab = realPrefab;
this.prefab = prefab.Clone(); //UnityEngine.Object.Instantiate(prefab); //this.prefab = realPrefab.Clone();
this.name = prefab.name; this.name = realPrefab.name;
this.cluster = parent; this.cluster = parent;
if (this.cluster != null) if (this.cluster != null)
this.cluster.nuclei.Add(this); this.cluster.nuclei.Add(this);
// foreach (IReceptor nucleus in this.prefab.nuclei) {
// IReceptor clone = nucleus.CloneTo(null);
// this.dynamicNuclei.Add(clone);
// }
} }
public virtual IReceptor Clone() { public virtual IReceptor Clone() {
@ -56,7 +49,112 @@ public class Cluster : INucleus {
return clone; return clone;
} }
public INucleus output => prefab.output; // public IReceptor CloneTo(ClusterPrefab parent) {
// Cluster clone = new(parent, this.prefab);
// return clone;
// }
public IReceptor ShallowCloneTo(Cluster parent) {
Cluster clone = new(parent);
return clone;
}
// public IReceptor ShallowCloneTo(ClusterPrefab parent) {
// Cluster clone = new(parent, this.prefab);
// return clone;
// }
// Deep clone a nucleus with its connections
public virtual Cluster InstantiatePrefab(ClusterPrefab prefab) {
Cluster clone = new Cluster {
nuclei = new()
};
IReceptor[] nucleiArray = this.nuclei.ToArray();
// first clone the nuclei without their connections
foreach (IReceptor nucleus in this.nuclei)
nucleus.ShallowCloneTo(clone);
IReceptor[] clonedNuclei = clone.nuclei.ToArray();
// Now clone the connections
for (int nucleusIx = 0; nucleusIx < nucleiArray.Length; nucleusIx++) {
IReceptor receptor = nucleiArray[nucleusIx];
IReceptor clonedReceptor = clonedNuclei[nucleusIx];
if (clonedReceptor == null)
continue;
// Copy the synapses
if (receptor is INucleus nucleus) {
foreach (Synapse synapse in nucleus.synapses) {
if (clonedReceptor is not INucleus clonedNucleus)
continue;
int ix = GetNucleusIndex(nucleiArray, synapse.nucleus);
if (ix < 0)
continue;
IReceptor clonedSynapseNucleus = clonedNuclei[ix];
if (clonedSynapseNucleus == null)
continue;
clonedNucleus.AddSynapse(clonedSynapseNucleus, synapse.weight);
}
}
// Copy the receivers
foreach (INucleus receiver in nucleiArray[nucleusIx].receivers) {
int ix = GetNucleusIndex(nucleiArray, receiver);
if (ix < 0)
continue;
if (clonedNuclei[ix] is not INucleus clonedReceiver)
continue;
clonedReceptor.AddReceiver(clonedReceiver);
}
}
return clone;
}
private int GetNucleusIndex(IReceptor[] nucleiArray, IReceptor nucleus) {
for (int i = 0; i < nucleiArray.Length; i++) {
if (nucleus == nucleiArray[i])
return i;
}
return -1;
}
public ClusterPrefab cluster { get; set; }
public Cluster parent { get; set; }
[SerializeReference]
public List<IReceptor> nuclei = new();
[SerializeField]
protected string _name;
public virtual string name {
get => _name;
set => _name = value;
}
public List<INucleus> _inputs = null;
public virtual List<INucleus> inputs {
get {
if (this._inputs == null) {
this._inputs = new();
foreach (IReceptor receptor in this.nuclei) {
if (receptor is INucleus nucleus) {
// inputs have no incoming synapses yet.
if (nucleus.synapses.Count == 0)
this._inputs.Add(nucleus);
}
}
}
return this._inputs;
}
}
//public INucleus output => prefab.output;
public virtual INucleus output => this.nuclei[0] as INucleus;
// Not sure if this belongs here... // Not sure if this belongs here...
[SerializeReference] [SerializeReference]
@ -68,27 +166,14 @@ public class Cluster : INucleus {
#region Synapses #region Synapses
// class ClusterSynapse : Synapse {
// public IReceptor receptor;
// public ClusterSynapse(IReceptor nucleus, INucleus receptor, float weight = 1.0f) : base(nucleus, weight) {
// this.receptor = receptor;
// }
// }
[SerializeField] [SerializeField]
private List<Synapse> _synapses = new(); private List<Synapse> _synapses = new();
public List<Synapse> synapses => _synapses; public List<Synapse> synapses => _synapses;
public Synapse AddSynapse(IReceptor sendingNucleus) { public Synapse AddSynapse(IReceptor sendingNucleus, float weight = 1.0f) {
Synapse synapse = new(sendingNucleus); //, this.prefab.inputs[0]); Synapse synapse = new(sendingNucleus, weight); //, this.prefab.inputs[0]);
this._synapses.Add(synapse); this._synapses.Add(synapse);
return synapse; return synapse;
// else {
// INucleus receptor = (INucleus)this.prefab.nuclei.Find(nucleus => nucleus is INucleus n && nucleus.name == nucleusName);
// ClusterSynapse synapse = new(sendingNucleus, receptor);
// receptor.AddSynapse(sendingNucleus);
// }
// // Add synapse to which neuron?
// return null;
} }
// Does this even exist already? // Does this even exist already?
@ -117,48 +202,6 @@ public class Cluster : INucleus {
receiverNucleus.synapses.RemoveAll(synapse => synapse.nucleus == this); receiverNucleus.synapses.RemoveAll(synapse => synapse.nucleus == this);
} }
// public void AddReceiver(INucleus receivingNucleus) {
// int newLength = this._receivers.Count + 1;
// INucleus[] newReceivers = new INucleus[newLength];
// // Copy the existing receivers
// for (int ix = 0; ix < this._receivers.Count; ix++)
// newReceivers[ix] = this._receivers[ix];
// // Add the new receivers
// newReceivers[this._receivers.Count] = receivingNucleus;
// // Replace the receivers with the new receivers
// this._receivers = new(newReceivers);
// receivingNucleus.AddSynapse(this);
// }
// public void RemoveReceiver(INucleus receivingNucleus) {
// Debug.Log("Clusterinstance. remote receiver");
// int newLength = this._receivers.Count - 1;
// if (newLength < 0)
// // Array was empty, so we cannot remove anything
// return;
// INucleus[] newReceivers = new INucleus[newLength];
// int newIx = 0;
// // Copy all receivers except receivingNucleus
// for (int ix = 0; ix < this._receivers.Count; ix++) {
// if (this._receivers[ix] == receivingNucleus)
// // skip the receiver we want to remote
// continue;
// if (newIx >= newLength)
// // We want to copy more elements than expected
// // the receivingNucleus is not found
// // and the original array is returned
// return;
// newReceivers[newIx] = this._receivers[ix];
// newIx++;
// }
// this._receivers = new(newReceivers);
// }
#endregion Receivers #endregion Receivers
#region Runtime #region Runtime
@ -191,9 +234,9 @@ public class Cluster : INucleus {
sum += synapse.weight * synapse.nucleus.outputValue; sum += synapse.weight * synapse.nucleus.outputValue;
} }
// This does not work because the prefab nucleus does not have a state //this.prefab.inputs[0].UpdateState(sum);
this.prefab.inputs[0].UpdateState(sum); this.inputs[0].UpdateState(sum);
//this._outputValue = this.output.outputValue;
UpdateResult(this.output.outputValue); UpdateResult(this.output.outputValue);
} }
@ -211,10 +254,11 @@ public class Cluster : INucleus {
public void UpdateNuclei() { public void UpdateNuclei() {
this.stale++; this.stale++;
if (this.stale > 2) if (this.stale > 2)
_outputValue = Vector3.zero; _outputValue = Vector3.zero;
foreach (IReceptor nucleus in this.prefab.nuclei) //foreach (IReceptor nucleus in this.prefab.nuclei)
foreach (IReceptor nucleus in this.nuclei)
nucleus.UpdateNuclei(); nucleus.UpdateNuclei();
} }

View File

@ -36,12 +36,67 @@ public class ClusterPrefab : ScriptableObject {
} }
} }
/*
// Deep clone a nucleus with its connections
public virtual ClusterPrefab Clone() { public virtual ClusterPrefab Clone() {
ClusterPrefab clone = Instantiate(this); ClusterPrefab clone = Instantiate(this);
clone.nuclei = new();
// foreach (IReceptor nucleus in this.nuclei)
// nucleus.CloneTo(clone);
IReceptor[] nucleiArray = this.nuclei.ToArray();
// first clone the nuclei without their connections
foreach (IReceptor nucleus in this.nuclei)
nucleus.ShallowCloneTo(clone);
IReceptor[] clonedNuclei = clone.nuclei.ToArray();
// Now clone the connections
for (int nucleusIx = 0; nucleusIx < nucleiArray.Length; nucleusIx++) {
IReceptor receptor = nucleiArray[nucleusIx];
IReceptor clonedReceptor = clonedNuclei[nucleusIx];
if (clonedReceptor == null)
continue;
// Copy the synapses
if (receptor is INucleus nucleus) {
foreach (Synapse synapse in nucleus.synapses) {
if (clonedReceptor is not INucleus clonedNucleus)
continue;
int ix = GetNucleusIndex(nucleiArray, synapse.nucleus);
if (ix < 0)
continue;
IReceptor clonedSynapseNucleus = clonedNuclei[ix];
if (clonedSynapseNucleus == null)
continue;
clonedNucleus.AddSynapse(clonedSynapseNucleus, synapse.weight);
}
}
// Copy the receivers
foreach (INucleus receiver in nucleiArray[nucleusIx].receivers) {
int ix = GetNucleusIndex(nucleiArray, receiver);
if (ix < 0)
continue;
if (clonedNuclei[ix] is not INucleus clonedReceiver)
continue;
clonedReceptor.AddReceiver(clonedReceiver);
}
}
return clone; return clone;
} }
private int GetNucleusIndex(IReceptor[] nucleiArray, IReceptor nucleus) {
for (int i = 0; i < nucleiArray.Length; i++) {
if (nucleus == nucleiArray[i])
return i;
}
return -1;
}
*/
// Call this function to ensure that there is at least one nucleus // Call this function to ensure that there is at least one nucleus
// This is an invariant and should be ensured before the nucleus is used // This is an invariant and should be ensured before the nucleus is used
// because output requires it. // because output requires it.

View File

@ -10,7 +10,7 @@ public interface INucleus : IReceptor {
// Senders // Senders
public List<Synapse> synapses { get; } public List<Synapse> synapses { get; }
public Synapse AddSynapse(IReceptor sender); public Synapse AddSynapse(IReceptor sender, float weight = 1.0f);
public NucleusArray array { get; set; } public NucleusArray array { get; set; }
@ -48,6 +48,8 @@ public interface IReceptor {
#endregion dynamic #endregion dynamic
//public IReceptor ShallowCloneTo(ClusterPrefab parent);
public IReceptor ShallowCloneTo(Cluster parent);
//public IReceptor CloneTo(ClusterPrefab parent); //public IReceptor CloneTo(ClusterPrefab parent);
public IReceptor Clone(); public IReceptor Clone();
} }

View File

@ -88,6 +88,7 @@ public class Neuron : INucleus {
#region Runtime state (not serialized) #region Runtime state (not serialized)
public ClusterPrefab cluster { get; set; } public ClusterPrefab cluster { get; set; }
public Cluster parent { get; set; }
#region Activation #region Activation
@ -159,12 +160,19 @@ public class Neuron : INucleus {
this.stale++; this.stale++;
// this._isSleeping = this.stale > 2; // this._isSleeping = this.stale > 2;
// if (isSleeping) // if (isSleeping)
if (this.stale > 2) if (this.stale > 2)
_outputValue = Vector3.zero; _outputValue = Vector3.zero;
} }
#endregion Runtime state #endregion Runtime state
public Neuron(Cluster parent, string name) {
this.parent = parent;
this.name = name;
if (this.cluster != null) {
this.cluster.nuclei.Add(this);
}
}
public Neuron(ClusterPrefab parent, string name) { public Neuron(ClusterPrefab parent, string name) {
this.cluster = parent; this.cluster = parent;
this.name = name; this.name = name;
@ -179,6 +187,28 @@ public class Neuron : INucleus {
// this._name = name; // this._name = name;
// } // }
// this clone the nucleus without the synapses and receivers
public virtual IReceptor ShallowCloneTo(Cluster newParent) {
Neuron clone = new(newParent, this.name) {
array = this.array,
curve = this.curve,
curvePreset = this.curvePreset,
curveMax = this.curveMax,
average = this.average
};
return clone;
}
public virtual IReceptor ShallowCloneTo(ClusterPrefab newParent) {
Neuron clone = new(newParent, this.name) {
array = this.array,
curve = this.curve,
curvePreset = this.curvePreset,
curveMax = this.curveMax,
average = this.average
};
return clone;
}
public virtual IReceptor CloneTo(ClusterPrefab parent) { public virtual IReceptor CloneTo(ClusterPrefab parent) {
Neuron clone = new(parent, this.name) { Neuron clone = new(parent, this.name) {
array = this.array, array = this.array,
@ -187,8 +217,6 @@ public class Neuron : INucleus {
curveMax = this.curveMax, curveMax = this.curveMax,
average = this.average average = this.average
}; };
// if (clone.cluster != null)
// clone.cluster.nuclei.Add(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);
@ -254,8 +282,8 @@ public class Neuron : INucleus {
} }
} }
public Synapse AddSynapse(IReceptor sendingNucleus) { public Synapse AddSynapse(IReceptor sendingNucleus, float weight = 1.0f) {
Synapse synapse = new(sendingNucleus); Synapse synapse = new(sendingNucleus, weight);
this.synapses.Add(synapse); this.synapses.Add(synapse);
return synapse; return synapse;
} }

View File

@ -1,11 +1,14 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using UnityEngine; using UnityEngine;
using Unity.Mathematics; using Unity.Mathematics;
using static Unity.Mathematics.math; using static Unity.Mathematics.math;
[Serializable]
public class Receptor : IReceptor { public class Receptor : IReceptor {
private Cluster cluster; private ClusterPrefab cluster;
private Cluster parent;
[SerializeField] [SerializeField]
protected string _name; protected string _name;
@ -14,16 +17,21 @@ public class Receptor : IReceptor {
set => _name = value; set => _name = value;
} }
public Receptor(Cluster cluster) { public Receptor(Cluster parent) {
this.parent = parent;
if (parent != null)
parent.nuclei.Add(this);
}
public Receptor(ClusterPrefab cluster) {
this.cluster = cluster; this.cluster = cluster;
if (cluster != null) if (cluster != null)
cluster.prefab.nuclei.Add(this); cluster.nuclei.Add(this);
} }
public Receptor(Cluster cluster, INucleus nucleus) { public Receptor(ClusterPrefab cluster, INucleus nucleus) {
this.cluster = cluster; this.cluster = cluster;
if (cluster != null) if (cluster != null)
cluster.prefab.nuclei.Add(this); cluster.nuclei.Add(this);
this.AddReceiver(nucleus); this.AddReceiver(nucleus);
} }
@ -31,8 +39,10 @@ public class Receptor : IReceptor {
if (cluster == null) if (cluster == null)
return null; return null;
//Receptor receptor = new(cluster.prefab);
Receptor receptor = new(cluster); Receptor receptor = new(cluster);
foreach (INucleus nucleus in cluster.prefab.inputs) { //foreach (INucleus nucleus in cluster.prefab.inputs) {
foreach (INucleus nucleus in cluster.inputs) {
if (nucleus != null && nucleus.name == nucleusName) { if (nucleus != null && nucleus.name == nucleusName) {
// Receptor receptor = new(cluster, nucleus); // Receptor receptor = new(cluster, nucleus);
// return receptor; // return receptor;
@ -45,7 +55,16 @@ public class Receptor : IReceptor {
return receptor; return receptor;
} }
public virtual IReceptor CloneTo(Cluster parent) { public virtual IReceptor ShallowCloneTo(Cluster parent) {
Receptor clone = new(parent);
return clone;
}
public virtual IReceptor ShallowCloneTo(ClusterPrefab parent) {
Receptor clone = new(parent);
return clone;
}
public virtual IReceptor CloneTo(ClusterPrefab parent) {
Receptor clone = new(parent); Receptor clone = new(parent);
foreach (INucleus receiver in this.receivers) { foreach (INucleus receiver in this.receivers) {

View File

@ -13,18 +13,18 @@ MonoBehaviour:
m_Name: Velocity m_Name: Velocity
m_EditorClassIdentifier: Assembly-CSharp::ClusterPrefab m_EditorClassIdentifier: Assembly-CSharp::ClusterPrefab
nuclei: nuclei:
- rid: 2243601420268863545 - rid: 2243601425629446539
references: references:
version: 2 version: 2
RefIds: RefIds:
- rid: 2243601420268863545 - rid: 2243601425629446539
type: {class: Neuron, ns: , asm: Assembly-CSharp} type: {class: Neuron, ns: , asm: Assembly-CSharp}
data: data:
_name: Output _name: Output
_synapses: [] _synapses: []
_receivers: [] _receivers: []
_array: _array:
rid: 2243601420268863546 rid: 2243601425629446540
_curvePreset: 0 _curvePreset: 0
curve: curve:
serializedVersion: 2 serializedVersion: 2
@ -52,9 +52,9 @@ MonoBehaviour:
m_RotationOrder: 4 m_RotationOrder: 4
curveMax: 1 curveMax: 1
average: 0 average: 0
- rid: 2243601420268863546 - rid: 2243601425629446540
type: {class: NucleusArray, ns: , asm: Assembly-CSharp} type: {class: NucleusArray, ns: , asm: Assembly-CSharp}
data: data:
_nuclei: _nuclei:
- rid: 2243601420268863545 - rid: 2243601425629446539
name: Output name: Output

View File

@ -620,19 +620,16 @@ public class ClusterInspector : Editor {
ClusterPickerWindow.ShowPicker(brain => OnClusterPicked(nucleus, brain), "Select Cluster"); ClusterPickerWindow.ShowPicker(brain => OnClusterPicked(nucleus, brain), "Select Cluster");
} }
private void OnClusterPicked(INucleus nucleus, ClusterPrefab subCluster) { private void OnClusterPicked(INucleus nucleus, ClusterPrefab prefab) {
Cluster subclusterInstance = new(this.cluster, subCluster); Cluster subclusterInstance = new(this.cluster, prefab);
//this.cluster.AddSubCluster(subclusterInstance);
//this.cluster.nuclei.Add(subclusterInstance);
subclusterInstance.AddReceiver(nucleus); subclusterInstance.AddReceiver(nucleus);
} }
private void EditCluster(Cluster subCluster) { private void EditCluster(Cluster subCluster) {
//var currentActiveObject = Selection.activeObject; // May be used with storedPrefab...
Selection.activeObject = subCluster.prefab; Selection.activeObject = subCluster.storedPrefab;
EditorGUIUtility.PingObject(subCluster.prefab); EditorGUIUtility.PingObject(subCluster.storedPrefab);
var editor = Editor.CreateEditor(subCluster.prefab); var editor = Editor.CreateEditor(subCluster.storedPrefab);
//Selection.activeObject = currentActiveObject;
} }
// Connect to another nucleus in the same cluster // Connect to another nucleus in the same cluster

View File

@ -22,8 +22,8 @@ public class NanoBrainComponent_Editor : Editor {
} }
public override VisualElement CreateInspectorGUI() { public override VisualElement CreateInspectorGUI() {
//NanoBrainComponent component = target as NanoBrainComponent; //ClusterPrefab brain = Application.isPlaying ? component.brain.prefab : component.defaultBrain;
ClusterPrefab brain = Application.isPlaying ? component.brain.prefab : component.defaultBrain; Cluster brain = component.brain;
if (Application.isPlaying == false) if (Application.isPlaying == false)
serializedObject.Update(); serializedObject.Update();
@ -79,7 +79,7 @@ public class NanoBrainComponent_Editor : Editor {
}); });
if (brain != null && board != null) if (brain != null && board != null)
board.SetGraph(component.gameObject, brain, brain.output, inspectorContainer); board.SetGraph(component.gameObject, brain.storedPrefab, brain.output, inspectorContainer);
// else // else
// Debug.LogWarning(" No brain!"); // Debug.LogWarning(" No brain!");

View File

@ -1,6 +1,7 @@
using UnityEngine; using UnityEngine;
public class NanoBrainComponent : MonoBehaviour { public class NanoBrainComponent : MonoBehaviour {
[SerializeField]
public ClusterPrefab defaultBrain; public ClusterPrefab defaultBrain;
private Cluster brainInstance; private Cluster brainInstance;