1
0
mirror of https://github.com/Siccity/xNode.git synced 2025-12-20 09:16:01 +08:00
xNode/Scripts/Node.cs
Thor Kramer Brigsted d3bb36fe0e Big update: Warning: Updating to this commit will break all node connections.
Internal NodePorts now uses dicts instead of lists. This is faster and more manageable.
Added instance ports.
Added Node.Ports, Node.Outputs, Node.Inputs, Node.InstanceOutputs, Node.InstanceInputs
Changed public GetInputByFieldName to GetInputValue and GetInputPort
2017-11-02 14:54:03 +01:00

186 lines
8.6 KiB
C#

using System;
using System.Collections.Generic;
using UnityEngine;
/// <summary> Base class for all nodes </summary>
[Serializable]
public abstract class Node : ScriptableObject {
public enum ShowBackingValue {
/// <summary> Never show the backing value </summary>
Never,
/// <summary> Show the backing value only when the port does not have any active connections </summary>
Unconnected,
/// <summary> Always show the backing value </summary>
Always
}
/// <summary> Iterate over all ports on this node. </summary>
public IEnumerable<NodePort> Ports { get { foreach (NodePort port in ports.Values) yield return port; } }
/// <summary> Iterate over all outputs on this node. </summary>
public IEnumerable<NodePort> Outputs { get { foreach (NodePort port in Ports) { if (port.direction == NodePort.IO.Output) yield return port; } } }
/// <summary> Iterate over all inputs on this node. </summary>
public IEnumerable<NodePort> Inputs { get { foreach (NodePort port in Ports) { if (port.direction == NodePort.IO.Input) yield return port; } } }
/// <summary> Iterate over all instance outputs on this node. </summary>
public IEnumerable<NodePort> InstanceOutputs { get { foreach (NodePort port in Ports) { if (port.direction == NodePort.IO.Input) yield return port; } } }
/// <summary> Iterate over all instance inputs on this node. </summary>
public IEnumerable<NodePort> InstanceInputs { get { foreach (NodePort port in Ports) { if (port.direction == NodePort.IO.Input) yield return port; } } }
/// <summary> Parent <see cref="NodeGraph"/> </summary>
[SerializeField] public NodeGraph graph;
/// <summary> Position on the <see cref="NodeGraph"/> </summary>
[SerializeField] public Vector2 position;
/// <summary> Input <see cref="NodePort"/>s. It is recommended not to modify these at hand. Instead, see <see cref="InputAttribute"/> </summary>
[SerializeField] private NodePortDictionary ports = new NodePortDictionary();
protected void OnEnable() {
NodeDataCache.UpdatePorts(this, ports);
Init();
}
/// <summary> Initialize node. Called on creation. </summary>
protected virtual void Init() { name = GetType().Name; }
/// <summary> Checks all connections for invalid references, and removes them. </summary>
public void VerifyConnections() {
foreach (NodePort port in Ports) port.VerifyConnections();
}
#region Instance Ports
/// <summary> Returns input port at index </summary>
public NodePort AddInstanceInput(Type type, string fieldName = null) {
return AddInstancePort(type, NodePort.IO.Input, fieldName);
}
/// <summary> Returns input port at index </summary>
public NodePort AddInstanceOutput(Type type, string fieldName = null) {
return AddInstancePort(type, NodePort.IO.Output, fieldName);
}
private NodePort AddInstancePort(Type type, NodePort.IO direction, string fieldName = null) {
if (fieldName == null) {
fieldName = "instanceInput_0";
int i = 0;
while (HasPort(fieldName)) fieldName = "instanceInput_" + (++i);
} else if (HasPort(fieldName)) {
Debug.LogWarning("Port '" + fieldName + "' already exists in " + name, this);
return ports[fieldName];
}
NodePort port = new NodePort(fieldName, type, direction, this);
ports.Add(fieldName, port);
return port;
}
public bool RemoveInstancePort(string fieldName) {
NodePort port = GetPort(fieldName);
if (port == null || port.IsStatic) return false;
ports.Remove(fieldName);
return true;
}
#endregion
#region Ports
/// <summary> Returns output port which matches fieldName </summary>
public NodePort GetOutputPort(string fieldName) {
NodePort port = GetPort(fieldName);
if (port == null || port.direction != NodePort.IO.Output) return null;
else return port;
}
/// <summary> Returns input port which matches fieldName </summary>
public NodePort GetInputPort(string fieldName) {
NodePort port = GetPort(fieldName);
if (port == null || port.direction != NodePort.IO.Input) return null;
else return port;
}
/// <summary> Returns port which matches fieldName </summary>
public NodePort GetPort(string fieldName) {
if (ports.ContainsKey(fieldName)) return ports[fieldName];
else return null;
}
public bool HasPort(string fieldName) {
return ports.ContainsKey(fieldName);
}
#endregion
#region Inputs/Outputs
/// <summary> Return input value for a specified port. Returns fallback value if no ports are connected </summary>
/// <param name="fieldName">Field name of requested input port</param>
/// <param name="fallback">If no ports are connected, this value will be returned</param>
public T GetInputValue<T>(string fieldName, T fallback = default(T)) {
NodePort port = GetPort(fieldName);
if (port != null && port.IsConnected) return port.GetInputValue<T>();
else return fallback;
}
/// <summary> Return all input values for a specified port. Returns fallback value if no ports are connected </summary>
/// <param name="fieldName">Field name of requested input port</param>
/// <param name="fallback">If no ports are connected, this value will be returned</param>
public T[] GetInputValues<T>(string fieldName, params T[] fallback) {
NodePort port = GetPort(fieldName);
if (port != null && port.IsConnected) return port.GetInputValues<T>();
else return fallback;
}
/// <summary> Returns a value based on requested port output. Should be overridden before used. </summary>
/// <param name="port">The requested port.</param>
public virtual object GetValue(NodePort port) {
Debug.LogWarning("No GetValue(NodePort port) override defined for " + GetType());
return null;
}
#endregion
/// <summary> Called whenever a connection is being made between two <see cref="NodePort"/>s</summary>
/// <param name="from">Output</param> <param name="to">Input</param>
public virtual void OnCreateConnection(NodePort from, NodePort to) { }
/// <summary> Disconnect everything from this node </summary>
public void ClearConnections() {
foreach (NodePort port in Ports) port.ClearConnections();
}
public override int GetHashCode() {
return JsonUtility.ToJson(this).GetHashCode();
}
/// <summary> Mark a serializable field as an input port. You can access this through <see cref="GetInput(string)"/> </summary>
[AttributeUsage(AttributeTargets.Field, AllowMultiple = true)]
public class InputAttribute : Attribute {
public ShowBackingValue backingValue;
/// <summary> Mark a serializable field as an input port. You can access this through <see cref="GetInput(string)"/> </summary>
/// <param name="backingValue">Should we display the backing value for this port as an editor field? </param>
public InputAttribute(ShowBackingValue backingValue = ShowBackingValue.Unconnected) { this.backingValue = backingValue; }
}
/// <summary> Mark a serializable field as an output port. You can access this through <see cref="GetOutput(string)"/> </summary>
[AttributeUsage(AttributeTargets.Field, AllowMultiple = true)]
public class OutputAttribute : Attribute {
/// <summary> Mark a serializable field as an output port. You can access this through <see cref="GetOutput(string)"/> </summary>
public OutputAttribute() { }
}
[Serializable] private class NodePortDictionary : Dictionary<string, NodePort>, ISerializationCallbackReceiver {
[SerializeField] private List<string> keys = new List<string>();
[SerializeField] private List<NodePort> values = new List<NodePort>();
public void OnBeforeSerialize() {
keys.Clear();
values.Clear();
foreach (KeyValuePair<string, NodePort> 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(string.Format("there are {0} keys and {1} 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]);
}
}
}