261 lines
9.6 KiB
C#
261 lines
9.6 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using UnityEngine;
|
|
using Unity.Mathematics;
|
|
using static Unity.Mathematics.math;
|
|
|
|
[Serializable]
|
|
public class ReceptorInstance : Nucleus {
|
|
public ReceptorInstance(Cluster parent, string name) {
|
|
this.parent = parent;
|
|
this.name = name;
|
|
// We explicitly do not add this to the parent, as it is serialized in the ReceptorArray
|
|
}
|
|
public ReceptorInstance(ClusterPrefab prefab, string name) {
|
|
this.clusterPrefab = prefab;
|
|
this.name = name;
|
|
// We explicitly do not add this to the prefab, as it is serialized in the ReceptorArray
|
|
}
|
|
public override Nucleus ShallowCloneTo(Cluster parent) {
|
|
ReceptorInstance clone = new(parent, name + " +1") {
|
|
receptor = this.receptor
|
|
};
|
|
return clone;
|
|
}
|
|
public override Nucleus Clone(ClusterPrefab prefab) {
|
|
ReceptorInstance clone = new(prefab, name) {
|
|
receptor = this.receptor
|
|
};
|
|
return clone;
|
|
}
|
|
|
|
[SerializeReference]
|
|
public ReceptorArray receptor;
|
|
|
|
public override void UpdateStateIsolated() {
|
|
}
|
|
}
|
|
|
|
[Serializable]
|
|
public class ReceptorArray : Nucleus {
|
|
public ReceptorArray(Cluster parent, string name) {
|
|
this.parent = parent;
|
|
this.name = name;
|
|
this._instances = new ReceptorInstance[1];
|
|
this._instances[0] = new ReceptorInstance(parent, this.name + "[0]") {
|
|
receptor = this
|
|
};
|
|
this.parent?.clusterNuclei.Add(this);
|
|
}
|
|
public ReceptorArray(ClusterPrefab prefab, string name) {
|
|
this.clusterPrefab = prefab;
|
|
this.name = name;
|
|
this._instances = new ReceptorInstance[1];
|
|
this._instances[0] = new ReceptorInstance(prefab, this.name + "[0]") {
|
|
receptor = this
|
|
};
|
|
if (this.clusterPrefab != null)
|
|
this.clusterPrefab.nuclei.Add(this);
|
|
else
|
|
Debug.LogError("No prefab when adding receptor to prefab");
|
|
|
|
}
|
|
|
|
public override Nucleus ShallowCloneTo(Cluster parent) {
|
|
ReceptorArray clone = new(parent, name) {
|
|
_instances = new ReceptorInstance[this.instances.Length]
|
|
};
|
|
for (int ix = 0; ix < this.instances.Length; ix++) {
|
|
clone._instances[ix] = new ReceptorInstance(parent, $"{this.name} [{ix}]") {
|
|
receptor = clone
|
|
};
|
|
}
|
|
|
|
return clone;
|
|
}
|
|
|
|
public override Nucleus Clone(ClusterPrefab prefab) {
|
|
ReceptorArray clone = new(prefab, this.name) {
|
|
_instances = new ReceptorInstance[this.instances.Length]
|
|
};
|
|
for (int ix = 0; ix < this.instances.Length; ix++) {
|
|
clone._instances[ix] = new ReceptorInstance(prefab, this.name) {
|
|
receptor = this
|
|
};
|
|
}
|
|
|
|
foreach (Synapse synapse in this.synapses) {
|
|
Synapse clonedSynapse = clone.AddSynapse(synapse.nucleus);
|
|
clonedSynapse.weight = synapse.weight;
|
|
}
|
|
// foreach (Nucleus receiver in this.receivers) {
|
|
// clone.AddReceiver(receiver);
|
|
// }
|
|
return clone;
|
|
}
|
|
|
|
[SerializeReference]
|
|
private ReceptorInstance[] _instances = new ReceptorInstance[0];
|
|
public ReceptorInstance[] instances {
|
|
get {
|
|
return _instances;
|
|
}
|
|
}
|
|
|
|
public void AddReceptor(ClusterPrefab prefab) {
|
|
if (this._instances.Length == 0) {
|
|
Debug.LogError("Empty receptor array, cannot add");
|
|
return;
|
|
}
|
|
int newLength = this._instances.Length + 1;
|
|
ReceptorInstance[] newArray = new ReceptorInstance[newLength];
|
|
|
|
for (int i = 0; i < this._instances.Length; i++)
|
|
newArray[i] = this._instances[i];
|
|
ReceptorInstance newReceptor = (ReceptorInstance)this._instances[0].Clone(prefab);
|
|
newReceptor.name = $"{this.name} [{this._instances.Length}]";
|
|
newArray[newLength - 1] = newReceptor;
|
|
|
|
this._instances = newArray;
|
|
}
|
|
|
|
public void RemoveReceptor() {
|
|
int newLength = this._instances.Length - 1;
|
|
if (newLength == 0) {
|
|
Debug.LogWarning("Receptor array cannot be empty");
|
|
return;
|
|
}
|
|
ReceptorInstance[] newPerceptei = new ReceptorInstance[newLength];
|
|
for (int i = 0; i < newLength; i++)
|
|
newPerceptei[i] = this._instances[i];
|
|
// Delete the last perception
|
|
if (this._instances[newLength] is Nucleus nucleus)
|
|
Neuron.Delete(nucleus); //this._nuclei[newLength]);
|
|
|
|
this._instances = newPerceptei;
|
|
}
|
|
|
|
private Dictionary<int, Nucleus> thingReceivers = new();
|
|
|
|
// public override void ProcessStimulus(int thingId, Vector3 inputValue, string thingName = null) {
|
|
// ProcessStimulus(inputValue, thingId, thingName);
|
|
// }
|
|
public override void ProcessStimulus(Vector3 inputValue, int thingId = 0, string thingName = null) {
|
|
CleanupReceivers();
|
|
if (!thingReceivers.TryGetValue(thingId, out Nucleus selectedReceiver)) {
|
|
//Debug.Log($" no receiver found for {thingId}");
|
|
// No existing nucleus for this thing
|
|
selectedReceiver = SelectReceptor(thingId, inputValue);
|
|
}
|
|
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;
|
|
}
|
|
|
|
//if (selectedReceiver is Neuron selectedNucleus) {
|
|
selectedReceiver.stale = 0;
|
|
selectedReceiver.outputValue = inputValue;
|
|
this.parent.UpdateFromNucleus(this);
|
|
//selectedNucleus.ProcessStimulus(inputValue);
|
|
//}
|
|
}
|
|
|
|
private void CleanupReceivers() {
|
|
// Remove a thing-receiver connection when the nucleus is inactive
|
|
List<int> receiversToRemove = new();
|
|
thingReceivers ??= new();
|
|
|
|
foreach (KeyValuePair<int, Nucleus> item in thingReceivers) {
|
|
if (item.Value.isSleeping)
|
|
receiversToRemove.Add(item.Key);
|
|
}
|
|
foreach (int thingId in receiversToRemove) {
|
|
Nucleus selectedReceiver = thingReceivers[thingId];
|
|
|
|
// Debug.Log($" removed receiver for {thingId}");
|
|
thingReceivers.Remove(thingId);
|
|
|
|
int colonPos = selectedReceiver.name.IndexOf(":");
|
|
if (colonPos > 0)
|
|
selectedReceiver.name = selectedReceiver.name[..colonPos];
|
|
}
|
|
|
|
}
|
|
|
|
private Nucleus SelectReceptor(int thingId, float3 inputValue) {
|
|
// No existing nucleus for this thing
|
|
float inputMagnitude = length(inputValue);
|
|
Nucleus selectedReceiver = null;
|
|
float selectedMagnitude = 0;
|
|
this._instances ??= new ReceptorInstance[0];
|
|
foreach (Nucleus receiver in this._instances) {
|
|
if (thingReceivers.ContainsValue(receiver) == false) {
|
|
// We found an unusued receiver
|
|
// Debug.Log($"{thingId} -> [{receiver.name}]");
|
|
thingReceivers.Add(thingId, receiver);
|
|
return receiver;
|
|
}
|
|
else if (receiver.isSleeping) {
|
|
// A sleeping receiver is not active and can therefore always be used
|
|
thingReceivers.Add(thingId, receiver);
|
|
Debug.Log($"{thingId} -> [{selectedReceiver.name}]");
|
|
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.outputValue);
|
|
}
|
|
// Look for the receiver with the lowest magnitude
|
|
else {
|
|
float magnitude = length(receiver.outputValue);
|
|
|
|
if (magnitude < inputMagnitude && length(receiver.outputValue) < selectedMagnitude) {
|
|
selectedReceiver = receiver;
|
|
selectedMagnitude = length(selectedReceiver.outputValue);
|
|
}
|
|
}
|
|
}
|
|
if (selectedReceiver != null) {
|
|
// 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;
|
|
}
|
|
|
|
public override void UpdateStateIsolated() {
|
|
float3 sum = this.bias;
|
|
|
|
// Receptors do not have inputs, so we ignore the synapses
|
|
foreach (Nucleus nucleus in this._instances)
|
|
sum += nucleus.outputValue;
|
|
|
|
this.outputValue = sum / _instances.Length;
|
|
this.stale = 0;
|
|
|
|
UpdateNuclei();
|
|
}
|
|
|
|
public override void UpdateNuclei() {
|
|
foreach (Nucleus nucleus in this.instances) {
|
|
nucleus.stale++;
|
|
if (nucleus.stale > staleValueForSleep && lengthsq(nucleus.outputValue) > 0) {
|
|
nucleus.outputValue = Vector3.zero;
|
|
//this.UpdateStateIsolated();
|
|
this.parent.UpdateFromNucleus(this);
|
|
}
|
|
}
|
|
}
|
|
} |