123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938 |
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
- using System;
- using System.Reflection;
- using Unity.Jobs;
- using System.Linq;
- namespace GraphProcessor
- {
- public delegate IEnumerable<PortData> CustomPortBehaviorDelegate(List<SerializableEdge> edges);
- public delegate IEnumerable<PortData> CustomPortTypeBehaviorDelegate(string fieldName, string displayName,
- object value);
- [Serializable]
- public abstract class BaseNode
- {
- [SerializeField] internal string nodeCustomName = null; // The name of the node in case it was renamed by a user
- /// <summary>
- /// Name of the node, it will be displayed in the title section
- /// </summary>
- /// <returns></returns>
- public virtual string name => GetType().Name;
- /// <summary>
- /// The accent color of the node
- /// </summary>
- public virtual Color color => Color.clear;
- /// <summary>
- /// Set a custom uss file for the node. We use a Resources.Load to get the stylesheet so be sure to put the correct resources path
- /// https://docs.unity3d.com/ScriptReference/Resources.Load.html
- /// </summary>
- public virtual string layoutStyle => string.Empty;
- /// <summary>
- /// If the node can be locked or not
- /// </summary>
- public virtual bool unlockable => true;
- /// <summary>
- /// Is the node is locked (if locked it can't be moved)
- /// </summary>
- public virtual bool isLocked => nodeLock;
- //id
- public string GUID;
- public int computeOrder = -1;
- /// <summary>Tell wether or not the node can be processed. Do not check anything from inputs because this step happens before inputs are sent to the node</summary>
- public virtual bool canProcess => true;
- /// <summary>Show the node controlContainer only when the mouse is over the node</summary>
- public virtual bool showControlsOnHover => false;
- /// <summary>True if the node can be deleted, false otherwise</summary>
- public virtual bool deletable => true;
- /// <summary>
- /// Container of input ports
- /// </summary>
- [NonSerialized] public readonly NodeInputPortContainer inputPorts;
- /// <summary>
- /// Container of output ports
- /// </summary>
- [NonSerialized] public readonly NodeOutputPortContainer outputPorts;
- //Node view datas
- public Rect position;
- /// <summary>
- /// Is the node expanded
- /// </summary>
- public bool expanded;
- /// <summary>
- /// Is debug visible
- /// </summary>
- public bool debug;
- /// <summary>
- /// Node locked state
- /// </summary>
- public bool nodeLock;
- public delegate void ProcessDelegate();
- /// <summary>
- /// Triggered when the node is processes
- /// </summary>
- public event ProcessDelegate onProcessed;
- public event Action<string, NodeMessageType> onMessageAdded;
- public event Action<string> onMessageRemoved;
- /// <summary>
- /// Triggered after an edge was connected on the node
- /// </summary>
- public event Action<SerializableEdge> onAfterEdgeConnected;
- /// <summary>
- /// Triggered after an edge was disconnected on the node
- /// </summary>
- public event Action<SerializableEdge> onAfterEdgeDisconnected;
- /// <summary>
- /// Triggered after a single/list of port(s) is updated, the parameter is the field name
- /// </summary>
- public event Action<string> onPortsUpdated;
- [NonSerialized] bool _needsInspector = false;
- /// <summary>
- /// Does the node needs to be visible in the inspector (when selected).
- /// </summary>
- public virtual bool needsInspector => _needsInspector;
- /// <summary>
- /// Can the node be renamed in the UI. By default a node can be renamed by double clicking it's name.
- /// </summary>
- public virtual bool isRenamable => false;
- /// <summary>
- /// Is the node created from a duplicate operation (either ctrl-D or copy/paste).
- /// </summary>
- public bool createdFromDuplication { get; internal set; } = false;
- /// <summary>
- /// True only when the node was created from a duplicate operation and is inside a group that was also duplicated at the same time.
- /// </summary>
- public bool createdWithinGroup { get; internal set; } = false;
- [NonSerialized]
- internal Dictionary<string, NodeFieldInformation> nodeFields = new Dictionary<string, NodeFieldInformation>();
- [NonSerialized] internal Dictionary<Type, CustomPortTypeBehaviorDelegate> customPortTypeBehaviorMap =
- new Dictionary<Type, CustomPortTypeBehaviorDelegate>();
- [NonSerialized] List<string> messages = new List<string>();
- [NonSerialized] protected BaseGraph graph;
- internal class NodeFieldInformation
- {
- public string name;
- public string fieldName;
- public FieldInfo info;
- public bool input;
- public bool isMultiple;
- public string tooltip;
- public CustomPortBehaviorDelegate behavior;
- public bool vertical;
- public NodeFieldInformation(FieldInfo info, string name, bool input, bool isMultiple, string tooltip,
- bool vertical, CustomPortBehaviorDelegate behavior)
- {
- this.input = input;
- this.isMultiple = isMultiple;
- this.info = info;
- this.name = name;
- this.fieldName = info.Name;
- this.behavior = behavior;
- this.tooltip = tooltip;
- this.vertical = vertical;
- }
- }
- struct PortUpdate
- {
- public List<string> fieldNames;
- public BaseNode node;
- public void Deconstruct(out List<string> fieldNames, out BaseNode node)
- {
- fieldNames = this.fieldNames;
- node = this.node;
- }
- }
- // Used in port update algorithm
- Stack<PortUpdate> fieldsToUpdate = new Stack<PortUpdate>();
- HashSet<PortUpdate> updatedFields = new HashSet<PortUpdate>();
- /// <summary>
- /// Creates a node of type T at a certain position
- /// </summary>
- /// <param name="position">position in the graph in pixels</param>
- /// <typeparam name="T">type of the node</typeparam>
- /// <returns>the node instance</returns>
- public static T CreateFromType<T>(Vector2 position) where T : BaseNode
- {
- return CreateFromType(typeof(T), position) as T;
- }
- /// <summary>
- /// Creates a node of type nodeType at a certain position
- /// </summary>
- /// <param name="position">position in the graph in pixels</param>
- /// <typeparam name="nodeType">type of the node</typeparam>
- /// <returns>the node instance</returns>
- public static BaseNode CreateFromType(Type nodeType, Vector2 position)
- {
- if (!nodeType.IsSubclassOf(typeof(BaseNode)))
- return null;
- var node = Activator.CreateInstance(nodeType) as BaseNode;
- node.position = new Rect(position, new Vector2(100, 100));
- ExceptionToLog.Call(() => node.OnNodeCreated());
- return node;
- }
- #region Initialization
- // called by the BaseGraph when the node is added to the graph
- public void Initialize(BaseGraph graph)
- {
- this.graph = graph;
- ExceptionToLog.Call(() => Enable());
- InitializePorts();
- }
- void InitializeCustomPortTypeMethods()
- {
- MethodInfo[] methods = new MethodInfo[0];
- Type baseType = GetType();
- while (true)
- {
- methods = baseType.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance);
- foreach (var method in methods)
- {
- var typeBehaviors = method.GetCustomAttributes<CustomPortTypeBehavior>().ToArray();
- if (typeBehaviors.Length == 0)
- continue;
- CustomPortTypeBehaviorDelegate deleg = null;
- try
- {
- deleg =
- Delegate.CreateDelegate(typeof(CustomPortTypeBehaviorDelegate), this, method) as
- CustomPortTypeBehaviorDelegate;
- }
- catch (Exception e)
- {
- Debug.LogError(e);
- Debug.LogError(
- $"Cannot convert method {method} to a delegate of type {typeof(CustomPortTypeBehaviorDelegate)}");
- }
- foreach (var typeBehavior in typeBehaviors)
- customPortTypeBehaviorMap[typeBehavior.type] = deleg;
- }
- // Try to also find private methods in the base class
- baseType = baseType.BaseType;
- if (baseType == null)
- break;
- }
- }
- /// <summary>
- /// Use this function to initialize anything related to ports generation in your node
- /// This will allow the node creation menu to correctly recognize ports that can be connected between nodes
- /// </summary>
- public virtual void InitializePorts()
- {
- InitializeCustomPortTypeMethods();
- foreach (var key in OverrideFieldOrder(nodeFields.Values.Select(k => k.info)))
- {
- var nodeField = nodeFields[key.Name];
- if (HasCustomBehavior(nodeField))
- {
- UpdatePortsForField(nodeField.fieldName, sendPortUpdatedEvent: false);
- }
- else
- {
- // If we don't have a custom behavior on the node, we just have to create a simple port
- AddPort(nodeField.input, nodeField.fieldName,
- new PortData
- {
- acceptMultipleEdges = nodeField.isMultiple, displayName = nodeField.name,
- tooltip = nodeField.tooltip, vertical = nodeField.vertical
- });
- }
- }
- }
- /// <summary>
- /// Override the field order inside the node. It allows to re-order all the ports and field in the UI.
- /// </summary>
- /// <param name="fields">List of fields to sort</param>
- /// <returns>Sorted list of fields</returns>
- public virtual IEnumerable<FieldInfo> OverrideFieldOrder(IEnumerable<FieldInfo> fields)
- {
- long GetFieldInheritanceLevel(FieldInfo f)
- {
- int level = 0;
- var t = f.DeclaringType;
- while (t != null)
- {
- t = t.BaseType;
- level++;
- }
- return level;
- }
- // Order by MetadataToken and inheritance level to sync the order with the port order (make sure FieldDrawers are next to the correct port)
- return fields.OrderByDescending(
- f => (long) (((GetFieldInheritanceLevel(f) << 32)) | (long) f.MetadataToken));
- }
- protected BaseNode()
- {
- inputPorts = new NodeInputPortContainer(this);
- outputPorts = new NodeOutputPortContainer(this);
- InitializeInOutDatas();
- }
- /// <summary>
- /// Update all ports of the node
- /// </summary>
- public bool UpdateAllPorts()
- {
- bool changed = false;
- foreach (var key in OverrideFieldOrder(nodeFields.Values.Select(k => k.info)))
- {
- var field = nodeFields[key.Name];
- changed |= UpdatePortsForField(field.fieldName);
- }
- return changed;
- }
- /// <summary>
- /// Update all ports of the node without updating the connected ports. Only use this method when you need to update all the nodes ports in your graph.
- /// </summary>
- public bool UpdateAllPortsLocal()
- {
- bool changed = false;
- foreach (var key in OverrideFieldOrder(nodeFields.Values.Select(k => k.info)))
- {
- var field = nodeFields[key.Name];
- changed |= UpdatePortsForFieldLocal(field.fieldName);
- }
- return changed;
- }
- /// <summary>
- /// Update the ports related to one C# property field (only for this node)
- /// </summary>
- /// <param name="fieldName"></param>
- public bool UpdatePortsForFieldLocal(string fieldName, bool sendPortUpdatedEvent = true)
- {
- bool changed = false;
- if (!nodeFields.ContainsKey(fieldName))
- return false;
- var fieldInfo = nodeFields[fieldName];
- if (!HasCustomBehavior(fieldInfo))
- return false;
- List<string> finalPorts = new List<string>();
- var portCollection = fieldInfo.input ? (NodePortContainer) inputPorts : outputPorts;
- // Gather all fields for this port (before to modify them)
- var nodePorts = portCollection.Where(p => p.fieldName == fieldName);
- // Gather all edges connected to these fields:
- var edges = nodePorts.SelectMany(n => n.GetEdges()).ToList();
- if (fieldInfo.behavior != null)
- {
- foreach (var portData in fieldInfo.behavior(edges))
- AddPortData(portData);
- }
- else
- {
- var customPortTypeBehavior = customPortTypeBehaviorMap[fieldInfo.info.FieldType];
- foreach (var portData in customPortTypeBehavior(fieldName, fieldInfo.name,
- fieldInfo.info.GetValue(this)))
- AddPortData(portData);
- }
- void AddPortData(PortData portData)
- {
- var port = nodePorts.FirstOrDefault(n => n.portData.identifier == portData.identifier);
- // Guard using the port identifier so we don't duplicate identifiers
- if (port == null)
- {
- AddPort(fieldInfo.input, fieldName, portData);
- changed = true;
- }
- else
- {
- // in case the port type have changed for an incompatible type, we disconnect all the edges attached to this port
- if (!BaseGraph.TypesAreConnectable(port.portData.displayType, portData.displayType))
- {
- foreach (var edge in port.GetEdges().ToList())
- graph.Disconnect(edge.GUID);
- }
- // patch the port data
- if (port.portData != portData)
- {
- port.portData.CopyFrom(portData);
- changed = true;
- }
- }
- finalPorts.Add(portData.identifier);
- }
- // TODO
- // Remove only the ports that are no more in the list
- if (nodePorts != null)
- {
- var currentPortsCopy = nodePorts.ToList();
- foreach (var currentPort in currentPortsCopy)
- {
- // If the current port does not appear in the list of final ports, we remove it
- if (!finalPorts.Any(id => id == currentPort.portData.identifier))
- {
- RemovePort(fieldInfo.input, currentPort);
- changed = true;
- }
- }
- }
- // Make sure the port order is correct:
- portCollection.Sort((p1, p2) =>
- {
- int p1Index = finalPorts.FindIndex(id => p1.portData.identifier == id);
- int p2Index = finalPorts.FindIndex(id => p2.portData.identifier == id);
- if (p1Index == -1 || p2Index == -1)
- return 0;
- return p1Index.CompareTo(p2Index);
- });
- if (sendPortUpdatedEvent)
- onPortsUpdated?.Invoke(fieldName);
- return changed;
- }
- bool HasCustomBehavior(NodeFieldInformation info)
- {
- if (info.behavior != null)
- return true;
- if (customPortTypeBehaviorMap.ContainsKey(info.info.FieldType))
- return true;
- return false;
- }
- /// <summary>
- /// Update the ports related to one C# property field and all connected nodes in the graph
- /// </summary>
- /// <param name="fieldName"></param>
- public bool UpdatePortsForField(string fieldName, bool sendPortUpdatedEvent = true)
- {
- bool changed = false;
- fieldsToUpdate.Clear();
- updatedFields.Clear();
- fieldsToUpdate.Push(new PortUpdate {fieldNames = new List<string>() {fieldName}, node = this});
- // Iterate through all the ports that needs to be updated, following graph connection when the
- // port is updated. This is required ton have type propagation multiple nodes that changes port types
- // are connected to each other (i.e. the relay node)
- while (fieldsToUpdate.Count != 0)
- {
- var (fields, node) = fieldsToUpdate.Pop();
- // Avoid updating twice a port
- if (updatedFields.Any((t) => t.node == node && fields.SequenceEqual(t.fieldNames)))
- continue;
- updatedFields.Add(new PortUpdate {fieldNames = fields, node = node});
- foreach (var field in fields)
- {
- if (node.UpdatePortsForFieldLocal(field, sendPortUpdatedEvent))
- {
- foreach (var port in node.IsFieldInput(field)
- ? (NodePortContainer) node.inputPorts
- : node.outputPorts)
- {
- if (port.fieldName != field)
- continue;
- foreach (var edge in port.GetEdges())
- {
- var edgeNode = (node.IsFieldInput(field)) ? edge.outputNode : edge.inputNode;
- var fieldsWithBehavior = edgeNode.nodeFields.Values.Where(f => HasCustomBehavior(f))
- .Select(f => f.fieldName).ToList();
- fieldsToUpdate.Push(new PortUpdate {fieldNames = fieldsWithBehavior, node = edgeNode});
- }
- }
- changed = true;
- }
- }
- }
- return changed;
- }
- HashSet<BaseNode> portUpdateHashSet = new HashSet<BaseNode>();
- internal void DisableInternal()
- {
- // port containers are initialized in the OnEnable
- inputPorts.Clear();
- outputPorts.Clear();
- ExceptionToLog.Call(() => Disable());
- }
- internal void DestroyInternal() => ExceptionToLog.Call(() => Destroy());
- /// <summary>
- /// Called only when the node is created, not when instantiated
- /// </summary>
- public virtual void OnNodeCreated() => GUID = Guid.NewGuid().ToString();
- public virtual FieldInfo[] GetNodeFields()
- => GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
- void InitializeInOutDatas()
- {
- var fields = GetNodeFields();
- var methods = GetType().GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
- foreach (var field in fields)
- {
- var inputAttribute = field.GetCustomAttribute<InputAttribute>();
- var outputAttribute = field.GetCustomAttribute<OutputAttribute>();
- var tooltipAttribute = field.GetCustomAttribute<TooltipAttribute>();
- var showInInspector = field.GetCustomAttribute<ShowInInspector>();
- var vertical = field.GetCustomAttribute<VerticalAttribute>();
- bool isMultiple = false;
- bool input = false;
- string name = field.Name;
- string tooltip = null;
- if (showInInspector != null)
- _needsInspector = true;
- if (inputAttribute == null && outputAttribute == null)
- continue;
- //check if field is a collection type
- isMultiple = (inputAttribute != null) ? inputAttribute.allowMultiple : (outputAttribute.allowMultiple);
- input = inputAttribute != null;
- tooltip = tooltipAttribute?.tooltip;
- if (!String.IsNullOrEmpty(inputAttribute?.name))
- name = inputAttribute.name;
- if (!String.IsNullOrEmpty(outputAttribute?.name))
- name = outputAttribute.name;
- // By default we set the behavior to null, if the field have a custom behavior, it will be set in the loop just below
- nodeFields[field.Name] =
- new NodeFieldInformation(field, name, input, isMultiple, tooltip, vertical != null, null);
- }
- foreach (var method in methods)
- {
- var customPortBehaviorAttribute = method.GetCustomAttribute<CustomPortBehaviorAttribute>();
- CustomPortBehaviorDelegate behavior = null;
- if (customPortBehaviorAttribute == null)
- continue;
- // Check if custom port behavior function is valid
- try
- {
- var referenceType = typeof(CustomPortBehaviorDelegate);
- behavior = (CustomPortBehaviorDelegate) Delegate.CreateDelegate(referenceType, this, method, true);
- }
- catch
- {
- Debug.LogError("The function " + method + " cannot be converted to the required delegate format: " +
- typeof(CustomPortBehaviorDelegate));
- }
- if (nodeFields.ContainsKey(customPortBehaviorAttribute.fieldName))
- nodeFields[customPortBehaviorAttribute.fieldName].behavior = behavior;
- else
- Debug.LogError("Invalid field name for custom port behavior: " + method + ", " +
- customPortBehaviorAttribute.fieldName);
- }
- }
- #endregion
- #region Events and Processing
- public void OnEdgeConnected(SerializableEdge edge)
- {
- bool input = edge.inputNode == this;
- NodePortContainer portCollection = (input) ? (NodePortContainer) inputPorts : outputPorts;
- portCollection.Add(edge);
- UpdateAllPorts();
- onAfterEdgeConnected?.Invoke(edge);
- }
- protected virtual bool CanResetPort(NodePort port) => true;
- public void OnEdgeDisconnected(SerializableEdge edge)
- {
- if (edge == null)
- return;
- bool input = edge.inputNode == this;
- NodePortContainer portCollection = (input) ? (NodePortContainer) inputPorts : outputPorts;
- portCollection.Remove(edge);
- // Reset default values of input port:
- bool haveConnectedEdges = edge.inputNode.inputPorts.Where(p => p.fieldName == edge.inputFieldName)
- .Any(p => p.GetEdges().Count != 0);
- if (edge.inputNode == this && !haveConnectedEdges && CanResetPort(edge.inputPort))
- edge.inputPort?.ResetToDefault();
- UpdateAllPorts();
- onAfterEdgeDisconnected?.Invoke(edge);
- }
- public void OnEnter()
- {
-
- }
- public void OnLeave()
- {
-
- }
- protected virtual void Enter()
- {
- }
- protected virtual void Leave()
- {
- }
- public void OnProcess()
- {
- inputPorts.PullDatas();
- ExceptionToLog.Call(() => Process());
- InvokeOnProcessed();
- outputPorts.PushDatas();
- }
- public void InvokeOnProcessed() => onProcessed?.Invoke();
- /// <summary>
- /// Called when the node is enabled
- /// </summary>
- protected virtual void Enable()
- {
- }
- /// <summary>
- /// Called when the node is disabled
- /// </summary>
- protected virtual void Disable()
- {
- }
- /// <summary>
- /// Called when the node is removed
- /// </summary>
- protected virtual void Destroy()
- {
- }
- /// <summary>
- /// Override this method to implement custom processing
- /// </summary>
- protected virtual void Process()
- {
- }
- #endregion
- #region API and utils
- /// <summary>
- /// Add a port
- /// </summary>
- /// <param name="input">is input port</param>
- /// <param name="fieldName">C# field name</param>
- /// <param name="portData">Data of the port</param>
- public void AddPort(bool input, string fieldName, PortData portData)
- {
- // Fixup port data info if needed:
- if (portData.displayType == null)
- portData.displayType = nodeFields[fieldName].info.FieldType;
- if (input)
- inputPorts.Add(new NodePort(this, fieldName, portData));
- else
- outputPorts.Add(new NodePort(this, fieldName, portData));
- }
- /// <summary>
- /// Remove a port
- /// </summary>
- /// <param name="input">is input port</param>
- /// <param name="port">the port to delete</param>
- public void RemovePort(bool input, NodePort port)
- {
- if (input)
- inputPorts.Remove(port);
- else
- outputPorts.Remove(port);
- }
- /// <summary>
- /// Remove port(s) from field name
- /// </summary>
- /// <param name="input">is input</param>
- /// <param name="fieldName">C# field name</param>
- public void RemovePort(bool input, string fieldName)
- {
- if (input)
- inputPorts.RemoveAll(p => p.fieldName == fieldName);
- else
- outputPorts.RemoveAll(p => p.fieldName == fieldName);
- }
- /// <summary>
- /// Get all the nodes connected to the input ports of this node
- /// </summary>
- /// <returns>an enumerable of node</returns>
- public IEnumerable<BaseNode> GetInputNodes()
- {
- foreach (var port in inputPorts)
- foreach (var edge in port.GetEdges())
- yield return edge.outputNode;
- }
- /// <summary>
- /// Get all the nodes connected to the output ports of this node
- /// </summary>
- /// <returns>an enumerable of node</returns>
- public IEnumerable<BaseNode> GetOutputNodes()
- {
- foreach (var port in outputPorts)
- foreach (var edge in port.GetEdges())
- yield return edge.inputNode;
- }
- public List<BaseNode> GetOutputNodesForList(string name=null)
- {
- List<BaseNode> allNode = new List<BaseNode>();
- foreach (var port in outputPorts)
- {
- if (name != null && port.fieldName != name)
- {
- continue;
- }
- foreach (var edge in port.GetEdges())
- {
- allNode.Add(edge.inputNode);
- }
- }
- return allNode;
- }
- /// <summary>
- /// Return a node matching the condition in the dependencies of the node
- /// </summary>
- /// <param name="condition">Condition to choose the node</param>
- /// <returns>Matched node or null</returns>
- public BaseNode FindInDependencies(Func<BaseNode, bool> condition)
- {
- Stack<BaseNode> dependencies = new Stack<BaseNode>();
- dependencies.Push(this);
- int depth = 0;
- while (dependencies.Count > 0)
- {
- var node = dependencies.Pop();
- // Guard for infinite loop (faster than a HashSet based solution)
- depth++;
- if (depth > 2000)
- break;
- if (condition(node))
- return node;
- foreach (var dep in node.GetInputNodes())
- dependencies.Push(dep);
- }
- return null;
- }
- /// <summary>
- /// Get the port from field name and identifier
- /// </summary>
- /// <param name="fieldName">C# field name</param>
- /// <param name="identifier">Unique port identifier</param>
- /// <returns></returns>
- public NodePort GetPort(string fieldName, string identifier)
- {
- return inputPorts.Concat(outputPorts).FirstOrDefault(p =>
- {
- var bothNull = String.IsNullOrEmpty(identifier) && String.IsNullOrEmpty(p.portData.identifier);
- return p.fieldName == fieldName && (bothNull || identifier == p.portData.identifier);
- });
- }
- /// <summary>
- /// Return all the ports of the node
- /// </summary>
- /// <returns></returns>
- public IEnumerable<NodePort> GetAllPorts()
- {
- foreach (var port in inputPorts)
- yield return port;
- foreach (var port in outputPorts)
- yield return port;
- }
- /// <summary>
- /// Return all the connected edges of the node
- /// </summary>
- /// <returns></returns>
- public IEnumerable<SerializableEdge> GetAllEdges()
- {
- foreach (var port in GetAllPorts())
- foreach (var edge in port.GetEdges())
- yield return edge;
- }
- /// <summary>
- /// Is the port an input
- /// </summary>
- /// <param name="fieldName"></param>
- /// <returns></returns>
- public bool IsFieldInput(string fieldName) => nodeFields[fieldName].input;
- /// <summary>
- /// Add a message on the node
- /// </summary>
- /// <param name="message"></param>
- /// <param name="messageType"></param>
- public void AddMessage(string message, NodeMessageType messageType)
- {
- if (messages.Contains(message))
- return;
- onMessageAdded?.Invoke(message, messageType);
- messages.Add(message);
- }
- /// <summary>
- /// Remove a message on the node
- /// </summary>
- /// <param name="message"></param>
- public void RemoveMessage(string message)
- {
- onMessageRemoved?.Invoke(message);
- messages.Remove(message);
- }
- /// <summary>
- /// Remove a message that contains
- /// </summary>
- /// <param name="subMessage"></param>
- public void RemoveMessageContains(string subMessage)
- {
- string toRemove = messages.Find(m => m.Contains(subMessage));
- messages.Remove(toRemove);
- onMessageRemoved?.Invoke(toRemove);
- }
- /// <summary>
- /// Remove all messages on the node
- /// </summary>
- public void ClearMessages()
- {
- foreach (var message in messages)
- onMessageRemoved?.Invoke(message);
- messages.Clear();
- }
- /// <summary>
- /// Set the custom name of the node. This is intended to be used by renamable nodes.
- /// This custom name will be serialized inside the node.
- /// </summary>
- /// <param name="customNodeName">New name of the node.</param>
- public void SetCustomName(string customName) => nodeCustomName = customName;
- /// <summary>
- /// Get the name of the node. If the node have a custom name (set using the UI by double clicking on the node title) then it will return this name first, otherwise it returns the value of the name field.
- /// </summary>
- /// <returns>The name of the node as written in the title</returns>
- public string GetCustomName() => String.IsNullOrEmpty(nodeCustomName) ? name : nodeCustomName;
- #endregion
- }
- }
|