NodeDataCache.cs 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  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 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> typePortCache;
  17. if (portDataCache.TryGetValue(nodeType, out typePortCache)) {
  18. for (int i = 0; i < typePortCache.Count; i++) {
  19. staticPorts.Add(typePortCache[i].fieldName, portDataCache[nodeType][i]);
  20. }
  21. }
  22. // Cleanup port dict - Remove nonexisting static ports - update static port types
  23. // Loop through current node ports
  24. foreach (NodePort port in ports.Values.ToList()) {
  25. // If port still exists, check it it has been changed
  26. NodePort staticPort;
  27. if (staticPorts.TryGetValue(port.fieldName, out staticPort)) {
  28. // If port exists but with wrong settings, remove it. Re-add it later.
  29. if (port.IsDynamic || port.direction != staticPort.direction || port.connectionType != staticPort.connectionType || port.typeConstraint != staticPort.typeConstraint) {
  30. // If port is not dynamic and direction hasn't changed, add it to the list so we can try reconnecting the ports connections.
  31. if (!port.IsDynamic && port.direction == staticPort.direction) removedPorts.Add(port.fieldName, port.GetConnections());
  32. port.ClearConnections();
  33. ports.Remove(port.fieldName);
  34. } else port.ValueType = staticPort.ValueType;
  35. }
  36. // If port doesn't exist anymore, remove it
  37. else if (port.IsStatic) {
  38. port.ClearConnections();
  39. ports.Remove(port.fieldName);
  40. }
  41. }
  42. // Add missing ports
  43. foreach (NodePort staticPort in staticPorts.Values) {
  44. if (!ports.ContainsKey(staticPort.fieldName)) {
  45. NodePort port = new NodePort(staticPort, node);
  46. //If we just removed the port, try re-adding the connections
  47. List<NodePort> reconnectConnections;
  48. if (removedPorts.TryGetValue(staticPort.fieldName, out reconnectConnections)) {
  49. for (int i = 0; i < reconnectConnections.Count; i++) {
  50. NodePort connection = reconnectConnections[i];
  51. if (connection == null) continue;
  52. if (port.CanConnectTo(connection)) port.Connect(connection);
  53. }
  54. }
  55. ports.Add(staticPort.fieldName, port);
  56. }
  57. }
  58. }
  59. /// <summary> Cache node types </summary>
  60. private static void BuildCache() {
  61. portDataCache = new PortDataCache();
  62. System.Type baseType = typeof(Node);
  63. List<System.Type> nodeTypes = new List<System.Type>();
  64. System.Reflection.Assembly[] assemblies = System.AppDomain.CurrentDomain.GetAssemblies();
  65. // Loop through assemblies and add node types to list
  66. foreach (Assembly assembly in assemblies) {
  67. // Skip certain dlls to improve performance
  68. string assemblyName = assembly.GetName().Name;
  69. int index = assemblyName.IndexOf('.');
  70. if (index != -1) assemblyName = assemblyName.Substring(0, index);
  71. switch (assemblyName) {
  72. // The following assemblies, and sub-assemblies (eg. UnityEngine.UI) are skipped
  73. case "UnityEditor":
  74. case "UnityEngine":
  75. case "System":
  76. case "mscorlib":
  77. continue;
  78. default:
  79. nodeTypes.AddRange(assembly.GetTypes().Where(t => !t.IsAbstract && baseType.IsAssignableFrom(t)).ToArray());
  80. break;
  81. }
  82. }
  83. for (int i = 0; i < nodeTypes.Count; i++) {
  84. CachePorts(nodeTypes[i]);
  85. }
  86. }
  87. public static List<FieldInfo> GetNodeFields(System.Type nodeType) {
  88. List<System.Reflection.FieldInfo> fieldInfo = new List<System.Reflection.FieldInfo>(nodeType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance));
  89. // GetFields doesnt return inherited private fields, so walk through base types and pick those up
  90. System.Type tempType = nodeType;
  91. while ((tempType = tempType.BaseType) != typeof(XNode.Node)) {
  92. fieldInfo.AddRange(tempType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance));
  93. }
  94. return fieldInfo;
  95. }
  96. private static void CachePorts(System.Type nodeType) {
  97. List<System.Reflection.FieldInfo> fieldInfo = GetNodeFields(nodeType);
  98. for (int i = 0; i < fieldInfo.Count; i++) {
  99. //Get InputAttribute and OutputAttribute
  100. object[] attribs = fieldInfo[i].GetCustomAttributes(true);
  101. Node.InputAttribute inputAttrib = attribs.FirstOrDefault(x => x is Node.InputAttribute) as Node.InputAttribute;
  102. Node.OutputAttribute outputAttrib = attribs.FirstOrDefault(x => x is Node.OutputAttribute) as Node.OutputAttribute;
  103. if (inputAttrib == null && outputAttrib == null) continue;
  104. if (inputAttrib != null && outputAttrib != null) Debug.LogError("Field " + fieldInfo[i].Name + " of type " + nodeType.FullName + " cannot be both input and output.");
  105. else {
  106. if (!portDataCache.ContainsKey(nodeType)) portDataCache.Add(nodeType, new List<NodePort>());
  107. portDataCache[nodeType].Add(new NodePort(fieldInfo[i]));
  108. }
  109. }
  110. }
  111. [System.Serializable]
  112. private class PortDataCache : Dictionary<System.Type, List<NodePort>>, ISerializationCallbackReceiver {
  113. [SerializeField] private List<System.Type> keys = new List<System.Type>();
  114. [SerializeField] private List<List<NodePort>> values = new List<List<NodePort>>();
  115. // save the dictionary to lists
  116. public void OnBeforeSerialize() {
  117. keys.Clear();
  118. values.Clear();
  119. foreach (var pair in this) {
  120. keys.Add(pair.Key);
  121. values.Add(pair.Value);
  122. }
  123. }
  124. // load dictionary from lists
  125. public void OnAfterDeserialize() {
  126. this.Clear();
  127. if (keys.Count != values.Count)
  128. 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."));
  129. for (int i = 0; i < keys.Count; i++)
  130. this.Add(keys[i], values[i]);
  131. }
  132. }
  133. }
  134. }