From c1bf54a1cc5be55b73389ee96ab5830d11117d95 Mon Sep 17 00:00:00 2001 From: Pascal Serrarens Date: Wed, 11 Feb 2026 11:49:03 +0100 Subject: [PATCH] Improve persisting changes --- Editor/ClusterInspector.cs | 365 ++++++++++++++++++++----------------- Neuron.cs | 71 ++++++-- 2 files changed, 253 insertions(+), 183 deletions(-) diff --git a/Editor/ClusterInspector.cs b/Editor/ClusterInspector.cs index b8b787d..b52cba9 100644 --- a/Editor/ClusterInspector.cs +++ b/Editor/ClusterInspector.cs @@ -18,9 +18,17 @@ public class ClusterInspector : Editor { public override VisualElement CreateInspectorGUI() { ClusterPrefab prefab = target as ClusterPrefab; + + string path = AssetDatabase.GetAssetPath(prefab); // or known path + Debug.Log($"{path}"); + ClusterPrefab currentWrapper = AssetDatabase.LoadAssetAtPath(path); + if (currentWrapper == null) + Debug.LogError("CreateInspectorGUI: Cluster Prefab is not found on disk"); + if (prefab != null) prefab.EnsureInitialization(); + serializedObject.Update(); VisualElement root = new(); @@ -76,7 +84,7 @@ public class ClusterInspector : Editor { private readonly Dictionary neuroidPositions = new(); private bool expandArray = false; - ClusterWrapper currentWrapper; + ClusterPrefab prefabAsset; readonly PopupField outputsField; public GraphView(ClusterPrefab prefab) { @@ -168,9 +176,20 @@ public class ClusterInspector : Editor { return; } - if (currentWrapper != null) - DestroyImmediate(currentWrapper); - currentWrapper = CreateInstance().Init(this.currentNucleus, prefab); + // if (currentWrapper != null) + // DestroyImmediate(currentWrapper); + // currentWrapper = CreateInstance().Init(this.currentNucleus, prefab); + + string path = AssetDatabase.GetAssetPath(this.prefab); // or known path + this.prefabAsset = AssetDatabase.LoadAssetAtPath(path); + if (this.prefabAsset == null) { + // create and save if it doesn't exist + this.prefabAsset = CreateInstance(); + // AssetDatabase.CreateAsset(currentWrapper, "Assets/ClusterPrefab.asset"); + // AssetDatabase.SaveAssets(); + Debug.LogError("Cluster Prefab is not found on disk"); + } + //currentWrapper.Init(this.currentNucleus, prefab); DrawInspector(inspectorContainer); } @@ -510,146 +529,164 @@ public class ClusterInspector : Editor { return; // create a SerializedObject wrapper so Unity inspector controls work (and Undo) - SerializedObject so = new(currentWrapper); - IMGUIContainer container = new(() => { - if (so.targetObject == null) - return; - so.Update(); - - if (this.currentNucleus == null) - return; - - GUIStyle headerStyle = new(EditorStyles.boldLabel) { - alignment = TextAnchor.MiddleLeft, - margin = new RectOffset(10, 0, 4, 4) - }; - GUIStyle boldTextFieldStyle = new(EditorStyles.textField) { - fontStyle = FontStyle.Bold - }; - - GUILayout.Label(this.currentNucleus.GetType().ToString(), headerStyle); - if (this.currentNucleus is Neuron neuron1) { - neuron1.type = (Nucleus.Type)EditorGUILayout.EnumPopup(neuron1.type); - } - string newName = EditorGUILayout.TextField(this.currentNucleus.name, boldTextFieldStyle); - if (newName != this.currentNucleus.name) { - this.currentNucleus.name = newName; - this.prefab.RefreshOutputs(); - outputsField.choices = this.prefab.outputs.Select(output => output.name).ToList(); - //outputsField.value = newName; - } - - if (Application.isPlaying) { - GUIContent nameLabel = new("Output", this.currentNucleus.outputValue.ToString()); - EditorGUILayout.FloatField(nameLabel, length(this.currentNucleus.outputValue)); - } - else - EditorGUILayout.LabelField(" "); - - if (this.currentNucleus is MemoryCell memory) { - memory.staticMemory = EditorGUILayout.Toggle("Static Memory", memory.staticMemory); - } - - // Synapses - - showSynapses = EditorGUILayout.BeginFoldoutHeaderGroup(showSynapses, "Synapses"); - if (showSynapses) { - ConnectNucleus(this.prefab, this.currentNucleus); - AddSynapse(this.prefab, this.currentNucleus); - - this.currentNucleus.bias = EditorGUILayout.Vector3Field("Bias", this.currentNucleus.bias); - - NucleusArray array = null; - if (this.currentNucleus.synapses.Count > 0) { - Synapse[] synapses = this.currentNucleus.synapses.ToArray(); - foreach (Synapse synapse in synapses) { - if (synapse.nucleus != null) { - if (array != null) { - if (array.nuclei.Contains(synapse.nucleus)) - continue; - } - else { - if (synapse.nucleus.array != null && synapse.nucleus.array.nuclei.Length > 1) - array = synapse.nucleus.array; - } - - EditorGUILayout.Space(); - - if (Application.isPlaying) { - Vector3 value = synapse.nucleus.outputValue * synapse.weight; - GUIContent synapseValueLabel = new(synapse.nucleus.name, synapse.nucleus.outputValue.ToString()); - EditorGUILayout.FloatField(synapseValueLabel, length(synapse.nucleus.outputValue)); - } - else { - EditorGUILayout.BeginHorizontal(); - EditorGUILayout.LabelField(synapse.nucleus.name); - if (GUILayout.Button("Disconnect")) - synapse.nucleus.RemoveReceiver(this.currentNucleus); - EditorGUILayout.EndHorizontal(); - } - - EditorGUI.indentLevel++; - synapse.weight = EditorGUILayout.FloatField("Weight", synapse.weight); - EditorGUI.indentLevel--; - } - } - } - } - EditorGUILayout.EndFoldoutHeaderGroup(); - - EditorGUILayout.Space(); - showActivation = EditorGUILayout.BeginFoldoutHeaderGroup(showActivation, "Activation"); - if (showActivation) { - - if (this.currentNucleus is Neuron neuron) { - if (this.currentNucleus is not MemoryCell) { - EditorGUILayout.BeginHorizontal(); - EditorGUILayout.LabelField("Activation Curve", GUILayout.Width(150)); - if (neuron.curveMax > 0) - EditorGUILayout.CurveField(neuron.curve, Color.cyan, new Rect(0, 0, 1, neuron.curveMax)); - else - EditorGUILayout.CurveField(neuron.curve, Color.cyan, new Rect(0, neuron.curveMax, 1, -neuron.curveMax)); - neuron.curvePreset = (Neuron.CurvePresets)EditorGUILayout.EnumPopup(neuron.curvePreset, GUILayout.Width(100)); - EditorGUILayout.EndHorizontal(); - } - if (neuron.array == null || neuron.array.nuclei == null || neuron.array.nuclei.Count() == 0) - neuron.array = new NucleusArray(neuron); - EditorGUILayout.BeginHorizontal(); - EditorGUILayout.IntField("Array size", neuron.array.nuclei.Count()); - if (GUILayout.Button("Add")) - neuron.array.AddNucleus(this.prefab); - if (GUILayout.Button("Del")) - neuron.array.RemoveNucleus(); - EditorGUILayout.EndHorizontal(); - } - - EditorGUILayout.Space(); - } - EditorGUILayout.EndFoldoutHeaderGroup(); - - - if (GUILayout.Button("Delete this neuron")) - DeleteNeuron(this.currentNucleus); - - if (this.currentNucleus is Cluster subCluster) { - if (GUILayout.Button("Edit Cluster")) - EditCluster(subCluster); - } - - EditorGUILayout.Space(); - breakOnWake = EditorGUILayout.Toggle("Break on wake", breakOnWake); - if (breakOnWake) { - if (this.currentNucleus.isSleeping == false) - Debug.Break(); - } - trace = EditorGUILayout.Toggle("Trace", trace); - this.currentNucleus.trace = trace; - - }); + SerializedObject so = new(prefabAsset); + IMGUIContainer container = new(() => InspectorHandler(so)); inspectorContainer.Add(container); } + void InspectorHandler(SerializedObject serializedObject) { + bool anythingChanged = false; + + if (serializedObject == null || serializedObject.targetObject == null) + return; + + if (this.currentNucleus == null) + return; + + serializedObject.Update(); + + GUIStyle headerStyle = new(EditorStyles.boldLabel) { + alignment = TextAnchor.MiddleLeft, + margin = new RectOffset(10, 0, 4, 4) + }; + GUIStyle boldTextFieldStyle = new(EditorStyles.textField) { + fontStyle = FontStyle.Bold + }; + + GUILayout.Label(this.currentNucleus.GetType().ToString(), headerStyle); + string newName = EditorGUILayout.TextField(this.currentNucleus.name, boldTextFieldStyle); + if (newName != this.currentNucleus.name) { + this.currentNucleus.name = newName; + this.prefab.RefreshOutputs(); + outputsField.choices = this.prefab.outputs.Select(output => output.name).ToList(); + //outputsField.value = newName; + } + + if (Application.isPlaying) { + GUIContent nameLabel = new("Output", this.currentNucleus.outputValue.ToString()); + EditorGUILayout.FloatField(nameLabel, length(this.currentNucleus.outputValue)); + } + else + EditorGUILayout.LabelField(" "); + + if (this.currentNucleus is MemoryCell memory) { + memory.staticMemory = EditorGUILayout.Toggle("Static Memory", memory.staticMemory); + } + + // Synapses + + showSynapses = EditorGUILayout.BeginFoldoutHeaderGroup(showSynapses, "Synapses"); + if (showSynapses) { + + ConnectNucleus(this.prefab, this.currentNucleus); + AddSynapse(this.prefab, this.currentNucleus); + EditorGUILayout.Space(); + + if (this.currentNucleus is Neuron neuron2) + neuron2.combinator = (Neuron.CombinatorType)EditorGUILayout.EnumPopup("Combinator", neuron2.combinator); + + this.currentNucleus.bias = EditorGUILayout.Vector3Field("Bias", this.currentNucleus.bias); + + NucleusArray array = null; + if (this.currentNucleus.synapses.Count > 0) { + Synapse[] synapses = this.currentNucleus.synapses.ToArray(); + foreach (Synapse synapse in synapses) { + if (synapse.nucleus != null) { + if (array != null) { + if (array.nuclei.Contains(synapse.nucleus)) + continue; + } + else { + if (synapse.nucleus.array != null && synapse.nucleus.array.nuclei.Length > 1) + array = synapse.nucleus.array; + } + + EditorGUILayout.Space(); + + if (Application.isPlaying) { + Vector3 value = synapse.nucleus.outputValue * synapse.weight; + GUIContent synapseValueLabel = new(synapse.nucleus.name, synapse.nucleus.outputValue.ToString()); + EditorGUILayout.FloatField(synapseValueLabel, length(synapse.nucleus.outputValue)); + } + else { + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField(synapse.nucleus.name); + if (GUILayout.Button("Disconnect")) + synapse.nucleus.RemoveReceiver(this.currentNucleus); + EditorGUILayout.EndHorizontal(); + } + + EditorGUI.indentLevel++; + synapse.weight = EditorGUILayout.FloatField("Weight", synapse.weight); + EditorGUI.indentLevel--; + } + } + } + } + EditorGUILayout.EndFoldoutHeaderGroup(); + + // Activation + + EditorGUILayout.Space(); + showActivation = EditorGUILayout.BeginFoldoutHeaderGroup(showActivation, "Activation"); + if (showActivation) { + if (this.currentNucleus is Neuron neuron) { + if (this.currentNucleus is not MemoryCell) { + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField("Activation Curve", GUILayout.Width(150)); + if (neuron.curveMax > 0) + EditorGUILayout.CurveField(neuron.curve, Color.cyan, new Rect(0, 0, 1, neuron.curveMax)); + else + EditorGUILayout.CurveField(neuron.curve, Color.cyan, new Rect(0, neuron.curveMax, 1, -neuron.curveMax)); + neuron.curvePreset = (Neuron.CurvePresets)EditorGUILayout.EnumPopup(neuron.curvePreset, GUILayout.Width(100)); + EditorGUILayout.EndHorizontal(); + } + if (neuron.array == null || neuron.array.nuclei == null || neuron.array.nuclei.Count() == 0) + neuron.array = new NucleusArray(neuron); + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.IntField("Array size", neuron.array.nuclei.Count()); + if (GUILayout.Button("Add")) { + Undo.RecordObject(prefabAsset, "Array add " + prefabAsset.name); + neuron.array.AddNucleus(this.prefab); + anythingChanged = true; + } + if (GUILayout.Button("Del")) { + Undo.RecordObject(prefabAsset, "Array delete " + prefabAsset.name); + neuron.array.RemoveNucleus(); + anythingChanged = true; + } + EditorGUILayout.EndHorizontal(); + } + + EditorGUILayout.Space(); + } + EditorGUILayout.EndFoldoutHeaderGroup(); + + + if (GUILayout.Button("Delete this neuron")) + DeleteNeuron(this.currentNucleus); + + if (this.currentNucleus is Cluster subCluster) { + if (GUILayout.Button("Edit Cluster")) + EditCluster(subCluster); + } + + EditorGUILayout.Space(); + breakOnWake = EditorGUILayout.Toggle("Break on wake", breakOnWake); + if (breakOnWake) { + if (this.currentNucleus.isSleeping == false) + Debug.Break(); + } + trace = EditorGUILayout.Toggle("Trace", trace); + this.currentNucleus.trace = trace; + + serializedObject.ApplyModifiedProperties(); + if (anythingChanged) { + EditorUtility.SetDirty(prefabAsset); + AssetDatabase.SaveAssets(); + } + } void OnSceneGUI(SceneView sceneView) { if (this.gameObject != null) { @@ -829,28 +866,28 @@ public class NeuroidLayer { public List neuroids = new(); } -public class ClusterWrapper : ScriptableObject { - // expose fields that map to GraphNode - //public string title; - public Vector2 position; - Nucleus node; - ClusterPrefab graph; // needed to write back and mark dirty +// public class ClusterWrapper : ScriptableObject { +// // expose fields that map to GraphNode +// //public string title; +// public Vector2 position; +// Nucleus node; +// ClusterPrefab graph; // needed to write back and mark dirty - public ClusterWrapper Init(Nucleus node, ClusterPrefab graphAsset) { - this.node = node; - this.graph = graphAsset; - //this.title = " A " + node.name; - //position = node.position; - return this; - } - void OnValidate() { - if (node != null) { - //node.name = title; - //node.position = position; -#if UNITY_EDITOR - if (graph != null) - UnityEditor.EditorUtility.SetDirty(graph); -#endif - } - } -} \ No newline at end of file +// public ClusterWrapper Init(Nucleus node, ClusterPrefab graphAsset) { +// this.node = node; +// this.graph = graphAsset; +// //this.title = " A " + node.name; +// //position = node.position; +// return this; +// } +// void OnValidate() { +// if (node != null) { +// //node.name = title; +// //node.position = position; +// #if UNITY_EDITOR +// if (graph != null) +// UnityEditor.EditorUtility.SetDirty(graph); +// #endif +// } +// } +// } \ No newline at end of file diff --git a/Neuron.cs b/Neuron.cs index 2b74465..53865d4 100644 --- a/Neuron.cs +++ b/Neuron.cs @@ -26,7 +26,13 @@ public class Neuron : Nucleus { #region Serialization - public Type type = Type.Neuron; + //public Type type = Type.Neuron; + public enum CombinatorType { + Sum, + Product, + Max + } + public CombinatorType combinator = CombinatorType.Sum; public enum CurvePresets { Linear, @@ -150,9 +156,9 @@ public class Neuron : Nucleus { } protected virtual void CloneFields(Neuron clone) { - clone.array = null; + clone.array = this.array; clone.bias = this.bias; - clone.type = this.type; + clone.combinator = this.combinator; clone.curve = this.curve; clone.curvePreset = this.curvePreset; clone.curveMax = this.curveMax; @@ -184,17 +190,19 @@ public class Neuron : Nucleus { } public override void UpdateStateIsolated() { - switch (this.type) { - case Type.Neuron: - UpdateSum(); - break; - case Type.Pulsar: - UpdateProduct(); - break; - default: - UpdateSum(); - break; - } + float3 result = CombinatorAction(); + this.outputValue = Activation(result); + // switch (this.type) { + // case Type.Neuron: + // UpdateSum(); + // break; + // case Type.Pulsar: + // UpdateProduct(); + // break; + // default: + // UpdateSum(); + // break; + // } // Vector3 sum = this.bias; // int n = 0; @@ -219,20 +227,45 @@ public class Neuron : Nucleus { // this.outputValue = result; } - public void UpdateSum() { + private Func CombinatorAction => combinator switch { + CombinatorType.Sum => UpdateSum, + CombinatorType.Product => UpdateProduct, + CombinatorType.Max => UpdateMax, + _ => UpdateSum + }; + + + public float3 UpdateSum() { Vector3 sum = this.bias; foreach (Synapse synapse in this.synapses) sum += synapse.weight * synapse.nucleus.outputValue; - - this.outputValue = Activation(sum); + return sum; + //this.outputValue = Activation(sum); } - public void UpdateProduct() { + public float3 UpdateProduct() { float3 product = this.bias; foreach (Synapse synapse in this.synapses) product *= synapse.weight * synapse.nucleus.outputValue; + return product; + //this.outputValue = Activation(product); + } - this.outputValue = Activation(product); + public float3 UpdateMax() { + float3 max = this.bias; + float maxSqrLength = lengthsq(max); + + //Applying the weight factors + foreach (Synapse synapse in this.synapses) { + float3 input = synapse.weight * synapse.nucleus.outputValue; + + float inputSqrlength = lengthsq(input); + if (inputSqrlength > maxSqrLength) { + max = input; + maxSqrLength = inputSqrlength; + } + } + return max; } protected float3 Activation(float3 input) {