NodeDataCache.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. using System.Collections.Generic;
  2. using System.Linq;
  3. using System.Reflection;
  4. using UnityEngine;
  5. namespace XNode {
  6. /// <summary> Precaches reflection data in editor so we won't have to do it runtime </summary>
  7. public static class NodeDataCache {
  8. private static PortDataCache portDataCache;
  9. private static bool Initialized { get { return portDataCache != null; } }
  10. /// <summary> Update static ports and dynamic ports managed by DynamicPortLists to reflect class fields. </summary>
  11. public static void UpdatePorts(Node node, Dictionary<string, NodePort> ports) {
  12. if (!Initialized) BuildCache();
  13. Dictionary<string, NodePort> staticPorts = new Dictionary<string, NodePort>();
  14. Dictionary<string, List<NodePort>> removedPorts = new Dictionary<string, List<NodePort>>();
  15. System.Type nodeType = node.GetType();
  16. List<NodePort> dynamicListPorts = new List<NodePort>();
  17. List<NodePort> typePortCache;
  18. if (portDataCache.TryGetValue(nodeType, out typePortCache)) {
  19. for (int i = 0; i < typePortCache.Count; i++) {
  20. staticPorts.Add(typePortCache[i].fieldName, portDataCache[nodeType][i]);
  21. }
  22. }
  23. // Cleanup port dict - Remove nonexisting static ports - update static port types
  24. // AND update dynamic ports (albeit only those in lists) too, in order to enforce proper serialisation.
  25. // Loop through current node ports
  26. foreach (NodePort port in ports.Values.ToList()) {
  27. // If port still exists, check it it has been changed
  28. NodePort staticPort;
  29. if (staticPorts.TryGetValue(port.fieldName, out staticPort)) {
  30. // If port exists but with wrong settings, remove it. Re-add it later.
  31. if (port.IsDynamic || port.direction != staticPort.direction || port.connectionType != staticPort.connectionType || port.typeConstraint != staticPort.typeConstraint) {
  32. // If port is not dynamic and direction hasn't changed, add it to the list so we can try reconnecting the ports connections.
  33. if (!port.IsDynamic && port.direction == staticPort.direction) removedPorts.Add(port.fieldName, port.GetConnections());
  34. port.ClearConnections();
  35. ports.Remove(port.fieldName);
  36. } else port.ValueType = staticPort.ValueType;
  37. }
  38. // If port doesn't exist anymore, remove it
  39. else if (port.IsStatic) {
  40. port.ClearConnections();
  41. ports.Remove(port.fieldName);
  42. }
  43. // If the port is dynamic and is managed by a dynamic port list, flag it for reference updates
  44. else if (IsDynamicListPort(port)) {
  45. dynamicListPorts.Add(port);
  46. }
  47. }
  48. // Add missing ports
  49. foreach (NodePort staticPort in staticPorts.Values) {
  50. if (!ports.ContainsKey(staticPort.fieldName)) {
  51. NodePort port = new NodePort(staticPort, node);
  52. //If we just removed the port, try re-adding the connections
  53. List<NodePort> reconnectConnections;
  54. if (removedPorts.TryGetValue(staticPort.fieldName, out reconnectConnections)) {
  55. for (int i = 0; i < reconnectConnections.Count; i++) {
  56. NodePort connection = reconnectConnections[i];
  57. if (connection == null) continue;
  58. if (port.CanConnectTo(connection)) port.Connect(connection);
  59. }
  60. }
  61. ports.Add(staticPort.fieldName, port);
  62. }
  63. }
  64. // Finally, make sure dynamic list port settings correspond to the settings of their "backing port"
  65. foreach (NodePort listPort in dynamicListPorts) {
  66. // At this point we know that ports here are dynamic list ports
  67. // which have passed name/"backing port" checks, ergo we can proceed more safely.
  68. string backingPortName = listPort.fieldName.Split(' ')[0];
  69. NodePort backingPort = staticPorts[backingPortName];
  70. // Update port constraints. Creating a new port instead will break the editor, mandating the need for setters.
  71. listPort.ValueType = GetBackingValueType(backingPort.ValueType);
  72. listPort.direction = backingPort.direction;
  73. listPort.connectionType = backingPort.connectionType;
  74. listPort.typeConstraint = backingPort.typeConstraint;
  75. }
  76. }
  77. /// <summary>
  78. /// Extracts the underlying types from arrays and lists, the only collections for dynamic port lists
  79. /// currently supported. If the given type is not applicable (i.e. if the dynamic list port was not
  80. /// defined as an array or a list), returns the given type itself.
  81. /// </summary>
  82. private static System.Type GetBackingValueType(System.Type portValType) {
  83. if (portValType.HasElementType) {
  84. return portValType.GetElementType();
  85. }
  86. if (portValType.IsGenericType && portValType.GetGenericTypeDefinition() == typeof(List<>)) {
  87. return portValType.GetGenericArguments()[0];
  88. }
  89. return portValType;
  90. }
  91. /// <summary>Returns true if the given port is in a dynamic port list.</summary>
  92. private static bool IsDynamicListPort(NodePort port) {
  93. // Ports flagged as "dynamicPortList = true" end up having a "backing port" and a name with an index, but we have
  94. // no guarantee that a dynamic port called "output 0" is an element in a list backed by a static "output" port.
  95. // Thus, we need to check for attributes... (but at least we don't need to look at all fields this time)
  96. string[] fieldNameParts = port.fieldName.Split(' ');
  97. if (fieldNameParts.Length != 2) return false;
  98. FieldInfo backingPortInfo = port.node.GetType().GetField(fieldNameParts[0]);
  99. if (backingPortInfo == null) return false;
  100. object[] attribs = backingPortInfo.GetCustomAttributes(true);
  101. return attribs.Any(x => {
  102. Node.InputAttribute inputAttribute = x as Node.InputAttribute;
  103. Node.OutputAttribute outputAttribute = x as Node.OutputAttribute;
  104. return inputAttribute != null && inputAttribute.dynamicPortList ||
  105. outputAttribute != null && outputAttribute.dynamicPortList;
  106. });
  107. }
  108. /// <summary> Cache node types </summary>
  109. private static void BuildCache() {
  110. portDataCache = new PortDataCache();
  111. System.Type baseType = typeof(Node);
  112. List<System.Type> nodeTypes = new List<System.Type>();
  113. System.Reflection.Assembly[] assemblies = System.AppDomain.CurrentDomain.GetAssemblies();
  114. // Loop through assemblies and add node types to list
  115. foreach (Assembly assembly in assemblies) {
  116. // Skip certain dlls to improve performance
  117. string assemblyName = assembly.GetName().Name;
  118. int index = assemblyName.IndexOf('.');
  119. if (index != -1) assemblyName = assemblyName.Substring(0, index);
  120. switch (assemblyName) {
  121. // The following assemblies, and sub-assemblies (eg. UnityEngine.UI) are skipped
  122. case "UnityEditor":
  123. case "UnityEngine":
  124. case "System":
  125. case "mscorlib":
  126. case "Microsoft":
  127. continue;
  128. default:
  129. nodeTypes.AddRange(assembly.GetTypes().Where(t => !t.IsAbstract && baseType.IsAssignableFrom(t)).ToArray());
  130. break;
  131. }
  132. }
  133. for (int i = 0; i < nodeTypes.Count; i++) {
  134. CachePorts(nodeTypes[i]);
  135. }
  136. }
  137. public static List<FieldInfo> GetNodeFields(System.Type nodeType) {
  138. List<System.Reflection.FieldInfo> fieldInfo = new List<System.Reflection.FieldInfo>(nodeType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance));
  139. // GetFields doesnt return inherited private fields, so walk through base types and pick those up
  140. System.Type tempType = nodeType;
  141. while ((tempType = tempType.BaseType) != typeof(XNode.Node)) {
  142. FieldInfo[] parentFields = tempType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance);
  143. for (int i = 0; i < parentFields.Length; i++) {
  144. // Ensure that we do not already have a member with this type and name
  145. FieldInfo parentField = parentFields[i];
  146. if (fieldInfo.TrueForAll(x => x.Name != parentField.Name)) {
  147. fieldInfo.Add(parentField);
  148. }
  149. }
  150. }
  151. return fieldInfo;
  152. }
  153. private static void CachePorts(System.Type nodeType) {
  154. List<System.Reflection.FieldInfo> fieldInfo = GetNodeFields(nodeType);
  155. for (int i = 0; i < fieldInfo.Count; i++) {
  156. //Get InputAttribute and OutputAttribute
  157. object[] attribs = fieldInfo[i].GetCustomAttributes(true);
  158. Node.InputAttribute inputAttrib = attribs.FirstOrDefault(x => x is Node.InputAttribute) as Node.InputAttribute;
  159. Node.OutputAttribute outputAttrib = attribs.FirstOrDefault(x => x is Node.OutputAttribute) as Node.OutputAttribute;
  160. if (inputAttrib == null && outputAttrib == null) continue;
  161. if (inputAttrib != null && outputAttrib != null) Debug.LogError("Field " + fieldInfo[i].Name + " of type " + nodeType.FullName + " cannot be both input and output.");
  162. else {
  163. if (!portDataCache.ContainsKey(nodeType)) portDataCache.Add(nodeType, new List<NodePort>());
  164. portDataCache[nodeType].Add(new NodePort(fieldInfo[i]));
  165. }
  166. }
  167. }
  168. [System.Serializable]
  169. private class PortDataCache : Dictionary<System.Type, List<NodePort>>, ISerializationCallbackReceiver {
  170. [SerializeField] private List<System.Type> keys = new List<System.Type>();
  171. [SerializeField] private List<List<NodePort>> values = new List<List<NodePort>>();
  172. // save the dictionary to lists
  173. public void OnBeforeSerialize() {
  174. keys.Clear();
  175. values.Clear();
  176. foreach (var pair in this) {
  177. keys.Add(pair.Key);
  178. values.Add(pair.Value);
  179. }
  180. }
  181. // load dictionary from lists
  182. public void OnAfterDeserialize() {
  183. this.Clear();
  184. if (keys.Count != values.Count)
  185. 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."));
  186. for (int i = 0; i < keys.Count; i++)
  187. this.Add(keys[i], values[i]);
  188. }
  189. }
  190. }
  191. }