| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852 | 
							- using System.Collections;
 
- using System.Collections.Generic;
 
- using System.Linq;
 
- using UnityEngine;
 
- using System;
 
- using UnityEngine.Serialization;
 
- using UnityEngine.SceneManagement;
 
- namespace GraphProcessor
 
- {
 
- 	public class GraphChanges
 
- 	{
 
- 		public SerializableEdge	removedEdge;
 
- 		public SerializableEdge	addedEdge;
 
- 		public BaseNode			removedNode;
 
- 		public BaseNode			addedNode;
 
- 		public BaseNode			nodeChanged;
 
- 		public Group			addedGroups;
 
- 		public Group			removedGroups;
 
- 		public BaseStackNode	addedStackNode;
 
- 		public BaseStackNode	removedStackNode;
 
- 		public StickyNote		addedStickyNotes;
 
- 		public StickyNote		removedStickyNotes;
 
- 	}
 
- 	/// <summary>
 
- 	/// Compute order type used to determine the compute order integer on the nodes
 
- 	/// </summary>
 
- 	public enum ComputeOrderType
 
- 	{
 
- 		DepthFirst,
 
- 		BreadthFirst,
 
- 	}
 
- 	
 
- 	[System.Serializable]
 
- 	public class BaseGraph : ScriptableObject, ISerializationCallbackReceiver
 
- 	{
 
- 		static readonly int			maxComputeOrderDepth = 1000;
 
- 		
 
- 		/// <summary>Invalid compute order number of a node when it's inside a loop</summary>
 
- 		public static readonly int loopComputeOrder = -2;
 
- 		/// <summary>Invalid compute order number of a node can't process</summary>
 
- 		public static readonly int invalidComputeOrder = -1;
 
- 		/// <summary>
 
- 		/// Json list of serialized nodes only used for copy pasting in the editor. Note that this field isn't serialized
 
- 		/// </summary>
 
- 		/// <typeparam name="JsonElement"></typeparam>
 
- 		/// <returns></returns>
 
- 		[SerializeField, Obsolete("Use BaseGraph.nodes instead")]
 
- 		public List< JsonElement >						serializedNodes = new List< JsonElement >();
 
- 		/// <summary>
 
- 		/// List of all the nodes in the graph.
 
- 		/// </summary>
 
- 		/// <typeparam name="BaseNode"></typeparam>
 
- 		/// <returns></returns>
 
- 		[SerializeReference]
 
- 		public List< BaseNode >							nodes = new List< BaseNode >();
 
- 		/// <summary>
 
- 		/// Dictionary to access node per GUID, faster than a search in a list
 
- 		/// </summary>
 
- 		/// <typeparam name="string"></typeparam>
 
- 		/// <typeparam name="BaseNode"></typeparam>
 
- 		/// <returns></returns>
 
- 		[System.NonSerialized]
 
- 		public Dictionary< string, BaseNode >			nodesPerGUID = new Dictionary< string, BaseNode >();
 
- 		/// <summary>
 
- 		/// Json list of edges
 
- 		/// </summary>
 
- 		/// <typeparam name="SerializableEdge"></typeparam>
 
- 		/// <returns></returns>
 
- 		[SerializeField]
 
- 		public List< SerializableEdge >					edges = new List< SerializableEdge >();
 
- 		/// <summary>
 
- 		/// Dictionary of edges per GUID, faster than a search in a list
 
- 		/// </summary>
 
- 		/// <typeparam name="string"></typeparam>
 
- 		/// <typeparam name="SerializableEdge"></typeparam>
 
- 		/// <returns></returns>
 
- 		[System.NonSerialized]
 
- 		public Dictionary< string, SerializableEdge >	edgesPerGUID = new Dictionary< string, SerializableEdge >();
 
- 		/// <summary>
 
- 		/// All groups in the graph
 
- 		/// </summary>
 
- 		/// <typeparam name="Group"></typeparam>
 
- 		/// <returns></returns>
 
-         [SerializeField, FormerlySerializedAs("commentBlocks")]
 
-         public List< Group >                     		groups = new List< Group >();
 
- 		/// <summary>
 
- 		/// All Stack Nodes in the graph
 
- 		/// </summary>
 
- 		/// <typeparam name="stackNodes"></typeparam>
 
- 		/// <returns></returns>
 
- 		[SerializeField, SerializeReference] // Polymorphic serialization
 
- 		public List< BaseStackNode >					stackNodes = new List< BaseStackNode >();
 
- 		/// <summary>
 
- 		/// All pinned elements in the graph
 
- 		/// </summary>
 
- 		/// <typeparam name="PinnedElement"></typeparam>
 
- 		/// <returns></returns>
 
- 		[SerializeField]
 
- 		public List< PinnedElement >					pinnedElements = new List< PinnedElement >();
 
- 		/// <summary>
 
- 		/// All exposed parameters in the graph
 
- 		/// </summary>
 
- 		/// <typeparam name="ExposedParameter"></typeparam>
 
- 		/// <returns></returns>
 
- 		[SerializeField, SerializeReference]
 
- 		public List< ExposedParameter >					exposedParameters = new List< ExposedParameter >();
 
- 		[SerializeField, FormerlySerializedAs("exposedParameters")] // We keep this for upgrade
 
- 		List< ExposedParameter >						serializedParameterList = new List<ExposedParameter>();
 
- 		[SerializeField]
 
- 		public List< StickyNote >						stickyNotes = new List<StickyNote>();
 
- 		[System.NonSerialized]
 
- 		Dictionary< BaseNode, int >						computeOrderDictionary = new Dictionary< BaseNode, int >();
 
- 		[NonSerialized]
 
- 		Scene							linkedScene;
 
- 		// Trick to keep the node inspector alive during the editor session
 
- 		[SerializeField]
 
- 		internal UnityEngine.Object		nodeInspectorReference;
 
- 		//graph visual properties
 
- 		public Vector3					position = Vector3.zero;
 
- 		public Vector3					scale = Vector3.one;
 
- 		/// <summary>
 
- 		/// Triggered when something is changed in the list of exposed parameters
 
- 		/// </summary>
 
- 		public event Action						onExposedParameterListChanged;
 
- 		public event Action< ExposedParameter >	onExposedParameterModified;
 
- 		public event Action< ExposedParameter >	onExposedParameterValueChanged;
 
- 		/// <summary>
 
- 		/// Triggered when the graph is linked to an active scene.
 
- 		/// </summary>
 
- 		public event Action< Scene >			onSceneLinked;
 
- 		/// <summary>
 
- 		/// Triggered when the graph is enabled
 
- 		/// </summary>
 
- 		public event Action				onEnabled;
 
- 		/// <summary>
 
- 		/// Triggered when the graph is changed
 
- 		/// </summary>
 
- 		public event Action< GraphChanges > onGraphChanges;
 
- 		[System.NonSerialized]
 
- 		bool _isEnabled = false;
 
- 		public bool isEnabled { get => _isEnabled; private set => _isEnabled = value; }
 
- 		
 
- 		public HashSet< BaseNode >		graphOutputs { get; private set; } = new HashSet<BaseNode>();
 
-         protected virtual void OnEnable()
 
-         {
 
- 			if (isEnabled)
 
- 				OnDisable();
 
- 			MigrateGraphIfNeeded();
 
- 			InitializeGraphElements();
 
- 			DestroyBrokenGraphElements();
 
- 			UpdateComputeOrder();
 
- 			isEnabled = true;
 
- 			onEnabled?.Invoke();
 
-         }
 
- 		void InitializeGraphElements()
 
- 		{
 
- 			// Sanitize the element lists (it's possible that nodes are null if their full class name have changed)
 
- 			// If you rename / change the assembly of a node or parameter, please use the MovedFrom() attribute to avoid breaking the graph.
 
- 			nodes.RemoveAll(n => n == null);
 
- 			exposedParameters.RemoveAll(e => e == null);
 
- 			foreach (var node in nodes.ToList())
 
- 			{
 
- 				nodesPerGUID[node.GUID] = node;
 
- 				node.Initialize(this);
 
- 			}
 
- 			foreach (var edge in edges.ToList())
 
- 			{
 
- 				edge.Deserialize();
 
- 				edgesPerGUID[edge.GUID] = edge;
 
- 				// Sanity check for the edge:
 
- 				if (edge.inputPort == null || edge.outputPort == null)
 
- 				{
 
- 					Disconnect(edge.GUID);
 
- 					continue;
 
- 				}
 
- 				// Add the edge to the non-serialized port data
 
- 				edge.inputPort.owner.OnEdgeConnected(edge);
 
- 				edge.outputPort.owner.OnEdgeConnected(edge);
 
- 			}
 
- 		}
 
- 		protected virtual void OnDisable()
 
- 		{
 
- 			isEnabled = false;
 
- 			foreach (var node in nodes)
 
- 				node.DisableInternal();
 
- 		}
 
- 		public virtual void OnAssetDeleted() {}
 
- 		/// <summary>
 
- 		/// Adds a node to the graph
 
- 		/// </summary>
 
- 		/// <param name="node"></param>
 
- 		/// <returns></returns>
 
- 		public BaseNode AddNode(BaseNode node)
 
- 		{
 
- 			nodesPerGUID[node.GUID] = node;
 
- 			nodes.Add(node);
 
- 			node.Initialize(this);
 
- 			onGraphChanges?.Invoke(new GraphChanges{ addedNode = node });
 
- 			return node;
 
- 		}
 
- 		/// <summary>
 
- 		/// Removes a node from the graph
 
- 		/// </summary>
 
- 		/// <param name="node"></param>
 
- 		public void RemoveNode(BaseNode node)
 
- 		{
 
- 			node.DisableInternal();
 
- 			node.DestroyInternal();
 
- 			nodesPerGUID.Remove(node.GUID);
 
- 			nodes.Remove(node);
 
- 			onGraphChanges?.Invoke(new GraphChanges{ removedNode = node });
 
- 		}
 
- 		/// <summary>
 
- 		/// Connect two ports with an edge
 
- 		/// </summary>
 
- 		/// <param name="inputPort">input port</param>
 
- 		/// <param name="outputPort">output port</param>
 
- 		/// <param name="DisconnectInputs">is the edge allowed to disconnect another edge</param>
 
- 		/// <returns>the connecting edge</returns>
 
- 		public SerializableEdge Connect(NodePort inputPort, NodePort outputPort, bool autoDisconnectInputs = true)
 
- 		{
 
- 			var edge = SerializableEdge.CreateNewEdge(this, inputPort, outputPort);
 
- 			
 
- 			//If the input port does not support multi-connection, we remove them
 
- 			if (autoDisconnectInputs && !inputPort.portData.acceptMultipleEdges)
 
- 			{
 
- 				foreach (var e in inputPort.GetEdges().ToList())
 
- 				{
 
- 					// TODO: do not disconnect them if the connected port is the same than the old connected
 
- 					Disconnect(e);
 
- 				}
 
- 			}
 
- 			// same for the output port:
 
- 			if (autoDisconnectInputs && !outputPort.portData.acceptMultipleEdges)
 
- 			{
 
- 				foreach (var e in outputPort.GetEdges().ToList())
 
- 				{
 
- 					// TODO: do not disconnect them if the connected port is the same than the old connected
 
- 					Disconnect(e);
 
- 				}
 
- 			}
 
- 			edges.Add(edge);
 
- 			
 
- 			// Add the edge to the list of connected edges in the nodes
 
- 			inputPort.owner.OnEdgeConnected(edge);
 
- 			outputPort.owner.OnEdgeConnected(edge);
 
- 			onGraphChanges?.Invoke(new GraphChanges{ addedEdge = edge });
 
- 			return edge;
 
- 		}
 
- 		/// <summary>
 
- 		/// Disconnect two ports
 
- 		/// </summary>
 
- 		/// <param name="inputNode">input node</param>
 
- 		/// <param name="inputFieldName">input field name</param>
 
- 		/// <param name="outputNode">output node</param>
 
- 		/// <param name="outputFieldName">output field name</param>
 
- 		public void Disconnect(BaseNode inputNode, string inputFieldName, BaseNode outputNode, string outputFieldName)
 
- 		{
 
- 			edges.RemoveAll(r => {
 
- 				bool remove = r.inputNode == inputNode
 
- 				&& r.outputNode == outputNode
 
- 				&& r.outputFieldName == outputFieldName
 
- 				&& r.inputFieldName == inputFieldName;
 
- 				if (remove)
 
- 				{
 
- 					r.inputNode?.OnEdgeDisconnected(r);
 
- 					r.outputNode?.OnEdgeDisconnected(r);
 
- 					onGraphChanges?.Invoke(new GraphChanges{ removedEdge = r });
 
- 				}
 
- 				return remove;
 
- 			});
 
- 		}
 
- 		/// <summary>
 
- 		/// Disconnect an edge
 
- 		/// </summary>
 
- 		/// <param name="edge"></param>
 
- 		public void Disconnect(SerializableEdge edge) => Disconnect(edge.GUID);
 
- 		/// <summary>
 
- 		/// Disconnect an edge
 
- 		/// </summary>
 
- 		/// <param name="edgeGUID"></param>
 
- 		public void Disconnect(string edgeGUID)
 
- 		{
 
- 			List<(BaseNode, SerializableEdge)> disconnectEvents = new List<(BaseNode, SerializableEdge)>();
 
- 			edges.RemoveAll(r => {
 
- 				if (r.GUID == edgeGUID)
 
- 				{
 
- 					disconnectEvents.Add((r.inputNode, r));
 
- 					disconnectEvents.Add((r.outputNode, r));
 
- 					onGraphChanges?.Invoke(new GraphChanges{ removedEdge = r });
 
- 				}
 
- 				return r.GUID == edgeGUID;
 
- 			});
 
- 			// Delay the edge disconnect event to avoid recursion
 
- 			foreach (var (node, edge) in disconnectEvents)
 
- 				node?.OnEdgeDisconnected(edge);
 
- 		}
 
- 		/// <summary>
 
- 		/// Add a group
 
- 		/// </summary>
 
- 		/// <param name="block"></param>
 
-         public void AddGroup(Group block)
 
-         {
 
-             groups.Add(block);
 
- 			onGraphChanges?.Invoke(new GraphChanges{ addedGroups = block });
 
-         }
 
- 		/// <summary>
 
- 		/// Removes a group
 
- 		/// </summary>
 
- 		/// <param name="block"></param>
 
-         public void RemoveGroup(Group block)
 
-         {
 
-             groups.Remove(block);
 
- 			onGraphChanges?.Invoke(new GraphChanges{ removedGroups = block });
 
-         }
 
- 		/// <summary>
 
- 		/// Add a StackNode
 
- 		/// </summary>
 
- 		/// <param name="stackNode"></param>
 
- 		public void AddStackNode(BaseStackNode stackNode)
 
- 		{
 
- 			stackNodes.Add(stackNode);
 
- 			onGraphChanges?.Invoke(new GraphChanges{ addedStackNode = stackNode });
 
- 		}
 
- 		
 
- 		/// <summary>
 
- 		/// Remove a StackNode
 
- 		/// </summary>
 
- 		/// <param name="stackNode"></param>
 
- 		public void RemoveStackNode(BaseStackNode stackNode)
 
- 		{
 
- 			stackNodes.Remove(stackNode);
 
- 			onGraphChanges?.Invoke(new GraphChanges{ removedStackNode = stackNode });
 
- 		}
 
- 		/// <summary>
 
- 		/// Add a sticky note 
 
- 		/// </summary>
 
- 		/// <param name="note"></param>
 
-         public void AddStickyNote(StickyNote note)
 
-         {
 
-             stickyNotes.Add(note);
 
- 			onGraphChanges?.Invoke(new GraphChanges{ addedStickyNotes = note });
 
-         }
 
- 		/// <summary>
 
- 		/// Removes a sticky note 
 
- 		/// </summary>
 
- 		/// <param name="note"></param>
 
-         public void RemoveStickyNote(StickyNote note)
 
-         {
 
-             stickyNotes.Remove(note);
 
- 			onGraphChanges?.Invoke(new GraphChanges{ removedStickyNotes = note });
 
-         }
 
- 		/// <summary>
 
- 		/// Invoke the onGraphChanges event, can be used as trigger to execute the graph when the content of a node is changed 
 
- 		/// </summary>
 
- 		/// <param name="node"></param>
 
- 		public void NotifyNodeChanged(BaseNode node) => onGraphChanges?.Invoke(new GraphChanges { nodeChanged = node });
 
- 		/// <summary>
 
- 		/// Open a pinned element of type viewType
 
- 		/// </summary>
 
- 		/// <param name="viewType">type of the pinned element</param>
 
- 		/// <returns>the pinned element</returns>
 
- 		public PinnedElement OpenPinned(Type viewType)
 
- 		{
 
- 			var pinned = pinnedElements.Find(p => p.editorType.type == viewType);
 
- 			if (pinned == null)
 
- 			{
 
- 				pinned = new PinnedElement(viewType);
 
- 				pinnedElements.Add(pinned);
 
- 			}
 
- 			else
 
- 				pinned.opened = true;
 
- 			return pinned;
 
- 		}
 
- 		/// <summary>
 
- 		/// Closes a pinned element of type viewType
 
- 		/// </summary>
 
- 		/// <param name="viewType">type of the pinned element</param>
 
- 		public void ClosePinned(Type viewType)
 
- 		{
 
- 			var pinned = pinnedElements.Find(p => p.editorType.type == viewType);
 
- 			pinned.opened = false;
 
- 		}
 
- 		public void OnBeforeSerialize()
 
- 		{
 
- 			// Cleanup broken elements
 
- 			stackNodes.RemoveAll(s => s == null);
 
- 			nodes.RemoveAll(n => n == null);
 
- 		}
 
- 		// We can deserialize data here because it's called in a unity context
 
- 		// so we can load objects references
 
- 		public void Deserialize()
 
- 		{
 
- 			// Disable nodes correctly before removing them:
 
- 			if (nodes != null)
 
- 			{
 
- 				foreach (var node in nodes)
 
- 					node.DisableInternal();
 
- 			}
 
- 			MigrateGraphIfNeeded();
 
- 			InitializeGraphElements();
 
- 		}
 
- 		public void MigrateGraphIfNeeded()
 
- 		{
 
- #pragma warning disable CS0618
 
- 			// Migration step from JSON serialized nodes to [SerializeReference]
 
- 			if (serializedNodes.Count > 0)
 
- 			{
 
- 				nodes.Clear();
 
- 				foreach (var serializedNode in serializedNodes.ToList())
 
- 				{
 
-                     var node = JsonSerializer.DeserializeNode(serializedNode) as BaseNode;
 
-                     if (node != null)
 
-                         nodes.Add(node);
 
- 				}
 
- 				serializedNodes.Clear();
 
- 				// we also migrate parameters here:
 
- 				var paramsToMigrate = serializedParameterList.ToList();
 
- 				exposedParameters.Clear();
 
- 				foreach (var param in paramsToMigrate)
 
- 				{
 
- 					if (param == null)
 
- 						continue;
 
- 					var newParam = param.Migrate();
 
- 					if (newParam == null)
 
- 					{
 
- 						Debug.LogError($"Can't migrate parameter of type {param.type}, please create an Exposed Parameter class that implements this type.");
 
- 						continue;
 
- 					}
 
- 					else
 
- 						exposedParameters.Add(newParam);
 
- 				}
 
- 			}
 
- #pragma warning restore CS0618
 
- 		}
 
- 		public void OnAfterDeserialize() {}
 
- 		/// <summary>
 
- 		/// Update the compute order of the nodes in the graph
 
- 		/// </summary>
 
- 		/// <param name="type">Compute order type</param>
 
- 		public void UpdateComputeOrder(ComputeOrderType type = ComputeOrderType.DepthFirst)
 
- 		{
 
- 			if (nodes.Count == 0)
 
- 				return ;
 
- 			// Find graph outputs (end nodes) and reset compute order
 
- 			graphOutputs.Clear();
 
- 			foreach (var node in nodes)
 
- 			{
 
- 				if (node.GetOutputNodes().Count() == 0)
 
- 					graphOutputs.Add(node);
 
- 				node.computeOrder = 0;
 
- 			}
 
- 			computeOrderDictionary.Clear();
 
- 			infiniteLoopTracker.Clear();
 
- 			switch (type)
 
- 			{
 
- 				default:
 
- 				case ComputeOrderType.DepthFirst:
 
- 					UpdateComputeOrderDepthFirst();
 
- 					break;
 
- 				case ComputeOrderType.BreadthFirst:
 
- 					foreach (var node in nodes)
 
- 						UpdateComputeOrderBreadthFirst(0, node);
 
- 					break;
 
- 			}
 
- 		}
 
- 		/// <summary>
 
- 		/// Add an exposed parameter
 
- 		/// </summary>
 
- 		/// <param name="name">parameter name</param>
 
- 		/// <param name="type">parameter type (must be a subclass of ExposedParameter)</param>
 
- 		/// <param name="value">default value</param>
 
- 		/// <returns>The unique id of the parameter</returns>
 
- 		public string AddExposedParameter(string name, Type type, object value = null)
 
- 		{
 
- 			if (!type.IsSubclassOf(typeof(ExposedParameter)))
 
- 			{
 
- 				Debug.LogError($"Can't add parameter of type {type}, the type doesn't inherit from ExposedParameter.");
 
- 			}
 
- 			var param = Activator.CreateInstance(type) as ExposedParameter;
 
- 			// patch value with correct type:
 
- 			if (param.GetValueType().IsValueType)
 
- 				value = Activator.CreateInstance(param.GetValueType());
 
- 			
 
- 			param.Initialize(name, value);
 
- 			exposedParameters.Add(param);
 
- 			onExposedParameterListChanged?.Invoke();
 
- 			return param.guid;
 
- 		}
 
- 		/// <summary>
 
- 		/// Add an already allocated / initialized parameter to the graph
 
- 		/// </summary>
 
- 		/// <param name="parameter">The parameter to add</param>
 
- 		/// <returns>The unique id of the parameter</returns>
 
- 		public string AddExposedParameter(ExposedParameter parameter)
 
- 		{
 
- 			string guid = Guid.NewGuid().ToString(); // Generated once and unique per parameter
 
- 			parameter.guid = guid;
 
- 			exposedParameters.Add(parameter);
 
- 			onExposedParameterListChanged?.Invoke();
 
- 			return guid;
 
- 		}
 
- 		/// <summary>
 
- 		/// Remove an exposed parameter
 
- 		/// </summary>
 
- 		/// <param name="ep">the parameter to remove</param>
 
- 		public void RemoveExposedParameter(ExposedParameter ep)
 
- 		{
 
- 			exposedParameters.Remove(ep);
 
- 			onExposedParameterListChanged?.Invoke();
 
- 		}
 
- 		/// <summary>
 
- 		/// Remove an exposed parameter
 
- 		/// </summary>
 
- 		/// <param name="guid">GUID of the parameter</param>
 
- 		public void RemoveExposedParameter(string guid)
 
- 		{
 
- 			if (exposedParameters.RemoveAll(e => e.guid == guid) != 0)
 
- 				onExposedParameterListChanged?.Invoke();
 
- 		}
 
- 		internal void NotifyExposedParameterListChanged()
 
- 			=> onExposedParameterListChanged?.Invoke();
 
- 		/// <summary>
 
- 		/// Update an exposed parameter value
 
- 		/// </summary>
 
- 		/// <param name="guid">GUID of the parameter</param>
 
- 		/// <param name="value">new value</param>
 
- 		public void UpdateExposedParameter(string guid, object value)
 
- 		{
 
- 			var param = exposedParameters.Find(e => e.guid == guid);
 
- 			if (param == null)
 
- 				return;
 
- 			if (value != null && !param.GetValueType().IsAssignableFrom(value.GetType()))
 
- 				throw new Exception("Type mismatch when updating parameter " + param.name + ": from " + param.GetValueType() + " to " + value.GetType().AssemblyQualifiedName);
 
- 			param.value = value;
 
- 			onExposedParameterModified?.Invoke(param);
 
- 		}
 
- 		/// <summary>
 
- 		/// Update the exposed parameter name
 
- 		/// </summary>
 
- 		/// <param name="parameter">The parameter</param>
 
- 		/// <param name="name">new name</param>
 
- 		public void UpdateExposedParameterName(ExposedParameter parameter, string name)
 
- 		{
 
- 			parameter.name = name;
 
- 			onExposedParameterModified?.Invoke(parameter);
 
- 		}
 
- 		/// <summary>
 
- 		/// Update parameter visibility
 
- 		/// </summary>
 
- 		/// <param name="parameter">The parameter</param>
 
- 		/// <param name="isHidden">is Hidden</param>
 
- 		public void NotifyExposedParameterChanged(ExposedParameter parameter)
 
- 		{
 
- 			onExposedParameterModified?.Invoke(parameter);
 
- 		}
 
- 		public void NotifyExposedParameterValueChanged(ExposedParameter parameter)
 
- 		{
 
- 			onExposedParameterValueChanged?.Invoke(parameter);
 
- 		}
 
- 		/// <summary>
 
- 		/// Get the exposed parameter from name
 
- 		/// </summary>
 
- 		/// <param name="name">name</param>
 
- 		/// <returns>the parameter or null</returns>
 
- 		public ExposedParameter GetExposedParameter(string name)
 
- 		{
 
- 			return exposedParameters.FirstOrDefault(e => e.name == name);
 
- 		}
 
- 		/// <summary>
 
- 		/// Get exposed parameter from GUID
 
- 		/// </summary>
 
- 		/// <param name="guid">GUID of the parameter</param>
 
- 		/// <returns>The parameter</returns>
 
- 		public ExposedParameter GetExposedParameterFromGUID(string guid)
 
- 		{
 
- 			return exposedParameters.FirstOrDefault(e => e?.guid == guid);
 
- 		}
 
- 		/// <summary>
 
- 		/// Set parameter value from name. (Warning: the parameter name can be changed by the user)
 
- 		/// </summary>
 
- 		/// <param name="name">name of the parameter</param>
 
- 		/// <param name="value">new value</param>
 
- 		/// <returns>true if the value have been assigned</returns>
 
- 		public bool SetParameterValue(string name, object value)
 
- 		{
 
- 			var e = exposedParameters.FirstOrDefault(p => p.name == name);
 
- 			if (e == null)
 
- 				return false;
 
- 			e.value = value;
 
- 			return true;
 
- 		}
 
- 		/// <summary>
 
- 		/// Get the parameter value
 
- 		/// </summary>
 
- 		/// <param name="name">parameter name</param>
 
- 		/// <returns>value</returns>
 
- 		public object GetParameterValue(string name) => exposedParameters.FirstOrDefault(p => p.name == name)?.value;
 
- 		/// <summary>
 
- 		/// Get the parameter value template
 
- 		/// </summary>
 
- 		/// <param name="name">parameter name</param>
 
- 		/// <typeparam name="T">type of the parameter</typeparam>
 
- 		/// <returns>value</returns>
 
- 		public T GetParameterValue< T >(string name) => (T)GetParameterValue(name);
 
- 		/// <summary>
 
- 		/// Link the current graph to the scene in parameter, allowing the graph to pick and serialize objects from the scene.
 
- 		/// </summary>
 
- 		/// <param name="scene">Target scene to link</param>
 
- 		public void LinkToScene(Scene scene)
 
- 		{
 
- 			linkedScene = scene;
 
- 			onSceneLinked?.Invoke(scene);
 
- 		}
 
- 		/// <summary>
 
- 		/// Return true when the graph is linked to a scene, false otherwise.
 
- 		/// </summary>
 
- 		public bool IsLinkedToScene() => linkedScene.IsValid();
 
- 		/// <summary>
 
- 		/// Get the linked scene. If there is no linked scene, it returns an invalid scene
 
- 		/// </summary>
 
- 		public Scene GetLinkedScene() => linkedScene;
 
- 		HashSet<BaseNode> infiniteLoopTracker = new HashSet<BaseNode>();
 
- 		int UpdateComputeOrderBreadthFirst(int depth, BaseNode node)
 
- 		{
 
- 			int computeOrder = 0;
 
- 			if (depth > maxComputeOrderDepth)
 
- 			{
 
- 				Debug.LogError("Recursion error while updating compute order");
 
- 				return -1;
 
- 			}
 
- 			if (computeOrderDictionary.ContainsKey(node))
 
- 				return node.computeOrder;
 
- 			if (!infiniteLoopTracker.Add(node))
 
- 				return -1;
 
- 			if (!node.canProcess)
 
- 			{
 
- 				node.computeOrder = -1;
 
- 				computeOrderDictionary[node] = -1;
 
- 				return -1;
 
- 			}
 
- 			foreach (var dep in node.GetInputNodes())
 
- 			{
 
- 				int c = UpdateComputeOrderBreadthFirst(depth + 1, dep);
 
- 				if (c == -1)
 
- 				{
 
- 					computeOrder = -1;
 
- 					break ;
 
- 				}
 
- 				computeOrder += c;
 
- 			}
 
- 			if (computeOrder != -1)
 
- 				computeOrder++;
 
- 			node.computeOrder = computeOrder;
 
- 			computeOrderDictionary[node] = computeOrder;
 
- 			return computeOrder;
 
- 		}
 
- 		void UpdateComputeOrderDepthFirst()
 
- 		{
 
- 			Stack<BaseNode> dfs = new Stack<BaseNode>();
 
- 			GraphUtils.FindCyclesInGraph(this, (n) => {
 
- 				PropagateComputeOrder(n, loopComputeOrder);
 
- 			});
 
- 			int computeOrder = 0;
 
- 			foreach (var node in GraphUtils.DepthFirstSort(this))
 
- 			{
 
- 				if (node.computeOrder == loopComputeOrder)
 
- 					continue;
 
- 				if (!node.canProcess)
 
- 					node.computeOrder = -1;
 
- 				else
 
- 					node.computeOrder = computeOrder++;
 
- 			}
 
- 		}
 
- 		void PropagateComputeOrder(BaseNode node, int computeOrder)
 
- 		{
 
- 			Stack<BaseNode> deps = new Stack<BaseNode>();
 
- 			HashSet<BaseNode> loop = new HashSet<BaseNode>();
 
- 			deps.Push(node);
 
- 			while (deps.Count > 0)
 
- 			{
 
- 				var n = deps.Pop();
 
- 				n.computeOrder = computeOrder;
 
- 			
 
- 				if (!loop.Add(n))
 
- 					continue;
 
- 				foreach (var dep in n.GetOutputNodes())
 
- 					deps.Push(dep);
 
- 			}
 
- 		}
 
- 		void DestroyBrokenGraphElements()
 
- 		{
 
- 			edges.RemoveAll(e => e.inputNode == null
 
- 				|| e.outputNode == null
 
- 				|| string.IsNullOrEmpty(e.outputFieldName)
 
- 				|| string.IsNullOrEmpty(e.inputFieldName)
 
- 			);
 
- 			nodes.RemoveAll(n => n == null);
 
- 		}
 
- 		
 
- 		/// <summary>
 
- 		/// Tell if two types can be connected in the context of a graph
 
- 		/// </summary>
 
- 		/// <param name="t1"></param>
 
- 		/// <param name="t2"></param>
 
- 		/// <returns></returns>
 
- 		public static bool TypesAreConnectable(Type t1, Type t2)
 
- 		{
 
- 			if (t1 == null || t2 == null)
 
- 				return false;
 
- 			if (TypeAdapter.AreIncompatible(t1, t2))
 
- 				return false;
 
- 			//Check if there is custom adapters for this assignation
 
- 			if (CustomPortIO.IsAssignable(t1, t2))
 
- 				return true;
 
- 			//Check for type assignability
 
- 			if (t2.IsReallyAssignableFrom(t1))
 
- 				return true;
 
- 			// User defined type convertions
 
- 			if (TypeAdapter.AreAssignable(t1, t2))
 
- 				return true;
 
- 			return false;
 
- 		}
 
- 	}
 
- }
 
 
  |