| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396 | // #define DEBUG_LAMBDAusing 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());		}	}}
 |