NodeProvider.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. using System.Collections.Generic;
  2. using UnityEngine;
  3. using UnityEditor;
  4. using System;
  5. using System.Linq;
  6. using System.IO;
  7. using System.Reflection;
  8. using UnityEditor.Experimental.GraphView;
  9. namespace GraphProcessor
  10. {
  11. public static class NodeProvider
  12. {
  13. public struct PortDescription
  14. {
  15. public Type nodeType;
  16. public Type portType;
  17. public bool isInput;
  18. public string portFieldName;
  19. public string portIdentifier;
  20. public string portDisplayName;
  21. }
  22. static Dictionary< Type, MonoScript > nodeViewScripts = new Dictionary< Type, MonoScript >();
  23. static Dictionary< Type, MonoScript > nodeScripts = new Dictionary< Type, MonoScript >();
  24. static Dictionary< Type, Type > nodeViewPerType = new Dictionary< Type, Type >();
  25. public class NodeDescriptions
  26. {
  27. public Dictionary< string, Type > nodePerMenuTitle = new Dictionary< string, Type >();
  28. public List< Type > slotTypes = new List< Type >();
  29. public List< PortDescription > nodeCreatePortDescription = new List<PortDescription>();
  30. }
  31. public struct NodeSpecificToGraph
  32. {
  33. public Type nodeType;
  34. public List<MethodInfo> isCompatibleWithGraph;
  35. public Type compatibleWithGraphType;
  36. }
  37. static Dictionary<BaseGraph, NodeDescriptions> specificNodeDescriptions = new Dictionary<BaseGraph, NodeDescriptions>();
  38. static List<NodeSpecificToGraph> specificNodes = new List<NodeSpecificToGraph>();
  39. static NodeDescriptions genericNodes = new NodeDescriptions();
  40. static NodeProvider()
  41. {
  42. BuildScriptCache();
  43. BuildGenericNodeCache();
  44. }
  45. public static void LoadGraph(BaseGraph graph)
  46. {
  47. // Clear old graph data in case there was some
  48. specificNodeDescriptions.Remove(graph);
  49. var descriptions = new NodeDescriptions();
  50. specificNodeDescriptions.Add(graph, descriptions);
  51. var graphType = graph.GetType();
  52. foreach (var nodeInfo in specificNodes)
  53. {
  54. bool compatible = nodeInfo.compatibleWithGraphType == null || nodeInfo.compatibleWithGraphType == graphType;
  55. if (nodeInfo.isCompatibleWithGraph != null)
  56. {
  57. foreach (var method in nodeInfo.isCompatibleWithGraph)
  58. compatible &= (bool)method?.Invoke(null, new object[]{ graph });
  59. }
  60. if (compatible)
  61. BuildCacheForNode(nodeInfo.nodeType, descriptions, graph);
  62. }
  63. }
  64. public static void UnloadGraph(BaseGraph graph)
  65. {
  66. specificNodeDescriptions.Remove(graph);
  67. }
  68. static void BuildGenericNodeCache()
  69. {
  70. foreach (var nodeType in TypeCache.GetTypesDerivedFrom<BaseNode>())
  71. {
  72. if (!IsNodeAccessibleFromMenu(nodeType))
  73. continue;
  74. if (IsNodeSpecificToGraph(nodeType))
  75. continue;
  76. BuildCacheForNode(nodeType, genericNodes);
  77. }
  78. }
  79. static void BuildCacheForNode(Type nodeType, NodeDescriptions targetDescription, BaseGraph graph = null)
  80. {
  81. var attrs = nodeType.GetCustomAttributes(typeof(NodeMenuItemAttribute), false) as NodeMenuItemAttribute[];
  82. if (attrs != null && attrs.Length > 0)
  83. {
  84. foreach (var attr in attrs)
  85. targetDescription.nodePerMenuTitle[attr.menuTitle] = nodeType;
  86. }
  87. foreach (var field in nodeType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
  88. {
  89. if (field.GetCustomAttribute<HideInInspector>() == null && field.GetCustomAttributes().Any(c => c is InputAttribute || c is OutputAttribute))
  90. targetDescription.slotTypes.Add(field.FieldType);
  91. }
  92. ProvideNodePortCreationDescription(nodeType, targetDescription, graph);
  93. }
  94. static bool IsNodeAccessibleFromMenu(Type nodeType)
  95. {
  96. if (nodeType.IsAbstract)
  97. return false;
  98. return nodeType.GetCustomAttributes<NodeMenuItemAttribute>().Count() > 0;
  99. }
  100. // Check if node has anything that depends on the graph type or settings
  101. static bool IsNodeSpecificToGraph(Type nodeType)
  102. {
  103. var isCompatibleWithGraphMethods = nodeType.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy).Where(m => m.GetCustomAttribute<IsCompatibleWithGraph>() != null);
  104. var nodeMenuAttributes = nodeType.GetCustomAttributes<NodeMenuItemAttribute>();
  105. List<Type> compatibleGraphTypes = nodeMenuAttributes.Where(n => n.onlyCompatibleWithGraph != null).Select(a => a.onlyCompatibleWithGraph).ToList();
  106. List<MethodInfo> compatibleMethods = new List<MethodInfo>();
  107. foreach (var method in isCompatibleWithGraphMethods)
  108. {
  109. // Check if the method is static and have the correct prototype
  110. var p = method.GetParameters();
  111. if (method.ReturnType != typeof(bool) || p.Count() != 1 || p[0].ParameterType != typeof(BaseGraph))
  112. Debug.LogError($"The function '{method.Name}' marked with the IsCompatibleWithGraph attribute either doesn't return a boolean or doesn't take one parameter of BaseGraph type.");
  113. else
  114. compatibleMethods.Add(method);
  115. }
  116. if (compatibleMethods.Count > 0 || compatibleGraphTypes.Count > 0)
  117. {
  118. // We still need to add the element in specificNode even without specific graph
  119. if (compatibleGraphTypes.Count == 0)
  120. compatibleGraphTypes.Add(null);
  121. foreach (var graphType in compatibleGraphTypes)
  122. {
  123. specificNodes.Add(new NodeSpecificToGraph{
  124. nodeType = nodeType,
  125. isCompatibleWithGraph = compatibleMethods,
  126. compatibleWithGraphType = graphType
  127. });
  128. }
  129. return true;
  130. }
  131. return false;
  132. }
  133. static void BuildScriptCache()
  134. {
  135. foreach (var nodeType in TypeCache.GetTypesDerivedFrom<BaseNode>())
  136. {
  137. if (!IsNodeAccessibleFromMenu(nodeType))
  138. continue;
  139. AddNodeScriptAsset(nodeType);
  140. }
  141. foreach (var nodeViewType in TypeCache.GetTypesDerivedFrom<BaseNodeView>())
  142. {
  143. if (!nodeViewType.IsAbstract)
  144. AddNodeViewScriptAsset(nodeViewType);
  145. }
  146. }
  147. static FieldInfo SetGraph = typeof(BaseNode).GetField("graph", BindingFlags.NonPublic | BindingFlags.Instance);
  148. static void ProvideNodePortCreationDescription(Type nodeType, NodeDescriptions targetDescription, BaseGraph graph = null)
  149. {
  150. var node = Activator.CreateInstance(nodeType) as BaseNode;
  151. try {
  152. SetGraph.SetValue(node, graph);
  153. node.InitializePorts();
  154. node.UpdateAllPorts();
  155. } catch (Exception) { }
  156. foreach (var p in node.inputPorts)
  157. AddPort(p, true);
  158. foreach (var p in node.outputPorts)
  159. AddPort(p, false);
  160. void AddPort(NodePort p, bool input)
  161. {
  162. targetDescription.nodeCreatePortDescription.Add(new PortDescription{
  163. nodeType = nodeType,
  164. portType = p.portData.displayType ?? p.fieldInfo.FieldType,
  165. isInput = input,
  166. portFieldName = p.fieldName,
  167. portDisplayName = p.portData.displayName ?? p.fieldName,
  168. portIdentifier = p.portData.identifier,
  169. });
  170. }
  171. }
  172. static void AddNodeScriptAsset(Type type)
  173. {
  174. var nodeScriptAsset = FindScriptFromClassName(type.Name);
  175. // Try find the class name with Node name at the end
  176. if (nodeScriptAsset == null)
  177. nodeScriptAsset = FindScriptFromClassName(type.Name + "Node");
  178. if (nodeScriptAsset != null)
  179. nodeScripts[type] = nodeScriptAsset;
  180. }
  181. static void AddNodeViewScriptAsset(Type type)
  182. {
  183. var attrs = type.GetCustomAttributes(typeof(NodeCustomEditor), false) as NodeCustomEditor[];
  184. if (attrs != null && attrs.Length > 0)
  185. {
  186. Type nodeType = attrs.First().nodeType;
  187. nodeViewPerType[nodeType] = type;
  188. var nodeViewScriptAsset = FindScriptFromClassName(type.Name);
  189. if (nodeViewScriptAsset == null)
  190. nodeViewScriptAsset = FindScriptFromClassName(type.Name + "View");
  191. if (nodeViewScriptAsset == null)
  192. nodeViewScriptAsset = FindScriptFromClassName(type.Name + "NodeView");
  193. if (nodeViewScriptAsset != null)
  194. nodeViewScripts[type] = nodeViewScriptAsset;
  195. }
  196. }
  197. static MonoScript FindScriptFromClassName(string className)
  198. {
  199. var scriptGUIDs = AssetDatabase.FindAssets($"t:script {className}");
  200. if (scriptGUIDs.Length == 0)
  201. return null;
  202. foreach (var scriptGUID in scriptGUIDs)
  203. {
  204. var assetPath = AssetDatabase.GUIDToAssetPath(scriptGUID);
  205. var script = AssetDatabase.LoadAssetAtPath<MonoScript>(assetPath);
  206. if (script != null && String.Equals(className, Path.GetFileNameWithoutExtension(assetPath), StringComparison.OrdinalIgnoreCase))
  207. return script;
  208. }
  209. return null;
  210. }
  211. public static Type GetNodeViewTypeFromType(Type nodeType)
  212. {
  213. Type view;
  214. if (nodeViewPerType.TryGetValue(nodeType, out view))
  215. return view;
  216. Type baseType = null;
  217. // Allow for inheritance in node views: multiple C# node using the same view
  218. foreach (var type in nodeViewPerType)
  219. {
  220. // Find a view (not first fitted view) of nodeType
  221. if (nodeType.IsSubclassOf(type.Key) && (baseType == null || type.Value.IsSubclassOf(baseType)))
  222. baseType = type.Value;
  223. }
  224. if (baseType != null)
  225. return baseType;
  226. return view;
  227. }
  228. public static IEnumerable<(string path, Type type)> GetNodeMenuEntries(BaseGraph graph = null)
  229. {
  230. foreach (var node in genericNodes.nodePerMenuTitle)
  231. yield return (node.Key, node.Value);
  232. if (graph != null && specificNodeDescriptions.TryGetValue(graph, out var specificNodes))
  233. {
  234. foreach (var node in specificNodes.nodePerMenuTitle)
  235. yield return (node.Key, node.Value);
  236. }
  237. }
  238. public static MonoScript GetNodeViewScript(Type type)
  239. {
  240. nodeViewScripts.TryGetValue(type, out var script);
  241. return script;
  242. }
  243. public static MonoScript GetNodeScript(Type type)
  244. {
  245. nodeScripts.TryGetValue(type, out var script);
  246. return script;
  247. }
  248. public static IEnumerable<Type> GetSlotTypes(BaseGraph graph = null)
  249. {
  250. foreach (var type in genericNodes.slotTypes)
  251. yield return type;
  252. if (graph != null && specificNodeDescriptions.TryGetValue(graph, out var specificNodes))
  253. {
  254. foreach (var type in specificNodes.slotTypes)
  255. yield return type;
  256. }
  257. }
  258. public static IEnumerable<PortDescription> GetEdgeCreationNodeMenuEntry(PortView portView, BaseGraph graph = null)
  259. {
  260. foreach (var description in genericNodes.nodeCreatePortDescription)
  261. {
  262. if (!IsPortCompatible(description))
  263. continue;
  264. yield return description;
  265. }
  266. if (graph != null && specificNodeDescriptions.TryGetValue(graph, out var specificNodes))
  267. {
  268. foreach (var description in specificNodes.nodeCreatePortDescription)
  269. {
  270. if (!IsPortCompatible(description))
  271. continue;
  272. yield return description;
  273. }
  274. }
  275. bool IsPortCompatible(PortDescription description)
  276. {
  277. if ((portView.direction == Direction.Input && description.isInput) || (portView.direction == Direction.Output && !description.isInput))
  278. return false;
  279. if (!BaseGraph.TypesAreConnectable(description.portType, portView.portType))
  280. return false;
  281. return true;
  282. }
  283. }
  284. }
  285. }