using System.Collections.Generic; using System.Linq; using System.Reflection; using UnityEngine; namespace XNode { /// Precaches reflection data in editor so we won't have to do it runtime public static class NodeDataCache { private static PortDataCache portDataCache; private static bool Initialized { get { return portDataCache != null; } } /// Update static ports to reflect class fields. public static void UpdatePorts(Node node, Dictionary ports) { if (!Initialized) BuildCache(); Dictionary staticPorts = new Dictionary(); System.Type nodeType = node.GetType(); if (portDataCache.ContainsKey(nodeType)) { for (int i = 0; i < portDataCache[nodeType].Count; i++) { staticPorts.Add(portDataCache[nodeType][i].fieldName, portDataCache[nodeType][i]); } } // Cleanup port dict - Remove nonexisting static ports - update static port types // Loop through current node ports foreach (NodePort port in ports.Values.ToList()) { // If port still exists, check it it has been changed if (staticPorts.ContainsKey(port.fieldName)) { NodePort staticPort = staticPorts[port.fieldName]; // If port exists but with wrong settings, remove it. Re-add it later. if (port.connectionType != staticPort.connectionType || port.IsDynamic || port.direction != staticPort.direction) ports.Remove(port.fieldName); else port.ValueType = staticPort.ValueType; } // If port doesn't exist anymore, remove it else if (port.IsStatic) ports.Remove(port.fieldName); } // Add missing ports foreach (NodePort staticPort in staticPorts.Values) { if (!ports.ContainsKey(staticPort.fieldName)) { ports.Add(staticPort.fieldName, new NodePort(staticPort, node)); } } } private static void BuildCache() { portDataCache = new PortDataCache(); System.Type baseType = typeof(Node); List nodeTypes = new List(); System.Reflection.Assembly[] assemblies = System.AppDomain.CurrentDomain.GetAssemblies(); Assembly selfAssembly = Assembly.GetAssembly(baseType); if (selfAssembly.FullName.StartsWith("Assembly-CSharp") && !selfAssembly.FullName.Contains("-firstpass")) { // If xNode is not used as a DLL, check only CSharp (fast) nodeTypes.AddRange(selfAssembly.GetTypes().Where(t => !t.IsAbstract && baseType.IsAssignableFrom(t))); } else { // Else, check all DDLs (slow) foreach (Assembly assembly in assemblies) { nodeTypes.AddRange(assembly.GetTypes().Where(t => !t.IsAbstract && baseType.IsAssignableFrom(t)).ToArray()); } } for (int i = 0; i < nodeTypes.Count; i++) { CachePorts(nodeTypes[i]); } } private static void CachePorts(System.Type nodeType) { 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[i].Name + " of type " + nodeType.FullName + " 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]); } } } }