// #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
{
///
/// Class that describe port attributes for it's creation
///
public class PortData : IEquatable< PortData >
{
///
/// Unique identifier for the port
///
public string identifier;
///
/// Display name on the node
///
public string displayName;
///
/// The type that will be used for coloring with the type stylesheet
///
public Type displayType;
///
/// If the port accept multiple connection
///
public bool acceptMultipleEdges;
///
/// Port size, will also affect the size of the connected edge
///
public int sizeInPixel;
///
/// Tooltip of the port
///
public string tooltip;
///
/// Is the port vertical
///
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;
}
}
///
/// Runtime class that stores all info about one port that is needed for the processing
///
public class NodePort
{
///
/// The actual name of the property behind the port (must be exact, it is used for Reflection)
///
public string fieldName;
///
/// The node on which the port is
///
public BaseNode owner;
///
/// The fieldInfo from the fieldName
///
public FieldInfo fieldInfo;
///
/// Data of the port
///
public PortData portData;
List< SerializableEdge > edges = new List< SerializableEdge >();
Dictionary< SerializableEdge, PushDataDelegate > pushDataDelegates = new Dictionary< SerializableEdge, PushDataDelegate >();
List< SerializableEdge > edgeWithRemoteCustomIO = new List< SerializableEdge >();
///
/// Owner of the FieldInfo, to be used in case of Get/SetValue
///
public object fieldOwner;
CustomPortIODelegate customPortIOMethod;
///
/// 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/
///
public delegate void PushDataDelegate();
///
/// Constructor
///
/// owner node
/// the C# property name
/// Data of the port
public NodePort(BaseNode owner, string fieldName, PortData portData) : this(owner, owner, fieldName, portData) {}
///
/// Constructor
///
/// owner node
///
/// the C# property name
/// Data of the port
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);
}
///
/// Connect an edge to this port
///
///
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;
}
}
///
/// Disconnect an Edge from this port
///
///
public void Remove(SerializableEdge edge)
{
if (!edges.Contains(edge))
return;
pushDataDelegates.Remove(edge);
edgeWithRemoteCustomIO.Remove(edge);
edges.Remove(edge);
}
///
/// Get all the edges connected to this port
///
///
public List< SerializableEdge > GetEdges() => edges;
///
/// Push the value of the port through the edges
/// This method can only be called on output ports
///
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;
}
///
/// Reset the value of the field to default if possible
///
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
}
}
///
/// Pull values from the edge (in case of a custom convertion method)
/// This method can only be called on input ports
///
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);
}
}
}
///
/// Container of ports and the edges connected to these ports
///
public abstract class NodePortContainer : List< NodePort >
{
protected BaseNode node;
public NodePortContainer(BaseNode node)
{
this.node = node;
}
///
/// Remove an edge that is connected to one of the node in the container
///
///
public void Remove(SerializableEdge edge)
{
ForEach(p => p.Remove(edge));
}
///
/// Add an edge that is connected to one of the node in the container
///
///
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);
}
}
///
public class NodeInputPortContainer : NodePortContainer
{
public NodeInputPortContainer(BaseNode node) : base(node) {}
public void PullDatas()
{
ForEach(p => p.PullData());
}
}
///
public class NodeOutputPortContainer : NodePortContainer
{
public NodeOutputPortContainer(BaseNode node) : base(node) {}
public void PushDatas()
{
ForEach(p => p.PushData());
}
}
}