| 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;		}	}}
 |