BaseNode.cs 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using System;
  5. using System.Reflection;
  6. using Unity.Jobs;
  7. using System.Linq;
  8. namespace GraphProcessor
  9. {
  10. public delegate IEnumerable<PortData> CustomPortBehaviorDelegate(List<SerializableEdge> edges);
  11. public delegate IEnumerable<PortData> CustomPortTypeBehaviorDelegate(string fieldName, string displayName,
  12. object value);
  13. [Serializable]
  14. public abstract class BaseNode
  15. {
  16. [SerializeField] internal string nodeCustomName = null; // The name of the node in case it was renamed by a user
  17. /// <summary>
  18. /// Name of the node, it will be displayed in the title section
  19. /// </summary>
  20. /// <returns></returns>
  21. public virtual string name => GetType().Name;
  22. /// <summary>
  23. /// The accent color of the node
  24. /// </summary>
  25. public virtual Color color => Color.clear;
  26. /// <summary>
  27. /// Set a custom uss file for the node. We use a Resources.Load to get the stylesheet so be sure to put the correct resources path
  28. /// https://docs.unity3d.com/ScriptReference/Resources.Load.html
  29. /// </summary>
  30. public virtual string layoutStyle => string.Empty;
  31. /// <summary>
  32. /// If the node can be locked or not
  33. /// </summary>
  34. public virtual bool unlockable => true;
  35. /// <summary>
  36. /// Is the node is locked (if locked it can't be moved)
  37. /// </summary>
  38. public virtual bool isLocked => nodeLock;
  39. //id
  40. public string GUID;
  41. public int computeOrder = -1;
  42. /// <summary>Tell wether or not the node can be processed. Do not check anything from inputs because this step happens before inputs are sent to the node</summary>
  43. public virtual bool canProcess => true;
  44. /// <summary>Show the node controlContainer only when the mouse is over the node</summary>
  45. public virtual bool showControlsOnHover => false;
  46. /// <summary>True if the node can be deleted, false otherwise</summary>
  47. public virtual bool deletable => true;
  48. /// <summary>
  49. /// Container of input ports
  50. /// </summary>
  51. [NonSerialized] public readonly NodeInputPortContainer inputPorts;
  52. /// <summary>
  53. /// Container of output ports
  54. /// </summary>
  55. [NonSerialized] public readonly NodeOutputPortContainer outputPorts;
  56. //Node view datas
  57. public Rect position;
  58. /// <summary>
  59. /// Is the node expanded
  60. /// </summary>
  61. public bool expanded;
  62. /// <summary>
  63. /// Is debug visible
  64. /// </summary>
  65. public bool debug;
  66. /// <summary>
  67. /// Node locked state
  68. /// </summary>
  69. public bool nodeLock;
  70. public delegate void ProcessDelegate();
  71. /// <summary>
  72. /// Triggered when the node is processes
  73. /// </summary>
  74. public event ProcessDelegate onProcessed;
  75. public event Action<string, NodeMessageType> onMessageAdded;
  76. public event Action<string> onMessageRemoved;
  77. /// <summary>
  78. /// Triggered after an edge was connected on the node
  79. /// </summary>
  80. public event Action<SerializableEdge> onAfterEdgeConnected;
  81. /// <summary>
  82. /// Triggered after an edge was disconnected on the node
  83. /// </summary>
  84. public event Action<SerializableEdge> onAfterEdgeDisconnected;
  85. /// <summary>
  86. /// Triggered after a single/list of port(s) is updated, the parameter is the field name
  87. /// </summary>
  88. public event Action<string> onPortsUpdated;
  89. [NonSerialized] bool _needsInspector = false;
  90. /// <summary>
  91. /// Does the node needs to be visible in the inspector (when selected).
  92. /// </summary>
  93. public virtual bool needsInspector => _needsInspector;
  94. /// <summary>
  95. /// Can the node be renamed in the UI. By default a node can be renamed by double clicking it's name.
  96. /// </summary>
  97. public virtual bool isRenamable => false;
  98. /// <summary>
  99. /// Is the node created from a duplicate operation (either ctrl-D or copy/paste).
  100. /// </summary>
  101. public bool createdFromDuplication { get; internal set; } = false;
  102. /// <summary>
  103. /// True only when the node was created from a duplicate operation and is inside a group that was also duplicated at the same time.
  104. /// </summary>
  105. public bool createdWithinGroup { get; internal set; } = false;
  106. [NonSerialized]
  107. internal Dictionary<string, NodeFieldInformation> nodeFields = new Dictionary<string, NodeFieldInformation>();
  108. [NonSerialized] internal Dictionary<Type, CustomPortTypeBehaviorDelegate> customPortTypeBehaviorMap =
  109. new Dictionary<Type, CustomPortTypeBehaviorDelegate>();
  110. [NonSerialized] List<string> messages = new List<string>();
  111. [NonSerialized] protected BaseGraph graph;
  112. internal class NodeFieldInformation
  113. {
  114. public string name;
  115. public string fieldName;
  116. public FieldInfo info;
  117. public bool input;
  118. public bool isMultiple;
  119. public string tooltip;
  120. public CustomPortBehaviorDelegate behavior;
  121. public bool vertical;
  122. public NodeFieldInformation(FieldInfo info, string name, bool input, bool isMultiple, string tooltip,
  123. bool vertical, CustomPortBehaviorDelegate behavior)
  124. {
  125. this.input = input;
  126. this.isMultiple = isMultiple;
  127. this.info = info;
  128. this.name = name;
  129. this.fieldName = info.Name;
  130. this.behavior = behavior;
  131. this.tooltip = tooltip;
  132. this.vertical = vertical;
  133. }
  134. }
  135. struct PortUpdate
  136. {
  137. public List<string> fieldNames;
  138. public BaseNode node;
  139. public void Deconstruct(out List<string> fieldNames, out BaseNode node)
  140. {
  141. fieldNames = this.fieldNames;
  142. node = this.node;
  143. }
  144. }
  145. // Used in port update algorithm
  146. Stack<PortUpdate> fieldsToUpdate = new Stack<PortUpdate>();
  147. HashSet<PortUpdate> updatedFields = new HashSet<PortUpdate>();
  148. /// <summary>
  149. /// Creates a node of type T at a certain position
  150. /// </summary>
  151. /// <param name="position">position in the graph in pixels</param>
  152. /// <typeparam name="T">type of the node</typeparam>
  153. /// <returns>the node instance</returns>
  154. public static T CreateFromType<T>(Vector2 position) where T : BaseNode
  155. {
  156. return CreateFromType(typeof(T), position) as T;
  157. }
  158. /// <summary>
  159. /// Creates a node of type nodeType at a certain position
  160. /// </summary>
  161. /// <param name="position">position in the graph in pixels</param>
  162. /// <typeparam name="nodeType">type of the node</typeparam>
  163. /// <returns>the node instance</returns>
  164. public static BaseNode CreateFromType(Type nodeType, Vector2 position)
  165. {
  166. if (!nodeType.IsSubclassOf(typeof(BaseNode)))
  167. return null;
  168. var node = Activator.CreateInstance(nodeType) as BaseNode;
  169. node.position = new Rect(position, new Vector2(100, 100));
  170. ExceptionToLog.Call(() => node.OnNodeCreated());
  171. return node;
  172. }
  173. #region Initialization
  174. // called by the BaseGraph when the node is added to the graph
  175. public void Initialize(BaseGraph graph)
  176. {
  177. this.graph = graph;
  178. ExceptionToLog.Call(() => Enable());
  179. InitializePorts();
  180. }
  181. void InitializeCustomPortTypeMethods()
  182. {
  183. MethodInfo[] methods = new MethodInfo[0];
  184. Type baseType = GetType();
  185. while (true)
  186. {
  187. methods = baseType.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance);
  188. foreach (var method in methods)
  189. {
  190. var typeBehaviors = method.GetCustomAttributes<CustomPortTypeBehavior>().ToArray();
  191. if (typeBehaviors.Length == 0)
  192. continue;
  193. CustomPortTypeBehaviorDelegate deleg = null;
  194. try
  195. {
  196. deleg =
  197. Delegate.CreateDelegate(typeof(CustomPortTypeBehaviorDelegate), this, method) as
  198. CustomPortTypeBehaviorDelegate;
  199. }
  200. catch (Exception e)
  201. {
  202. Debug.LogError(e);
  203. Debug.LogError(
  204. $"Cannot convert method {method} to a delegate of type {typeof(CustomPortTypeBehaviorDelegate)}");
  205. }
  206. foreach (var typeBehavior in typeBehaviors)
  207. customPortTypeBehaviorMap[typeBehavior.type] = deleg;
  208. }
  209. // Try to also find private methods in the base class
  210. baseType = baseType.BaseType;
  211. if (baseType == null)
  212. break;
  213. }
  214. }
  215. /// <summary>
  216. /// Use this function to initialize anything related to ports generation in your node
  217. /// This will allow the node creation menu to correctly recognize ports that can be connected between nodes
  218. /// </summary>
  219. public virtual void InitializePorts()
  220. {
  221. InitializeCustomPortTypeMethods();
  222. foreach (var key in OverrideFieldOrder(nodeFields.Values.Select(k => k.info)))
  223. {
  224. var nodeField = nodeFields[key.Name];
  225. if (HasCustomBehavior(nodeField))
  226. {
  227. UpdatePortsForField(nodeField.fieldName, sendPortUpdatedEvent: false);
  228. }
  229. else
  230. {
  231. // If we don't have a custom behavior on the node, we just have to create a simple port
  232. AddPort(nodeField.input, nodeField.fieldName,
  233. new PortData
  234. {
  235. acceptMultipleEdges = nodeField.isMultiple, displayName = nodeField.name,
  236. tooltip = nodeField.tooltip, vertical = nodeField.vertical
  237. });
  238. }
  239. }
  240. }
  241. /// <summary>
  242. /// Override the field order inside the node. It allows to re-order all the ports and field in the UI.
  243. /// </summary>
  244. /// <param name="fields">List of fields to sort</param>
  245. /// <returns>Sorted list of fields</returns>
  246. public virtual IEnumerable<FieldInfo> OverrideFieldOrder(IEnumerable<FieldInfo> fields)
  247. {
  248. long GetFieldInheritanceLevel(FieldInfo f)
  249. {
  250. int level = 0;
  251. var t = f.DeclaringType;
  252. while (t != null)
  253. {
  254. t = t.BaseType;
  255. level++;
  256. }
  257. return level;
  258. }
  259. // Order by MetadataToken and inheritance level to sync the order with the port order (make sure FieldDrawers are next to the correct port)
  260. return fields.OrderByDescending(
  261. f => (long) (((GetFieldInheritanceLevel(f) << 32)) | (long) f.MetadataToken));
  262. }
  263. protected BaseNode()
  264. {
  265. inputPorts = new NodeInputPortContainer(this);
  266. outputPorts = new NodeOutputPortContainer(this);
  267. InitializeInOutDatas();
  268. }
  269. /// <summary>
  270. /// Update all ports of the node
  271. /// </summary>
  272. public bool UpdateAllPorts()
  273. {
  274. bool changed = false;
  275. foreach (var key in OverrideFieldOrder(nodeFields.Values.Select(k => k.info)))
  276. {
  277. var field = nodeFields[key.Name];
  278. changed |= UpdatePortsForField(field.fieldName);
  279. }
  280. return changed;
  281. }
  282. /// <summary>
  283. /// Update all ports of the node without updating the connected ports. Only use this method when you need to update all the nodes ports in your graph.
  284. /// </summary>
  285. public bool UpdateAllPortsLocal()
  286. {
  287. bool changed = false;
  288. foreach (var key in OverrideFieldOrder(nodeFields.Values.Select(k => k.info)))
  289. {
  290. var field = nodeFields[key.Name];
  291. changed |= UpdatePortsForFieldLocal(field.fieldName);
  292. }
  293. return changed;
  294. }
  295. /// <summary>
  296. /// Update the ports related to one C# property field (only for this node)
  297. /// </summary>
  298. /// <param name="fieldName"></param>
  299. public bool UpdatePortsForFieldLocal(string fieldName, bool sendPortUpdatedEvent = true)
  300. {
  301. bool changed = false;
  302. if (!nodeFields.ContainsKey(fieldName))
  303. return false;
  304. var fieldInfo = nodeFields[fieldName];
  305. if (!HasCustomBehavior(fieldInfo))
  306. return false;
  307. List<string> finalPorts = new List<string>();
  308. var portCollection = fieldInfo.input ? (NodePortContainer) inputPorts : outputPorts;
  309. // Gather all fields for this port (before to modify them)
  310. var nodePorts = portCollection.Where(p => p.fieldName == fieldName);
  311. // Gather all edges connected to these fields:
  312. var edges = nodePorts.SelectMany(n => n.GetEdges()).ToList();
  313. if (fieldInfo.behavior != null)
  314. {
  315. foreach (var portData in fieldInfo.behavior(edges))
  316. AddPortData(portData);
  317. }
  318. else
  319. {
  320. var customPortTypeBehavior = customPortTypeBehaviorMap[fieldInfo.info.FieldType];
  321. foreach (var portData in customPortTypeBehavior(fieldName, fieldInfo.name,
  322. fieldInfo.info.GetValue(this)))
  323. AddPortData(portData);
  324. }
  325. void AddPortData(PortData portData)
  326. {
  327. var port = nodePorts.FirstOrDefault(n => n.portData.identifier == portData.identifier);
  328. // Guard using the port identifier so we don't duplicate identifiers
  329. if (port == null)
  330. {
  331. AddPort(fieldInfo.input, fieldName, portData);
  332. changed = true;
  333. }
  334. else
  335. {
  336. // in case the port type have changed for an incompatible type, we disconnect all the edges attached to this port
  337. if (!BaseGraph.TypesAreConnectable(port.portData.displayType, portData.displayType))
  338. {
  339. foreach (var edge in port.GetEdges().ToList())
  340. graph.Disconnect(edge.GUID);
  341. }
  342. // patch the port data
  343. if (port.portData != portData)
  344. {
  345. port.portData.CopyFrom(portData);
  346. changed = true;
  347. }
  348. }
  349. finalPorts.Add(portData.identifier);
  350. }
  351. // TODO
  352. // Remove only the ports that are no more in the list
  353. if (nodePorts != null)
  354. {
  355. var currentPortsCopy = nodePorts.ToList();
  356. foreach (var currentPort in currentPortsCopy)
  357. {
  358. // If the current port does not appear in the list of final ports, we remove it
  359. if (!finalPorts.Any(id => id == currentPort.portData.identifier))
  360. {
  361. RemovePort(fieldInfo.input, currentPort);
  362. changed = true;
  363. }
  364. }
  365. }
  366. // Make sure the port order is correct:
  367. portCollection.Sort((p1, p2) =>
  368. {
  369. int p1Index = finalPorts.FindIndex(id => p1.portData.identifier == id);
  370. int p2Index = finalPorts.FindIndex(id => p2.portData.identifier == id);
  371. if (p1Index == -1 || p2Index == -1)
  372. return 0;
  373. return p1Index.CompareTo(p2Index);
  374. });
  375. if (sendPortUpdatedEvent)
  376. onPortsUpdated?.Invoke(fieldName);
  377. return changed;
  378. }
  379. bool HasCustomBehavior(NodeFieldInformation info)
  380. {
  381. if (info.behavior != null)
  382. return true;
  383. if (customPortTypeBehaviorMap.ContainsKey(info.info.FieldType))
  384. return true;
  385. return false;
  386. }
  387. /// <summary>
  388. /// Update the ports related to one C# property field and all connected nodes in the graph
  389. /// </summary>
  390. /// <param name="fieldName"></param>
  391. public bool UpdatePortsForField(string fieldName, bool sendPortUpdatedEvent = true)
  392. {
  393. bool changed = false;
  394. fieldsToUpdate.Clear();
  395. updatedFields.Clear();
  396. fieldsToUpdate.Push(new PortUpdate {fieldNames = new List<string>() {fieldName}, node = this});
  397. // Iterate through all the ports that needs to be updated, following graph connection when the
  398. // port is updated. This is required ton have type propagation multiple nodes that changes port types
  399. // are connected to each other (i.e. the relay node)
  400. while (fieldsToUpdate.Count != 0)
  401. {
  402. var (fields, node) = fieldsToUpdate.Pop();
  403. // Avoid updating twice a port
  404. if (updatedFields.Any((t) => t.node == node && fields.SequenceEqual(t.fieldNames)))
  405. continue;
  406. updatedFields.Add(new PortUpdate {fieldNames = fields, node = node});
  407. foreach (var field in fields)
  408. {
  409. if (node.UpdatePortsForFieldLocal(field, sendPortUpdatedEvent))
  410. {
  411. foreach (var port in node.IsFieldInput(field)
  412. ? (NodePortContainer) node.inputPorts
  413. : node.outputPorts)
  414. {
  415. if (port.fieldName != field)
  416. continue;
  417. foreach (var edge in port.GetEdges())
  418. {
  419. var edgeNode = (node.IsFieldInput(field)) ? edge.outputNode : edge.inputNode;
  420. var fieldsWithBehavior = edgeNode.nodeFields.Values.Where(f => HasCustomBehavior(f))
  421. .Select(f => f.fieldName).ToList();
  422. fieldsToUpdate.Push(new PortUpdate {fieldNames = fieldsWithBehavior, node = edgeNode});
  423. }
  424. }
  425. changed = true;
  426. }
  427. }
  428. }
  429. return changed;
  430. }
  431. HashSet<BaseNode> portUpdateHashSet = new HashSet<BaseNode>();
  432. internal void DisableInternal()
  433. {
  434. // port containers are initialized in the OnEnable
  435. inputPorts.Clear();
  436. outputPorts.Clear();
  437. ExceptionToLog.Call(() => Disable());
  438. }
  439. internal void DestroyInternal() => ExceptionToLog.Call(() => Destroy());
  440. /// <summary>
  441. /// Called only when the node is created, not when instantiated
  442. /// </summary>
  443. public virtual void OnNodeCreated() => GUID = Guid.NewGuid().ToString();
  444. public virtual FieldInfo[] GetNodeFields()
  445. => GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
  446. void InitializeInOutDatas()
  447. {
  448. var fields = GetNodeFields();
  449. var methods = GetType().GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
  450. foreach (var field in fields)
  451. {
  452. var inputAttribute = field.GetCustomAttribute<InputAttribute>();
  453. var outputAttribute = field.GetCustomAttribute<OutputAttribute>();
  454. var tooltipAttribute = field.GetCustomAttribute<TooltipAttribute>();
  455. var showInInspector = field.GetCustomAttribute<ShowInInspector>();
  456. var vertical = field.GetCustomAttribute<VerticalAttribute>();
  457. bool isMultiple = false;
  458. bool input = false;
  459. string name = field.Name;
  460. string tooltip = null;
  461. if (showInInspector != null)
  462. _needsInspector = true;
  463. if (inputAttribute == null && outputAttribute == null)
  464. continue;
  465. //check if field is a collection type
  466. isMultiple = (inputAttribute != null) ? inputAttribute.allowMultiple : (outputAttribute.allowMultiple);
  467. input = inputAttribute != null;
  468. tooltip = tooltipAttribute?.tooltip;
  469. if (!String.IsNullOrEmpty(inputAttribute?.name))
  470. name = inputAttribute.name;
  471. if (!String.IsNullOrEmpty(outputAttribute?.name))
  472. name = outputAttribute.name;
  473. // By default we set the behavior to null, if the field have a custom behavior, it will be set in the loop just below
  474. nodeFields[field.Name] =
  475. new NodeFieldInformation(field, name, input, isMultiple, tooltip, vertical != null, null);
  476. }
  477. foreach (var method in methods)
  478. {
  479. var customPortBehaviorAttribute = method.GetCustomAttribute<CustomPortBehaviorAttribute>();
  480. CustomPortBehaviorDelegate behavior = null;
  481. if (customPortBehaviorAttribute == null)
  482. continue;
  483. // Check if custom port behavior function is valid
  484. try
  485. {
  486. var referenceType = typeof(CustomPortBehaviorDelegate);
  487. behavior = (CustomPortBehaviorDelegate) Delegate.CreateDelegate(referenceType, this, method, true);
  488. }
  489. catch
  490. {
  491. Debug.LogError("The function " + method + " cannot be converted to the required delegate format: " +
  492. typeof(CustomPortBehaviorDelegate));
  493. }
  494. if (nodeFields.ContainsKey(customPortBehaviorAttribute.fieldName))
  495. nodeFields[customPortBehaviorAttribute.fieldName].behavior = behavior;
  496. else
  497. Debug.LogError("Invalid field name for custom port behavior: " + method + ", " +
  498. customPortBehaviorAttribute.fieldName);
  499. }
  500. }
  501. #endregion
  502. #region Events and Processing
  503. public void OnEdgeConnected(SerializableEdge edge)
  504. {
  505. bool input = edge.inputNode == this;
  506. NodePortContainer portCollection = (input) ? (NodePortContainer) inputPorts : outputPorts;
  507. portCollection.Add(edge);
  508. UpdateAllPorts();
  509. onAfterEdgeConnected?.Invoke(edge);
  510. }
  511. protected virtual bool CanResetPort(NodePort port) => true;
  512. public void OnEdgeDisconnected(SerializableEdge edge)
  513. {
  514. if (edge == null)
  515. return;
  516. bool input = edge.inputNode == this;
  517. NodePortContainer portCollection = (input) ? (NodePortContainer) inputPorts : outputPorts;
  518. portCollection.Remove(edge);
  519. // Reset default values of input port:
  520. bool haveConnectedEdges = edge.inputNode.inputPorts.Where(p => p.fieldName == edge.inputFieldName)
  521. .Any(p => p.GetEdges().Count != 0);
  522. if (edge.inputNode == this && !haveConnectedEdges && CanResetPort(edge.inputPort))
  523. edge.inputPort?.ResetToDefault();
  524. UpdateAllPorts();
  525. onAfterEdgeDisconnected?.Invoke(edge);
  526. }
  527. public void OnEnter()
  528. {
  529. }
  530. public void OnLeave()
  531. {
  532. }
  533. protected virtual void Enter()
  534. {
  535. }
  536. protected virtual void Leave()
  537. {
  538. }
  539. public void OnProcess()
  540. {
  541. inputPorts.PullDatas();
  542. ExceptionToLog.Call(() => Process());
  543. InvokeOnProcessed();
  544. outputPorts.PushDatas();
  545. }
  546. public void InvokeOnProcessed() => onProcessed?.Invoke();
  547. /// <summary>
  548. /// Called when the node is enabled
  549. /// </summary>
  550. protected virtual void Enable()
  551. {
  552. }
  553. /// <summary>
  554. /// Called when the node is disabled
  555. /// </summary>
  556. protected virtual void Disable()
  557. {
  558. }
  559. /// <summary>
  560. /// Called when the node is removed
  561. /// </summary>
  562. protected virtual void Destroy()
  563. {
  564. }
  565. /// <summary>
  566. /// Override this method to implement custom processing
  567. /// </summary>
  568. protected virtual void Process()
  569. {
  570. }
  571. #endregion
  572. #region API and utils
  573. /// <summary>
  574. /// Add a port
  575. /// </summary>
  576. /// <param name="input">is input port</param>
  577. /// <param name="fieldName">C# field name</param>
  578. /// <param name="portData">Data of the port</param>
  579. public void AddPort(bool input, string fieldName, PortData portData)
  580. {
  581. // Fixup port data info if needed:
  582. if (portData.displayType == null)
  583. portData.displayType = nodeFields[fieldName].info.FieldType;
  584. if (input)
  585. inputPorts.Add(new NodePort(this, fieldName, portData));
  586. else
  587. outputPorts.Add(new NodePort(this, fieldName, portData));
  588. }
  589. /// <summary>
  590. /// Remove a port
  591. /// </summary>
  592. /// <param name="input">is input port</param>
  593. /// <param name="port">the port to delete</param>
  594. public void RemovePort(bool input, NodePort port)
  595. {
  596. if (input)
  597. inputPorts.Remove(port);
  598. else
  599. outputPorts.Remove(port);
  600. }
  601. /// <summary>
  602. /// Remove port(s) from field name
  603. /// </summary>
  604. /// <param name="input">is input</param>
  605. /// <param name="fieldName">C# field name</param>
  606. public void RemovePort(bool input, string fieldName)
  607. {
  608. if (input)
  609. inputPorts.RemoveAll(p => p.fieldName == fieldName);
  610. else
  611. outputPorts.RemoveAll(p => p.fieldName == fieldName);
  612. }
  613. /// <summary>
  614. /// Get all the nodes connected to the input ports of this node
  615. /// </summary>
  616. /// <returns>an enumerable of node</returns>
  617. public IEnumerable<BaseNode> GetInputNodes()
  618. {
  619. foreach (var port in inputPorts)
  620. foreach (var edge in port.GetEdges())
  621. yield return edge.outputNode;
  622. }
  623. /// <summary>
  624. /// Get all the nodes connected to the output ports of this node
  625. /// </summary>
  626. /// <returns>an enumerable of node</returns>
  627. public IEnumerable<BaseNode> GetOutputNodes()
  628. {
  629. foreach (var port in outputPorts)
  630. foreach (var edge in port.GetEdges())
  631. yield return edge.inputNode;
  632. }
  633. public List<BaseNode> GetOutputNodesForList(string name=null)
  634. {
  635. List<BaseNode> allNode = new List<BaseNode>();
  636. foreach (var port in outputPorts)
  637. {
  638. if (name != null && port.fieldName != name)
  639. {
  640. continue;
  641. }
  642. foreach (var edge in port.GetEdges())
  643. {
  644. allNode.Add(edge.inputNode);
  645. }
  646. }
  647. return allNode;
  648. }
  649. /// <summary>
  650. /// Return a node matching the condition in the dependencies of the node
  651. /// </summary>
  652. /// <param name="condition">Condition to choose the node</param>
  653. /// <returns>Matched node or null</returns>
  654. public BaseNode FindInDependencies(Func<BaseNode, bool> condition)
  655. {
  656. Stack<BaseNode> dependencies = new Stack<BaseNode>();
  657. dependencies.Push(this);
  658. int depth = 0;
  659. while (dependencies.Count > 0)
  660. {
  661. var node = dependencies.Pop();
  662. // Guard for infinite loop (faster than a HashSet based solution)
  663. depth++;
  664. if (depth > 2000)
  665. break;
  666. if (condition(node))
  667. return node;
  668. foreach (var dep in node.GetInputNodes())
  669. dependencies.Push(dep);
  670. }
  671. return null;
  672. }
  673. /// <summary>
  674. /// Get the port from field name and identifier
  675. /// </summary>
  676. /// <param name="fieldName">C# field name</param>
  677. /// <param name="identifier">Unique port identifier</param>
  678. /// <returns></returns>
  679. public NodePort GetPort(string fieldName, string identifier)
  680. {
  681. return inputPorts.Concat(outputPorts).FirstOrDefault(p =>
  682. {
  683. var bothNull = String.IsNullOrEmpty(identifier) && String.IsNullOrEmpty(p.portData.identifier);
  684. return p.fieldName == fieldName && (bothNull || identifier == p.portData.identifier);
  685. });
  686. }
  687. /// <summary>
  688. /// Return all the ports of the node
  689. /// </summary>
  690. /// <returns></returns>
  691. public IEnumerable<NodePort> GetAllPorts()
  692. {
  693. foreach (var port in inputPorts)
  694. yield return port;
  695. foreach (var port in outputPorts)
  696. yield return port;
  697. }
  698. /// <summary>
  699. /// Return all the connected edges of the node
  700. /// </summary>
  701. /// <returns></returns>
  702. public IEnumerable<SerializableEdge> GetAllEdges()
  703. {
  704. foreach (var port in GetAllPorts())
  705. foreach (var edge in port.GetEdges())
  706. yield return edge;
  707. }
  708. /// <summary>
  709. /// Is the port an input
  710. /// </summary>
  711. /// <param name="fieldName"></param>
  712. /// <returns></returns>
  713. public bool IsFieldInput(string fieldName) => nodeFields[fieldName].input;
  714. /// <summary>
  715. /// Add a message on the node
  716. /// </summary>
  717. /// <param name="message"></param>
  718. /// <param name="messageType"></param>
  719. public void AddMessage(string message, NodeMessageType messageType)
  720. {
  721. if (messages.Contains(message))
  722. return;
  723. onMessageAdded?.Invoke(message, messageType);
  724. messages.Add(message);
  725. }
  726. /// <summary>
  727. /// Remove a message on the node
  728. /// </summary>
  729. /// <param name="message"></param>
  730. public void RemoveMessage(string message)
  731. {
  732. onMessageRemoved?.Invoke(message);
  733. messages.Remove(message);
  734. }
  735. /// <summary>
  736. /// Remove a message that contains
  737. /// </summary>
  738. /// <param name="subMessage"></param>
  739. public void RemoveMessageContains(string subMessage)
  740. {
  741. string toRemove = messages.Find(m => m.Contains(subMessage));
  742. messages.Remove(toRemove);
  743. onMessageRemoved?.Invoke(toRemove);
  744. }
  745. /// <summary>
  746. /// Remove all messages on the node
  747. /// </summary>
  748. public void ClearMessages()
  749. {
  750. foreach (var message in messages)
  751. onMessageRemoved?.Invoke(message);
  752. messages.Clear();
  753. }
  754. /// <summary>
  755. /// Set the custom name of the node. This is intended to be used by renamable nodes.
  756. /// This custom name will be serialized inside the node.
  757. /// </summary>
  758. /// <param name="customNodeName">New name of the node.</param>
  759. public void SetCustomName(string customName) => nodeCustomName = customName;
  760. /// <summary>
  761. /// Get the name of the node. If the node have a custom name (set using the UI by double clicking on the node title) then it will return this name first, otherwise it returns the value of the name field.
  762. /// </summary>
  763. /// <returns>The name of the node as written in the title</returns>
  764. public string GetCustomName() => String.IsNullOrEmpty(nodeCustomName) ? name : nodeCustomName;
  765. #endregion
  766. }
  767. }