using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Reflection; using UnityEngine; using UnityEngine.Serialization; namespace XNode { [Serializable] public class NodePort : ISerializationCallbackReceiver { public enum IO { Input, Output } public int ConnectionCount { get { return connectionCache.Count; } } /// Return the first non-null connection public NodePort Connection { get { for (int i = 0; i < connectionCache.Count; i++) { if (connectionCache[i] != null) return connectionCache[i]; } return null; } } public IO direction { get { return _direction; } } public Node.ConnectionType connectionType { get { return _connectionType; } } public Node.TypeConstraint typeConstraint { get { return _typeConstraint; } } public ReadOnlyCollection Connections { get { VerifyConnectionCache(); return _Connections; } } private ReadOnlyCollection _Connections; /// Is this port connected to anytihng? public bool IsConnected { get { return connectionCache.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; [NonSerialized] readonly private List connectionCache = new List(); [NonSerialized] readonly private Dictionary> rerouteCache = new Dictionary>(); [NonSerialized] private bool initializedCache; private NodePort() { connectionCache = new List(); rerouteCache = new Dictionary>(); } /// Construct a static targetless nodeport. Used as a template. public NodePort(FieldInfo fieldInfo) { connectionCache = new List(); rerouteCache = new Dictionary>(); _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; } } } /// Copy a nodePort but assign it to another node. public NodePort(NodePort nodePort, Node node) { connectionCache = new List(); rerouteCache = new Dictionary>(); _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) { Debug.Log("Create"); _fieldName = fieldName; this.ValueType = type; _direction = direction; _node = node; _dynamic = true; _connectionType = connectionType; _typeConstraint = typeConstraint; } private void VerifyConnectionCache() { if (!initializedCache) { connectionCache.Clear(); rerouteCache.Clear(); for (int i = 0; i < connections.Count; i++) { NodePort port = connections[i].GetPort(); connectionCache.Add(port); rerouteCache.Add(port, connections[i].reroutePoints); } _Connections = new ReadOnlyCollection(connectionCache); initializedCache = true; } } void ISerializationCallbackReceiver.OnBeforeSerialize() { if (initializedCache) { connections = new List(); for (int i = 0; i < connectionCache.Count; i++) { List reroutes; if (rerouteCache.TryGetValue(connectionCache[i], out reroutes)) { connections.Add(new PortConnection(connectionCache[i], reroutes)); } else { connections.Add(new PortConnection(connectionCache[i])); } } } } void ISerializationCallbackReceiver.OnAfterDeserialize() { } /// Checks all connections for invalid references, and removes them. public void VerifyConnections() { VerifyConnectionCache(); for (int i = connectionCache.Count - 1; i >= 0; i--) { if (connectionCache[i].node != null && !string.IsNullOrEmpty(connectionCache[i].fieldName) && connectionCache[i].node.GetPort(connectionCache[i].fieldName) != null) continue; connectionCache.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() { VerifyConnectionCache(); object[] objs = new object[ConnectionCount]; for (int i = 0; i < ConnectionCount; i++) { NodePort connectedPort = connectionCache[i]; if (connectedPort == null) { // if we happen to find a null port, remove it and look again connectionCache.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) { VerifyConnectionCache(); 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 (port.connectionType == Node.ConnectionType.Override && port.ConnectionCount != 0) { port.ClearConnections(); } if (connectionType == Node.ConnectionType.Override && ConnectionCount != 0) { ClearConnections(); } connectionCache.Add(port); port.connectionCache.Add(this); node.OnCreateConnection(this, port); port.node.OnCreateConnection(this, port); } [Obsolete("Use Connections property instead")] public List GetConnections() { VerifyConnectionCache(); List result = new List(); for (int i = 0; i < connectionCache.Count; i++) { NodePort port = connectionCache[i]; result.Add(port); } return result; } [Obsolete("Use Connections[i] instead")] public NodePort GetConnection(int i) { VerifyConnectionCache(); return connectionCache[i]; } [Obsolete("Use Connections.IndexOf(port) instead")] /// Get index of the connection connecting this and specified ports public int GetConnectionIndex(NodePort port) { VerifyConnectionCache(); return connectionCache.IndexOf(port); } public bool IsConnectedTo(NodePort port) { VerifyConnectionCache(); return connectionCache.Contains(port); } /// 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 type constraints 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; // Success return true; } /// Disconnect this port from another port public void Disconnect(NodePort port) { VerifyConnectionCache(); // Remove this ports connection to the other for (int i = connectionCache.Count - 1; i >= 0; i--) { connectionCache.Remove(port); } if (port != null) { // Remove the other ports connection to this port for (int i = 0; i < port.connectionCache.Count; i++) { port.connectionCache.Remove(this); } } else Debug.LogWarning("Trying to disconnect a null port"); // Trigger OnRemoveConnection node.OnRemoveConnection(this); if (port != null) port.node.OnRemoveConnection(port); } /// Disconnect this port from another port public void Disconnect(int i) { Disconnect(connectionCache[i]); } /// Disconnect all ports from this port public void ClearConnections() { for (int i = connectionCache.Count - 1; i >= 0; i--) { Disconnect(connectionCache[i]); } } /// Get reroute points for a given connection. This is used for organization public List GetReroutePoints(NodePort port) { VerifyConnectionCache(); if (!rerouteCache.ContainsKey(port)) { List reroutes = new List(); rerouteCache.Add(port, reroutes); } return rerouteCache[port]; } /// Get reroute points for a given connection. This is used for organization public List GetReroutePoints(int index) { return GetReroutePoints(connectionCache[index]); } /// Swap connections with another node public void SwapConnections(NodePort targetPort) { VerifyConnectionCache(); int aConnectionCount = connectionCache.Count; int bConnectionCount = targetPort.connectionCache.Count; List portConnections = new List(); List targetPortConnections = new List(); // Cache port connections for (int i = 0; i < aConnectionCount; i++) portConnections.Add(connectionCache[i]); // Cache target port connections for (int i = 0; i < bConnectionCount; i++) targetPortConnections.Add(targetPort.connectionCache[i]); 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) { VerifyConnectionCache(); int connectionCount = targetPort.ConnectionCount; for (int i = 0; i < connectionCount; i++) { NodePort otherPort = targetPort.connectionCache[i]; Connect(otherPort); } } /// Move all connections pointing to this node, to another node public void MoveConnections(NodePort targetPort) { VerifyConnectionCache(); int connectionCount = connectionCache.Count; // Add connections to target port for (int i = 0; i < connectionCount; i++) { NodePort otherPort = targetPort.connectionCache[i]; Connect(otherPort); } ClearConnections(); } /// Swap connected nodes from the old list with nodes from the new list public void Redirect(List oldNodes, List newNodes) { VerifyConnectionCache(); foreach (NodePort port in connectionCache) { int index = oldNodes.IndexOf(port._node); if (index >= 0) port._node = newNodes[index]; } } [Serializable] private class PortConnection { [SerializeField] private string fieldName; [SerializeField] private Node node; /// Extra connection path points for organization [SerializeField] public List reroutePoints = new List(); public PortConnection(NodePort port) { node = port.node; fieldName = port.fieldName; } public PortConnection(NodePort port, List reroutePoints) { node = port.node; fieldName = port.fieldName; this.reroutePoints = reroutePoints; } public NodePort GetPort() { return node.GetPort(fieldName); } } } }