Cluster instance per prefab instance
This commit is contained in:
parent
76023718cb
commit
a87118fc23
@ -1,3 +1,4 @@
|
||||
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
@ -7,7 +8,7 @@ using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace NanoBrain.Unity {
|
||||
|
||||
/*
|
||||
[CustomPropertyDrawer(typeof(ClusterPrefab))]
|
||||
class ClusterPrefab_Drawer : PropertyDrawer {
|
||||
public static void Insepctor(SerializedObject serializedObject, string propertyName) {
|
||||
@ -51,7 +52,7 @@ namespace NanoBrain.Unity {
|
||||
|
||||
// foldout header rect
|
||||
Rect headerRect = new(fieldRect.x, fieldRect.yMax + 4f, fieldRect.width, EditorGUIUtility.singleLineHeight);
|
||||
isOpen = EditorGUI.Foldout(headerRect, isOpen, "Graph", true);
|
||||
isOpen = EditorGUI.Foldout(headerRect, isOpen, "Gaph", true);
|
||||
s_foldouts[key] = isOpen;
|
||||
|
||||
if (isOpen) {
|
||||
@ -126,4 +127,4 @@ namespace NanoBrain.Unity {
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,7 +37,7 @@ namespace NanoBrain.Unity {
|
||||
|
||||
public static void Render(Rect drawRect, Cluster cluster, SerializedProperty property) {
|
||||
ClusterView clusterView = GetClusterView(property);
|
||||
if (clusterView.currentCluster == null) {
|
||||
if (clusterView.currentCluster == null || clusterView.currentCluster != cluster) {
|
||||
clusterView.currentCluster = cluster;
|
||||
clusterView.currentNucleus = cluster.defaultOutput;
|
||||
clusterView.selectedOutput = clusterView.currentNucleus;
|
||||
@ -46,7 +46,7 @@ namespace NanoBrain.Unity {
|
||||
}
|
||||
public static void Render(Rect drawRect, Cluster cluster, SerializedObject obj) {
|
||||
ClusterView clusterView = GetClusterView(obj);
|
||||
if (clusterView.currentCluster == null) {
|
||||
if (clusterView.currentCluster == null || clusterView.currentCluster != cluster) {
|
||||
clusterView.currentCluster = cluster;
|
||||
clusterView.currentNucleus = cluster.defaultOutput;
|
||||
clusterView.selectedOutput = clusterView.currentNucleus;
|
||||
|
||||
@ -25,7 +25,7 @@ namespace NanoBrain.Unity {
|
||||
protected Nucleus selectedSynapseNeuron;
|
||||
|
||||
protected GameObject gameObject;
|
||||
private bool expandArray = false;
|
||||
//private bool expandArray = false;
|
||||
|
||||
protected ClusterPrefab prefabAsset;
|
||||
protected VisualElement topMenuContainer;
|
||||
|
||||
248
Editor/Cluster_Drawer.cs
Normal file
248
Editor/Cluster_Drawer.cs
Normal file
@ -0,0 +1,248 @@
|
||||
using System.Linq;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using UnityEditor;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace NanoBrain.Unity {
|
||||
|
||||
[CustomPropertyDrawer(typeof(Cluster))]
|
||||
class Cluster_Drawer : PropertyDrawer {
|
||||
public static void Insepctor(SerializedObject serializedObject, string propertyName) {
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty(propertyName));
|
||||
}
|
||||
|
||||
const float padding = 4f;
|
||||
const float elementHeight = 64f; // height reserved for the VisualElement
|
||||
|
||||
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
|
||||
float height = EditorGUIUtility.singleLineHeight + padding;
|
||||
string key = property.propertyPath + "_" + property.serializedObject.targetObject.GetInstanceID();//GetEntityId();
|
||||
s_foldouts.TryGetValue(key, out bool isOpen);
|
||||
SerializedProperty prefabProp = property.FindPropertyRelative(nameof(Cluster.prefab));
|
||||
if (prefabProp.objectReferenceValue != null && isOpen) {
|
||||
height += padding + elementHeight;
|
||||
height = 500;
|
||||
}
|
||||
else
|
||||
height = 18;
|
||||
return height;
|
||||
}
|
||||
|
||||
static readonly Dictionary<string, bool> s_foldouts = new();
|
||||
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
|
||||
label = EditorGUI.BeginProperty(position, label, property);
|
||||
|
||||
// Begin indent block
|
||||
int indent = EditorGUI.indentLevel;
|
||||
EditorGUI.indentLevel = 0;
|
||||
|
||||
SerializedProperty prefabProp = property.FindPropertyRelative(nameof(Cluster.prefab));
|
||||
|
||||
// Draw the object field on the top line
|
||||
Rect fieldRect = new(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight);
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUI.PropertyField(fieldRect, prefabProp, label);
|
||||
if (EditorGUI.EndChangeCheck()) {
|
||||
prefabProp.serializedObject.ApplyModifiedProperties();
|
||||
ClusterPrefab clusterPrefab = prefabProp.objectReferenceValue as ClusterPrefab;
|
||||
if (clusterPrefab != null) {
|
||||
Cluster newCluster = new(clusterPrefab);
|
||||
|
||||
SerializedObject serializedObject = property.serializedObject;
|
||||
foreach (UnityEngine.Object targetObject in serializedObject.targetObjects) {
|
||||
var parent = SerializedPropertyUtility.GetParentObjectAndMember(targetObject, property.propertyPath, out var memberInfo, out int outIndex);
|
||||
if (parent != null && memberInfo is FieldInfo fieldInfo) {
|
||||
fieldInfo.SetValue(parent, newCluster);
|
||||
EditorUtility.SetDirty(targetObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (prefabProp.objectReferenceValue is ClusterPrefab prefab) {
|
||||
if (property.serializedObject.targetObjects.Length == 1) {
|
||||
// Graph is not shown when multi-editing
|
||||
UnityEngine.Object targetObject = property.serializedObject.targetObject;
|
||||
Cluster cluster = SerializedPropertyUtility.GetManagedObjectForProperty(targetObject, property.propertyPath) as Cluster;
|
||||
|
||||
// key per field instance
|
||||
string key = property.propertyPath + "_" + property.serializedObject.targetObject.GetInstanceID();//GetEntityId();
|
||||
if (!s_foldouts.TryGetValue(key, out bool isOpen))
|
||||
isOpen = true;
|
||||
|
||||
// foldout header rect
|
||||
Rect headerRect = new(fieldRect.x, fieldRect.yMax + 4f, fieldRect.width, EditorGUIUtility.singleLineHeight);
|
||||
isOpen = EditorGUI.Foldout(headerRect, isOpen, "Graph", true);
|
||||
s_foldouts[key] = isOpen;
|
||||
|
||||
if (isOpen) {
|
||||
// content rect below header
|
||||
Rect drawRect = new(fieldRect.x, headerRect.yMax + 2f, fieldRect.width, 450f);
|
||||
|
||||
ClusterView.Render(drawRect, cluster, property);
|
||||
//Debug.Log(prefab.cluster.defaultOutput.outputMagnitude);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel = indent;
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
}
|
||||
|
||||
public static class SerializedPropertyUtility {
|
||||
// Walks path like "myField.nested.arrayField.Array.data[0].value"
|
||||
// Returns parent object that contains the final member, and sets outMember (FieldInfo or PropertyInfo) for the final member.
|
||||
// If final target is an array/list element, returns parent as the IList and outMember = null, and outIndex set to element index.
|
||||
public static object GetParentObjectAndMember(object root, string propertyPath, out MemberInfo outMember, out int outIndex) {
|
||||
outMember = null;
|
||||
outIndex = -1;
|
||||
if (root == null || string.IsNullOrEmpty(propertyPath)) return null;
|
||||
|
||||
object obj = root;
|
||||
var parts = propertyPath.Split('.');
|
||||
for (int i = 0; i < parts.Length; i++) {
|
||||
string part = parts[i];
|
||||
|
||||
// handle array/list: Unity path uses "Array" then "data[index]"
|
||||
if (part == "Array" && i + 2 < parts.Length && parts[i + 1].StartsWith("data[")) {
|
||||
// previous part name is the list/array field name
|
||||
string listFieldName = parts[i - 1];
|
||||
// get the field info for listFieldName on current obj's type
|
||||
var fiList = GetFieldInTypeHierarchy(obj.GetType(), listFieldName);
|
||||
object listObj = fiList != null ? fiList.GetValue(obj) : GetPropValue(obj, listFieldName);
|
||||
|
||||
if (listObj == null) return null;
|
||||
|
||||
// parse index from e.g. "data[3]"
|
||||
string indexPart = parts[i + 1]; // like data[3]
|
||||
int start = indexPart.IndexOf('[') + 1;
|
||||
int end = indexPart.IndexOf(']');
|
||||
int index = int.Parse(indexPart.Substring(start, end - start));
|
||||
|
||||
// if this is the last element in the path, return parent=the IList and outIndex=index
|
||||
if (i + 2 == parts.Length - 0) {
|
||||
outMember = null;
|
||||
outIndex = index;
|
||||
return listObj as IList;
|
||||
}
|
||||
|
||||
// otherwise step into the element and continue
|
||||
if (listObj is IList ilist) {
|
||||
obj = ilist[index];
|
||||
}
|
||||
else {
|
||||
// not an IList — cannot continue
|
||||
return null;
|
||||
}
|
||||
|
||||
i += 1; // skip the data[...] part (we already consumed it)
|
||||
continue;
|
||||
}
|
||||
|
||||
// normal field/property access: if this is the last part, return parent and member
|
||||
bool isLast = (i == parts.Length - 1);
|
||||
|
||||
var fi = GetFieldInTypeHierarchy(obj.GetType(), part);
|
||||
if (fi != null) {
|
||||
if (isLast) {
|
||||
outMember = fi;
|
||||
return obj;
|
||||
}
|
||||
else {
|
||||
obj = fi.GetValue(obj);
|
||||
if (obj == null) return null;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
var pi = GetPropertyInTypeHierarchy(obj.GetType(), part);
|
||||
if (pi != null) {
|
||||
if (isLast) {
|
||||
outMember = pi;
|
||||
return obj;
|
||||
}
|
||||
else {
|
||||
obj = pi.GetValue(obj);
|
||||
if (obj == null) return null;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// if nothing found, fail
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static object GetManagedObjectForProperty(object root, string propertyPath) {
|
||||
var obj = root;
|
||||
var parts = propertyPath.Split('.');
|
||||
for (int i = 0; i < parts.Length; i++) {
|
||||
var part = parts[i];
|
||||
|
||||
// handle array: "arrayField.Array.data[index]"
|
||||
if (part == "Array") {
|
||||
i += 2; // skip "Array" and "data[index]"
|
||||
var indexPart = parts[i]; // like "data[0]"
|
||||
int start = indexPart.IndexOf('[') + 1;
|
||||
int end = indexPart.IndexOf(']');
|
||||
int idx = int.Parse(indexPart.Substring(start, end - start));
|
||||
var listField = parts[i - 2];
|
||||
var fi = obj.GetType().GetField(listField, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
||||
var list = fi.GetValue(obj) as System.Collections.IList;
|
||||
obj = list[idx];
|
||||
continue;
|
||||
}
|
||||
|
||||
var field = obj.GetType().GetField(part, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
||||
if (field == null) {
|
||||
var prop = obj.GetType().GetProperty(part, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
||||
if (prop == null) return null;
|
||||
obj = prop.GetValue(obj);
|
||||
}
|
||||
else {
|
||||
obj = field.GetValue(obj);
|
||||
}
|
||||
|
||||
if (obj == null) return null;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
static FieldInfo GetFieldInTypeHierarchy(Type type, string fieldName) {
|
||||
while (type != null) {
|
||||
var fi = type.GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
if (fi != null) return fi;
|
||||
type = type.BaseType;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static PropertyInfo GetPropertyInTypeHierarchy(Type type, string propName) {
|
||||
while (type != null) {
|
||||
var pi = type.GetProperty(propName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
if (pi != null) return pi;
|
||||
type = type.BaseType;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static object GetPropValue(object obj, string name) {
|
||||
var pi = GetPropertyInTypeHierarchy(obj.GetType(), name);
|
||||
if (pi != null) return pi.GetValue(obj);
|
||||
var fi = GetFieldInTypeHierarchy(obj.GetType(), name);
|
||||
if (fi != null) return fi.GetValue(obj);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Editor/Cluster_Drawer.cs.meta
Normal file
11
Editor/Cluster_Drawer.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 18e075a03ca2efdb2895079f63eb333a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -65,7 +65,7 @@ namespace NanoBrain {
|
||||
public List<Nucleus> nuclei = new();
|
||||
// the nuclei sorted using topological sorting
|
||||
// to ensure that the cluster is computer in the right order
|
||||
|
||||
|
||||
//public List<Nucleus> sortedNuclei;
|
||||
|
||||
#region Init
|
||||
@ -108,6 +108,9 @@ namespace NanoBrain {
|
||||
/// Strange that this does not take any parameters or return values.
|
||||
/// Where which the clone be found???
|
||||
private void ClonePrefab() {
|
||||
if (this.prefab == null || this.prefab.cluster == null || this.prefab.cluster.nuclei == null)
|
||||
return;
|
||||
|
||||
Nucleus[] prefabNuclei = this.prefab.cluster.nuclei.ToArray();
|
||||
|
||||
// first clone the nuclei without their connections
|
||||
@ -460,7 +463,7 @@ namespace NanoBrain {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The neurons without outgoing connections
|
||||
/// </summary>
|
||||
@ -548,6 +551,9 @@ namespace NanoBrain {
|
||||
/// <param name="neuronName">The name of the neuron to find</param>
|
||||
/// <returns>The found neuron or null when it is not found</returns>
|
||||
public Neuron GetNeuron(string neuronName) {
|
||||
if (this.nuclei == null)
|
||||
return null;
|
||||
|
||||
foreach (Nucleus nucleus in this.nuclei) {
|
||||
if (nucleus is Neuron neuron && neuron.name == neuronName)
|
||||
return neuron;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user