From bef7ee24e549963b5cabfb91ada9289bc6dddbe0 Mon Sep 17 00:00:00 2001 From: Pascal Serrarens Date: Tue, 7 Apr 2026 13:04:20 +0200 Subject: [PATCH] Made Unity.Mathematics optional --- Cluster.cs | 2 + ClusterReceptor.cs | 59 ++++++++++++++ Editor/ClusterInspector.cs | 30 ++++---- Editor/ConfigurationChecker.cs | 78 +++++++++++++++++++ Editor/ConfigurationChecker.cs.meta | 2 + MemoryCell.cs | 8 +- Neuron.cs | 115 +++++++++++++++++++++++++++- NucleusArray.cs | 51 ++++++++++++ Receptor.cs | 16 ++++ Runtime.meta | 8 ++ Runtime/Vector.cs | 8 ++ Runtime/Vector.cs.meta | 2 + 12 files changed, 362 insertions(+), 17 deletions(-) create mode 100644 Editor/ConfigurationChecker.cs create mode 100644 Editor/ConfigurationChecker.cs.meta create mode 100644 Runtime.meta create mode 100644 Runtime/Vector.cs create mode 100644 Runtime/Vector.cs.meta diff --git a/Cluster.cs b/Cluster.cs index 996fb2c..ba71852 100644 --- a/Cluster.cs +++ b/Cluster.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; using UnityEngine; +#if UNITY_MATHEMATICS using Unity.Mathematics; using static Unity.Mathematics.math; +#endif [Serializable] public class Cluster : Nucleus { diff --git a/ClusterReceptor.cs b/ClusterReceptor.cs index ac65e7a..fd925ed 100644 --- a/ClusterReceptor.cs +++ b/ClusterReceptor.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; using UnityEngine; +#if UNITY_MATHEMATICS using Unity.Mathematics; using static Unity.Mathematics.math; +#endif using System.Linq; [Serializable] @@ -140,6 +142,8 @@ public class ClusterReceptor : Cluster, IReceptor { selectedNeuron.ProcessStimulusDirect(inputValue); } +#if UNITY_MATHEMATICS + private ClusterReceptor FindReceiver2(int thingId, float3 inputValue, Neuron input) { // No existing nucleus for this thing ClusterReceptor selectedReceiver = null; @@ -192,6 +196,61 @@ public class ClusterReceptor : Cluster, IReceptor { return selectedReceiver; } +#else + + private ClusterReceptor FindReceiver2(int thingId, Vector3 inputValue, Neuron input) { + // No existing nucleus for this thing + ClusterReceptor selectedReceiver = null; + float selectedMagnitude = 0; + foreach (ClusterReceptor receiver in this.nucleiArray.Cast()) { + 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 = new Vector3(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 = selectedReceiver.defaultOutput.outputValue.magnitude; + } + // Look for the receiver with the lowest output magnitude + else { + float magnitude = receiver.defaultOutput.outputValue.magnitude; + + if (receiver.defaultOutput.outputValue.magnitude < selectedMagnitude) { + selectedReceiver = receiver; + selectedMagnitude = selectedReceiver.defaultOutput.outputValue.magnitude; + } + } + } + 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; + } + +#endif private void CleanupReceivers() { // Remove a thing-receiver connection when the nucleus is inactive diff --git a/Editor/ClusterInspector.cs b/Editor/ClusterInspector.cs index 5aeb9c9..14e83fa 100644 --- a/Editor/ClusterInspector.cs +++ b/Editor/ClusterInspector.cs @@ -4,8 +4,10 @@ using UnityEditor; using UnityEngine; using UnityEngine.UIElements; -using Unity.Mathematics; -using static Unity.Mathematics.math; +// #if UNITY_MATHEMATICS +// using Unity.Mathematics; +// using static Unity.Mathematics.math; +// #endif [CustomEditor(typeof(ClusterPrefab))] public class ClusterInspector : Editor { @@ -263,7 +265,7 @@ public class ClusterInspector : Editor { float maxValue = 0; foreach (Nucleus nucleus in receptor1.nucleiArray) { if (nucleus is Neuron neuron) { - float value = length(neuron.outputValue); + float value = neuron.outputMagnitude; if (value > maxValue) maxValue = value; } @@ -313,9 +315,9 @@ public class ClusterInspector : Editor { Handles.DrawSolidDisc(position, Vector3.forward, size + 2); float maxValue = 1; if (this.currentNucleus is Neuron neuron) - maxValue = length(neuron.outputValue); + maxValue = neuron.outputMagnitude; else if (this.currentNucleus is Cluster cluster) - maxValue = length(cluster.defaultOutput.outputValue); + maxValue = cluster.defaultOutput.outputMagnitude; DrawNucleus(this.currentNucleus, position, maxValue, 20); @@ -327,9 +329,9 @@ public class ClusterInspector : Editor { Handles.DrawSolidDisc(position, Vector3.forward, size + 2); float maxValue = 1; if (this.currentNucleus is Neuron neuron) - maxValue = length(neuron.outputValue); + maxValue = neuron.outputMagnitude; else if (this.currentNucleus is Cluster cluster) - maxValue = length(cluster.defaultOutput.outputValue); + maxValue = cluster.defaultOutput.outputMagnitude; DrawNucleus(this.currentNucleus, position, maxValue, 20); } } @@ -350,7 +352,7 @@ public class ClusterInspector : Editor { float maxValue = 0; foreach (Nucleus receiver in receivers) { if (receiver is Neuron neuroid) { - float value = length(neuroid.outputValue); + float value = neuroid.outputMagnitude; if (value > maxValue) maxValue = value; } @@ -405,7 +407,7 @@ public class ClusterInspector : Editor { drawnArrays.Add(clusterReceptor.nucleiArray); } if (synapse.neuron is Neuron synapseNeuron) { - float value = length(synapseNeuron.outputValue) * synapse.weight; + float value = synapseNeuron.outputMagnitude * synapse.weight; // Debug.Log($"{synapse.nucleus.name}: {value} {length(synapse.nucleus.outputValue)} {synapse.weight}"); if (value > maxValue) maxValue = value; @@ -442,7 +444,7 @@ public class ClusterInspector : Editor { maxValue = 1; float brightness = 0; if (synapse.neuron is Neuron synapseNeuron) - brightness = length(synapseNeuron.outputValue * synapse.weight) / maxValue; + brightness = synapseNeuron.outputMagnitude * synapse.weight / maxValue; color = new Color(brightness, brightness, brightness, 1f); } if (synapse.neuron.parent != null && synapse.neuron.parent != this.currentNucleus.parent) { @@ -464,7 +466,7 @@ public class ClusterInspector : Editor { if (Application.isPlaying) { float brightness = 0; if (nucleus is Neuron neuron) - brightness = length(neuron.outputValue) / maxValue; + brightness = neuron.outputMagnitude / maxValue; color = new Color(brightness, brightness, brightness, 1f); } else @@ -557,7 +559,7 @@ public class ClusterInspector : Editor { if (nucleus is Neuron neuron) { tooltip = new( $"{nucleus.name}" + - $"\nValue: {length(neuron.outputValue)}"); + $"\nValue: {neuron.outputMagnitude}"); } else tooltip = new($"{nucleus.name}"); @@ -641,7 +643,7 @@ public class ClusterInspector : Editor { if (Application.isPlaying) { if (currentNucleus is Neuron currentNeuron1) { GUIContent nameLabel = new("Output", currentNeuron1.outputValue.ToString()); - EditorGUILayout.FloatField(nameLabel, length(currentNeuron1.outputValue)); + EditorGUILayout.FloatField(nameLabel, currentNeuron1.outputMagnitude); } else EditorGUILayout.LabelField(" "); @@ -723,7 +725,7 @@ public class ClusterInspector : Editor { if (synapse.neuron is Neuron synapseNeuron) { Vector3 value = synapseNeuron.outputValue * synapse.weight; GUIContent synapseValueLabel = new(synapse.neuron.name, synapseNeuron.outputValue.ToString()); - EditorGUILayout.FloatField(synapseValueLabel, length(synapseNeuron.outputValue)); + EditorGUILayout.FloatField(synapseValueLabel, synapseNeuron.outputMagnitude); } } else { diff --git a/Editor/ConfigurationChecker.cs b/Editor/ConfigurationChecker.cs new file mode 100644 index 0000000..e487d6d --- /dev/null +++ b/Editor/ConfigurationChecker.cs @@ -0,0 +1,78 @@ +using System; +using UnityEditor; +using UnityEditor.Build; +using UnityEditor.Callbacks; + +class ConfigurationChecker { + + [DidReloadScripts] + protected static void DidReloadScripts() { + CheckUnityMathematics(); + } + + public static bool CheckUnityMathematics() { + bool available = isUnityMathematicsAvailable; + UpdateDefine(available, "UNITY_MATHEMATICS"); + return available; + } + + protected static void UpdateDefine(bool enabled, string define) { + if (enabled) + GlobalDefine(define); + else + GlobalUndefine(define); + } + + + public static void GlobalDefine(string name) { + UnityEngine.Debug.Log("Define " + name); + BuildTargetGroup buildTargetGroup = EditorUserBuildSettings.selectedBuildTargetGroup; + NamedBuildTarget namedBuildTarget = UnityEditor.Build.NamedBuildTarget.FromBuildTargetGroup(buildTargetGroup); + //string scriptDefines = PlayerSettings.GetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup); + string scriptDefines = PlayerSettings.GetScriptingDefineSymbols(namedBuildTarget); + if (!scriptDefines.Contains(name)) { + string newScriptDefines = scriptDefines + " " + name; + if (EditorUserBuildSettings.selectedBuildTargetGroup != 0) + PlayerSettings.SetScriptingDefineSymbols(namedBuildTarget, newScriptDefines); + //PlayerSettings.SetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup, newScriptDefines); + } + } + + public static void GlobalUndefine(string name) { + UnityEngine.Debug.Log("Undefine " + name); + BuildTargetGroup buildTargetGroup = EditorUserBuildSettings.selectedBuildTargetGroup; + NamedBuildTarget namedBuildTarget = UnityEditor.Build.NamedBuildTarget.FromBuildTargetGroup(buildTargetGroup); + // string scriptDefines = PlayerSettings.GetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup); + string scriptDefines = PlayerSettings.GetScriptingDefineSymbols(namedBuildTarget); + if (scriptDefines.Contains(name)) { + int playMakerIndex = scriptDefines.IndexOf(name); + string newScriptDefines = scriptDefines.Remove(playMakerIndex, name.Length); + PlayerSettings.SetScriptingDefineSymbols(namedBuildTarget, newScriptDefines); + // PlayerSettings.SetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup, newScriptDefines); + } + + } + + #region Availability + + #region Packages + + private static bool isUnityMathematicsAvailable { + get => DoesTypeExist("Unity.Mathematics.float3"); + // { + // return DoesTypeExist("Passer.Tracking.HydraBaseStation"); + // } + } + #endregion Packages + + public static bool DoesTypeExist(string className) { + System.Reflection.Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); + foreach (System.Reflection.Assembly assembly in assemblies) { + if (assembly.GetType(className) != null) + return true; + } + return false; + } + + #endregion Availability +} diff --git a/Editor/ConfigurationChecker.cs.meta b/Editor/ConfigurationChecker.cs.meta new file mode 100644 index 0000000..b8bea95 --- /dev/null +++ b/Editor/ConfigurationChecker.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: c7539a20f7894542ca347730cd8417b1 \ No newline at end of file diff --git a/MemoryCell.cs b/MemoryCell.cs index 7b7b8e5..6b49084 100644 --- a/MemoryCell.cs +++ b/MemoryCell.cs @@ -1,5 +1,7 @@ using System; +#if UNITY_MATHEMATICS using Unity.Mathematics; +#endif [Serializable] public class MemoryCell : Neuron { @@ -28,11 +30,15 @@ public class MemoryCell : Neuron { private bool initialized = false; +#if UNITY_MATHEMATICS private float3 _memorizedValue; +#else + private UnityEngine.Vector3 _memorizedValue; +#endif public override void UpdateStateIsolated() { // A memorycell does not have an activation function - float3 result = Combinator(); + var result = Combinator(); if (initialized) // Output the previous, memorized value diff --git a/Neuron.cs b/Neuron.cs index 05982de..e1a0052 100644 --- a/Neuron.cs +++ b/Neuron.cs @@ -2,8 +2,10 @@ using System; using System.Collections.Generic; using UnityEngine; using UnityEditor; +#if UNITY_MATHEMATICS using Unity.Mathematics; using static Unity.Mathematics.math; +#endif [Serializable] public class Neuron : Nucleus { @@ -116,6 +118,8 @@ public class Neuron : Nucleus { #endregion Serialization +#if UNITY_MATHEMATICS + protected float3 _outputValue; public virtual float3 outputValue { get { return _outputValue; } @@ -125,10 +129,30 @@ public class Neuron : Nucleus { WhenFiring?.Invoke(); } } + public float outputMagnitude => length(_outputValue); + public bool isFiring => length(_outputValue) > 0.5f; + public virtual bool isSleeping => lengthsq(this.outputValue) == 0; + +#else + + protected Vector3 _outputValue; + public virtual Vector3 outputValue { + get { return _outputValue; } + set { + _outputValue = value; + if (this.isFiring) + WhenFiring?.Invoke(); + } + } + public float outputMagnitude => _outputValue.magnitude; + + public bool isFiring => _outputValue.magnitude > 0.5f; + public virtual bool isSleeping => this.outputValue.sqrMagnitude == 0; + +#endif public Action WhenFiring; - public virtual bool isSleeping => lengthsq(this.outputValue) == 0; [NonSerialized] public int stale = 1000; public readonly int staleValueForSleep = 20; @@ -199,12 +223,14 @@ public class Neuron : Nucleus { } public override void UpdateStateIsolated() { - float3 result = Combinator(); + var result = Combinator(); this.outputValue = Activator(result); } #region Combinator +#if UNITY_MATHEMATICS + protected Func Combinator => combinator switch { CombinatorType.Sum => CombinatorSum, CombinatorType.Product => CombinatorProduct, @@ -244,10 +270,54 @@ public class Neuron : Nucleus { return max; } +#else + + protected Func Combinator => combinator switch { + CombinatorType.Sum => CombinatorSum, + CombinatorType.Product => CombinatorProduct, + CombinatorType.Max => CombinatorMax, + _ => CombinatorSum + }; + + public Vector3 CombinatorSum() { + Vector3 sum = this.bias; + foreach (Synapse synapse in this.synapses) + sum += synapse.weight * synapse.neuron.outputValue; + return sum; + } + + public Vector3 CombinatorProduct() { + Vector3 product = this.bias; + foreach (Synapse synapse in this.synapses) { + //product *= synapse.weight * synapse.neuron.outputValue; + product = Vector3.Scale(product, synapse.weight * synapse.neuron.outputValue); + } + return product; + } + + public Vector3 CombinatorMax() { + Vector3 max = this.bias; + float maxLength = max.magnitude; + + //Applying the weight factors + foreach (Synapse synapse in this.synapses) { + Vector3 input = synapse.weight * synapse.neuron.outputValue; + + float inputLength = input.magnitude; + if (inputLength > maxLength) { + max = input; + maxLength = inputLength; + } + } + return max; + } +#endif #endregion Combinator #region Activator +#if UNITY_MATHEMATICS + public Func Activator => this.curvePreset switch { CurvePresets.Linear => ActivatorLinear, CurvePresets.Sqrt => ActivatorSqrt, @@ -285,6 +355,47 @@ public class Neuron : Nucleus { return result; } +#else + + public Func Activator => this.curvePreset switch { + CurvePresets.Linear => ActivatorLinear, + CurvePresets.Sqrt => ActivatorSqrt, + CurvePresets.Power => ActivatorPower, + CurvePresets.Reciprocal => ActivatorReciprocal, + _ => ActivatorCustom + }; + + protected Vector3 ActivatorLinear(Vector3 input) { + return input; + } + + protected Vector3 ActivatorSqrt(Vector3 input) { + Vector3 result = input.normalized * System.MathF.Sqrt(input.magnitude); + return result; + } + + protected Vector3 ActivatorPower(Vector3 input) { + Vector3 result = input.normalized * System.MathF.Pow(input.magnitude, 2); + return result; + } + + protected Vector3 ActivatorReciprocal(Vector3 input) { + float magnitude = input.magnitude; + if (magnitude == 0) + return new Vector3(0, 0, 0); + + Vector3 result = input.normalized * (1 / magnitude); + return result; + } + + protected Vector3 ActivatorCustom(Vector3 input) { + float activatedValue = this.curve.Evaluate(input.magnitude); + Vector3 result = input.normalized * activatedValue; + return result; + } + +#endif + #endregion Activator #region Receivers diff --git a/NucleusArray.cs b/NucleusArray.cs index 9f8a172..6e48950 100644 --- a/NucleusArray.cs +++ b/NucleusArray.cs @@ -1,8 +1,10 @@ using System.Linq; using System.Collections.Generic; using UnityEngine; +#if UNITY_MATHEMATICS using Unity.Mathematics; using static Unity.Mathematics.math; +#endif [System.Serializable] public class NucleusArray { @@ -65,6 +67,7 @@ public class NucleusArray { public Dictionary thingReceivers = new(); +#if UNITY_MATHEMATICS private Nucleus FindReceiver(int thingId, float3 inputValue) { // No existing nucleus for this thing @@ -111,6 +114,54 @@ public class NucleusArray { return selectedReceiver; } +#else + + private Nucleus FindReceiver(int thingId, Vector3 inputValue) { + // No existing nucleus for this thing + float inputMagnitude = inputValue.magnitude; + Neuron selectedReceiver = null; + float selectedMagnitude = 0; + foreach (Nucleus nucleusReceiver in this._nuclei) { + if (nucleusReceiver is not Neuron receiver) + continue; + if (thingReceivers.ContainsValue(receiver) == false) { + // We found an unusued receiver + 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); + return receiver; + } + else if (selectedReceiver == null) { + // If we haven't found a receiver yet, just start by taking the first + selectedReceiver = receiver; + selectedMagnitude = selectedReceiver.outputMagnitude; + } + // Look for the receiver with the lowest magnitude + else { + float magnitude = receiver.outputMagnitude; + + if (magnitude < inputMagnitude && receiver.outputMagnitude < selectedMagnitude) { + selectedReceiver = receiver; + selectedMagnitude = selectedReceiver.outputMagnitude; + } + } + } + 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; + } +#endif + public virtual void ProcessStimulus(int thingId, Vector3 inputValue, string thingName = null) { CleanupReceivers(); diff --git a/Receptor.cs b/Receptor.cs index a102835..15ce3c6 100644 --- a/Receptor.cs +++ b/Receptor.cs @@ -1,6 +1,8 @@ using UnityEngine; +#if UNITY_MATHEMATICS using Unity.Mathematics; using static Unity.Mathematics.math; +#endif [System.Serializable] public class Receptor : Neuron, IReceptor { @@ -63,6 +65,8 @@ public class Receptor : Neuron, IReceptor { this.outputValue = this.bias; } +#if UNITY_MATHEMATICS + public override void UpdateNuclei() { this.stale++; if (this.stale > staleValueForSleep && lengthsq(this.bias) > 0) { @@ -71,6 +75,18 @@ public class Receptor : Neuron, IReceptor { } } +#else + + public override void UpdateNuclei() { + this.stale++; + if (this.stale > staleValueForSleep && this.bias.sqrMagnitude > 0) { + this.bias = new Vector3(0, 0, 0); + this.parent.UpdateFromNucleus(this); + } + } + + +#endif public override void ProcessStimulus(Vector3 inputValue, int thingId = 0, string thingName = null) { this._array ??= new NucleusArray(this.parent); this._array.ProcessStimulus(thingId, inputValue, thingName); diff --git a/Runtime.meta b/Runtime.meta new file mode 100644 index 0000000..1fe660e --- /dev/null +++ b/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5a33d0cc40bc19fe98b76f6aed80a38e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Vector.cs b/Runtime/Vector.cs new file mode 100644 index 0000000..40855b3 --- /dev/null +++ b/Runtime/Vector.cs @@ -0,0 +1,8 @@ +using UnityEngine; + +#if UNITY_MATHEMATICS +using Unity.Mathematics; +using static Unity.Mathematics.math; +//#endif + +#endif diff --git a/Runtime/Vector.cs.meta b/Runtime/Vector.cs.meta new file mode 100644 index 0000000..aa9e666 --- /dev/null +++ b/Runtime/Vector.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 76e9f0d4925b7ac278baa9932582ed10 \ No newline at end of file