123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348 |
- using System.Collections.Generic;
- using UnityEngine;
- using UnityEditor;
- using System;
- using System.Linq;
- using System.IO;
- using System.Reflection;
- using UnityEditor.Experimental.GraphView;
- namespace GraphProcessor
- {
- public static class NodeProvider
- {
- public struct PortDescription
- {
- public Type nodeType;
- public Type portType;
- public bool isInput;
- public string portFieldName;
- public string portIdentifier;
- public string portDisplayName;
- }
- static Dictionary< Type, MonoScript > nodeViewScripts = new Dictionary< Type, MonoScript >();
- static Dictionary< Type, MonoScript > nodeScripts = new Dictionary< Type, MonoScript >();
- static Dictionary< Type, Type > nodeViewPerType = new Dictionary< Type, Type >();
- public class NodeDescriptions
- {
- public Dictionary< string, Type > nodePerMenuTitle = new Dictionary< string, Type >();
- public List< Type > slotTypes = new List< Type >();
- public List< PortDescription > nodeCreatePortDescription = new List<PortDescription>();
- }
- public struct NodeSpecificToGraph
- {
- public Type nodeType;
- public List<MethodInfo> isCompatibleWithGraph;
- public Type compatibleWithGraphType;
- }
- static Dictionary<BaseGraph, NodeDescriptions> specificNodeDescriptions = new Dictionary<BaseGraph, NodeDescriptions>();
- static List<NodeSpecificToGraph> specificNodes = new List<NodeSpecificToGraph>();
- static NodeDescriptions genericNodes = new NodeDescriptions();
- static NodeProvider()
- {
- BuildScriptCache();
- BuildGenericNodeCache();
- }
- public static void LoadGraph(BaseGraph graph)
- {
- // Clear old graph data in case there was some
- specificNodeDescriptions.Remove(graph);
- var descriptions = new NodeDescriptions();
- specificNodeDescriptions.Add(graph, descriptions);
- var graphType = graph.GetType();
- foreach (var nodeInfo in specificNodes)
- {
- bool compatible = nodeInfo.compatibleWithGraphType == null || nodeInfo.compatibleWithGraphType == graphType;
- if (nodeInfo.isCompatibleWithGraph != null)
- {
- foreach (var method in nodeInfo.isCompatibleWithGraph)
- compatible &= (bool)method?.Invoke(null, new object[]{ graph });
- }
- if (compatible)
- BuildCacheForNode(nodeInfo.nodeType, descriptions, graph);
- }
- }
- public static void UnloadGraph(BaseGraph graph)
- {
- specificNodeDescriptions.Remove(graph);
- }
- static void BuildGenericNodeCache()
- {
- foreach (var nodeType in TypeCache.GetTypesDerivedFrom<BaseNode>())
- {
- if (!IsNodeAccessibleFromMenu(nodeType))
- continue;
- if (IsNodeSpecificToGraph(nodeType))
- continue;
- BuildCacheForNode(nodeType, genericNodes);
- }
- }
- static void BuildCacheForNode(Type nodeType, NodeDescriptions targetDescription, BaseGraph graph = null)
- {
- var attrs = nodeType.GetCustomAttributes(typeof(NodeMenuItemAttribute), false) as NodeMenuItemAttribute[];
- if (attrs != null && attrs.Length > 0)
- {
- foreach (var attr in attrs)
- targetDescription.nodePerMenuTitle[attr.menuTitle] = nodeType;
- }
- foreach (var field in nodeType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
- {
- if (field.GetCustomAttribute<HideInInspector>() == null && field.GetCustomAttributes().Any(c => c is InputAttribute || c is OutputAttribute))
- targetDescription.slotTypes.Add(field.FieldType);
- }
- ProvideNodePortCreationDescription(nodeType, targetDescription, graph);
- }
- static bool IsNodeAccessibleFromMenu(Type nodeType)
- {
- if (nodeType.IsAbstract)
- return false;
- return nodeType.GetCustomAttributes<NodeMenuItemAttribute>().Count() > 0;
- }
- // Check if node has anything that depends on the graph type or settings
- static bool IsNodeSpecificToGraph(Type nodeType)
- {
- var isCompatibleWithGraphMethods = nodeType.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy).Where(m => m.GetCustomAttribute<IsCompatibleWithGraph>() != null);
- var nodeMenuAttributes = nodeType.GetCustomAttributes<NodeMenuItemAttribute>();
- List<Type> compatibleGraphTypes = nodeMenuAttributes.Where(n => n.onlyCompatibleWithGraph != null).Select(a => a.onlyCompatibleWithGraph).ToList();
- List<MethodInfo> compatibleMethods = new List<MethodInfo>();
- foreach (var method in isCompatibleWithGraphMethods)
- {
- // Check if the method is static and have the correct prototype
- var p = method.GetParameters();
- if (method.ReturnType != typeof(bool) || p.Count() != 1 || p[0].ParameterType != typeof(BaseGraph))
- 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.");
- else
- compatibleMethods.Add(method);
- }
- if (compatibleMethods.Count > 0 || compatibleGraphTypes.Count > 0)
- {
- // We still need to add the element in specificNode even without specific graph
- if (compatibleGraphTypes.Count == 0)
- compatibleGraphTypes.Add(null);
- foreach (var graphType in compatibleGraphTypes)
- {
- specificNodes.Add(new NodeSpecificToGraph{
- nodeType = nodeType,
- isCompatibleWithGraph = compatibleMethods,
- compatibleWithGraphType = graphType
- });
- }
- return true;
- }
- return false;
- }
-
- static void BuildScriptCache()
- {
- foreach (var nodeType in TypeCache.GetTypesDerivedFrom<BaseNode>())
- {
- if (!IsNodeAccessibleFromMenu(nodeType))
- continue;
- AddNodeScriptAsset(nodeType);
- }
- foreach (var nodeViewType in TypeCache.GetTypesDerivedFrom<BaseNodeView>())
- {
- if (!nodeViewType.IsAbstract)
- AddNodeViewScriptAsset(nodeViewType);
- }
- }
- static FieldInfo SetGraph = typeof(BaseNode).GetField("graph", BindingFlags.NonPublic | BindingFlags.Instance);
- static void ProvideNodePortCreationDescription(Type nodeType, NodeDescriptions targetDescription, BaseGraph graph = null)
- {
- var node = Activator.CreateInstance(nodeType) as BaseNode;
- try {
- SetGraph.SetValue(node, graph);
- node.InitializePorts();
- node.UpdateAllPorts();
- } catch (Exception) { }
- foreach (var p in node.inputPorts)
- AddPort(p, true);
- foreach (var p in node.outputPorts)
- AddPort(p, false);
- void AddPort(NodePort p, bool input)
- {
- targetDescription.nodeCreatePortDescription.Add(new PortDescription{
- nodeType = nodeType,
- portType = p.portData.displayType ?? p.fieldInfo.FieldType,
- isInput = input,
- portFieldName = p.fieldName,
- portDisplayName = p.portData.displayName ?? p.fieldName,
- portIdentifier = p.portData.identifier,
- });
- }
- }
- static void AddNodeScriptAsset(Type type)
- {
- var nodeScriptAsset = FindScriptFromClassName(type.Name);
- // Try find the class name with Node name at the end
- if (nodeScriptAsset == null)
- nodeScriptAsset = FindScriptFromClassName(type.Name + "Node");
- if (nodeScriptAsset != null)
- nodeScripts[type] = nodeScriptAsset;
- }
- static void AddNodeViewScriptAsset(Type type)
- {
- var attrs = type.GetCustomAttributes(typeof(NodeCustomEditor), false) as NodeCustomEditor[];
- if (attrs != null && attrs.Length > 0)
- {
- Type nodeType = attrs.First().nodeType;
- nodeViewPerType[nodeType] = type;
- var nodeViewScriptAsset = FindScriptFromClassName(type.Name);
- if (nodeViewScriptAsset == null)
- nodeViewScriptAsset = FindScriptFromClassName(type.Name + "View");
- if (nodeViewScriptAsset == null)
- nodeViewScriptAsset = FindScriptFromClassName(type.Name + "NodeView");
- if (nodeViewScriptAsset != null)
- nodeViewScripts[type] = nodeViewScriptAsset;
- }
- }
- static MonoScript FindScriptFromClassName(string className)
- {
- var scriptGUIDs = AssetDatabase.FindAssets($"t:script {className}");
- if (scriptGUIDs.Length == 0)
- return null;
- foreach (var scriptGUID in scriptGUIDs)
- {
- var assetPath = AssetDatabase.GUIDToAssetPath(scriptGUID);
- var script = AssetDatabase.LoadAssetAtPath<MonoScript>(assetPath);
- if (script != null && String.Equals(className, Path.GetFileNameWithoutExtension(assetPath), StringComparison.OrdinalIgnoreCase))
- return script;
- }
- return null;
- }
- public static Type GetNodeViewTypeFromType(Type nodeType)
- {
- Type view;
- if (nodeViewPerType.TryGetValue(nodeType, out view))
- return view;
- Type baseType = null;
- // Allow for inheritance in node views: multiple C# node using the same view
- foreach (var type in nodeViewPerType)
- {
- // Find a view (not first fitted view) of nodeType
- if (nodeType.IsSubclassOf(type.Key) && (baseType == null || type.Value.IsSubclassOf(baseType)))
- baseType = type.Value;
- }
- if (baseType != null)
- return baseType;
- return view;
- }
- public static IEnumerable<(string path, Type type)> GetNodeMenuEntries(BaseGraph graph = null)
- {
- foreach (var node in genericNodes.nodePerMenuTitle)
- yield return (node.Key, node.Value);
- if (graph != null && specificNodeDescriptions.TryGetValue(graph, out var specificNodes))
- {
- foreach (var node in specificNodes.nodePerMenuTitle)
- yield return (node.Key, node.Value);
- }
- }
- public static MonoScript GetNodeViewScript(Type type)
- {
- nodeViewScripts.TryGetValue(type, out var script);
- return script;
- }
- public static MonoScript GetNodeScript(Type type)
- {
- nodeScripts.TryGetValue(type, out var script);
- return script;
- }
- public static IEnumerable<Type> GetSlotTypes(BaseGraph graph = null)
- {
- foreach (var type in genericNodes.slotTypes)
- yield return type;
- if (graph != null && specificNodeDescriptions.TryGetValue(graph, out var specificNodes))
- {
- foreach (var type in specificNodes.slotTypes)
- yield return type;
- }
- }
- public static IEnumerable<PortDescription> GetEdgeCreationNodeMenuEntry(PortView portView, BaseGraph graph = null)
- {
- foreach (var description in genericNodes.nodeCreatePortDescription)
- {
- if (!IsPortCompatible(description))
- continue;
- yield return description;
- }
- if (graph != null && specificNodeDescriptions.TryGetValue(graph, out var specificNodes))
- {
- foreach (var description in specificNodes.nodeCreatePortDescription)
- {
- if (!IsPortCompatible(description))
- continue;
- yield return description;
- }
- }
- bool IsPortCompatible(PortDescription description)
- {
- if ((portView.direction == Direction.Input && description.isInput) || (portView.direction == Direction.Output && !description.isInput))
- return false;
-
- if (!BaseGraph.TypesAreConnectable(description.portType, portView.portType))
- return false;
-
- return true;
- }
- }
- }
- }
|