using System;
using System.Collections.Generic;
using UnityEngine;
using Object = UnityEngine.Object;
namespace XNode {
///
/// Base class for all nodes
///
///
/// Classes extending this class will be considered as valid nodes by xNode.
///
/// [System.Serializable]
/// public class Adder : Node {
/// [Input] public float a;
/// [Input] public float b;
/// [Output] public float result;
///
/// // GetValue should be overridden to return a value for any specified output port
/// public override object GetValue(NodePort port) {
/// return a + b;
/// }
/// }
///
///
[Serializable]
public abstract class Node : ScriptableObject, XNode.INode {
/// Iterate over all ports on this node.
public IEnumerable Ports { get { foreach (NodePort port in ports.Values) yield return port; } }
/// Iterate over all outputs on this node.
public IEnumerable Outputs { get { foreach (NodePort port in Ports) { if (port.IsOutput) yield return port; } } }
/// Iterate over all inputs on this node.
public IEnumerable Inputs { get { foreach (NodePort port in Ports) { if (port.IsInput) yield return port; } } }
/// Iterate over all dynamic ports on this node.
public IEnumerable DynamicPorts { get { foreach (NodePort port in Ports) { if (port.IsDynamic) yield return port; } } }
/// Iterate over all dynamic outputs on this node.
public IEnumerable DynamicOutputs { get { foreach (NodePort port in Ports) { if (port.IsDynamic && port.IsOutput) yield return port; } } }
/// Iterate over all dynamic inputs on this node.
public IEnumerable DynamicInputs { get { foreach (NodePort port in Ports) { if (port.IsDynamic && port.IsInput) yield return port; } } }
/// Parent
[SerializeField] public NodeGraph graph;
/// Position on the
[SerializeField] public Vector2 position;
/// It is recommended not to modify these at hand. Instead, see and
[SerializeField] private NodePortDictionary ports = new NodePortDictionary();
/// Used during node instantiation to fix null/misconfigured graph during OnEnable/Init. Set it before instantiating a node. Will automatically be unset during OnEnable
public static NodeGraph graphHotfix;
private void Awake() {
if (graphHotfix != null) graph = graphHotfix;
graphHotfix = null;
}
/// Update static ports to reflect class fields. This happens automatically on enable.
public void UpdateStaticPorts() {
NodeDataCache.UpdatePorts(this, ports);
}
/// Checks all connections for invalid references, and removes them.
public void VerifyConnections() {
foreach (NodePort port in Ports) port.VerifyConnections();
}
#region Dynamic Ports
/// Convenience function.
///
///
public NodePort AddDynamicInput(Type type, ConnectionType connectionType = ConnectionType.Multiple, TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) {
return (NodePort) ((INode) this).AddDynamicPort(type, IO.Input, connectionType, typeConstraint, fieldName);
}
/// Convenience function.
///
///
public NodePort AddDynamicOutput(Type type, ConnectionType connectionType = ConnectionType.Multiple, TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) {
return (NodePort) ((INode) this).AddDynamicPort(type, IO.Output, connectionType, typeConstraint, fieldName);
}
/// Remove an dynamic port from the node
public void RemoveDynamicPort(string fieldName) {
NodePort dynamicPort = GetPort(fieldName);
if (dynamicPort == null) throw new ArgumentException("port " + fieldName + " doesn't exist");
RemoveDynamicPort(GetPort(fieldName));
}
/// Remove an dynamic port from the node
public void RemoveDynamicPort(NodePort port) {
if (port == null) throw new ArgumentNullException("port");
else if (port.IsStatic) throw new ArgumentException("cannot remove static port");
port.ClearConnections();
ports.Remove(port.fieldName);
}
/// Removes all dynamic ports from the node
[ContextMenu("Clear Dynamic Ports")]
public void ClearDynamicPorts() {
List dynamicPorts = new List(DynamicPorts);
foreach (NodePort port in dynamicPorts) {
RemoveDynamicPort(port);
}
}
#endregion
#region Ports
/// Returns output port which matches fieldName
public NodePort GetOutputPort(string fieldName) {
NodePort port = GetPort(fieldName);
if (port == null || port.direction != IO.Output) return null;
else return port;
}
/// Returns input port which matches fieldName
public NodePort GetInputPort(string fieldName) {
NodePort port = GetPort(fieldName);
if (port == null || port.direction != IO.Input) return null;
else return port;
}
/// Returns port which matches fieldName
public NodePort GetPort(string fieldName) {
NodePort port;
if (ports.TryGetValue(fieldName, out port)) return port;
else return null;
}
public bool HasPort(string fieldName) {
return ports.ContainsKey(fieldName);
}
#endregion
#region Inputs/Outputs
/// Return input value for a specified port. Returns fallback value if no ports are connected
/// Field name of requested input port
/// If no ports are connected, this value will be returned
public T GetInputValue(string fieldName, T fallback = default(T)) {
NodePort port = GetPort(fieldName);
if (port != null && port.IsConnected) return port.GetInputValue();
else return fallback;
}
/// Return all input values for a specified port. Returns fallback value if no ports are connected
/// Field name of requested input port
/// If no ports are connected, this value will be returned
public T[] GetInputValues(string fieldName, params T[] fallback) {
NodePort port = GetPort(fieldName);
if (port != null && port.IsConnected) return port.GetInputValues();
else return fallback;
}
/// Returns a value based on requested port output. Should be overridden in all derived nodes with outputs.
/// The requested port.
public virtual object GetValue(NodePort port) {
Debug.LogWarning("No GetValue(NodePort port) override defined for " + GetType());
return null;
}
#endregion
/// Called after a connection between two s is created
/// Output Input
public virtual void OnCreateConnection(NodePort from, NodePort to) { }
/// Called after a connection is removed from this port
/// Output or Input
public virtual void OnRemoveConnection(NodePort port) { }
/// Disconnect everything from this node
public void ClearConnections() {
foreach (NodePort port in Ports) port.ClearConnections();
}
[Serializable] private class NodePortDictionary : Dictionary, ISerializationCallbackReceiver {
[SerializeField] private List keys = new List();
[SerializeField] private List values = new List();
public void OnBeforeSerialize() {
keys.Clear();
values.Clear();
foreach (KeyValuePair pair in this) {
keys.Add(pair.Key);
values.Add(pair.Value);
}
}
public void OnAfterDeserialize() {
this.Clear();
if (keys.Count != values.Count)
throw new System.Exception("there are " + keys.Count + " keys and " + values.Count + " values after deserialization. Make sure that both key and value types are serializable.");
for (int i = 0; i < keys.Count; i++)
this.Add(keys[i], values[i]);
}
}
#region Interface implementation
string INode.Name { get { return name; } set { name = value; } }
INodeGraph INode.Graph { get { return graph; } }
Vector2 INode.Position { get { return position; } set { position = value; } }
Object INode.Object { get { return this; } }
/// Add a dynamic, serialized port to this node.
///
///
INodePort INode.AddDynamicPort(Type type, IO direction, ConnectionType connectionType, TypeConstraint typeConstraint, string fieldName) {
if (fieldName == null) {
fieldName = "dynamicInput_0";
int i = 0;
while (HasPort(fieldName)) fieldName = "dynamicInput_" + (++i);
} else if (HasPort(fieldName)) {
Debug.LogWarning("Port '" + fieldName + "' already exists in " + name, this);
return ports[fieldName];
}
NodePort port = new NodePort(fieldName, type, direction, connectionType, typeConstraint, this);
ports.Add(fieldName, port);
return port;
}
#endregion
}
}