From 76e8b7031658d5637c9cbbff703d1189976c67f1 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Mon, 9 Oct 2017 19:52:41 +0200 Subject: [PATCH] Initialization performance updates (wip) --- Resources.meta | 9 +++ Resources/NodeDataCache.asset | Bin 0 -> 4184 bytes Resources/NodeDataCache.asset.meta | 9 +++ Scripts/Editor/NodeEditor.cs | 4 +- Scripts/Editor/NodeEditorToolbar.cs | 2 +- Scripts/Node.cs | 47 ++---------- Scripts/NodeDataCache.cs | 113 ++++++++++++++++++++++++++++ Scripts/NodeDataCache.cs.meta | 12 +++ Scripts/NodePort.cs | 12 ++- 9 files changed, 159 insertions(+), 49 deletions(-) create mode 100644 Resources.meta create mode 100644 Resources/NodeDataCache.asset create mode 100644 Resources/NodeDataCache.asset.meta create mode 100644 Scripts/NodeDataCache.cs create mode 100644 Scripts/NodeDataCache.cs.meta diff --git a/Resources.meta b/Resources.meta new file mode 100644 index 0000000..f101f36 --- /dev/null +++ b/Resources.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 65ed9b337b7f0b74ab007799bb407a5e +folderAsset: yes +timeCreated: 1507567488 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Resources/NodeDataCache.asset b/Resources/NodeDataCache.asset new file mode 100644 index 0000000000000000000000000000000000000000..d1187300b7f1e4551ac517d2a4276b409a2423f4 GIT binary patch literal 4184 zcmeH~y=zlZ7{=d|n;Ny&Z>#-K2gQ%&;?+W-Xo!%cN=u0jrHj}|BS~pKNJ5B%awkC? zIuyatMHh($kvh0`=^xOwI15f)N{7bhIrrvrqPx?1;c(9Ro%cNFJax}NJj_vE14?J5OU*DF$eJn-f1s<)Mx-}pGu+V8x4)q3~)$L!wC#J$yp z>+csYA#DncTM|nANf93sUA@~USMtdb|0~wwVIFhI8Qb8;(8tgM9+Mqt3}eoG$KWn> zE@OT;TI@k9rp_UQn>vYzE17^!E7ai`_w`zTJx2^~>Ku)@k`Ji!33OQB zAT=P=^I-;@_u`#<^%rsOukhR#2hd4$|$G|taNJkY-r>YpX|eb(8he~vsw-mlN^iLIKO`sX9A zWE-{Iz?^knK+pJFerB`4wIXvEe*i#Dk2e4S literal 0 HcmV?d00001 diff --git a/Resources/NodeDataCache.asset.meta b/Resources/NodeDataCache.asset.meta new file mode 100644 index 0000000..cd7469f --- /dev/null +++ b/Resources/NodeDataCache.asset.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: a6799b9b87b05a14f9053390f4500d7b +timeCreated: 1507567505 +licenseType: Free +NativeFormatImporter: + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/Editor/NodeEditor.cs b/Scripts/Editor/NodeEditor.cs index 901feb5..1bde7d3 100644 --- a/Scripts/Editor/NodeEditor.cs +++ b/Scripts/Editor/NodeEditor.cs @@ -63,14 +63,14 @@ public class NodeEditor { /// Draw node port GUI using automatic layouting. Returns port handle position. protected Vector2 DrawNodePortGUI(NodePort port) { GUIStyle style = port.direction == NodePort.IO.Input ? NodeEditorResources.styles.inputPort : NodeEditorResources.styles.outputPort; - Rect rect = GUILayoutUtility.GetRect(new GUIContent(port.name.PrettifyCamelCase()), style); + Rect rect = GUILayoutUtility.GetRect(new GUIContent(port.fieldName.PrettifyCamelCase()), style); return DrawNodePortGUI(rect, port); } /// Draw node port GUI in rect. Returns port handle position. protected Vector2 DrawNodePortGUI(Rect rect, NodePort port) { GUIStyle style = port.direction == NodePort.IO.Input ? NodeEditorResources.styles.inputPort : NodeEditorResources.styles.outputPort; - GUI.Label(rect, new GUIContent(port.name.PrettifyCamelCase()), style); + GUI.Label(rect, new GUIContent(port.fieldName.PrettifyCamelCase()), style); Vector2 handlePoint = rect.center; diff --git a/Scripts/Editor/NodeEditorToolbar.cs b/Scripts/Editor/NodeEditorToolbar.cs index eb89b6f..8d7900b 100644 --- a/Scripts/Editor/NodeEditorToolbar.cs +++ b/Scripts/Editor/NodeEditorToolbar.cs @@ -19,7 +19,7 @@ public partial class NodeEditorWindow { if (IsHoveringNode) { GUILayout.Space(20); string hoverInfo = hoveredNode.GetType().ToString(); - if (IsHoveringPort) hoverInfo += " > " + hoveredPort.name; + if (IsHoveringPort) hoverInfo += " > " + hoveredPort.fieldName; GUILayout.Label(hoverInfo); } } diff --git a/Scripts/Node.cs b/Scripts/Node.cs index 49f62d0..ea0d485 100644 --- a/Scripts/Node.cs +++ b/Scripts/Node.cs @@ -20,12 +20,13 @@ public abstract class Node : ScriptableObject { public int OutputCount { get { return outputs.Count; } } protected Node() { - CachePorts(); //Cache the ports at creation time so we don't have to use reflection at runtime + + GetPorts(); //Cache the ports at creation time so we don't have to use reflection at runtime } protected void OnEnable() { VerifyConnections(); - CachePorts(); + GetPorts(); Init(); } @@ -117,45 +118,7 @@ public abstract class Node : ScriptableObject { } } - /// Use reflection to find all fields with or , and write to and - private void CachePorts() { - List inputPorts = new List(); - List outputPorts = new List(); - - System.Reflection.FieldInfo[] fieldInfo = GetType().GetFields(); - for (int i = 0; i < fieldInfo.Length; i++) { - - //Get InputAttribute and OutputAttribute - object[] attribs = fieldInfo[i].GetCustomAttributes(false); - InputAttribute inputAttrib = null; - OutputAttribute outputAttrib = null; - for (int k = 0; k < attribs.Length; k++) { - if (attribs[k] is InputAttribute) inputAttrib = attribs[k] as InputAttribute; - else if (attribs[k] is OutputAttribute) outputAttrib = attribs[k] as OutputAttribute; - } - - if (inputAttrib != null && outputAttrib != null) Debug.LogError("Field " + fieldInfo + " cannot be both input and output."); - else if (inputAttrib != null) inputPorts.Add(new NodePort(fieldInfo[i], this)); - else if (outputAttrib != null) outputPorts.Add(new NodePort(fieldInfo[i], this)); - } - - //Remove - for (int i = inputs.Count-1; i >= 0; i--) { - //If input nodeport does not exist, remove it - if (!inputPorts.Any(x => inputs[i].fieldName == x.fieldName)) inputs.RemoveAt(i); - } - for (int i = outputs.Count - 1; i >= 0; i--) { - //If output nodeport does not exist, remove it - if (!outputPorts.Any(x => outputs[i].fieldName == x.fieldName)) outputs.RemoveAt(i); - } - //Add - for (int i = 0; i < inputPorts.Count; i++) { - //If inputports contains a new port, add it - if (!inputs.Any(x => x.fieldName == inputPorts[i].fieldName)) inputs.Add(inputPorts[i]); - } - for (int i = 0; i < outputPorts.Count; i++) { - //If inputports contains a new port, add it - if (!outputs.Any(x => x.fieldName == outputPorts[i].fieldName)) outputs.Add(outputPorts[i]); - } + private void GetPorts() { + NodeDataCache.GetPorts(this, out inputs, out outputs); } } diff --git a/Scripts/NodeDataCache.cs b/Scripts/NodeDataCache.cs new file mode 100644 index 0000000..8608d21 --- /dev/null +++ b/Scripts/NodeDataCache.cs @@ -0,0 +1,113 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using System.Reflection; +using System.Linq; + +/// Precaches reflection data in editor so we won't have to do it runtime +public sealed class NodeDataCache : ScriptableObject { + public static NodeDataCache instance { get { return _instance; } } + private static NodeDataCache _instance; + + [SerializeField] + private PortDataCache portDataCache = new PortDataCache(); + + [RuntimeInitializeOnLoadMethod] + private static void InitializeInstance() { + Debug.Log("INIT"); + NodeDataCache[] ndc = Resources.FindObjectsOfTypeAll(); + if (ndc == null || ndc.Length == 0) { + Debug.LogWarning("No NodeDataCache found. Creating."); + _instance = ScriptableObject.CreateInstance(); + _instance.BuildCache(); + } + else if (ndc.Length > 1) { + Debug.LogWarning("Multiple NodeDataCaches found."); + } + _instance = ndc[0]; + } + + /// Return port data from cache + public static void GetPorts(Node node, out List inputs, out List outputs) { + if (_instance == null) InitializeInstance(); + + System.Type nodeType = node.GetType(); + inputs = new List(); + outputs = new List(); + if (!_instance.portDataCache.ContainsKey(nodeType)) return; + for (int i = 0; i < _instance.portDataCache[nodeType].Count; i++) { + if (_instance.portDataCache[nodeType][i].direction == NodePort.IO.Input) inputs.Add(new NodePort(_instance.portDataCache[nodeType][i], node)); + else outputs.Add(new NodePort(_instance.portDataCache[nodeType][i], node)); + } + } + +#if UNITY_EDITOR + [UnityEditor.InitializeOnLoadMethod] +#endif + private static void Init() { + instance.BuildCache(); + } + + private void BuildCache() { + System.Type baseType = typeof(Node); + Assembly assembly = Assembly.GetAssembly(baseType); + System.Type[] nodeTypes = assembly.GetTypes().Where(t => + !t.IsAbstract && + baseType.IsAssignableFrom(t) + ).ToArray(); + portDataCache.Clear(); + + for (int i = 0; i < nodeTypes.Length; i++) { + CachePorts(nodeTypes[i]); + } + } + + private void CachePorts(System.Type nodeType) { + List inputPorts = new List(); + List outputPorts = new List(); + + System.Reflection.FieldInfo[] fieldInfo = nodeType.GetFields(); + for (int i = 0; i < fieldInfo.Length; i++) { + + //Get InputAttribute and OutputAttribute + object[] attribs = fieldInfo[i].GetCustomAttributes(false); + Node.InputAttribute inputAttrib = attribs.FirstOrDefault(x => x is Node.InputAttribute) as Node.InputAttribute; + Node.OutputAttribute outputAttrib = attribs.FirstOrDefault(x => x is Node.OutputAttribute) as Node.OutputAttribute; + + if (inputAttrib == null && outputAttrib == null) continue; + + if (inputAttrib != null && outputAttrib != null) Debug.LogError("Field " + fieldInfo + " cannot be both input and output."); + else { + if (!portDataCache.ContainsKey(nodeType)) portDataCache.Add(nodeType, new List()); + portDataCache[nodeType].Add(new NodePort(fieldInfo[i])); + } + } + } + + [System.Serializable] + private class PortDataCache : Dictionary>, ISerializationCallbackReceiver { + [SerializeField] private List keys = new List(); + [SerializeField] private List> values = new List>(); + + // save the dictionary to lists + public void OnBeforeSerialize() { + keys.Clear(); + values.Clear(); + foreach (var pair in this) { + keys.Add(pair.Key); + values.Add(pair.Value); + } + } + + // load dictionary from lists + public void OnAfterDeserialize() { + this.Clear(); + + if (keys.Count != values.Count) + throw new System.Exception(string.Format("there are {0} keys and {1} values after deserialization. Make sure that both key and value types are serializable.")); + + for (int i = 0; i < keys.Count; i++) + this.Add(keys[i], values[i]); + } + } +} diff --git a/Scripts/NodeDataCache.cs.meta b/Scripts/NodeDataCache.cs.meta new file mode 100644 index 0000000..34482f2 --- /dev/null +++ b/Scripts/NodeDataCache.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 64ea6af1e195d024d8df0ead1921e517 +timeCreated: 1507566823 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/NodePort.cs b/Scripts/NodePort.cs index dfef17c..54e55d9 100644 --- a/Scripts/NodePort.cs +++ b/Scripts/NodePort.cs @@ -19,7 +19,6 @@ public class NodePort { public bool IsOutput { get { return direction == IO.Output; } } public Node node { get; private set; } - [SerializeField] public string name; public bool enabled { get { return _enabled; } set { _enabled = value; } } public string fieldName { get { return _fieldName; } } @@ -30,11 +29,9 @@ public class NodePort { [SerializeField] private bool _enabled = true; [SerializeField] private IO _direction; - public NodePort(FieldInfo fieldInfo, Node node) { + public NodePort(FieldInfo fieldInfo) { _fieldName = fieldInfo.Name; - name = _fieldName; type = fieldInfo.FieldType; - this.node = node; var attribs = fieldInfo.GetCustomAttributes(false); for (int i = 0; i < attribs.Length; i++) { @@ -43,6 +40,13 @@ public class NodePort { } } + public NodePort(NodePort nodePort, Node node) { + _fieldName = nodePort._fieldName; + type = nodePort.type; + this.node = node; + _direction = nodePort.direction; + } + /// Checks all connections for invalid references, and removes them. public void VerifyConnections() { for (int i = 0; i < connections.Count; i++) {