using System; using System.Collections.Generic; using System.Reflection; using UnityEngine; namespace XNode { [Serializable] public class NodePort { public enum IO { Input, Output } public int ConnectionCount { get { return connections.Count; } } /// Return the first non-null connection public NodePort Connection { get { for (int i = 0; i < connections.Count; i++) { if (connections[i] != null) return connections[i].Port; } return null; } } public IO direction { get { return _direction; } } public Node.ConnectionType connectionType { get { return _connectionType; } } /// Is this port connected to anytihng? public bool IsConnected { get { return connections.Count != 0; } } public bool IsInput { get { return direction == IO.Input; } } public bool IsOutput { get { return direction == IO.Output; } } public string fieldName { get { return _fieldName; } } public Node node { get { return _node; } } public bool IsDynamic { get { return _dynamic; } } public bool IsStatic { get { return !_dynamic; } } public Type ValueType { get { if (valueType == null && !string.IsNullOrEmpty(_typeQualifiedName)) valueType = Type.GetType(_typeQualifiedName, false); return valueType; } set { valueType = value; if (value != null) _typeQualifiedName = value.AssemblyQualifiedName; } } private Type valueType; [SerializeField] private string _fieldName; [SerializeField] private Node _node; [SerializeField] private string _typeQualifiedName; [SerializeField] private List connections = new List(); [SerializeField] private IO _direction; [SerializeField] private Node.ConnectionType _connectionType; [SerializeField] private bool _dynamic; /// Construct a static targetless nodeport. Used as a template. public NodePort(FieldInfo fieldInfo) { _fieldName = fieldInfo.Name; ValueType = fieldInfo.FieldType; _dynamic = false; var attribs = fieldInfo.GetCustomAttributes(false); for (int i = 0; i < attribs.Length; i++) { if (attribs[i] is Node.InputAttribute) { _direction = IO.Input; _connectionType = (attribs[i] as Node.InputAttribute).connectionType; } else if (attribs[i] is Node.OutputAttribute) { _direction = IO.Output; _connectionType = (attribs[i] as Node.OutputAttribute).connectionType; } } } /// Copy a nodePort but assign it to another node. public NodePort(NodePort nodePort, Node node) { _fieldName = nodePort._fieldName; ValueType = nodePort.valueType; _direction = nodePort.direction; _dynamic = nodePort._dynamic; _connectionType = nodePort._connectionType; _node = node; } /// Construct a dynamic port. Dynamic ports are not forgotten on reimport, and is ideal for runtime-created ports. public NodePort(string fieldName, Type type, IO direction, Node.ConnectionType connectionType, Node node) { _fieldName = fieldName; this.ValueType = type; _direction = direction; _node = node; _dynamic = true; _connectionType = connectionType; } /// Checks all connections for invalid references, and removes them. public void VerifyConnections() { for (int i = connections.Count - 1; i >= 0; i--) { if (connections[i].node != null && !string.IsNullOrEmpty(connections[i].fieldName) && connections[i].node.GetPort(connections[i].fieldName) != null) continue; connections.RemoveAt(i); } } /// Return the output value of this node through its parent nodes GetValue override method. /// public object GetOutputValue() { if (direction == IO.Input) return null; return node.GetValue(this); } /// Return the output value of the first connected port. Returns null if none found or invalid. /// public object GetInputValue() { NodePort connectedPort = Connection; if (connectedPort == null) return null; return connectedPort.GetOutputValue(); } /// Return the output values of all connected ports. /// public object[] GetInputValues() { object[] objs = new object[ConnectionCount]; for (int i = 0; i < ConnectionCount; i++) { NodePort connectedPort = connections[i].Port; if (connectedPort == null) { // if we happen to find a null port, remove it and look again connections.RemoveAt(i); i--; continue; } objs[i] = connectedPort.GetOutputValue(); } return objs; } /// Return the output value of the first connected port. Returns null if none found or invalid. /// public T GetInputValue() { object obj = GetInputValue(); return obj is T ? (T) obj : default(T); } /// Return the output values of all connected ports. /// public T[] GetInputValues() { object[] objs = GetInputValues(); T[] ts = new T[objs.Length]; for (int i = 0; i < objs.Length; i++) { if (objs[i] is T) ts[i] = (T) objs[i]; } return ts; } /// Return true if port is connected and has a valid input. /// public bool TryGetInputValue(out T value) { object obj = GetInputValue(); if (obj is T) { value = (T) obj; return true; } else { value = default(T); return false; } } /// Return the sum of all inputs. /// public float GetInputSum(float fallback) { object[] objs = GetInputValues(); if (objs.Length == 0) return fallback; float result = 0; for (int i = 0; i < objs.Length; i++) { if (objs[i] is float) result += (float) objs[i]; } return result; } /// Return the sum of all inputs. /// public int GetInputSum(int fallback) { object[] objs = GetInputValues(); if (objs.Length == 0) return fallback; int result = 0; for (int i = 0; i < objs.Length; i++) { if (objs[i] is int) result += (int) objs[i]; } return result; } /// Connect this to another /// The to connect to public void Connect(NodePort port) { if (connections == null) connections = new List(); if (port == null) { Debug.LogWarning("Cannot connect to null port"); return; } if (port == this) { Debug.LogWarning("Attempting to connect port to self."); return; } if (IsConnectedTo(port)) { Debug.LogWarning("Port already connected. "); return; } if (direction == port.direction) { Debug.LogWarning("Cannot connect two " + (direction == IO.Input ? "input" : "output") + " connections"); return; } if (port.connectionType == Node.ConnectionType.Override && port.ConnectionCount != 0) { port.ClearConnections(); } if (connectionType == Node.ConnectionType.Override && ConnectionCount != 0) { ClearConnections(); } connections.Add(new PortConnection(port)); if (port.connections == null) port.connections = new List(); if (!port.IsConnectedTo(this)) port.connections.Add(new PortConnection(this)); node.OnCreateConnection(this, port); port.node.OnCreateConnection(this, port); } public NodePort GetConnection(int i) { //If the connection is broken for some reason, remove it. if (connections[i].node == null || string.IsNullOrEmpty(connections[i].fieldName)) { connections.RemoveAt(i); return null; } NodePort port = connections[i].node.GetPort(connections[i].fieldName); if (port == null) { connections.RemoveAt(i); return null; } return port; } /// Get index of the connection connecting this and specified ports public int GetConnectionIndex(NodePort port) { for (int i = 0; i < ConnectionCount; i++) { if (connections[i].Port == port) return i; } return -1; } public bool IsConnectedTo(NodePort port) { for (int i = 0; i < connections.Count; i++) { if (connections[i].Port == port) return true; } return false; } /// Disconnect this port from another port public void Disconnect(NodePort port) { // Remove this ports connection to the other for (int i = connections.Count - 1; i >= 0; i--) { if (connections[i].Port == port) { connections.RemoveAt(i); } } if (port != null) { // Remove the other ports connection to this port for (int i = 0; i < port.connections.Count; i++) { if (port.connections[i].Port == this) { port.connections.RemoveAt(i); } } } // Trigger OnRemoveConnection node.OnRemoveConnection(this); if (port != null) port.node.OnRemoveConnection(port); } public void ClearConnections() { while (connections.Count > 0) { Disconnect(connections[0].Port); } } /// Get reroute points for a given connection. This is used for organization public List GetReroutePoints(int index) { return connections[index].reroutePoints; } /// Swap connected nodes from the old list with nodes from the new list public void Redirect(List oldNodes, List newNodes) { foreach (PortConnection connection in connections) { int index = oldNodes.IndexOf(connection.node); if (index >= 0) connection.node = newNodes[index]; } } [Serializable] private class PortConnection { [SerializeField] public string fieldName; [SerializeField] public Node node; public NodePort Port { get { return port != null ? port : port = GetPort(); } } [NonSerialized] private NodePort port; /// Extra connection path points for organization [SerializeField] public List reroutePoints = new List(); public PortConnection(NodePort port) { this.port = port; node = port.node; fieldName = port.fieldName; } /// Returns the port that this points to private NodePort GetPort() { if (node == null || string.IsNullOrEmpty(fieldName)) return null; return node.GetPort(fieldName); } } } }