NodePort.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. // #define DEBUG_LAMBDA
  2. using System.Linq;
  3. using System.Collections.Generic;
  4. using System.Collections;
  5. using UnityEngine;
  6. using System.Reflection;
  7. using System.Linq.Expressions;
  8. using System;
  9. namespace GraphProcessor
  10. {
  11. /// <summary>
  12. /// Class that describe port attributes for it's creation
  13. /// </summary>
  14. public class PortData : IEquatable< PortData >
  15. {
  16. /// <summary>
  17. /// Unique identifier for the port
  18. /// </summary>
  19. public string identifier;
  20. /// <summary>
  21. /// Display name on the node
  22. /// </summary>
  23. public string displayName;
  24. /// <summary>
  25. /// The type that will be used for coloring with the type stylesheet
  26. /// </summary>
  27. public Type displayType;
  28. /// <summary>
  29. /// If the port accept multiple connection
  30. /// </summary>
  31. public bool acceptMultipleEdges;
  32. /// <summary>
  33. /// Port size, will also affect the size of the connected edge
  34. /// </summary>
  35. public int sizeInPixel;
  36. /// <summary>
  37. /// Tooltip of the port
  38. /// </summary>
  39. public string tooltip;
  40. /// <summary>
  41. /// Is the port vertical
  42. /// </summary>
  43. public bool vertical;
  44. public bool Equals(PortData other)
  45. {
  46. return identifier == other.identifier
  47. && displayName == other.displayName
  48. && displayType == other.displayType
  49. && acceptMultipleEdges == other.acceptMultipleEdges
  50. && sizeInPixel == other.sizeInPixel
  51. && tooltip == other.tooltip
  52. && vertical == other.vertical;
  53. }
  54. public void CopyFrom(PortData other)
  55. {
  56. identifier = other.identifier;
  57. displayName = other.displayName;
  58. displayType = other.displayType;
  59. acceptMultipleEdges = other.acceptMultipleEdges;
  60. sizeInPixel = other.sizeInPixel;
  61. tooltip = other.tooltip;
  62. vertical = other.vertical;
  63. }
  64. }
  65. /// <summary>
  66. /// Runtime class that stores all info about one port that is needed for the processing
  67. /// </summary>
  68. public class NodePort
  69. {
  70. /// <summary>
  71. /// The actual name of the property behind the port (must be exact, it is used for Reflection)
  72. /// </summary>
  73. public string fieldName;
  74. /// <summary>
  75. /// The node on which the port is
  76. /// </summary>
  77. public BaseNode owner;
  78. /// <summary>
  79. /// The fieldInfo from the fieldName
  80. /// </summary>
  81. public FieldInfo fieldInfo;
  82. /// <summary>
  83. /// Data of the port
  84. /// </summary>
  85. public PortData portData;
  86. List< SerializableEdge > edges = new List< SerializableEdge >();
  87. Dictionary< SerializableEdge, PushDataDelegate > pushDataDelegates = new Dictionary< SerializableEdge, PushDataDelegate >();
  88. List< SerializableEdge > edgeWithRemoteCustomIO = new List< SerializableEdge >();
  89. /// <summary>
  90. /// Owner of the FieldInfo, to be used in case of Get/SetValue
  91. /// </summary>
  92. public object fieldOwner;
  93. CustomPortIODelegate customPortIOMethod;
  94. /// <summary>
  95. /// Delegate that is made to send the data from this port to another port connected through an edge
  96. /// This is an optimization compared to dynamically setting values using Reflection (which is really slow)
  97. /// More info: https://codeblog.jonskeet.uk/2008/08/09/making-reflection-fly-and-exploring-delegates/
  98. /// </summary>
  99. public delegate void PushDataDelegate();
  100. /// <summary>
  101. /// Constructor
  102. /// </summary>
  103. /// <param name="owner">owner node</param>
  104. /// <param name="fieldName">the C# property name</param>
  105. /// <param name="portData">Data of the port</param>
  106. public NodePort(BaseNode owner, string fieldName, PortData portData) : this(owner, owner, fieldName, portData) {}
  107. /// <summary>
  108. /// Constructor
  109. /// </summary>
  110. /// <param name="owner">owner node</param>
  111. /// <param name="fieldOwner"></param>
  112. /// <param name="fieldName">the C# property name</param>
  113. /// <param name="portData">Data of the port</param>
  114. public NodePort(BaseNode owner, object fieldOwner, string fieldName, PortData portData)
  115. {
  116. this.fieldName = fieldName;
  117. this.owner = owner;
  118. this.portData = portData;
  119. this.fieldOwner = fieldOwner;
  120. fieldInfo = fieldOwner.GetType().GetField(
  121. fieldName,
  122. BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
  123. customPortIOMethod = CustomPortIO.GetCustomPortMethod(owner.GetType(), fieldName);
  124. }
  125. /// <summary>
  126. /// Connect an edge to this port
  127. /// </summary>
  128. /// <param name="edge"></param>
  129. public void Add(SerializableEdge edge)
  130. {
  131. if (!edges.Contains(edge))
  132. edges.Add(edge);
  133. if (edge.inputNode == owner)
  134. {
  135. if (edge.outputPort.customPortIOMethod != null)
  136. edgeWithRemoteCustomIO.Add(edge);
  137. }
  138. else
  139. {
  140. if (edge.inputPort.customPortIOMethod != null)
  141. edgeWithRemoteCustomIO.Add(edge);
  142. }
  143. //if we have a custom io implementation, we don't need to genereate the defaut one
  144. if (edge.inputPort.customPortIOMethod != null || edge.outputPort.customPortIOMethod != null)
  145. return ;
  146. PushDataDelegate edgeDelegate = CreatePushDataDelegateForEdge(edge);
  147. if (edgeDelegate != null)
  148. pushDataDelegates[edge] = edgeDelegate;
  149. }
  150. PushDataDelegate CreatePushDataDelegateForEdge(SerializableEdge edge)
  151. {
  152. try
  153. {
  154. //Creation of the delegate to move the data from the input node to the output node:
  155. FieldInfo inputField = edge.inputNode.GetType().GetField(edge.inputFieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
  156. FieldInfo outputField = edge.outputNode.GetType().GetField(edge.outputFieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
  157. Type inType, outType;
  158. #if DEBUG_LAMBDA
  159. return new PushDataDelegate(() => {
  160. var outValue = outputField.GetValue(edge.outputNode);
  161. inType = edge.inputPort.portData.displayType ?? inputField.FieldType;
  162. outType = edge.outputPort.portData.displayType ?? outputField.FieldType;
  163. Debug.Log($"Push: {inType}({outValue}) -> {outType} | {owner.name}");
  164. object convertedValue = outValue;
  165. if (TypeAdapter.AreAssignable(outType, inType))
  166. {
  167. var convertionMethod = TypeAdapter.GetConvertionMethod(outType, inType);
  168. Debug.Log("Convertion method: " + convertionMethod.Name);
  169. convertedValue = convertionMethod.Invoke(null, new object[]{ outValue });
  170. }
  171. inputField.SetValue(edge.inputNode, convertedValue);
  172. });
  173. #endif
  174. // We keep slow checks inside the editor
  175. #if UNITY_EDITOR
  176. if (!BaseGraph.TypesAreConnectable(inputField.FieldType, outputField.FieldType))
  177. {
  178. Debug.LogError("Can't convert from " + inputField.FieldType + " to " + outputField.FieldType + ", you must specify a custom port function (i.e CustomPortInput or CustomPortOutput) for non-implicit convertions");
  179. return null;
  180. }
  181. #endif
  182. Expression inputParamField = Expression.Field(Expression.Constant(edge.inputNode), inputField);
  183. Expression outputParamField = Expression.Field(Expression.Constant(edge.outputNode), outputField);
  184. inType = edge.inputPort.portData.displayType ?? inputField.FieldType;
  185. outType = edge.outputPort.portData.displayType ?? outputField.FieldType;
  186. // If there is a user defined convertion function, then we call it
  187. if (TypeAdapter.AreAssignable(outType, inType))
  188. {
  189. // We add a cast in case there we're calling the conversion method with a base class parameter (like object)
  190. var convertedParam = Expression.Convert(outputParamField, outType);
  191. outputParamField = Expression.Call(TypeAdapter.GetConvertionMethod(outType, inType), convertedParam);
  192. // In case there is a custom port behavior in the output, then we need to re-cast to the base type because
  193. // the convertion method return type is not always assignable directly:
  194. outputParamField = Expression.Convert(outputParamField, inputField.FieldType);
  195. }
  196. else // otherwise we cast
  197. outputParamField = Expression.Convert(outputParamField, inputField.FieldType);
  198. BinaryExpression assign = Expression.Assign(inputParamField, outputParamField);
  199. return Expression.Lambda< PushDataDelegate >(assign).Compile();
  200. } catch (Exception e) {
  201. Debug.LogError(e);
  202. return null;
  203. }
  204. }
  205. /// <summary>
  206. /// Disconnect an Edge from this port
  207. /// </summary>
  208. /// <param name="edge"></param>
  209. public void Remove(SerializableEdge edge)
  210. {
  211. if (!edges.Contains(edge))
  212. return;
  213. pushDataDelegates.Remove(edge);
  214. edgeWithRemoteCustomIO.Remove(edge);
  215. edges.Remove(edge);
  216. }
  217. /// <summary>
  218. /// Get all the edges connected to this port
  219. /// </summary>
  220. /// <returns></returns>
  221. public List< SerializableEdge > GetEdges() => edges;
  222. /// <summary>
  223. /// Push the value of the port through the edges
  224. /// This method can only be called on output ports
  225. /// </summary>
  226. public void PushData()
  227. {
  228. if (customPortIOMethod != null)
  229. {
  230. customPortIOMethod(owner, edges, this);
  231. return ;
  232. }
  233. foreach (var pushDataDelegate in pushDataDelegates)
  234. pushDataDelegate.Value();
  235. if (edgeWithRemoteCustomIO.Count == 0)
  236. return ;
  237. //if there are custom IO implementation on the other ports, they'll need our value in the passThrough buffer
  238. object ourValue = fieldInfo.GetValue(fieldOwner);
  239. foreach (var edge in edgeWithRemoteCustomIO)
  240. edge.passThroughBuffer = ourValue;
  241. }
  242. /// <summary>
  243. /// Reset the value of the field to default if possible
  244. /// </summary>
  245. public void ResetToDefault()
  246. {
  247. // Clear lists, set classes to null and struct to default value.
  248. if (typeof(IList).IsAssignableFrom(fieldInfo.FieldType))
  249. (fieldInfo.GetValue(fieldOwner) as IList)?.Clear();
  250. else if (fieldInfo.FieldType.GetTypeInfo().IsClass)
  251. fieldInfo.SetValue(fieldOwner, null);
  252. else
  253. {
  254. try
  255. {
  256. fieldInfo.SetValue(fieldOwner, Activator.CreateInstance(fieldInfo.FieldType));
  257. } catch {} // Catch types that don't have any constructors
  258. }
  259. }
  260. /// <summary>
  261. /// Pull values from the edge (in case of a custom convertion method)
  262. /// This method can only be called on input ports
  263. /// </summary>
  264. public void PullData()
  265. {
  266. if (customPortIOMethod != null)
  267. {
  268. customPortIOMethod(owner, edges, this);
  269. return ;
  270. }
  271. // check if this port have connection to ports that have custom output functions
  272. if (edgeWithRemoteCustomIO.Count == 0)
  273. return ;
  274. // Only one input connection is handled by this code, if you want to
  275. // take multiple inputs, you must create a custom input function see CustomPortsNode.cs
  276. if (edges.Count > 0)
  277. {
  278. var passThroughObject = edges.First().passThroughBuffer;
  279. // We do an extra convertion step in case the buffer output is not compatible with the input port
  280. if (passThroughObject != null)
  281. if (TypeAdapter.AreAssignable(fieldInfo.FieldType, passThroughObject.GetType()))
  282. passThroughObject = TypeAdapter.Convert(passThroughObject, fieldInfo.FieldType);
  283. fieldInfo.SetValue(fieldOwner, passThroughObject);
  284. }
  285. }
  286. }
  287. /// <summary>
  288. /// Container of ports and the edges connected to these ports
  289. /// </summary>
  290. public abstract class NodePortContainer : List< NodePort >
  291. {
  292. protected BaseNode node;
  293. public NodePortContainer(BaseNode node)
  294. {
  295. this.node = node;
  296. }
  297. /// <summary>
  298. /// Remove an edge that is connected to one of the node in the container
  299. /// </summary>
  300. /// <param name="edge"></param>
  301. public void Remove(SerializableEdge edge)
  302. {
  303. ForEach(p => p.Remove(edge));
  304. }
  305. /// <summary>
  306. /// Add an edge that is connected to one of the node in the container
  307. /// </summary>
  308. /// <param name="edge"></param>
  309. public void Add(SerializableEdge edge)
  310. {
  311. string portFieldName = (edge.inputNode == node) ? edge.inputFieldName : edge.outputFieldName;
  312. string portIdentifier = (edge.inputNode == node) ? edge.inputPortIdentifier : edge.outputPortIdentifier;
  313. // Force empty string to null since portIdentifier is a serialized value
  314. if (String.IsNullOrEmpty(portIdentifier))
  315. portIdentifier = null;
  316. var port = this.FirstOrDefault(p =>
  317. {
  318. return p.fieldName == portFieldName && p.portData.identifier == portIdentifier;
  319. });
  320. if (port == null)
  321. {
  322. Debug.LogError("The edge can't be properly connected because it's ports can't be found");
  323. return;
  324. }
  325. port.Add(edge);
  326. }
  327. }
  328. /// <inheritdoc/>
  329. public class NodeInputPortContainer : NodePortContainer
  330. {
  331. public NodeInputPortContainer(BaseNode node) : base(node) {}
  332. public void PullDatas()
  333. {
  334. ForEach(p => p.PullData());
  335. }
  336. }
  337. /// <inheritdoc/>
  338. public class NodeOutputPortContainer : NodePortContainer
  339. {
  340. public NodeOutputPortContainer(BaseNode node) : base(node) {}
  341. public void PushDatas()
  342. {
  343. ForEach(p => p.PushData());
  344. }
  345. }
  346. }