123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396 |
- // #define DEBUG_LAMBDA
- using System.Linq;
- using System.Collections.Generic;
- using System.Collections;
- using UnityEngine;
- using System.Reflection;
- using System.Linq.Expressions;
- using System;
- namespace GraphProcessor
- {
- /// <summary>
- /// Class that describe port attributes for it's creation
- /// </summary>
- public class PortData : IEquatable< PortData >
- {
- /// <summary>
- /// Unique identifier for the port
- /// </summary>
- public string identifier;
- /// <summary>
- /// Display name on the node
- /// </summary>
- public string displayName;
- /// <summary>
- /// The type that will be used for coloring with the type stylesheet
- /// </summary>
- public Type displayType;
- /// <summary>
- /// If the port accept multiple connection
- /// </summary>
- public bool acceptMultipleEdges;
- /// <summary>
- /// Port size, will also affect the size of the connected edge
- /// </summary>
- public int sizeInPixel;
- /// <summary>
- /// Tooltip of the port
- /// </summary>
- public string tooltip;
- /// <summary>
- /// Is the port vertical
- /// </summary>
- public bool vertical;
- public bool Equals(PortData other)
- {
- return identifier == other.identifier
- && displayName == other.displayName
- && displayType == other.displayType
- && acceptMultipleEdges == other.acceptMultipleEdges
- && sizeInPixel == other.sizeInPixel
- && tooltip == other.tooltip
- && vertical == other.vertical;
- }
- public void CopyFrom(PortData other)
- {
- identifier = other.identifier;
- displayName = other.displayName;
- displayType = other.displayType;
- acceptMultipleEdges = other.acceptMultipleEdges;
- sizeInPixel = other.sizeInPixel;
- tooltip = other.tooltip;
- vertical = other.vertical;
- }
- }
- /// <summary>
- /// Runtime class that stores all info about one port that is needed for the processing
- /// </summary>
- public class NodePort
- {
- /// <summary>
- /// The actual name of the property behind the port (must be exact, it is used for Reflection)
- /// </summary>
- public string fieldName;
- /// <summary>
- /// The node on which the port is
- /// </summary>
- public BaseNode owner;
- /// <summary>
- /// The fieldInfo from the fieldName
- /// </summary>
- public FieldInfo fieldInfo;
- /// <summary>
- /// Data of the port
- /// </summary>
- public PortData portData;
- List< SerializableEdge > edges = new List< SerializableEdge >();
- Dictionary< SerializableEdge, PushDataDelegate > pushDataDelegates = new Dictionary< SerializableEdge, PushDataDelegate >();
- List< SerializableEdge > edgeWithRemoteCustomIO = new List< SerializableEdge >();
- /// <summary>
- /// Owner of the FieldInfo, to be used in case of Get/SetValue
- /// </summary>
- public object fieldOwner;
- CustomPortIODelegate customPortIOMethod;
- /// <summary>
- /// Delegate that is made to send the data from this port to another port connected through an edge
- /// This is an optimization compared to dynamically setting values using Reflection (which is really slow)
- /// More info: https://codeblog.jonskeet.uk/2008/08/09/making-reflection-fly-and-exploring-delegates/
- /// </summary>
- public delegate void PushDataDelegate();
- /// <summary>
- /// Constructor
- /// </summary>
- /// <param name="owner">owner node</param>
- /// <param name="fieldName">the C# property name</param>
- /// <param name="portData">Data of the port</param>
- public NodePort(BaseNode owner, string fieldName, PortData portData) : this(owner, owner, fieldName, portData) {}
- /// <summary>
- /// Constructor
- /// </summary>
- /// <param name="owner">owner node</param>
- /// <param name="fieldOwner"></param>
- /// <param name="fieldName">the C# property name</param>
- /// <param name="portData">Data of the port</param>
- public NodePort(BaseNode owner, object fieldOwner, string fieldName, PortData portData)
- {
- this.fieldName = fieldName;
- this.owner = owner;
- this.portData = portData;
- this.fieldOwner = fieldOwner;
- fieldInfo = fieldOwner.GetType().GetField(
- fieldName,
- BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
- customPortIOMethod = CustomPortIO.GetCustomPortMethod(owner.GetType(), fieldName);
- }
- /// <summary>
- /// Connect an edge to this port
- /// </summary>
- /// <param name="edge"></param>
- public void Add(SerializableEdge edge)
- {
- if (!edges.Contains(edge))
- edges.Add(edge);
- if (edge.inputNode == owner)
- {
- if (edge.outputPort.customPortIOMethod != null)
- edgeWithRemoteCustomIO.Add(edge);
- }
- else
- {
- if (edge.inputPort.customPortIOMethod != null)
- edgeWithRemoteCustomIO.Add(edge);
- }
- //if we have a custom io implementation, we don't need to genereate the defaut one
- if (edge.inputPort.customPortIOMethod != null || edge.outputPort.customPortIOMethod != null)
- return ;
- PushDataDelegate edgeDelegate = CreatePushDataDelegateForEdge(edge);
- if (edgeDelegate != null)
- pushDataDelegates[edge] = edgeDelegate;
- }
- PushDataDelegate CreatePushDataDelegateForEdge(SerializableEdge edge)
- {
- try
- {
- //Creation of the delegate to move the data from the input node to the output node:
- FieldInfo inputField = edge.inputNode.GetType().GetField(edge.inputFieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
- FieldInfo outputField = edge.outputNode.GetType().GetField(edge.outputFieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
- Type inType, outType;
- #if DEBUG_LAMBDA
- return new PushDataDelegate(() => {
- var outValue = outputField.GetValue(edge.outputNode);
- inType = edge.inputPort.portData.displayType ?? inputField.FieldType;
- outType = edge.outputPort.portData.displayType ?? outputField.FieldType;
- Debug.Log($"Push: {inType}({outValue}) -> {outType} | {owner.name}");
- object convertedValue = outValue;
- if (TypeAdapter.AreAssignable(outType, inType))
- {
- var convertionMethod = TypeAdapter.GetConvertionMethod(outType, inType);
- Debug.Log("Convertion method: " + convertionMethod.Name);
- convertedValue = convertionMethod.Invoke(null, new object[]{ outValue });
- }
- inputField.SetValue(edge.inputNode, convertedValue);
- });
- #endif
- // We keep slow checks inside the editor
- #if UNITY_EDITOR
- if (!BaseGraph.TypesAreConnectable(inputField.FieldType, outputField.FieldType))
- {
- Debug.LogError("Can't convert from " + inputField.FieldType + " to " + outputField.FieldType + ", you must specify a custom port function (i.e CustomPortInput or CustomPortOutput) for non-implicit convertions");
- return null;
- }
- #endif
- Expression inputParamField = Expression.Field(Expression.Constant(edge.inputNode), inputField);
- Expression outputParamField = Expression.Field(Expression.Constant(edge.outputNode), outputField);
- inType = edge.inputPort.portData.displayType ?? inputField.FieldType;
- outType = edge.outputPort.portData.displayType ?? outputField.FieldType;
- // If there is a user defined convertion function, then we call it
- if (TypeAdapter.AreAssignable(outType, inType))
- {
- // We add a cast in case there we're calling the conversion method with a base class parameter (like object)
- var convertedParam = Expression.Convert(outputParamField, outType);
- outputParamField = Expression.Call(TypeAdapter.GetConvertionMethod(outType, inType), convertedParam);
- // In case there is a custom port behavior in the output, then we need to re-cast to the base type because
- // the convertion method return type is not always assignable directly:
- outputParamField = Expression.Convert(outputParamField, inputField.FieldType);
- }
- else // otherwise we cast
- outputParamField = Expression.Convert(outputParamField, inputField.FieldType);
- BinaryExpression assign = Expression.Assign(inputParamField, outputParamField);
- return Expression.Lambda< PushDataDelegate >(assign).Compile();
- } catch (Exception e) {
- Debug.LogError(e);
- return null;
- }
- }
- /// <summary>
- /// Disconnect an Edge from this port
- /// </summary>
- /// <param name="edge"></param>
- public void Remove(SerializableEdge edge)
- {
- if (!edges.Contains(edge))
- return;
- pushDataDelegates.Remove(edge);
- edgeWithRemoteCustomIO.Remove(edge);
- edges.Remove(edge);
- }
- /// <summary>
- /// Get all the edges connected to this port
- /// </summary>
- /// <returns></returns>
- public List< SerializableEdge > GetEdges() => edges;
- /// <summary>
- /// Push the value of the port through the edges
- /// This method can only be called on output ports
- /// </summary>
- public void PushData()
- {
- if (customPortIOMethod != null)
- {
- customPortIOMethod(owner, edges, this);
- return ;
- }
- foreach (var pushDataDelegate in pushDataDelegates)
- pushDataDelegate.Value();
- if (edgeWithRemoteCustomIO.Count == 0)
- return ;
- //if there are custom IO implementation on the other ports, they'll need our value in the passThrough buffer
- object ourValue = fieldInfo.GetValue(fieldOwner);
- foreach (var edge in edgeWithRemoteCustomIO)
- edge.passThroughBuffer = ourValue;
- }
- /// <summary>
- /// Reset the value of the field to default if possible
- /// </summary>
- public void ResetToDefault()
- {
- // Clear lists, set classes to null and struct to default value.
- if (typeof(IList).IsAssignableFrom(fieldInfo.FieldType))
- (fieldInfo.GetValue(fieldOwner) as IList)?.Clear();
- else if (fieldInfo.FieldType.GetTypeInfo().IsClass)
- fieldInfo.SetValue(fieldOwner, null);
- else
- {
- try
- {
- fieldInfo.SetValue(fieldOwner, Activator.CreateInstance(fieldInfo.FieldType));
- } catch {} // Catch types that don't have any constructors
- }
- }
- /// <summary>
- /// Pull values from the edge (in case of a custom convertion method)
- /// This method can only be called on input ports
- /// </summary>
- public void PullData()
- {
- if (customPortIOMethod != null)
- {
- customPortIOMethod(owner, edges, this);
- return ;
- }
- // check if this port have connection to ports that have custom output functions
- if (edgeWithRemoteCustomIO.Count == 0)
- return ;
- // Only one input connection is handled by this code, if you want to
- // take multiple inputs, you must create a custom input function see CustomPortsNode.cs
- if (edges.Count > 0)
- {
- var passThroughObject = edges.First().passThroughBuffer;
- // We do an extra convertion step in case the buffer output is not compatible with the input port
- if (passThroughObject != null)
- if (TypeAdapter.AreAssignable(fieldInfo.FieldType, passThroughObject.GetType()))
- passThroughObject = TypeAdapter.Convert(passThroughObject, fieldInfo.FieldType);
- fieldInfo.SetValue(fieldOwner, passThroughObject);
- }
- }
- }
- /// <summary>
- /// Container of ports and the edges connected to these ports
- /// </summary>
- public abstract class NodePortContainer : List< NodePort >
- {
- protected BaseNode node;
- public NodePortContainer(BaseNode node)
- {
- this.node = node;
- }
- /// <summary>
- /// Remove an edge that is connected to one of the node in the container
- /// </summary>
- /// <param name="edge"></param>
- public void Remove(SerializableEdge edge)
- {
- ForEach(p => p.Remove(edge));
- }
- /// <summary>
- /// Add an edge that is connected to one of the node in the container
- /// </summary>
- /// <param name="edge"></param>
- public void Add(SerializableEdge edge)
- {
- string portFieldName = (edge.inputNode == node) ? edge.inputFieldName : edge.outputFieldName;
- string portIdentifier = (edge.inputNode == node) ? edge.inputPortIdentifier : edge.outputPortIdentifier;
- // Force empty string to null since portIdentifier is a serialized value
- if (String.IsNullOrEmpty(portIdentifier))
- portIdentifier = null;
- var port = this.FirstOrDefault(p =>
- {
- return p.fieldName == portFieldName && p.portData.identifier == portIdentifier;
- });
- if (port == null)
- {
- Debug.LogError("The edge can't be properly connected because it's ports can't be found");
- return;
- }
- port.Add(edge);
- }
- }
- /// <inheritdoc/>
- public class NodeInputPortContainer : NodePortContainer
- {
- public NodeInputPortContainer(BaseNode node) : base(node) {}
- public void PullDatas()
- {
- ForEach(p => p.PullData());
- }
- }
- /// <inheritdoc/>
- public class NodeOutputPortContainer : NodePortContainer
- {
- public NodeOutputPortContainer(BaseNode node) : base(node) {}
- public void PushDatas()
- {
- ForEach(p => p.PushData());
- }
- }
- }
|