git-subtree-dir: NanoBrain git-subtree-split: b3423b99a752cdabbc4e7c51565fb54425481feb
214 lines
8.1 KiB
C#
214 lines
8.1 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using Unity.Mathematics;
|
|
using static Unity.Mathematics.math;
|
|
using System.Linq;
|
|
|
|
[Serializable]
|
|
public class ClusterReceptor : Cluster, IReceptor {
|
|
public ClusterReceptor(ClusterPrefab prefab, Cluster parent, string name) : base(prefab, parent) {
|
|
this.name = name;
|
|
this.array = new NucleusArray(this);
|
|
if (this.name.IndexOf(":") < 0)
|
|
this.name += ": 0";
|
|
|
|
}
|
|
public ClusterReceptor(ClusterPrefab prefab, ClusterPrefab parent, string name) : base(prefab, parent) {
|
|
this.name = name;
|
|
this.array = new NucleusArray(this);
|
|
}
|
|
|
|
public string GetName() {
|
|
return this.name;
|
|
}
|
|
|
|
public override Nucleus ShallowCloneTo(Cluster parent) {
|
|
ClusterReceptor clone = new(this.prefab, parent, this.name) {
|
|
clusterPrefab = this.clusterPrefab,
|
|
};
|
|
|
|
return clone;
|
|
}
|
|
|
|
public override Nucleus Clone(ClusterPrefab parent) {
|
|
ClusterReceptor clone = new(prefab, parent, this.name) {
|
|
array = this._array
|
|
};
|
|
|
|
foreach (Synapse synapse in this.synapses) {
|
|
Synapse clonedSynapse = clone.AddSynapse(synapse.neuron);
|
|
clonedSynapse.weight = synapse.weight;
|
|
}
|
|
|
|
this._outputs = null; // Make sure the output are regenerated
|
|
foreach (Neuron output in this.outputs) {
|
|
int ix = GetNucleusIndex(this.clusterNuclei, output);
|
|
if (ix < 0 || clone.clusterNuclei[ix] is not Neuron clonedOutput)
|
|
continue;
|
|
|
|
foreach (Nucleus receiver in output.receivers)
|
|
clonedOutput.AddReceiver(receiver);
|
|
}
|
|
return clone;
|
|
}
|
|
|
|
public override List<Nucleus> CollectReceivers() {
|
|
List<Nucleus> receivers = new();
|
|
foreach (Nucleus element in this.nucleiArray) {
|
|
if (element is not Cluster clusterElement)
|
|
continue;
|
|
|
|
foreach (Nucleus outputNucleus in clusterElement.clusterNuclei) {
|
|
if (outputNucleus is not Neuron output)
|
|
continue;
|
|
|
|
// this should be clusterElement.outputs,
|
|
// but outputs is not updated when correctly and may contain old data...
|
|
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 {
|
|
set { _array = value; }
|
|
}
|
|
|
|
public Nucleus[] nucleiArray {
|
|
get { return _array.nuclei; }
|
|
set { _array.nuclei = value; }
|
|
}
|
|
|
|
public void AddReceptorElement(ClusterPrefab prefab) {
|
|
IReceptorHelpers.AddReceptorElement(this, prefab);
|
|
}
|
|
|
|
public void RemoveReceptorElement() {
|
|
IReceptorHelpers.RemoveReceptorElement(this);
|
|
}
|
|
|
|
public void AddArrayReceiver(Nucleus receiverToAdd, float weight = 1) {
|
|
IReceptorHelpers.AddArrayReceiver(this, receiverToAdd, weight);
|
|
}
|
|
|
|
public override void UpdateStateIsolated() {
|
|
// Clusters don't do anything,
|
|
// The nuclei in them do the work
|
|
// and should be called directly, not from the cluster
|
|
}
|
|
|
|
public override void UpdateNuclei() {
|
|
foreach (Nucleus nucleus in this.clusterNuclei)
|
|
nucleus.UpdateNuclei();
|
|
}
|
|
|
|
public override void ProcessStimulus(Vector3 inputValue, int thingId = 0, string thingName = null) {
|
|
Debug.LogError("Process Stimulus was called on clusterreceptor without a neuron specified");
|
|
}
|
|
|
|
private readonly Dictionary<int, ClusterReceptor> thingReceivers = new();
|
|
|
|
public virtual void ProcessStimulus(Neuron input, Vector3 inputValue, int thingId = 0, string thingName = null) {
|
|
CleanupReceivers();
|
|
|
|
if (!thingReceivers.TryGetValue(thingId, out ClusterReceptor selectedReceiver))
|
|
selectedReceiver = FindReceiver2(thingId, inputValue, input);
|
|
if (selectedReceiver == null)
|
|
return;
|
|
|
|
if (thingName != null) {
|
|
string baseName = selectedReceiver.name;
|
|
int colonPos = selectedReceiver.name.IndexOf(":");
|
|
if (colonPos > 0)
|
|
baseName = selectedReceiver.name[..colonPos];
|
|
selectedReceiver.name = baseName + ": " + thingName;
|
|
}
|
|
|
|
int inputIx = GetNucleusIndex(this.clusterNuclei, input);
|
|
if (inputIx < 0)
|
|
return;
|
|
|
|
if (selectedReceiver.clusterNuclei[inputIx] is Neuron selectedNeuron)
|
|
selectedNeuron.ProcessStimulusDirect(inputValue);
|
|
}
|
|
|
|
private ClusterReceptor FindReceiver2(int thingId, float3 inputValue, Neuron input) {
|
|
// No existing nucleus for this thing
|
|
ClusterReceptor selectedReceiver = null;
|
|
float selectedMagnitude = 0;
|
|
foreach (ClusterReceptor receiver in this.nucleiArray.Cast<ClusterReceptor>()) {
|
|
if (thingReceivers.ContainsValue(receiver) == false) {
|
|
// We found an unusued receiver
|
|
thingReceivers.Add(thingId, receiver);
|
|
return receiver;
|
|
}
|
|
else if (receiver.defaultOutput.isSleeping) {
|
|
// A sleeping receiver is not active and can therefore always be used
|
|
thingReceivers.Add(thingId, receiver);
|
|
receiver.bias = float3(0, 0, 0);
|
|
return receiver;
|
|
}
|
|
else if (selectedReceiver == null) {
|
|
// If we haven't found a receiver yet, just start by taking the first
|
|
selectedReceiver = receiver;
|
|
selectedMagnitude = length(selectedReceiver.defaultOutput.outputValue);
|
|
}
|
|
// Look for the receiver with the lowest output magnitude
|
|
else {
|
|
float magnitude = length(receiver.defaultOutput.outputValue);
|
|
|
|
if (length(receiver.defaultOutput.outputValue) < selectedMagnitude) {
|
|
selectedReceiver = receiver;
|
|
selectedMagnitude = length(selectedReceiver.defaultOutput.outputValue);
|
|
}
|
|
}
|
|
}
|
|
if (selectedReceiver != null) {
|
|
// To re-initialize the cluster (esp. memory cells)
|
|
// we update the cluster neuron twice.
|
|
// Bit of a hack.....
|
|
int inputIx = GetNucleusIndex(this.clusterNuclei, input);
|
|
if (inputIx >= 0) {
|
|
if (selectedReceiver.clusterNuclei[inputIx] is Neuron selectedNeuron)
|
|
selectedNeuron.ProcessStimulusDirect(inputValue);
|
|
}
|
|
|
|
// Replace the receiver
|
|
// Find the thingId current associated with the receiver
|
|
int keyToRemove = thingReceivers.FirstOrDefault(r => r.Value.Equals(selectedReceiver)).Key;
|
|
if (keyToRemove != 0 || thingReceivers.ContainsKey(keyToRemove))
|
|
thingReceivers.Remove(keyToRemove);
|
|
// And add the new association
|
|
thingReceivers.Add(thingId, selectedReceiver);
|
|
}
|
|
return selectedReceiver;
|
|
}
|
|
|
|
|
|
private void CleanupReceivers() {
|
|
// Remove a thing-receiver connection when the nucleus is inactive
|
|
List<int> receiversToRemove = new();
|
|
foreach (KeyValuePair<int, ClusterReceptor> item in thingReceivers) {
|
|
if (item.Value != null && item.Value.defaultOutput.isSleeping)
|
|
receiversToRemove.Add(item.Key);
|
|
}
|
|
foreach (int thingId in receiversToRemove) {
|
|
Nucleus selectedReceiver = thingReceivers[thingId];
|
|
|
|
thingReceivers.Remove(thingId);
|
|
|
|
int colonPos = selectedReceiver.name.IndexOf(":");
|
|
if (colonPos > 0)
|
|
selectedReceiver.name = selectedReceiver.name[..colonPos];
|
|
|
|
}
|
|
}
|
|
} |