NodePort.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Reflection;
  4. using UnityEngine;
  5. namespace XNode {
  6. [Serializable]
  7. public class NodePort {
  8. public enum IO { Input, Output }
  9. public int ConnectionCount { get { return connections.Count; } }
  10. /// <summary> Return the first non-null connection </summary>
  11. public NodePort Connection {
  12. get {
  13. for (int i = 0; i < connections.Count; i++) {
  14. if (connections[i] != null) return connections[i].Port;
  15. }
  16. return null;
  17. }
  18. }
  19. public IO direction {
  20. get { return _direction; }
  21. internal set { _direction = value; }
  22. }
  23. public Node.ConnectionType connectionType {
  24. get { return _connectionType; }
  25. internal set { _connectionType = value; }
  26. }
  27. public Node.TypeConstraint typeConstraint {
  28. get { return _typeConstraint; }
  29. internal set { _typeConstraint = value; }
  30. }
  31. /// <summary> Is this port connected to anytihng? </summary>
  32. public bool IsConnected { get { return connections.Count != 0; } }
  33. public bool IsInput { get { return direction == IO.Input; } }
  34. public bool IsOutput { get { return direction == IO.Output; } }
  35. public string fieldName { get { return _fieldName; } }
  36. public Node node { get { return _node; } }
  37. public bool IsDynamic { get { return _dynamic; } }
  38. public bool IsStatic { get { return !_dynamic; } }
  39. public Type ValueType {
  40. get {
  41. if (valueType == null && !string.IsNullOrEmpty(_typeQualifiedName)) valueType = Type.GetType(_typeQualifiedName, false);
  42. return valueType;
  43. }
  44. set {
  45. valueType = value;
  46. if (value != null) _typeQualifiedName = value.AssemblyQualifiedName;
  47. }
  48. }
  49. private Type valueType;
  50. [SerializeField] private string _fieldName;
  51. [SerializeField] private Node _node;
  52. [SerializeField] private string _typeQualifiedName;
  53. [SerializeField] private List<PortConnection> connections = new List<PortConnection>();
  54. [SerializeField] private IO _direction;
  55. [SerializeField] private Node.ConnectionType _connectionType;
  56. [SerializeField] private Node.TypeConstraint _typeConstraint;
  57. [SerializeField] private bool _dynamic;
  58. /// <summary> Construct a static targetless nodeport. Used as a template. </summary>
  59. public NodePort(FieldInfo fieldInfo) {
  60. _fieldName = fieldInfo.Name;
  61. ValueType = fieldInfo.FieldType;
  62. _dynamic = false;
  63. var attribs = fieldInfo.GetCustomAttributes(false);
  64. for (int i = 0; i < attribs.Length; i++) {
  65. if (attribs[i] is Node.InputAttribute) {
  66. _direction = IO.Input;
  67. _connectionType = (attribs[i] as Node.InputAttribute).connectionType;
  68. _typeConstraint = (attribs[i] as Node.InputAttribute).typeConstraint;
  69. } else if (attribs[i] is Node.OutputAttribute) {
  70. _direction = IO.Output;
  71. _connectionType = (attribs[i] as Node.OutputAttribute).connectionType;
  72. _typeConstraint = (attribs[i] as Node.OutputAttribute).typeConstraint;
  73. }
  74. }
  75. }
  76. /// <summary> Copy a nodePort but assign it to another node. </summary>
  77. public NodePort(NodePort nodePort, Node node) {
  78. _fieldName = nodePort._fieldName;
  79. ValueType = nodePort.valueType;
  80. _direction = nodePort.direction;
  81. _dynamic = nodePort._dynamic;
  82. _connectionType = nodePort._connectionType;
  83. _typeConstraint = nodePort._typeConstraint;
  84. _node = node;
  85. }
  86. /// <summary> Construct a dynamic port. Dynamic ports are not forgotten on reimport, and is ideal for runtime-created ports. </summary>
  87. public NodePort(string fieldName, Type type, IO direction, Node.ConnectionType connectionType, Node.TypeConstraint typeConstraint, Node node) {
  88. _fieldName = fieldName;
  89. this.ValueType = type;
  90. _direction = direction;
  91. _node = node;
  92. _dynamic = true;
  93. _connectionType = connectionType;
  94. _typeConstraint = typeConstraint;
  95. }
  96. /// <summary> Checks all connections for invalid references, and removes them. </summary>
  97. public void VerifyConnections() {
  98. for (int i = connections.Count - 1; i >= 0; i--) {
  99. if (connections[i].node != null &&
  100. !string.IsNullOrEmpty(connections[i].fieldName) &&
  101. connections[i].node.GetPort(connections[i].fieldName) != null)
  102. continue;
  103. connections.RemoveAt(i);
  104. }
  105. }
  106. /// <summary> Return the output value of this node through its parent nodes GetValue override method. </summary>
  107. /// <returns> <see cref="Node.GetValue(NodePort)"/> </returns>
  108. public object GetOutputValue() {
  109. if (direction == IO.Input) return null;
  110. return node.GetValue(this);
  111. }
  112. /// <summary> Return the output value of the first connected port. Returns null if none found or invalid.</summary>
  113. /// <returns> <see cref="NodePort.GetOutputValue"/> </returns>
  114. public object GetInputValue() {
  115. NodePort connectedPort = Connection;
  116. if (connectedPort == null) return null;
  117. return connectedPort.GetOutputValue();
  118. }
  119. /// <summary> Return the output values of all connected ports. </summary>
  120. /// <returns> <see cref="NodePort.GetOutputValue"/> </returns>
  121. public object[] GetInputValues() {
  122. object[] objs = new object[ConnectionCount];
  123. for (int i = 0; i < ConnectionCount; i++) {
  124. NodePort connectedPort = connections[i].Port;
  125. if (connectedPort == null) { // if we happen to find a null port, remove it and look again
  126. connections.RemoveAt(i);
  127. i--;
  128. continue;
  129. }
  130. objs[i] = connectedPort.GetOutputValue();
  131. }
  132. return objs;
  133. }
  134. /// <summary> Return the output value of the first connected port. Returns null if none found or invalid. </summary>
  135. /// <returns> <see cref="NodePort.GetOutputValue"/> </returns>
  136. public T GetInputValue<T>() {
  137. object obj = GetInputValue();
  138. return obj is T ? (T) obj : default(T);
  139. }
  140. /// <summary> Return the output values of all connected ports. </summary>
  141. /// <returns> <see cref="NodePort.GetOutputValue"/> </returns>
  142. public T[] GetInputValues<T>() {
  143. object[] objs = GetInputValues();
  144. T[] ts = new T[objs.Length];
  145. for (int i = 0; i < objs.Length; i++) {
  146. if (objs[i] is T) ts[i] = (T) objs[i];
  147. }
  148. return ts;
  149. }
  150. /// <summary> Return true if port is connected and has a valid input. </summary>
  151. /// <returns> <see cref="NodePort.GetOutputValue"/> </returns>
  152. public bool TryGetInputValue<T>(out T value) {
  153. object obj = GetInputValue();
  154. if (obj is T) {
  155. value = (T) obj;
  156. return true;
  157. } else {
  158. value = default(T);
  159. return false;
  160. }
  161. }
  162. /// <summary> Return the sum of all inputs. </summary>
  163. /// <returns> <see cref="NodePort.GetOutputValue"/> </returns>
  164. public float GetInputSum(float fallback) {
  165. object[] objs = GetInputValues();
  166. if (objs.Length == 0) return fallback;
  167. float result = 0;
  168. for (int i = 0; i < objs.Length; i++) {
  169. if (objs[i] is float) result += (float) objs[i];
  170. }
  171. return result;
  172. }
  173. /// <summary> Return the sum of all inputs. </summary>
  174. /// <returns> <see cref="NodePort.GetOutputValue"/> </returns>
  175. public int GetInputSum(int fallback) {
  176. object[] objs = GetInputValues();
  177. if (objs.Length == 0) return fallback;
  178. int result = 0;
  179. for (int i = 0; i < objs.Length; i++) {
  180. if (objs[i] is int) result += (int) objs[i];
  181. }
  182. return result;
  183. }
  184. /// <summary> Connect this <see cref="NodePort"/> to another </summary>
  185. /// <param name="port">The <see cref="NodePort"/> to connect to</param>
  186. public void Connect(NodePort port) {
  187. if (connections == null) connections = new List<PortConnection>();
  188. if (port == null) { Debug.LogWarning("Cannot connect to null port"); return; }
  189. if (port == this) { Debug.LogWarning("Cannot connect port to self."); return; }
  190. if (IsConnectedTo(port)) { Debug.LogWarning("Port already connected. "); return; }
  191. if (direction == port.direction) { Debug.LogWarning("Cannot connect two " + (direction == IO.Input ? "input" : "output") + " connections"); return; }
  192. #if UNITY_EDITOR
  193. UnityEditor.Undo.RecordObject(node, "Connect Port");
  194. UnityEditor.Undo.RecordObject(port.node, "Connect Port");
  195. #endif
  196. if (port.connectionType == Node.ConnectionType.Override && port.ConnectionCount != 0) { port.ClearConnections(); }
  197. if (connectionType == Node.ConnectionType.Override && ConnectionCount != 0) { ClearConnections(); }
  198. connections.Add(new PortConnection(port));
  199. if (port.connections == null) port.connections = new List<PortConnection>();
  200. if (!port.IsConnectedTo(this)) port.connections.Add(new PortConnection(this));
  201. node.OnCreateConnection(this, port);
  202. port.node.OnCreateConnection(this, port);
  203. }
  204. public List<NodePort> GetConnections() {
  205. List<NodePort> result = new List<NodePort>();
  206. for (int i = 0; i < connections.Count; i++) {
  207. NodePort port = GetConnection(i);
  208. if (port != null) result.Add(port);
  209. }
  210. return result;
  211. }
  212. public NodePort GetConnection(int i) {
  213. //If the connection is broken for some reason, remove it.
  214. if (connections[i].node == null || string.IsNullOrEmpty(connections[i].fieldName)) {
  215. connections.RemoveAt(i);
  216. return null;
  217. }
  218. NodePort port = connections[i].node.GetPort(connections[i].fieldName);
  219. if (port == null) {
  220. connections.RemoveAt(i);
  221. return null;
  222. }
  223. return port;
  224. }
  225. /// <summary> Get index of the connection connecting this and specified ports </summary>
  226. public int GetConnectionIndex(NodePort port) {
  227. for (int i = 0; i < ConnectionCount; i++) {
  228. if (connections[i].Port == port) return i;
  229. }
  230. return -1;
  231. }
  232. public bool IsConnectedTo(NodePort port) {
  233. for (int i = 0; i < connections.Count; i++) {
  234. if (connections[i].Port == port) return true;
  235. }
  236. return false;
  237. }
  238. /// <summary> Returns true if this port can connect to specified port </summary>
  239. public bool CanConnectTo(NodePort port) {
  240. // Figure out which is input and which is output
  241. NodePort input = null, output = null;
  242. if (IsInput) input = this;
  243. else output = this;
  244. if (port.IsInput) input = port;
  245. else output = port;
  246. // If there isn't one of each, they can't connect
  247. if (input == null || output == null) return false;
  248. // Check input type constraints
  249. #if UNITY_EDITOR
  250. // Debug.Log(input.node.GetType().ToString()+"___"+output.node.GetType().ToString());
  251. if (!IsConflict( input,output))
  252. {
  253. return false;
  254. }
  255. #endif
  256. if (input.typeConstraint == XNode.Node.TypeConstraint.Inherited && !input.ValueType.IsAssignableFrom(output.ValueType)) return false;
  257. if (input.typeConstraint == XNode.Node.TypeConstraint.Strict && input.ValueType != output.ValueType) return false;
  258. if (input.typeConstraint == XNode.Node.TypeConstraint.InheritedInverse && !output.ValueType.IsAssignableFrom(input.ValueType)) return false;
  259. // Check output type constraints
  260. if (output.typeConstraint == XNode.Node.TypeConstraint.Inherited && !input.ValueType.IsAssignableFrom(output.ValueType)) return false;
  261. if (output.typeConstraint == XNode.Node.TypeConstraint.Strict && input.ValueType != output.ValueType) return false;
  262. if (output.typeConstraint == XNode.Node.TypeConstraint.InheritedInverse && !output.ValueType.IsAssignableFrom(input.ValueType)) return false;
  263. // Success
  264. return true;
  265. }
  266. #if UNITY_EDITOR
  267. private bool IsConflict( NodePort input , NodePort output )
  268. {
  269. XNode.Node.CreateNodeMenuAttribute cma;
  270. GetAttrib(input.node.GetType(), out cma);
  271. if (cma != null && cma.isServerRecord)
  272. {
  273. List<NodePort> nodes= output.GetConnections();
  274. for (int i = 0; i < nodes.Count; i++)
  275. {
  276. XNode.Node.CreateNodeMenuAttribute cma2;
  277. if (GetAttrib(nodes[i].node.GetType(), out cma2))
  278. {
  279. if (cma2.isServerRecord)
  280. {
  281. return false;
  282. }
  283. }
  284. }
  285. }
  286. return true;
  287. }
  288. public static bool GetAttrib<T>(Type classType, out T attribOut) where T : Attribute {
  289. object[] attribs = classType.GetCustomAttributes(typeof(T), false);
  290. return GetAttrib(attribs, out attribOut);
  291. }
  292. public static bool GetAttrib<T>(object[] attribs, out T attribOut) where T : Attribute {
  293. for (int i = 0; i < attribs.Length; i++) {
  294. if (attribs[i] is T) {
  295. attribOut = attribs[i] as T;
  296. return true;
  297. }
  298. }
  299. attribOut = null;
  300. return false;
  301. }
  302. #endif
  303. /// <summary> Disconnect this port from another port </summary>
  304. public void Disconnect(NodePort port) {
  305. // Remove this ports connection to the other
  306. for (int i = connections.Count - 1; i >= 0; i--) {
  307. if (connections[i].Port == port) {
  308. connections.RemoveAt(i);
  309. }
  310. }
  311. if (port != null) {
  312. // Remove the other ports connection to this port
  313. for (int i = 0; i < port.connections.Count; i++) {
  314. if (port.connections[i].Port == this) {
  315. port.connections.RemoveAt(i);
  316. }
  317. }
  318. }
  319. // Trigger OnRemoveConnection
  320. node.OnRemoveConnection(this);
  321. if (port != null) port.node.OnRemoveConnection(port);
  322. }
  323. /// <summary> Disconnect this port from another port </summary>
  324. public void Disconnect(int i) {
  325. // Remove the other ports connection to this port
  326. NodePort otherPort = connections[i].Port;
  327. if (otherPort != null) {
  328. for (int k = 0; k < otherPort.connections.Count; k++) {
  329. if (otherPort.connections[k].Port == this) {
  330. otherPort.connections.RemoveAt(i);
  331. }
  332. }
  333. }
  334. // Remove this ports connection to the other
  335. connections.RemoveAt(i);
  336. // Trigger OnRemoveConnection
  337. node.OnRemoveConnection(this);
  338. if (otherPort != null) otherPort.node.OnRemoveConnection(otherPort);
  339. }
  340. public void ClearConnections() {
  341. while (connections.Count > 0) {
  342. Disconnect(connections[0].Port);
  343. }
  344. }
  345. /// <summary> Get reroute points for a given connection. This is used for organization </summary>
  346. public List<Vector2> GetReroutePoints(int index) {
  347. return connections[index].reroutePoints;
  348. }
  349. /// <summary> Swap connections with another node </summary>
  350. public void SwapConnections(NodePort targetPort) {
  351. int aConnectionCount = connections.Count;
  352. int bConnectionCount = targetPort.connections.Count;
  353. List<NodePort> portConnections = new List<NodePort>();
  354. List<NodePort> targetPortConnections = new List<NodePort>();
  355. // Cache port connections
  356. for (int i = 0; i < aConnectionCount; i++)
  357. portConnections.Add(connections[i].Port);
  358. // Cache target port connections
  359. for (int i = 0; i < bConnectionCount; i++)
  360. targetPortConnections.Add(targetPort.connections[i].Port);
  361. ClearConnections();
  362. targetPort.ClearConnections();
  363. // Add port connections to targetPort
  364. for (int i = 0; i < portConnections.Count; i++)
  365. targetPort.Connect(portConnections[i]);
  366. // Add target port connections to this one
  367. for (int i = 0; i < targetPortConnections.Count; i++)
  368. Connect(targetPortConnections[i]);
  369. }
  370. /// <summary> Copy all connections pointing to a node and add them to this one </summary>
  371. public void AddConnections(NodePort targetPort) {
  372. int connectionCount = targetPort.ConnectionCount;
  373. for (int i = 0; i < connectionCount; i++) {
  374. PortConnection connection = targetPort.connections[i];
  375. NodePort otherPort = connection.Port;
  376. Connect(otherPort);
  377. }
  378. }
  379. /// <summary> Move all connections pointing to this node, to another node </summary>
  380. public void MoveConnections(NodePort targetPort) {
  381. int connectionCount = connections.Count;
  382. // Add connections to target port
  383. for (int i = 0; i < connectionCount; i++) {
  384. PortConnection connection = targetPort.connections[i];
  385. NodePort otherPort = connection.Port;
  386. Connect(otherPort);
  387. }
  388. ClearConnections();
  389. }
  390. /// <summary> Swap connected nodes from the old list with nodes from the new list </summary>
  391. public void Redirect(List<Node> oldNodes, List<Node> newNodes) {
  392. foreach (PortConnection connection in connections) {
  393. int index = oldNodes.IndexOf(connection.node);
  394. if (index >= 0) connection.node = newNodes[index];
  395. }
  396. }
  397. [Serializable]
  398. private class PortConnection {
  399. [SerializeField] public string fieldName;
  400. [SerializeField] public Node node;
  401. public NodePort Port { get { return port != null ? port : port = GetPort(); } }
  402. [NonSerialized] private NodePort port;
  403. /// <summary> Extra connection path points for organization </summary>
  404. [SerializeField] public List<Vector2> reroutePoints = new List<Vector2>();
  405. public PortConnection(NodePort port) {
  406. this.port = port;
  407. node = port.node;
  408. fieldName = port.fieldName;
  409. }
  410. /// <summary> Returns the port that this <see cref="PortConnection"/> points to </summary>
  411. private NodePort GetPort() {
  412. if (node == null || string.IsNullOrEmpty(fieldName)) return null;
  413. return node.GetPort(fieldName);
  414. }
  415. }
  416. }
  417. }