using System;
using System.Collections.Generic;
using UnityEngine;
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
{
/// Used by and to determine when to display the field value associated with a
public enum ShowBackingValue
{
/// Never show the backing value
Never,
/// Show the backing value only when the port does not have any active connections
Unconnected,
/// Always show the backing value
Always
}
public enum ConnectionType
{
/// Allow multiple connections
Multiple,
/// always override the current connection
Override,
}
/// Tells which types of input to allow
public enum TypeConstraint
{
/// Allow all types of input
None,
/// Allow connections where input value type is assignable from output value type (eg. ScriptableObject --> Object)
Inherited,
/// Allow only similar types
Strict,
/// Allow connections where output value type is assignable from input value type (eg. Object --> ScriptableObject)
InheritedInverse,
}
public virtual bool IsRemove()
{
return true;
}
#region Obsolete
[Obsolete("Use DynamicPorts instead")]
public IEnumerable InstancePorts
{
get { return DynamicPorts; }
}
[Obsolete("Use DynamicOutputs instead")]
public IEnumerable InstanceOutputs
{
get { return DynamicOutputs; }
}
[Obsolete("Use DynamicInputs instead")]
public IEnumerable InstanceInputs
{
get { return DynamicInputs; }
}
[Obsolete("Use AddDynamicInput instead")]
public NodePort AddInstanceInput(Type type, ConnectionType connectionType = ConnectionType.Multiple, TypeConstraint typeConstraint = TypeConstraint.None,
string fieldName = null)
{
return AddDynamicInput(type, connectionType, typeConstraint, fieldName);
}
[Obsolete("Use AddDynamicOutput instead")]
public NodePort AddInstanceOutput(Type type, ConnectionType connectionType = ConnectionType.Multiple, TypeConstraint typeConstraint = TypeConstraint.None,
string fieldName = null)
{
return AddDynamicOutput(type, connectionType, typeConstraint, fieldName);
}
[Obsolete("Use AddDynamicPort instead")]
private NodePort AddInstancePort(Type type, NodePort.IO direction, ConnectionType connectionType = ConnectionType.Multiple, TypeConstraint typeConstraint = TypeConstraint.None,
string fieldName = null)
{
return AddDynamicPort(type, direction, connectionType, typeConstraint, fieldName);
}
[Obsolete("Use RemoveDynamicPort instead")]
public void RemoveInstancePort(string fieldName)
{
RemoveDynamicPort(fieldName);
}
[Obsolete("Use RemoveDynamicPort instead")]
public void RemoveInstancePort(NodePort port)
{
RemoveDynamicPort(port);
}
[Obsolete("Use ClearDynamicPorts instead")]
public void ClearInstancePorts()
{
ClearDynamicPorts();
}
#endregion
/// 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;
[NodeSerialize("GUID")] [SerializeField]
public int guid;
[HideInInspector] [SerializeField] private int uniqueID;
///
/// 唯一ID,首次创建的时候赋值
///
public int UniqueID
{
get
{
if (uniqueID == 0)
{
uniqueID = GetInstanceID();
}
return uniqueID;
}
set
{
if (UniqueID == 0)
{
uniqueID = value;
}
}
}
///
/// 复制组件的时候调用
///
public void ResetUniqueID()
{
uniqueID = GetInstanceID();
}
/// 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;
protected void OnEnable()
{
if (graphHotfix != null) graph = graphHotfix;
graphHotfix = null;
UpdatePorts();
Init();
}
/// Update static ports and dynamic ports managed by DynamicPortLists to reflect class fields. This happens automatically on enable or on redrawing a dynamic port list.
public void UpdatePorts()
{
NodeDataCache.UpdatePorts(this, ports);
}
/// Initialize node. Called on enable.
public virtual void Init()
{
}
/// 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 AddDynamicPort(type, NodePort.IO.Input, connectionType, typeConstraint, fieldName);
}
/// Convenience function.
///
///
public NodePort AddDynamicOutput(Type type, ConnectionType connectionType = ConnectionType.Multiple, TypeConstraint typeConstraint = TypeConstraint.None,
string fieldName = null)
{
return AddDynamicPort(type, NodePort.IO.Output, connectionType, typeConstraint, fieldName);
}
/// Add a dynamic, serialized port to this node.
///
///
private NodePort AddDynamicPort(Type type, NodePort.IO direction, ConnectionType connectionType = ConnectionType.Multiple, TypeConstraint typeConstraint = TypeConstraint.None,
string fieldName = null)
{
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;
}
/// 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 != NodePort.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 != NodePort.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();
}
#region Attributes
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Field | AttributeTargets.Property)]
public class NodeSerializeAttribute : Attribute
{
public string showName;
public string tooltip;
public int showType;
public bool isDisable;
public NodeSerializeAttribute(string showName, string tooltip = "", int showType = 0, bool isDisable = false)
{
this.showName = showName;
this.tooltip = tooltip;
this.showType = showType;
this.isDisable = isDisable;
}
}
///
/// 这里暂时用来处理RandomNode的
///
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Field | AttributeTargets.Property)]
public class NodeSerializeAttributeSpecial : Attribute
{
}
// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Field | AttributeTargets.Property)]
// public class DisabledAttribute : Attribute
// {
// }
/// Mark a serializable field as an input port. You can access this through
[AttributeUsage(AttributeTargets.Field)]
public class InputAttribute : Attribute
{
public ShowBackingValue backingValue;
public ConnectionType connectionType;
[Obsolete("Use dynamicPortList instead")]
public bool instancePortList
{
get { return dynamicPortList; }
set { dynamicPortList = value; }
}
public bool dynamicPortList;
public TypeConstraint typeConstraint;
/// Mark a serializable field as an input port. You can access this through
/// Should we display the backing value for this port as an editor field?
/// Should we allow multiple connections?
/// Constrains which input connections can be made to this port
/// If true, will display a reorderable list of inputs instead of a single port. Will automatically add and display values for lists and arrays
public InputAttribute(ShowBackingValue backingValue = ShowBackingValue.Unconnected, ConnectionType connectionType = ConnectionType.Multiple,
TypeConstraint typeConstraint = TypeConstraint.None, bool dynamicPortList = false)
{
this.backingValue = backingValue;
this.connectionType = connectionType;
this.dynamicPortList = dynamicPortList;
this.typeConstraint = typeConstraint;
}
}
/// Mark a serializable field as an output port. You can access this through
[AttributeUsage(AttributeTargets.Field)]
public class OutputAttribute : Attribute
{
public ShowBackingValue backingValue;
public ConnectionType connectionType;
[Obsolete("Use dynamicPortList instead")]
public bool instancePortList
{
get { return dynamicPortList; }
set { dynamicPortList = value; }
}
public bool dynamicPortList;
public TypeConstraint typeConstraint;
/// Mark a serializable field as an output port. You can access this through
/// Should we display the backing value for this port as an editor field?
/// Should we allow multiple connections?
/// Constrains which input connections can be made from this port
/// If true, will display a reorderable list of outputs instead of a single port. Will automatically add and display values for lists and arrays
public OutputAttribute(ShowBackingValue backingValue = ShowBackingValue.Never, ConnectionType connectionType = ConnectionType.Multiple, TypeConstraint typeConstraint = TypeConstraint.None,
bool dynamicPortList = false)
{
this.backingValue = backingValue;
this.connectionType = connectionType;
this.dynamicPortList = dynamicPortList;
this.typeConstraint = typeConstraint;
}
/// Mark a serializable field as an output port. You can access this through
/// Should we display the backing value for this port as an editor field?
/// Should we allow multiple connections?
/// If true, will display a reorderable list of outputs instead of a single port. Will automatically add and display values for lists and arrays
[Obsolete("Use constructor with TypeConstraint")]
public OutputAttribute(ShowBackingValue backingValue, ConnectionType connectionType, bool dynamicPortList) : this(backingValue, connectionType, TypeConstraint.None, dynamicPortList)
{
}
}
/// Manually supply node class with a context menu path
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Field, AllowMultiple = true)]
public class CreateNodeMenuAttribute : Attribute
{
public string menuName;
public int order;
public bool isServerRecord;
public string tooltip = "帮助文本默认在[CreateNodeMenuAttribute]中配置,暂时由程序维护,有更好的意见可以及时反馈";
public string showName;
/// Manually supply node class with a context menu path
/// Path to this node in the context menu. Null or empty hides it.
public CreateNodeMenuAttribute(string menuName)
{
this.menuName = menuName;
this.order = 0;
}
/// Manually supply node class with a context menu path
/// Path to this node in the context menu. Null or empty hides it.
/// The order by which the menu items are displayed.
public CreateNodeMenuAttribute(string menuName, int order)
{
this.menuName = menuName;
this.order = order;
}
public CreateNodeMenuAttribute(string menuName, int order, bool isServerRecord, string tooltip, string showName)
{
this.menuName = menuName;
this.order = order;
this.isServerRecord = isServerRecord;
this.tooltip = tooltip;
this.showName = showName;
}
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Field, AllowMultiple = true)]
public class CreateDialogueNodeMenuAttribute : Attribute
{
public string menuName;
public int order;
public bool isServerRecord;
public string tooltip;
public string showName;
/// Manually supply node class with a context menu path
/// Path to this node in the context menu. Null or empty hides it.
public CreateDialogueNodeMenuAttribute(string menuName)
{
this.menuName = menuName;
this.order = 0;
}
/// Manually supply node class with a context menu path
/// Path to this node in the context menu. Null or empty hides it.
/// The order by which the menu items are displayed.
public CreateDialogueNodeMenuAttribute(string menuName, int order)
{
this.menuName = menuName;
this.order = order;
}
public CreateDialogueNodeMenuAttribute(string menuName, int order, bool isServerRecord, string tooltip, string showName)
{
this.menuName = menuName;
this.order = order;
this.isServerRecord = isServerRecord;
this.tooltip = tooltip;
this.showName = showName;
}
}
/// Prevents Node of the same type to be added more than once (configurable) to a NodeGraph
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class DisallowMultipleNodesAttribute : Attribute
{
// TODO: Make inheritance work in such a way that applying [DisallowMultipleNodes(1)] to type NodeBar : Node
// while type NodeFoo : NodeBar exists, will let you add *either one* of these nodes, but not both.
public int max;
/// Prevents Node of the same type to be added more than once (configurable) to a NodeGraph
/// How many nodes to allow. Defaults to 1.
public DisallowMultipleNodesAttribute(int max = 1)
{
this.max = max;
}
}
/// Specify a color for this node type
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class NodeTintAttribute : Attribute
{
public Color color;
/// Specify a color for this node type
/// Red [0.0f .. 1.0f]
/// Green [0.0f .. 1.0f]
/// Blue [0.0f .. 1.0f]
public NodeTintAttribute(float r, float g, float b)
{
color = new Color(r, g, b);
}
/// Specify a color for this node type
/// HEX color value
public NodeTintAttribute(string hex)
{
ColorUtility.TryParseHtmlString(hex, out color);
}
/// Specify a color for this node type
/// Red [0 .. 255]
/// Green [0 .. 255]
/// Blue [0 .. 255]
public NodeTintAttribute(byte r, byte g, byte b)
{
color = new Color32(r, g, b, byte.MaxValue);
}
}
/// Specify a width for this node type
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class NodeWidthAttribute : Attribute
{
public int width;
/// Specify a width for this node type
/// Width
public NodeWidthAttribute(int width)
{
this.width = width;
}
}
#endregion
[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 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]);
}
}
}
}