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.cluster = 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 Receptor receptor; public override void UpdateStateIsolated() { } } [Serializable] public class Receptor : Nucleus { public Receptor(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?.nuclei.Add(this); } public Receptor(ClusterPrefab prefab, string name) { this.cluster = prefab; this.name = name; this._instances = new ReceptorInstance[1]; this._instances[0] = new ReceptorInstance(prefab, this.name + "[0]") { receptor = this }; if (this.cluster != null) this.cluster.nuclei.Add(this); else Debug.LogError("No prefab when adding receptor to prefab"); } public override Nucleus ShallowCloneTo(Cluster parent) { Receptor 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) { Receptor 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; 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 thingReceivers = new(); public override void ProcessStimulus(int thingId, Vector3 inputValue, string thingName = null) { ProcessStimulus(inputValue, thingId, thingName); } public 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 receiversToRemove = new(); thingReceivers ??= new(); foreach (KeyValuePair 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; 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); } } } }