| 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;			}		}	}}
 |