diff --git a/Editor/ClusterEditor.cs b/Editor/ClusterEditor.cs index e42dc95..e3ed2ef 100644 --- a/Editor/ClusterEditor.cs +++ b/Editor/ClusterEditor.cs @@ -15,12 +15,24 @@ namespace NanoBrain.Unity { void OnEnable() { clusterPrefab = (ClusterPrefab)target; - clusterPrefab.cluster.name = clusterPrefab.name; view = ClusterView.GetClusterView(serializedObject); + + string assetPath = AssetDatabase.GetAssetPath(clusterPrefab); + if (!string.IsNullOrEmpty(assetPath)) { + string folder = System.IO.Path.GetDirectoryName(assetPath); + string jsonPath = System.IO.Path.Combine(folder, clusterPrefab.name + ".json"); + if (System.IO.File.Exists(jsonPath)) { + ClusterData newClusterData = Cluster.Import(jsonPath); + clusterPrefab.cluster = new(newClusterData); + } + } + view.currentCluster ??= clusterPrefab.cluster; view.currentNucleus = clusterPrefab.cluster.defaultOutput; view.selectedOutput = view.currentNucleus; + clusterPrefab.cluster.name = clusterPrefab.name; + clusterPrefab.cluster.Cleanup(); } @@ -218,25 +230,25 @@ namespace NanoBrain.Unity { showSynapses = EditorGUILayout.Foldout(showSynapses, "Synapses", true); if (showSynapses) { EditorGUI.indentLevel--; - if (this.view.currentNucleus is Neuron neuron2) { - Neuron.CombinatorType newCombinator = (Neuron.CombinatorType)EditorGUILayout.EnumPopup("Combinator", neuron2.combinator); - anythingChanged |= newCombinator != neuron2.combinator; - neuron2.combinator = newCombinator; + if (this.view.currentNucleus is Neuron neuron) { + Neuron.CombinatorType newCombinator = (Neuron.CombinatorType)EditorGUILayout.EnumPopup("Combinator", neuron.combinator); + anythingChanged |= newCombinator != neuron.combinator; + neuron.combinator = newCombinator; EditorGUIUtility.wideMode = true; float previousLabelWidth = EditorGUIUtility.labelWidth; EditorGUIUtility.labelWidth = 100; - Vector3 newBias = EditorGUILayout.Vector3Field("Bias", neuron2.bias); - if (newBias != neuron2.bias) { + Vector3 newBias = EditorGUILayout.Vector3Field("Bias", neuron.bias); + if (newBias != neuron.bias) { anythingChanged = true; - neuron2.bias = newBias; + neuron.bias = newBias; } - bool newTrainable = EditorGUILayout.Toggle("Trainable", neuron2.trainable); - if (newTrainable != neuron2.trainable) { + bool newTrainable = EditorGUILayout.Toggle("Trainable", neuron.trainable); + if (newTrainable != neuron.trainable) { anythingChanged = true; - neuron2.trainable = newTrainable; + neuron.trainable = newTrainable; } EditorGUIUtility.labelWidth = previousLabelWidth; diff --git a/Editor/ClusterJson_Drawer.cs b/Editor/ClusterJson_Drawer.cs index 25acfe8..b2a0d8c 100644 --- a/Editor/ClusterJson_Drawer.cs +++ b/Editor/ClusterJson_Drawer.cs @@ -24,7 +24,7 @@ namespace NanoBrain.Unity { EditorGUI.BeginChangeCheck(); TextAsset current = jsonFileProp.objectReferenceValue as TextAsset; - Rect fieldRect = new(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight); + Rect fieldRect = new(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight); TextAsset newAsset = (TextAsset)EditorGUI.ObjectField(fieldRect, label, current, typeof(TextAsset), false); if (EditorGUI.EndChangeCheck()) { @@ -41,7 +41,8 @@ namespace NanoBrain.Unity { ); propertyExtraHeight = 2 + 2 * EditorGUIUtility.singleLineHeight; EditorGUI.HelpBox(helpRect, "Selected asset is not a .json file.", MessageType.Error); - } else + } + else propertyExtraHeight = 0; EditorGUI.EndProperty(); @@ -56,16 +57,19 @@ namespace NanoBrain.Unity { private readonly Dictionary _invalidCache = new(); private bool IsInvalidJson(SerializedProperty jsonFileProp) { - TextAsset current = jsonFileProp.objectReferenceValue as TextAsset; - if (current == null) return false; + TextAsset jsonFile = jsonFileProp.objectReferenceValue as TextAsset; + if (jsonFile == null) + return false; - EntityId id = current.GetEntityId(); + EntityId id = jsonFile.GetEntityId(); if (_invalidCache.TryGetValue(id, out bool invalid)) return invalid; - string path = AssetDatabase.GetAssetPath(current); + string path = AssetDatabase.GetAssetPath(jsonFile); invalid = !path.EndsWith(".json", System.StringComparison.OrdinalIgnoreCase); _invalidCache[id] = invalid; + + return invalid; } } diff --git a/Editor/ClusterPickerWindow.cs b/Editor/ClusterPickerWindow.cs index 4e2597f..c37fb31 100644 --- a/Editor/ClusterPickerWindow.cs +++ b/Editor/ClusterPickerWindow.cs @@ -44,7 +44,7 @@ namespace NanoBrain.Unity { EditorGUILayout.Space(); scroll = EditorGUILayout.BeginScrollView(scroll); - foreach (var it in items) { + foreach (ClusterPrefab it in this.items) { if (!string.IsNullOrEmpty(search) && it.name.IndexOf(search, StringComparison.OrdinalIgnoreCase) < 0) continue; diff --git a/Editor/Cluster_Drawer.cs b/Editor/Cluster_Drawer.cs index abc7308..21e4002 100644 --- a/Editor/Cluster_Drawer.cs +++ b/Editor/Cluster_Drawer.cs @@ -93,41 +93,41 @@ namespace NanoBrain.Unity { } // If a brain has been selected - //if (prefabProp.objectReferenceValue != null) { - // Graph is not shown when multi-editing - if (property.serializedObject.targetObjects.Length == 1) { - UnityEngine.Object targetObject = property.serializedObject.targetObject; - Cluster_Drawer.selectedTarget = targetObject; + if (prefabProp.objectReferenceValue != null) { + // Graph is not shown when multi-editing + if (property.serializedObject.targetObjects.Length == 1) { + UnityEngine.Object targetObject = property.serializedObject.targetObject; + Cluster_Drawer.selectedTarget = targetObject; - Cluster cluster = SerializedPropertyUtility.GetManagedObjectForProperty(targetObject, property.propertyPath) as Cluster; - if (cluster != null) { - // if (cluster.version != cluster.prefab.version) { - // // Debug.Log($"prefab version: {cluster.prefab.version} cluster version: {cluster.version}"); - // clusterView.initialized = false; - // EditorApplication.delayCall += () => InstantiateCluster(property, clusterView); - // } - - // foldout header rect - Rect headerRect = new(fieldRect.x, fieldRect.yMax + 4f, fieldRect.width, EditorGUIUtility.singleLineHeight); - clusterView.isOpen = EditorGUI.Foldout(headerRect, clusterView.isOpen, "Graph", true); - - if (clusterView.isOpen) { - // content rect below header - Rect drawRect = new(fieldRect.x, headerRect.yMax + 2f, fieldRect.width, 450f); - - if (cluster is not null && (clusterView.currentCluster == null || clusterView.currentCluster != cluster)) { - clusterView.currentCluster = cluster; - clusterView.currentNucleus = cluster.defaultOutput; - clusterView.selectedOutput = clusterView.currentNucleus; + Cluster cluster = SerializedPropertyUtility.GetManagedObjectForProperty(targetObject, property.propertyPath) as Cluster; + if (cluster != null) { + if (cluster.version != cluster.prefab.version) { + // Debug.Log($"prefab version: {cluster.prefab.version} cluster version: {cluster.version}"); + clusterView.initialized = false; + EditorApplication.delayCall += () => InstantiateCluster(property, clusterView); } - Cluster_Drawer.currentClusterView = clusterView; - clusterView.Render(drawRect); - //Debug.Log(prefab.cluster.defaultOutput.outputMagnitude); - } + // foldout header rect + Rect headerRect = new(fieldRect.x, fieldRect.yMax + 4f, fieldRect.width, EditorGUIUtility.singleLineHeight); + clusterView.isOpen = EditorGUI.Foldout(headerRect, clusterView.isOpen, "Graph", true); + + if (clusterView.isOpen) { + // content rect below header + Rect drawRect = new(fieldRect.x, headerRect.yMax + 2f, fieldRect.width, 450f); + + if (cluster is not null && (clusterView.currentCluster == null || clusterView.currentCluster != cluster)) { + clusterView.currentCluster = cluster; + clusterView.currentNucleus = cluster.defaultOutput; + clusterView.selectedOutput = clusterView.currentNucleus; + } + Cluster_Drawer.currentClusterView = clusterView; + clusterView.Render(drawRect); + //Debug.Log(prefab.cluster.defaultOutput.outputMagnitude); + } + + } } } - //} EditorGUI.indentLevel = indent; EditorGUI.EndProperty(); diff --git a/Runtime/Scripts/Core/Cluster.cs b/Runtime/Scripts/Core/Cluster.cs index f4e0e2c..77ed36b 100644 --- a/Runtime/Scripts/Core/Cluster.cs +++ b/Runtime/Scripts/Core/Cluster.cs @@ -25,6 +25,7 @@ namespace NanoBrain { /// /// Cluster should always be created from prefabs public ClusterPrefab prefab; + public ClusterJson json; //[HideInInspector] @@ -129,6 +130,46 @@ namespace NanoBrain { } } } + public Cluster(ClusterData clusterData) { + this.name = clusterData.name; + // First create all neurons + foreach (NeuronData neuronData in clusterData.neurons) { + if (neuronData.type == Type.MemoryCell) + new MemoryCell(this, neuronData.name) { + bias = neuronData.bias, + combinator = neuronData.combinatorType, + activator = neuronData.activationType + }; + else + new Neuron(this, neuronData.name) { + bias = neuronData.bias, + combinator = neuronData.combinatorType, + activator = neuronData.activationType + }; + + } + // Now create the synapses between them + foreach (NeuronData neuronData in clusterData.neurons) { + Neuron receiver = this.GetNeuron(neuronData.name); + foreach (SynapseData synapseData in neuronData.synapses) { + if (synapseData.clusterName != this.name) { + // Add reference to external cluster + ClusterPrefab extPrefab = Resources.Load(synapseData.clusterName) as ClusterPrefab; + if (extPrefab == null) + Debug.LogError($"Could not find cluster Resource {synapseData.clusterName}"); + else { + Cluster extCluster = new Cluster(extPrefab, this); + Neuron extNeuron = extCluster.GetNeuron(synapseData.neuronName); + extNeuron.AddReceiver(receiver); + } + } + else { + Neuron sender = this.GetNeuron(synapseData.neuronName); + sender?.AddReceiver(receiver, synapseData.weight); + } + } + } + } /// @@ -804,8 +845,9 @@ namespace NanoBrain { System.IO.File.WriteAllText(path, json); } - public ClusterData Import(string path) { + public static ClusterData Import(string path) { string json = File.ReadAllText(path); + Debug.Log($"Importing json from {path}"); return JsonUtility.FromJson(json); } @@ -824,10 +866,12 @@ namespace NanoBrain { if (nucleus is Neuron neuron) { NeuronData neuronData = new(neuron); this.neurons.Add(neuronData); - } - else if (nucleus is Cluster extCluster) { - ExternalClusterData clusterData = new(extCluster); - this.clusters.Add(clusterData); + foreach (Synapse synapse in neuron.synapses) { + if (synapse.neuron.parent.name != cluster.name) { + ExternalClusterData clusterData = new(synapse.neuron.parent); + this.clusters.Add(clusterData); + } + } } } } diff --git a/Runtime/Scripts/Core/Neuron.cs b/Runtime/Scripts/Core/Neuron.cs index 921de93..6e4dd1b 100644 --- a/Runtime/Scripts/Core/Neuron.cs +++ b/Runtime/Scripts/Core/Neuron.cs @@ -737,12 +737,17 @@ namespace NanoBrain { [Serializable] public class NeuronData { public string name; + public Nucleus.Type type; public Vector3 bias = Vector3.zero; public Neuron.CombinatorType combinatorType; public List synapses = new(); public Neuron.ActivationType activationType; public NeuronData(Neuron neuron) { + if (neuron is MemoryCell) + this.type = Nucleus.Type.MemoryCell; + else + this.type = Nucleus.Type.Neuron; this.name = neuron.name; this.bias = neuron.bias; this.combinatorType = neuron.combinator;