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; } internal set { _direction = value; } } public Node.ConnectionType connectionType { get { return _connectionType; } internal set { _connectionType = value; } } public Node.TypeConstraint typeConstraint { get { return _typeConstraint; } internal set { _typeConstraint = value; } } /// 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 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; 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; _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; } } } /// 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; this.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(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("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 UnityEditor.Undo.RecordObject(node, "Connect Port"); UnityEditor.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() { List 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 UNITY_EDITOR // Debug.Log(input.node.GetType().ToString()+"___"+output.node.GetType().ToString()); if (!IsConflict( input,output)) { return false; } #endif if (input.typeConstraint == XNode.Node.TypeConstraint.Inherited && !input.ValueType.IsAssignableFrom(output.ValueType)) return false; if (input.typeConstraint == XNode.Node.TypeConstraint.Strict && input.ValueType != output.ValueType) return false; if (input.typeConstraint == XNode.Node.TypeConstraint.InheritedInverse && !output.ValueType.IsAssignableFrom(input.ValueType)) return false; // Check output type constraints if (output.typeConstraint == XNode.Node.TypeConstraint.Inherited && !input.ValueType.IsAssignableFrom(output.ValueType)) return false; if (output.typeConstraint == XNode.Node.TypeConstraint.Strict && input.ValueType != output.ValueType) return false; if (output.typeConstraint == XNode.Node.TypeConstraint.InheritedInverse && !output.ValueType.IsAssignableFrom(input.ValueType)) return false; // Success return true; } #if UNITY_EDITOR private bool IsConflict( NodePort input , NodePort output ) { XNode.Node.CreateNodeMenuAttribute cma; GetAttrib(input.node.GetType(), out cma); if (cma != null && cma.isServerRecord) { List nodes= output.GetConnections(); for (int i = 0; i < nodes.Count; i++) { XNode.Node.CreateNodeMenuAttribute cma2; if (GetAttrib(nodes[i].node.GetType(), out cma2)) { if (cma2.isServerRecord) { return false; } } } } return true; } public static bool GetAttrib(Type classType, out T attribOut) where T : Attribute { object[] attribs = classType.GetCustomAttributes(typeof(T), false); return GetAttrib(attribs, out attribOut); } public static bool GetAttrib(object[] attribs, out T attribOut) where T : Attribute { for (int i = 0; i < attribs.Length; i++) { if (attribs[i] is T) { attribOut = attribs[i] as T; return true; } } attribOut = null; return false; } #endif /// 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); } /// 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) { for (int k = 0; k < otherPort.connections.Count; k++) { if (otherPort.connections[k].Port == this) { otherPort.connections.RemoveAt(i); } } } // 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; List portConnections = new List(); List 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 { 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); } } } }