Node.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. using System;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. namespace XNode {
  5. /// <summary>
  6. /// Base class for all nodes
  7. /// </summary>
  8. /// <example>
  9. /// Classes extending this class will be considered as valid nodes by xNode.
  10. /// <code>
  11. /// [System.Serializable]
  12. /// public class Adder : Node {
  13. /// [Input] public float a;
  14. /// [Input] public float b;
  15. /// [Output] public float result;
  16. ///
  17. /// // GetValue should be overridden to return a value for any specified output port
  18. /// public override object GetValue(NodePort port) {
  19. /// return a + b;
  20. /// }
  21. /// }
  22. /// </code>
  23. /// </example>
  24. [Serializable]
  25. public abstract class Node : ScriptableObject {
  26. /// <summary> Used by <see cref="InputAttribute"/> and <see cref="OutputAttribute"/> to determine when to display the field value associated with a <see cref="NodePort"/> </summary>
  27. public enum ShowBackingValue {
  28. /// <summary> Never show the backing value </summary>
  29. Never,
  30. /// <summary> Show the backing value only when the port does not have any active connections </summary>
  31. Unconnected,
  32. /// <summary> Always show the backing value </summary>
  33. Always
  34. }
  35. public enum ConnectionType {
  36. /// <summary> Allow multiple connections</summary>
  37. Multiple,
  38. /// <summary> always override the current connection </summary>
  39. Override,
  40. }
  41. /// <summary> Tells which types of input to allow </summary>
  42. public enum TypeConstraint {
  43. /// <summary> Allow all types of input</summary>
  44. None,
  45. /// <summary> Allow connections where input value type is assignable from output value type (eg. ScriptableObject --> Object)</summary>
  46. Inherited,
  47. /// <summary> Allow only similar types </summary>
  48. Strict,
  49. /// <summary> Allow connections where output value type is assignable from input value type (eg. Object --> ScriptableObject)</summary>
  50. InheritedInverse,
  51. }
  52. #region Obsolete
  53. [Obsolete("Use DynamicPorts instead")]
  54. public IEnumerable<NodePort> InstancePorts { get { return DynamicPorts; } }
  55. [Obsolete("Use DynamicOutputs instead")]
  56. public IEnumerable<NodePort> InstanceOutputs { get { return DynamicOutputs; } }
  57. [Obsolete("Use DynamicInputs instead")]
  58. public IEnumerable<NodePort> InstanceInputs { get { return DynamicInputs; } }
  59. [Obsolete("Use AddDynamicInput instead")]
  60. public NodePort AddInstanceInput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) {
  61. return AddInstanceInput(type, connectionType, typeConstraint, fieldName);
  62. }
  63. [Obsolete("Use AddDynamicOutput instead")]
  64. public NodePort AddInstanceOutput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) {
  65. return AddDynamicOutput(type, connectionType, typeConstraint, fieldName);
  66. }
  67. [Obsolete("Use AddDynamicPort instead")]
  68. private NodePort AddInstancePort(Type type, NodePort.IO direction, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) {
  69. return AddDynamicPort(type, direction, connectionType, typeConstraint, fieldName);
  70. }
  71. [Obsolete("Use RemoveDynamicPort instead")]
  72. public void RemoveInstancePort(string fieldName) {
  73. RemoveDynamicPort(fieldName);
  74. }
  75. [Obsolete("Use RemoveDynamicPort instead")]
  76. public void RemoveInstancePort(NodePort port) {
  77. RemoveDynamicPort(port);
  78. }
  79. [Obsolete("Use ClearDynamicPorts instead")]
  80. public void ClearInstancePorts() {
  81. ClearDynamicPorts();
  82. }
  83. #endregion
  84. /// <summary> Iterate over all ports on this node. </summary>
  85. public IEnumerable<NodePort> Ports { get { foreach (NodePort port in ports.Values) yield return port; } }
  86. /// <summary> Iterate over all outputs on this node. </summary>
  87. public IEnumerable<NodePort> Outputs { get { foreach (NodePort port in Ports) { if (port.IsOutput) yield return port; } } }
  88. /// <summary> Iterate over all inputs on this node. </summary>
  89. public IEnumerable<NodePort> Inputs { get { foreach (NodePort port in Ports) { if (port.IsInput) yield return port; } } }
  90. /// <summary> Iterate over all dynamic ports on this node. </summary>
  91. public IEnumerable<NodePort> DynamicPorts { get { foreach (NodePort port in Ports) { if (port.IsDynamic) yield return port; } } }
  92. /// <summary> Iterate over all dynamic outputs on this node. </summary>
  93. public IEnumerable<NodePort> DynamicOutputs { get { foreach (NodePort port in Ports) { if (port.IsDynamic && port.IsOutput) yield return port; } } }
  94. /// <summary> Iterate over all dynamic inputs on this node. </summary>
  95. public IEnumerable<NodePort> DynamicInputs { get { foreach (NodePort port in Ports) { if (port.IsDynamic && port.IsInput) yield return port; } } }
  96. /// <summary> Parent <see cref="NodeGraph"/> </summary>
  97. [SerializeField] public NodeGraph graph;
  98. /// <summary> Position on the <see cref="NodeGraph"/> </summary>
  99. [SerializeField] public Vector2 position;
  100. /// <summary> It is recommended not to modify these at hand. Instead, see <see cref="InputAttribute"/> and <see cref="OutputAttribute"/> </summary>
  101. [SerializeField] private NodePortDictionary ports = new NodePortDictionary();
  102. /// <summary> Used during node instantiation to fix null/misconfigured graph during OnEnable/Init. Set it before instantiating a node. Will automatically be unset during OnEnable </summary>
  103. public static NodeGraph graphHotfix;
  104. protected void OnEnable() {
  105. if (graphHotfix != null) graph = graphHotfix;
  106. graphHotfix = null;
  107. UpdateStaticPorts();
  108. Init();
  109. }
  110. /// <summary> Update static ports to reflect class fields. This happens automatically on enable. </summary>
  111. public void UpdateStaticPorts() {
  112. NodeDataCache.UpdatePorts(this, ports);
  113. }
  114. /// <summary> Initialize node. Called on enable. </summary>
  115. protected virtual void Init() { }
  116. /// <summary> Checks all connections for invalid references, and removes them. </summary>
  117. public void VerifyConnections() {
  118. foreach (NodePort port in Ports) port.VerifyConnections();
  119. }
  120. #region Dynamic Ports
  121. /// <summary> Convenience function. </summary>
  122. /// <seealso cref="AddInstancePort"/>
  123. /// <seealso cref="AddInstanceOutput"/>
  124. public NodePort AddDynamicInput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) {
  125. return AddDynamicPort(type, NodePort.IO.Input, connectionType, typeConstraint, fieldName);
  126. }
  127. /// <summary> Convenience function. </summary>
  128. /// <seealso cref="AddInstancePort"/>
  129. /// <seealso cref="AddInstanceInput"/>
  130. public NodePort AddDynamicOutput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) {
  131. return AddDynamicPort(type, NodePort.IO.Output, connectionType, typeConstraint, fieldName);
  132. }
  133. /// <summary> Add a dynamic, serialized port to this node. </summary>
  134. /// <seealso cref="AddDynamicInput"/>
  135. /// <seealso cref="AddDynamicOutput"/>
  136. private NodePort AddDynamicPort(Type type, NodePort.IO direction, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) {
  137. if (fieldName == null) {
  138. fieldName = "dynamicInput_0";
  139. int i = 0;
  140. while (HasPort(fieldName)) fieldName = "dynamicInput_" + (++i);
  141. } else if (HasPort(fieldName)) {
  142. Debug.LogWarning("Port '" + fieldName + "' already exists in " + name, this);
  143. return ports[fieldName];
  144. }
  145. NodePort port = new NodePort(fieldName, type, direction, connectionType, typeConstraint, this);
  146. ports.Add(fieldName, port);
  147. return port;
  148. }
  149. /// <summary> Remove an dynamic port from the node </summary>
  150. public void RemoveDynamicPort(string fieldName) {
  151. NodePort dynamicPort = GetPort(fieldName);
  152. if (dynamicPort == null) throw new ArgumentException("port " + fieldName + " doesn't exist");
  153. RemoveDynamicPort(GetPort(fieldName));
  154. }
  155. /// <summary> Remove an dynamic port from the node </summary>
  156. public void RemoveDynamicPort(NodePort port) {
  157. if (port == null) throw new ArgumentNullException("port");
  158. else if (port.IsStatic) throw new ArgumentException("cannot remove static port");
  159. port.ClearConnections();
  160. ports.Remove(port.fieldName);
  161. }
  162. /// <summary> Removes all dynamic ports from the node </summary>
  163. [ContextMenu("Clear Dynamic Ports")]
  164. public void ClearDynamicPorts() {
  165. List<NodePort> dynamicPorts = new List<NodePort>(DynamicPorts);
  166. foreach (NodePort port in dynamicPorts) {
  167. RemoveDynamicPort(port);
  168. }
  169. }
  170. #endregion
  171. #region Ports
  172. /// <summary> Returns output port which matches fieldName </summary>
  173. public NodePort GetOutputPort(string fieldName) {
  174. NodePort port = GetPort(fieldName);
  175. if (port == null || port.direction != NodePort.IO.Output) return null;
  176. else return port;
  177. }
  178. /// <summary> Returns input port which matches fieldName </summary>
  179. public NodePort GetInputPort(string fieldName) {
  180. NodePort port = GetPort(fieldName);
  181. if (port == null || port.direction != NodePort.IO.Input) return null;
  182. else return port;
  183. }
  184. /// <summary> Returns port which matches fieldName </summary>
  185. public NodePort GetPort(string fieldName) {
  186. NodePort port;
  187. if (ports.TryGetValue(fieldName, out port)) return port;
  188. else return null;
  189. }
  190. public bool HasPort(string fieldName) {
  191. return ports.ContainsKey(fieldName);
  192. }
  193. #endregion
  194. #region Inputs/Outputs
  195. /// <summary> Return input value for a specified port. Returns fallback value if no ports are connected </summary>
  196. /// <param name="fieldName">Field name of requested input port</param>
  197. /// <param name="fallback">If no ports are connected, this value will be returned</param>
  198. public T GetInputValue<T>(string fieldName, T fallback = default(T)) {
  199. NodePort port = GetPort(fieldName);
  200. if (port != null && port.IsConnected) return port.GetInputValue<T>();
  201. else return fallback;
  202. }
  203. /// <summary> Return all input values for a specified port. Returns fallback value if no ports are connected </summary>
  204. /// <param name="fieldName">Field name of requested input port</param>
  205. /// <param name="fallback">If no ports are connected, this value will be returned</param>
  206. public T[] GetInputValues<T>(string fieldName, params T[] fallback) {
  207. NodePort port = GetPort(fieldName);
  208. if (port != null && port.IsConnected) return port.GetInputValues<T>();
  209. else return fallback;
  210. }
  211. /// <summary> Returns a value based on requested port output. Should be overridden in all derived nodes with outputs. </summary>
  212. /// <param name="port">The requested port.</param>
  213. public virtual object GetValue(NodePort port) {
  214. Debug.LogWarning("No GetValue(NodePort port) override defined for " + GetType());
  215. return null;
  216. }
  217. #endregion
  218. /// <summary> Called after a connection between two <see cref="NodePort"/>s is created </summary>
  219. /// <param name="from">Output</param> <param name="to">Input</param>
  220. public virtual void OnCreateConnection(NodePort from, NodePort to) { }
  221. /// <summary> Called after a connection is removed from this port </summary>
  222. /// <param name="port">Output or Input</param>
  223. public virtual void OnRemoveConnection(NodePort port) { }
  224. /// <summary> Disconnect everything from this node </summary>
  225. public void ClearConnections() {
  226. foreach (NodePort port in Ports) port.ClearConnections();
  227. }
  228. #region Attributes
  229. /// <summary> Mark a serializable field as an input port. You can access this through <see cref="GetInputPort(string)"/> </summary>
  230. [AttributeUsage(AttributeTargets.Field, AllowMultiple = true)]
  231. public class InputAttribute : Attribute {
  232. public ShowBackingValue backingValue;
  233. public ConnectionType connectionType;
  234. [Obsolete("Use dynamicPortList instead")]
  235. public bool instancePortList { get { return dynamicPortList; } set { dynamicPortList = value; } }
  236. public bool dynamicPortList;
  237. public TypeConstraint typeConstraint;
  238. /// <summary> Mark a serializable field as an input port. You can access this through <see cref="GetInputPort(string)"/> </summary>
  239. /// <param name="backingValue">Should we display the backing value for this port as an editor field? </param>
  240. /// <param name="connectionType">Should we allow multiple connections? </param>
  241. /// <param name="typeConstraint">Constrains which input connections can be made to this port </param>
  242. /// <param name="dynamicPortList">If true, will display a reorderable list of inputs instead of a single port. Will automatically add and display values for lists and arrays </param>
  243. public InputAttribute(ShowBackingValue backingValue = ShowBackingValue.Unconnected, ConnectionType connectionType = ConnectionType.Multiple, TypeConstraint typeConstraint = TypeConstraint.None, bool dynamicPortList = false) {
  244. this.backingValue = backingValue;
  245. this.connectionType = connectionType;
  246. this.dynamicPortList = dynamicPortList;
  247. this.typeConstraint = typeConstraint;
  248. }
  249. }
  250. /// <summary> Mark a serializable field as an output port. You can access this through <see cref="GetOutputPort(string)"/> </summary>
  251. [AttributeUsage(AttributeTargets.Field, AllowMultiple = true)]
  252. public class OutputAttribute : Attribute {
  253. public ShowBackingValue backingValue;
  254. public ConnectionType connectionType;
  255. [Obsolete("Use dynamicPortList instead")]
  256. public bool instancePortList { get { return dynamicPortList; } set { dynamicPortList = value; } }
  257. public bool dynamicPortList;
  258. public TypeConstraint typeConstraint;
  259. /// <summary> Mark a serializable field as an output port. You can access this through <see cref="GetOutputPort(string)"/> </summary>
  260. /// <param name="backingValue">Should we display the backing value for this port as an editor field? </param>
  261. /// <param name="connectionType">Should we allow multiple connections? </param>
  262. /// <param name="typeConstraint">Constrains which input connections can be made from this port </param>
  263. /// <param name="dynamicPortList">If true, will display a reorderable list of outputs instead of a single port. Will automatically add and display values for lists and arrays </param>
  264. public OutputAttribute(ShowBackingValue backingValue = ShowBackingValue.Never, ConnectionType connectionType = ConnectionType.Multiple, TypeConstraint typeConstraint = TypeConstraint.None, bool dynamicPortList = false) {
  265. this.backingValue = backingValue;
  266. this.connectionType = connectionType;
  267. this.dynamicPortList = dynamicPortList;
  268. this.typeConstraint = typeConstraint;
  269. }
  270. /// <summary> Mark a serializable field as an output port. You can access this through <see cref="GetOutputPort(string)"/> </summary>
  271. /// <param name="backingValue">Should we display the backing value for this port as an editor field? </param>
  272. /// <param name="connectionType">Should we allow multiple connections? </param>
  273. /// <param name="dynamicPortList">If true, will display a reorderable list of outputs instead of a single port. Will automatically add and display values for lists and arrays </param>
  274. [Obsolete("Use constructor with TypeConstraint")]
  275. public OutputAttribute(ShowBackingValue backingValue, ConnectionType connectionType, bool dynamicPortList) : this(backingValue, connectionType, TypeConstraint.None, dynamicPortList) { }
  276. }
  277. [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
  278. public class CreateNodeMenuAttribute : Attribute {
  279. public string menuName;
  280. /// <summary> Manually supply node class with a context menu path </summary>
  281. /// <param name="menuName"> Path to this node in the context menu. Null or empty hides it. </param>
  282. public CreateNodeMenuAttribute(string menuName) {
  283. this.menuName = menuName;
  284. }
  285. }
  286. [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
  287. public class NodeTintAttribute : Attribute {
  288. public Color color;
  289. /// <summary> Specify a color for this node type </summary>
  290. /// <param name="r"> Red [0.0f .. 1.0f] </param>
  291. /// <param name="g"> Green [0.0f .. 1.0f] </param>
  292. /// <param name="b"> Blue [0.0f .. 1.0f] </param>
  293. public NodeTintAttribute(float r, float g, float b) {
  294. color = new Color(r, g, b);
  295. }
  296. /// <summary> Specify a color for this node type </summary>
  297. /// <param name="hex"> HEX color value </param>
  298. public NodeTintAttribute(string hex) {
  299. ColorUtility.TryParseHtmlString(hex, out color);
  300. }
  301. /// <summary> Specify a color for this node type </summary>
  302. /// <param name="r"> Red [0 .. 255] </param>
  303. /// <param name="g"> Green [0 .. 255] </param>
  304. /// <param name="b"> Blue [0 .. 255] </param>
  305. public NodeTintAttribute(byte r, byte g, byte b) {
  306. color = new Color32(r, g, b, byte.MaxValue);
  307. }
  308. }
  309. [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
  310. public class NodeWidthAttribute : Attribute {
  311. public int width;
  312. /// <summary> Specify a width for this node type </summary>
  313. /// <param name="width"> Width </param>
  314. public NodeWidthAttribute(int width) {
  315. this.width = width;
  316. }
  317. }
  318. #endregion
  319. [Serializable] private class NodePortDictionary : Dictionary<string, NodePort>, ISerializationCallbackReceiver {
  320. [SerializeField] private List<string> keys = new List<string>();
  321. [SerializeField] private List<NodePort> values = new List<NodePort>();
  322. public void OnBeforeSerialize() {
  323. keys.Clear();
  324. values.Clear();
  325. foreach (KeyValuePair<string, NodePort> pair in this) {
  326. keys.Add(pair.Key);
  327. values.Add(pair.Value);
  328. }
  329. }
  330. public void OnAfterDeserialize() {
  331. this.Clear();
  332. if (keys.Count != values.Count)
  333. throw new System.Exception("there are " + keys.Count + " keys and " + values.Count + " values after deserialization. Make sure that both key and value types are serializable.");
  334. for (int i = 0; i < keys.Count; i++)
  335. this.Add(keys[i], values[i]);
  336. }
  337. }
  338. }
  339. }