using System; using System.Collections.Generic; using System.Reflection; using Attributes; using UnityEditor; using UnityEngine; namespace XNode { [Serializable] public class NodePort { public enum IO { Input, Output } public int ConnectionCount => 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 => _direction; internal set => _direction = value; } public Node.ConnectionType connectionType { get => _connectionType; internal set => _connectionType = value; } public Node.TypeConstraint typeConstraint { get => _typeConstraint; internal set => _typeConstraint = value; } /// Is this port connected to anytihng? public bool IsConnected => connections.Count != 0; public bool IsInput => direction == IO.Input; public bool IsOutput => direction == IO.Output; public string fieldName => _fieldName; public Node node => _node; public bool IsDynamic => _dynamic; public bool IsStatic => !_dynamic; public Type ValueType { get { if (valueType == null && !string.IsNullOrEmpty(_typeQualifiedName)) { valueType = Type.GetType(_typeQualifiedName, false); } return valueType; } set { if (valueType == value) { return; } valueType = value; if (value != null) { _typeQualifiedName = NodeDataCache.GetTypeQualifiedName(value); } } } 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 Node.TypeConstraint _typeConstraint; [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; object[] 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; _typeConstraint = (attribs[i] as Node.InputAttribute).typeConstraint; } else if (attribs[i] is Node.OutputAttribute) { _direction = IO.Output; _connectionType = (attribs[i] as Node.OutputAttribute).connectionType; _typeConstraint = (attribs[i] as Node.OutputAttribute).typeConstraint; } // Override ValueType of the Port if (attribs[i] is PortTypeOverrideAttribute) { ValueType = (attribs[i] as PortTypeOverrideAttribute).type; } } } /// 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; _typeConstraint = nodePort._typeConstraint; _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.TypeConstraint typeConstraint, Node node) { _fieldName = fieldName; ValueType = type; _direction = direction; _node = node; _dynamic = true; _connectionType = connectionType; _typeConstraint = typeConstraint; } /// 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; } /// Return the output values of all connected ports. /// /// /// public T[] GetInputValues() { object[] objs = GetInputValues(); var 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; } value = default; 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("Cannot 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 UNITY_EDITOR Undo.RecordObject(node, "Connect Port"); Undo.RecordObject(port.node, "Connect Port"); #endif 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 List GetConnections() { var result = new List(); for (int i = 0; i < connections.Count; i++) { NodePort port = GetConnection(i); if (port != null) { result.Add(port); } } return result; } 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; } /// Returns true if this port can connect to specified port public bool CanConnectTo(NodePort port) { // Figure out which is input and which is output NodePort input = null, output = null; if (IsInput) { input = this; } else { output = this; } if (port.IsInput) { input = port; } else { output = port; } // If there isn't one of each, they can't connect if (input == null || output == null) { return false; } // Check input type constraints if (input.typeConstraint == Node.TypeConstraint.Inherited && !input.ValueType.IsAssignableFrom(output.ValueType)) { return false; } if (input.typeConstraint == Node.TypeConstraint.Strict && input.ValueType != output.ValueType) { return false; } if (input.typeConstraint == Node.TypeConstraint.InheritedInverse && !output.ValueType.IsAssignableFrom(input.ValueType)) { return false; } if (input.typeConstraint == Node.TypeConstraint.InheritedAny && !input.ValueType.IsAssignableFrom(output.ValueType) && !output.ValueType.IsAssignableFrom(input.ValueType)) { return false; } // Check output type constraints if (output.typeConstraint == Node.TypeConstraint.Inherited && !input.ValueType.IsAssignableFrom(output.ValueType)) { return false; } if (output.typeConstraint == Node.TypeConstraint.Strict && input.ValueType != output.ValueType) { return false; } if (output.typeConstraint == Node.TypeConstraint.InheritedInverse && !output.ValueType.IsAssignableFrom(input.ValueType)) { return false; } if (output.typeConstraint == Node.TypeConstraint.InheritedAny && !input.ValueType.IsAssignableFrom(output.ValueType) && !output.ValueType.IsAssignableFrom(input.ValueType)) { return false; } // Success return true; } /// 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 from this side port port.node.OnRemoveConnection(port); } } } // Trigger OnRemoveConnection node.OnRemoveConnection(this); } /// Disconnect this port from another port public void Disconnect(int i) { // Remove the other ports connection to this port NodePort otherPort = connections[i].Port; if (otherPort != null) { otherPort.connections.RemoveAll(it => { return it.Port == this; }); } // Remove this ports connection to the other connections.RemoveAt(i); // Trigger OnRemoveConnection node.OnRemoveConnection(this); if (otherPort != null) { otherPort.node.OnRemoveConnection(otherPort); } } 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 connections with another node public void SwapConnections(NodePort targetPort) { int aConnectionCount = connections.Count; int bConnectionCount = targetPort.connections.Count; var portConnections = new List(); var targetPortConnections = new List(); // Cache port connections for (int i = 0; i < aConnectionCount; i++) { portConnections.Add(connections[i].Port); } // Cache target port connections for (int i = 0; i < bConnectionCount; i++) { targetPortConnections.Add(targetPort.connections[i].Port); } ClearConnections(); targetPort.ClearConnections(); // Add port connections to targetPort for (int i = 0; i < portConnections.Count; i++) { targetPort.Connect(portConnections[i]); } // Add target port connections to this one for (int i = 0; i < targetPortConnections.Count; i++) { Connect(targetPortConnections[i]); } } /// Copy all connections pointing to a node and add them to this one public void AddConnections(NodePort targetPort) { int connectionCount = targetPort.ConnectionCount; for (int i = 0; i < connectionCount; i++) { PortConnection connection = targetPort.connections[i]; NodePort otherPort = connection.Port; Connect(otherPort); } } /// Move all connections pointing to this node, to another node public void MoveConnections(NodePort targetPort) { int connectionCount = connections.Count; // Add connections to target port for (int i = 0; i < connectionCount; i++) { PortConnection connection = targetPort.connections[i]; NodePort otherPort = connection.Port; Connect(otherPort); } ClearConnections(); } /// 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 => 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); } } } }