diff --git a/Assets/Examples/BasicExample.asset b/Assets/Examples/BasicExample.asset
index df80ce1f..5342cefc 100644
--- a/Assets/Examples/BasicExample.asset
+++ b/Assets/Examples/BasicExample.asset
@@ -14,30 +14,31 @@ MonoBehaviour:
   m_EditorClassIdentifier: 
   serializedNodes: []
   nodes:
-  - id: 0
-  - id: 1
-  - id: 2
-  - id: 3
-  - id: 4
-  - id: 5
-  - id: 6
-  - id: 7
-  - id: 8
-  - id: 9
-  - id: 10
-  - id: 11
-  - id: 12
-  - id: 13
-  - id: 14
-  - id: 15
-  - id: 16
-  - id: 17
-  - id: 18
-  - id: 19
-  - id: 20
-  - id: 21
-  - id: 22
-  - id: 23
+  - rid: 0
+  - rid: 1
+  - rid: 2
+  - rid: 3
+  - rid: 4
+  - rid: 5
+  - rid: 6
+  - rid: 7
+  - rid: 8
+  - rid: 9
+  - rid: 10
+  - rid: 11
+  - rid: 12
+  - rid: 13
+  - rid: 14
+  - rid: 15
+  - rid: 16
+  - rid: 17
+  - rid: 18
+  - rid: 19
+  - rid: 20
+  - rid: 21
+  - rid: 22
+  - rid: 23
+  - rid: 3708072270747926535
   edges:
   - GUID: 04cee6c7-b233-40e1-b41a-31f6093f1482
     owner: {fileID: 11400000}
@@ -165,7 +166,7 @@ MonoBehaviour:
     size: {x: 300, y: 100}
     innerNodeGUIDs: []
   stackNodes:
-  - id: 24
+  - rid: 24
   pinnedElements:
   - position:
       serializedVersion: 2
@@ -208,7 +209,7 @@ MonoBehaviour:
       serializedType: GraphProcessor.ProcessorView, com.alelievr.NodeGraphProcessor.Editor,
         Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
   exposedParameters:
-  - id: 25
+  - rid: 25
   serializedParameterList:
   - guid: 
     name: 
@@ -219,7 +220,7 @@ MonoBehaviour:
       serializedValue: 
     input: 1
     settings:
-      id: 26
+      rid: -2
   - guid: 
     name: 
     type: 
@@ -229,7 +230,7 @@ MonoBehaviour:
       serializedValue: 
     input: 1
     settings:
-      id: 26
+      rid: -2
   - guid: 
     name: 
     type: 
@@ -239,7 +240,7 @@ MonoBehaviour:
       serializedValue: 
     input: 1
     settings:
-      id: 26
+      rid: -2
   - guid: 
     name: 
     type: 
@@ -249,7 +250,7 @@ MonoBehaviour:
       serializedValue: 
     input: 1
     settings:
-      id: 26
+      rid: -2
   - guid: 
     name: 
     type: 
@@ -259,7 +260,7 @@ MonoBehaviour:
       serializedValue: 
     input: 1
     settings:
-      id: 26
+      rid: -2
   - guid: 
     name: 
     type: 
@@ -269,7 +270,7 @@ MonoBehaviour:
       serializedValue: 
     input: 1
     settings:
-      id: 26
+      rid: -2
   - guid: 
     name: 
     type: 
@@ -279,7 +280,7 @@ MonoBehaviour:
       serializedValue: 
     input: 1
     settings:
-      id: 26
+      rid: -2
   - guid: 
     name: 
     type: 
@@ -289,7 +290,7 @@ MonoBehaviour:
       serializedValue: 
     input: 1
     settings:
-      id: 26
+      rid: -2
   - guid: 
     name: 
     type: 
@@ -299,7 +300,7 @@ MonoBehaviour:
       serializedValue: 
     input: 1
     settings:
-      id: 26
+      rid: -2
   stickyNotes:
   - position:
       serializedVersion: 2
@@ -318,11 +319,14 @@ MonoBehaviour:
     title: New Sticky Note
     content: Write your text here
   nodeInspectorReference: {fileID: 0}
-  position: {x: 781, y: -9.999998, z: 0}
-  scale: {x: 0.7561437, y: 0.7561437, z: 1}
+  position: {x: 854, y: -5, z: 0}
+  scale: {x: 0.65751624, y: 0.65751624, z: 1}
   references:
-    version: 1
-    00000000:
+    version: 2
+    RefIds:
+    - rid: -2
+      type: {class: , ns: , asm: }
+    - rid: 0
       type: {class: ColorNode, ns: , asm: Assembly-CSharp}
       data:
         nodeCustomName: 
@@ -338,7 +342,7 @@ MonoBehaviour:
         debug: 0
         nodeLock: 0
         color: {r: 1, g: 0, b: 0.25098038, a: 1}
-    00000001:
+    - rid: 1
       type: {class: FloatNode, ns: , asm: Assembly-CSharp}
       data:
         nodeCustomName: 
@@ -355,7 +359,7 @@ MonoBehaviour:
         nodeLock: 0
         output: 10
         input: 10
-    00000002:
+    - rid: 2
       type: {class: MultiAddNode, ns: , asm: Assembly-CSharp}
       data:
         nodeCustomName: 
@@ -371,7 +375,7 @@ MonoBehaviour:
         debug: 1
         nodeLock: 0
         output: 10
-    00000003:
+    - rid: 3
       type: {class: SubNode, ns: , asm: Assembly-CSharp}
       data:
         nodeCustomName: 
@@ -389,7 +393,7 @@ MonoBehaviour:
         inputA: 0
         inputB: 140
         output: -140
-    00000004:
+    - rid: 4
       type: {class: PrintNode, ns: , asm: Assembly-CSharp}
       data:
         nodeCustomName: 
@@ -404,7 +408,7 @@ MonoBehaviour:
         expanded: 0
         debug: 1
         nodeLock: 0
-    00000005:
+    - rid: 5
       type: {class: PrintNode, ns: , asm: Assembly-CSharp}
       data:
         nodeCustomName: 
@@ -419,7 +423,7 @@ MonoBehaviour:
         expanded: 0
         debug: 0
         nodeLock: 0
-    00000006:
+    - rid: 6
       type: {class: PrefabNode, ns: , asm: Assembly-CSharp}
       data:
         nodeCustomName: 
@@ -436,7 +440,7 @@ MonoBehaviour:
         nodeLock: 0
         output: {fileID: 1636575971871760, guid: f78111bdbdeaf6644806fc49fcaf1d30,
           type: 3}
-    00000007:
+    - rid: 7
       type: {class: FloatNode, ns: , asm: Assembly-CSharp}
       data:
         nodeCustomName: 
@@ -453,7 +457,7 @@ MonoBehaviour:
         nodeLock: 0
         output: 140
         input: 140
-    00000008:
+    - rid: 8
       type: {class: TextNode, ns: , asm: Assembly-CSharp}
       data:
         nodeCustomName: 
@@ -469,7 +473,7 @@ MonoBehaviour:
         debug: 0
         nodeLock: 0
         output: Hello World
-    00000009:
+    - rid: 9
       type: {class: PrintNode, ns: , asm: Assembly-CSharp}
       data:
         nodeCustomName: 
@@ -484,7 +488,7 @@ MonoBehaviour:
         expanded: 0
         debug: 0
         nodeLock: 0
-    0000000A:
+    - rid: 10
       type: {class: FloatNode, ns: , asm: Assembly-CSharp}
       data:
         nodeCustomName: 
@@ -501,7 +505,7 @@ MonoBehaviour:
         nodeLock: 0
         output: 140
         input: 140
-    0000000B:
+    - rid: 11
       type: {class: SettingsNode, ns: , asm: Assembly-CSharp}
       data:
         nodeCustomName: 
@@ -519,7 +523,7 @@ MonoBehaviour:
         setting: 0
         input: 0
         output: 0
-    0000000C:
+    - rid: 12
       type: {class: PrintNode, ns: , asm: Assembly-CSharp}
       data:
         nodeCustomName: 
@@ -534,7 +538,7 @@ MonoBehaviour:
         expanded: 0
         debug: 0
         nodeLock: 0
-    0000000D:
+    - rid: 13
       type: {class: FloatNode, ns: , asm: Assembly-CSharp}
       data:
         nodeCustomName: 
@@ -551,7 +555,7 @@ MonoBehaviour:
         nodeLock: 0
         output: 0
         input: 0
-    0000000E:
+    - rid: 14
       type: {class: CustomPortData, ns: , asm: Assembly-CSharp}
       data:
         nodeCustomName: 
@@ -567,7 +571,7 @@ MonoBehaviour:
         debug: 0
         nodeLock: 0
         output: 0
-    0000000F:
+    - rid: 15
       type: {class: PrintNode, ns: , asm: Assembly-CSharp}
       data:
         nodeCustomName: 
@@ -582,7 +586,7 @@ MonoBehaviour:
         expanded: 0
         debug: 0
         nodeLock: 0
-    00000010:
+    - rid: 16
       type: {class: PortConnectionTests, ns: , asm: Assembly-CSharp}
       data:
         nodeCustomName: 
@@ -598,7 +602,7 @@ MonoBehaviour:
         debug: 0
         nodeLock: 0
         padding: 0
-    00000011:
+    - rid: 17
       type: {class: PortConnectionTests, ns: , asm: Assembly-CSharp}
       data:
         nodeCustomName: 
@@ -614,7 +618,7 @@ MonoBehaviour:
         debug: 0
         nodeLock: 0
         padding: 0
-    00000012:
+    - rid: 18
       type: {class: DrawerFieldTestNode, ns: , asm: Assembly-CSharp}
       data:
         nodeCustomName: 
@@ -650,7 +654,7 @@ MonoBehaviour:
         layerMask:
           serializedVersion: 2
           m_Bits: 0
-    00000013:
+    - rid: 19
       type: {class: InspectorNode, ns: , asm: Assembly-CSharp}
       data:
         nodeCustomName: 
@@ -669,7 +673,7 @@ MonoBehaviour:
         output: 0
         additionalSettings: 0
         additionalParam: 
-    00000014:
+    - rid: 20
       type: {class: DrawerFieldTestNode, ns: , asm: Assembly-CSharp}
       data:
         nodeCustomName: 
@@ -705,7 +709,7 @@ MonoBehaviour:
         layerMask:
           serializedVersion: 2
           m_Bits: 0
-    00000015:
+    - rid: 21
       type: {class: FloatNode, ns: , asm: Assembly-CSharp}
       data:
         nodeCustomName: 
@@ -722,7 +726,7 @@ MonoBehaviour:
         nodeLock: 0
         output: 42
         input: 42
-    00000016:
+    - rid: 22
       type: {class: ColorNode, ns: , asm: Assembly-CSharp}
       data:
         nodeCustomName: 
@@ -738,7 +742,7 @@ MonoBehaviour:
         debug: 0
         nodeLock: 0
         color: {r: 0.47539377, g: 0, b: 1, a: 0}
-    00000017:
+    - rid: 23
       type: {class: MultiAddNode, ns: , asm: Assembly-CSharp}
       data:
         nodeCustomName: 
@@ -754,7 +758,7 @@ MonoBehaviour:
         debug: 0
         nodeLock: 0
         output: 0
-    00000018:
+    - rid: 24
       type: {class: BaseStackNode, ns: GraphProcessor, asm: com.alelievr.NodeGraphProcessor.Runtime}
       data:
         position: {x: 914.0975, y: 6.5799994}
@@ -765,7 +769,7 @@ MonoBehaviour:
         - ffd2cf4b-87c3-42a6-9822-04bae7a5700b
         - 42eb43b8-ac7e-4f38-b49b-26ba1bc42732
         - e99da4fb-6a11-4b19-8594-f37f55d96114
-    00000019:
+    - rid: 25
       type: {class: FloatParameter, ns: GraphProcessor, asm: com.alelievr.NodeGraphProcessor.Runtime}
       data:
         guid: eb80df62-f248-4ec9-afd4-f9ed08bfaa16
@@ -777,11 +781,9 @@ MonoBehaviour:
           serializedValue: 
         input: 1
         settings:
-          id: 27
+          rid: 27
         val: 0
-    0000001A:
-      type: {class: , ns: , asm: }
-    0000001B:
+    - rid: 27
       type: {class: FloatParameter/FloatSettings, ns: GraphProcessor, asm: com.alelievr.NodeGraphProcessor.Runtime}
       data:
         isHidden: 0
@@ -790,3 +792,24 @@ MonoBehaviour:
         mode: 0
         min: 0
         max: 1
+    - rid: 3708072270747926535
+      type: {class: NamerNode, ns: , asm: Assembly-CSharp}
+      data:
+        nodeCustomName: 
+        GUID: c8d89035-6a0f-4046-8735-16c760df589b
+        computeOrder: 24
+        position:
+          serializedVersion: 2
+          x: -507.4715
+          y: 334.4306
+          width: 180
+          height: 156
+        expanded: 0
+        debug: 0
+        nodeLock: 0
+        data:
+          name: esfesfesf
+          value: 0
+        dataOutput:
+          name: esfesfesf
+          value: 0
diff --git a/Assets/Examples/DefaultNodes/Nodes/CustomParameterNodes.meta b/Assets/Examples/DefaultNodes/Nodes/CustomParameterNodes.meta
new file mode 100644
index 00000000..b706f84e
--- /dev/null
+++ b/Assets/Examples/DefaultNodes/Nodes/CustomParameterNodes.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: d88b06b27152b997c9a1ce2e708582d8
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/Examples/DefaultNodes/Nodes/CustomParameterNodes/CustomParameterNode.cs b/Assets/Examples/DefaultNodes/Nodes/CustomParameterNodes/CustomParameterNode.cs
new file mode 100644
index 00000000..fe874c29
--- /dev/null
+++ b/Assets/Examples/DefaultNodes/Nodes/CustomParameterNodes/CustomParameterNode.cs
@@ -0,0 +1,14 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using GraphProcessor;
+using System.Linq;
+
+[System.Serializable, NodeMenuItem("Custom/CustomParameterNode")]
+public class CustomParameterNode : ParameterNode
+{
+    protected override IEnumerable<PortData> GetOutputPort(List<SerializableEdge> edges)
+    {
+        return new List<PortData>();
+    }
+}
diff --git a/Assets/Examples/DefaultNodes/Nodes/CustomParameterNodes/CustomParameterNode.cs.meta b/Assets/Examples/DefaultNodes/Nodes/CustomParameterNodes/CustomParameterNode.cs.meta
new file mode 100644
index 00000000..22073daa
--- /dev/null
+++ b/Assets/Examples/DefaultNodes/Nodes/CustomParameterNodes/CustomParameterNode.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: e0eb73353d256c38fb33d5cb258c6802
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/Examples/DefaultNodes/Nodes/CustomParameterNodes/MyFloatParam.cs b/Assets/Examples/DefaultNodes/Nodes/CustomParameterNodes/MyFloatParam.cs
new file mode 100644
index 00000000..0e0a7513
--- /dev/null
+++ b/Assets/Examples/DefaultNodes/Nodes/CustomParameterNodes/MyFloatParam.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using GraphProcessor;
+using UnityEngine;
+
+[System.Serializable]
+public class MyFloatParam : FloatParameter
+{
+    public override Type CustomParameterNodeType => typeof(CustomParameterNode);
+}
diff --git a/Assets/Examples/DefaultNodes/Nodes/CustomParameterNodes/MyFloatParam.cs.meta b/Assets/Examples/DefaultNodes/Nodes/CustomParameterNodes/MyFloatParam.cs.meta
new file mode 100644
index 00000000..eff06fa9
--- /dev/null
+++ b/Assets/Examples/DefaultNodes/Nodes/CustomParameterNodes/MyFloatParam.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: cbf4b2e7f74fbc94b89db62700f7d7f0
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/Examples/DefaultNodes/Nodes/DrawerFieldTestNode.cs b/Assets/Examples/DefaultNodes/Nodes/DrawerFieldTestNode.cs
index b30fd069..5753595c 100644
--- a/Assets/Examples/DefaultNodes/Nodes/DrawerFieldTestNode.cs
+++ b/Assets/Examples/DefaultNodes/Nodes/DrawerFieldTestNode.cs
@@ -8,49 +8,49 @@
 public class DrawerFieldTestNode : BaseNode
 {
 
-	[Input(name = "Vector 4"), ShowAsDrawer]
-	public Vector4 vector4;
+    [Input(name = "Vector 4"), ShowAsDrawer]
+    public Vector4 vector4;
 
-	[Input(name = "Vector 3"), ShowAsDrawer]
-	public Vector3 vector3;
+    [Input(name = "Vector 3"), ShowAsDrawer]
+    public Vector3 vector3;
 
-	[Input(name = "Vector 2"), ShowAsDrawer]
-	public Vector2 vector2;
+    [Input(name = "Vector 2"), ShowAsDrawer]
+    public Vector2 vector2;
 
-	[Input(name = "Float"), ShowAsDrawer]
-	public float floatInput;
+    [Input(name = "Float"), ShowAsDrawer]
+    public float floatInput;
 
-	[Input(name = "Vector 3 Int"), ShowAsDrawer]
-	public Vector3Int vector3Int;
+    [Input(name = "Vector 3 Int"), ShowAsDrawer]
+    public Vector3Int vector3Int;
 
-	[Input(name = "Vector 2 Int"), ShowAsDrawer]
-	public Vector2Int vector2Int;
+    [Input(name = "Vector 2 Int"), ShowAsDrawer]
+    public Vector2Int vector2Int;
 
-	[Input(name = "Int"), ShowAsDrawer]
-	public int intInput;
+    [Input(name = "Int"), ShowAsDrawer]
+    public int intInput;
 
-	[Input(name = "Empty")]
-	public int intInput2;
+    [Input(name = "Empty")]
+    public int intInput2;
 
-	[Input(name = "String"), ShowAsDrawer]
-	public string stringInput;
+    [Input(name = "String"), ShowAsDrawer]
+    public string stringInput;
 
-	[Input(name = "Color"), ShowAsDrawer]
-	new public Color color;
+    [Input(name = "Color"), ShowAsDrawer]
+    new public Color color;
 
-	[Input(name = "Game Object"), ShowAsDrawer]
-	public GameObject gameObject;
+    [Input(name = "Game Object"), ShowAsDrawer]
+    public GameObject gameObject;
 
-	[Input(name = "Animation Curve"), ShowAsDrawer]
-	public AnimationCurve animationCurve;
+    [Input(name = "Animation Curve"), ShowAsDrawer]
+    public AnimationCurve animationCurve;
 
-	[Input(name = "Rigidbody"), ShowAsDrawer]
-	public Rigidbody rigidbody;
+    [Input(name = "Rigidbody"), ShowAsDrawer]
+    public Rigidbody rigidbody;
 
-	[Input("Layer Mask"), ShowAsDrawer]
-	public LayerMask layerMask;
+    [Input("Layer Mask"), ShowAsDrawer]
+    public LayerMask layerMask;
 
-	public override string name => "Drawer Field Test";
+    public override string name => "Drawer Field Test";
 
-	protected override void Process() {}
+    protected override void Process() { }
 }
\ No newline at end of file
diff --git a/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration.meta b/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration.meta
new file mode 100644
index 00000000..0c62d05a
--- /dev/null
+++ b/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: d1aa8d1481699370f82eb69770a82239
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/DynamicNode.cs b/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/DynamicNode.cs
new file mode 100644
index 00000000..dd9a2e0d
--- /dev/null
+++ b/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/DynamicNode.cs
@@ -0,0 +1,151 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using GraphProcessor;
+using System.Linq;
+using System.Reflection;
+using System;
+
+[System.Serializable]
+public abstract class DynamicNode<T> : BaseNode
+{
+    [Input("Action Data", true)]
+    public Dictionary<string, List<object>> actionData = new Dictionary<string, List<object>>();
+
+    public T data;
+
+    public override bool needsInspector => true;
+
+    protected override void Process()
+    {
+        UpdateActionWithCustomPortData();
+    }
+
+    protected virtual void UpdateActionWithCustomPortData()
+    {
+        // We clone due to reference issues 
+        Dictionary<string, List<object>> actionDataClone = new Dictionary<string, List<object>>(actionData);
+
+        foreach (var field in GetInputFieldsOfType())
+        {
+            if (!actionDataClone.ContainsKey(field.fieldInfo.Name))
+            {
+                if (field.inputAttribute.showAsDrawer || field.fieldInfo.HasCustomAttribute<ShowAsDrawer>())
+                    continue;
+
+                field.fieldInfo.SetValue(data, default);
+                continue;
+            }
+
+            field.fieldInfo.SetValue(data, actionDataClone[field.fieldInfo.Name][0]);
+        }
+
+        actionData.Clear();
+    }
+
+    #region Reflection Generation Of Ports
+
+    private List<FieldPortInfo> GetInputFieldsOfType()
+    {
+        List<FieldPortInfo> foundInputFields = new List<FieldPortInfo>();
+
+        Type dataType = data != null ? data.GetType() : typeof(T);
+        foreach (var field in dataType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))
+        {
+            foreach (var attribute in field.GetCustomAttributes(typeof(InputAttribute), true))
+            {
+                if (attribute.GetType() != typeof(InputAttribute) && !attribute.GetType().IsSubclassOf(typeof(InputAttribute))) continue;
+
+                foundInputFields.Add(new FieldPortInfo(field, attribute as InputAttribute));
+                break;
+            }
+        }
+
+        return foundInputFields;
+    }
+
+    private FieldPortInfo GetFieldPortInfo(string fieldName)
+    {
+        Type dataType = data != null ? data.GetType() : typeof(T);
+
+        FieldInfo fieldInfo = dataType.GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
+        InputAttribute inputAttribute = fieldInfo.GetCustomAttribute<InputAttribute>();
+
+        return new FieldPortInfo(fieldInfo, inputAttribute);
+    }
+
+    [CustomPortInput(nameof(actionData), typeof(object))]
+    protected void PullInputs(List<SerializableEdge> connectedEdges)
+    {
+        if (connectedEdges.Count == 0) return;
+
+        FieldPortInfo field = GetFieldPortInfo(connectedEdges.ElementAt(0).inputPortIdentifier);
+
+        if (actionData == null) actionData = new Dictionary<string, List<object>>();
+        foreach (var edge in connectedEdges)
+        {
+            if (!actionData.ContainsKey(field.fieldInfo.Name))
+                actionData.Add(field.fieldInfo.Name, new List<object>());
+
+            actionData[field.fieldInfo.Name].Add(edge.passThroughBuffer);
+        }
+    }
+
+    [CustomPortBehavior(nameof(actionData))]
+    protected IEnumerable<PortData> ActionDataBehaviour(List<SerializableEdge> edges) // Try changing edge here when ports update  
+    {
+        foreach (var field in GetInputFieldsOfType())
+        {
+            Type displayType = field.fieldInfo.FieldType;
+
+            yield return new PortData
+            {
+                displayName = field.inputAttribute.name,
+                displayType = displayType,
+                identifier = field.fieldInfo.Name,
+                showAsDrawer = field.inputAttribute.showAsDrawer,
+                vertical = false,
+                proxiedFieldPath = nameof(data) + '.' + field.fieldInfo.Name,
+                acceptMultipleEdges = field.inputAttribute.allowMultiple,
+            };
+        }
+
+        // Debug.Log(this.GetCustomName() + " BEHAVE: " + this.inputPorts.Count); 
+    }
+
+    // public override IEnumerable<FieldInfo> OverrideFieldOrder(IEnumerable<FieldInfo> fields)   
+    // {
+    //     return base.OverrideFieldOrder(fields).Reverse();
+
+    //     // static long GetFieldInheritanceLevel(FieldInfo f)
+    //     // {
+    //     //     int level = 0;
+    //     //     var t = f.DeclaringType;
+    //     //     while (t != null)
+    //     //     {
+    //     //         t = t.BaseType;
+    //     //         level++;
+    //     //     }
+
+    //     //     return level;
+    //     // }
+
+    //     // // Order by MetadataToken and inheritance level to sync the order with the port order (make sure FieldDrawers are next to the correct port)
+    //     // return fields.OrderByDescending(f => (GetFieldInheritanceLevel(f) << 32) | (long)f.MetadataToken);
+
+    // }
+
+    #endregion
+}
+
+public struct FieldPortInfo
+{
+    public FieldInfo fieldInfo;
+    public InputAttribute inputAttribute;
+
+    public FieldPortInfo(FieldInfo fieldInfo, InputAttribute inputAttribute)
+    {
+        this.fieldInfo = fieldInfo;
+        this.inputAttribute = inputAttribute;
+    }
+}
\ No newline at end of file
diff --git a/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/DynamicNode.cs.meta b/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/DynamicNode.cs.meta
new file mode 100644
index 00000000..930590b6
--- /dev/null
+++ b/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/DynamicNode.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 9c7e41fbf9b5f5a6aaff6ceef7a38e06
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/DynamicNodeWithOutput.cs b/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/DynamicNodeWithOutput.cs
new file mode 100644
index 00000000..b92c3c9a
--- /dev/null
+++ b/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/DynamicNodeWithOutput.cs
@@ -0,0 +1,20 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using GraphProcessor;
+using System.Linq;
+
+[System.Serializable]
+public abstract class DynamicNodeWithOutput<T> : DynamicNode<T>
+{
+    [Output(name = "Out")]
+    public T dataOutput;
+
+    public override string name => "DynamicNodeWithOutput";
+
+    protected override void Process()
+    {
+        base.Process();
+        dataOutput = data;
+    }
+}
diff --git a/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/DynamicNodeWithOutput.cs.meta b/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/DynamicNodeWithOutput.cs.meta
new file mode 100644
index 00000000..72c0ff3e
--- /dev/null
+++ b/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/DynamicNodeWithOutput.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 403bad8732c99c8efb7192137e8e3301
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/Namer.cs b/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/Namer.cs
new file mode 100644
index 00000000..6b2b4820
--- /dev/null
+++ b/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/Namer.cs
@@ -0,0 +1,14 @@
+using System.Reflection;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using GraphProcessor;
+using UnityEngine;
+
+[Serializable]
+public class Namer
+{
+    [SerializeField, Input("Name"), ShowAsDrawer] string name;
+    [SerializeField, Input("Bool")] bool value;
+}
\ No newline at end of file
diff --git a/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/Namer.cs.meta b/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/Namer.cs.meta
new file mode 100644
index 00000000..aeebf036
--- /dev/null
+++ b/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/Namer.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: e363163a6a14c292096a628b16828935
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/NamerNode.cs b/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/NamerNode.cs
new file mode 100644
index 00000000..32a86ea3
--- /dev/null
+++ b/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/NamerNode.cs
@@ -0,0 +1,11 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using GraphProcessor;
+using System.Linq;
+
+[System.Serializable, NodeMenuItem("Custom/ProxiedInputsNode")]
+public class NamerNode : DynamicNodeWithOutput<Namer>
+{
+    public override string name => "ConditionalNameNode";
+}
diff --git a/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/NamerNode.cs.meta b/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/NamerNode.cs.meta
new file mode 100644
index 00000000..e41b6497
--- /dev/null
+++ b/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/NamerNode.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 5d5842b118d32744a84e93b4530d1208
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/Examples/DefaultNodes/Nodes/FloatToStringNode.cs b/Assets/Examples/DefaultNodes/Nodes/FloatToStringNode.cs
new file mode 100644
index 00000000..d4a401b9
--- /dev/null
+++ b/Assets/Examples/DefaultNodes/Nodes/FloatToStringNode.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Globalization;
+using GraphProcessor;
+using UnityEngine;
+
+[Serializable, NodeMenuItem("Convert/Float to String"), ConverterNode(typeof(float), typeof(string))]
+public class FloatToStringsNode : BaseNode, IConversionNode
+{
+    [Input("In")]
+    public float input;
+
+    public int decimalPlaces = 2;
+
+    [Output("Out")]
+    public string output;
+
+    public override string name => "To String";
+
+    public string GetConversionInput()
+    {
+        return nameof(input);
+    }
+
+    public string GetConversionOutput()
+    {
+        return nameof(output);
+    }
+
+    protected override void Process()
+    {
+        output = input.ToString("F" + decimalPlaces, CultureInfo.InvariantCulture);
+    }
+}
diff --git a/Assets/Examples/DefaultNodes/Nodes/FloatToStringNode.cs.meta b/Assets/Examples/DefaultNodes/Nodes/FloatToStringNode.cs.meta
new file mode 100644
index 00000000..08d319d5
--- /dev/null
+++ b/Assets/Examples/DefaultNodes/Nodes/FloatToStringNode.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 5947dfd18c94461281d83969aff7d203
+timeCreated: 1643494663
\ No newline at end of file
diff --git a/Assets/Examples/Editor/GraphAssetCallbacks.cs b/Assets/Examples/Editor/GraphAssetCallbacks.cs
index dd61c37a..c44904a9 100644
--- a/Assets/Examples/Editor/GraphAssetCallbacks.cs
+++ b/Assets/Examples/Editor/GraphAssetCallbacks.cs
@@ -9,7 +9,7 @@
 public class GraphAssetCallbacks
 {
 	[MenuItem("Assets/Create/GraphProcessor", false, 10)]
-	public static void CreateGraphPorcessor()
+	public static void CreateGraphProcessor()
 	{
 		var graph = ScriptableObject.CreateInstance< BaseGraph >();
 		ProjectWindowUtil.CreateAsset(graph, "GraphProcessor.asset");
diff --git a/Assets/com.alelievr.NodeGraphProcessor/Editor/Logic/EdgeConnectorListener.cs b/Assets/com.alelievr.NodeGraphProcessor/Editor/Logic/EdgeConnectorListener.cs
index cef006d2..180df519 100644
--- a/Assets/com.alelievr.NodeGraphProcessor/Editor/Logic/EdgeConnectorListener.cs
+++ b/Assets/com.alelievr.NodeGraphProcessor/Editor/Logic/EdgeConnectorListener.cs
@@ -64,7 +64,7 @@ public virtual void OnDrop(GraphView graphView, Edge edge)
             try
             {
                 this.graphView.RegisterCompleteObjectUndo("Connected " + edgeView.input.node.name + " and " + edgeView.output.node.name);
-                if (!this.graphView.Connect(edge as EdgeView, autoDisconnectInputs: !wasOnTheSamePort))
+                if (!this.graphView.ConnectConvertable(edge as EdgeView, !wasOnTheSamePort))
                     this.graphView.Disconnect(edge as EdgeView);
             } catch (System.Exception)
             {
diff --git a/Assets/com.alelievr.NodeGraphProcessor/Editor/Utils/NodeProvider.cs b/Assets/com.alelievr.NodeGraphProcessor/Editor/Utils/NodeProvider.cs
index da054683..9b525d12 100644
--- a/Assets/com.alelievr.NodeGraphProcessor/Editor/Utils/NodeProvider.cs
+++ b/Assets/com.alelievr.NodeGraphProcessor/Editor/Utils/NodeProvider.cs
@@ -337,9 +337,18 @@ bool IsPortCompatible(PortDescription description)
 			{
 				if ((portView.direction == Direction.Input && description.isInput) || (portView.direction == Direction.Output && !description.isInput))
 					return false;
+
+				if (portView.direction == Direction.Input)
+				{
+					if (!BaseGraph.TypesAreConnectable(description.portType, portView.portType))
+						return false;
+				}
+				else
+				{
+					if (!BaseGraph.TypesAreConnectable( portView.portType, description.portType))
+						return false;
+				}
 	
-				if (!BaseGraph.TypesAreConnectable(description.portType, portView.portType))
-					return false;
 					
 				return true;
 			}
diff --git a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseGraphView.cs b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseGraphView.cs
index 7940f6ee..2dc69c85 100644
--- a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseGraphView.cs
+++ b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseGraphView.cs
@@ -15,408 +15,418 @@
 
 namespace GraphProcessor
 {
-	/// <summary>
-	/// Base class to write a custom view for a node
-	/// </summary>
-	public class BaseGraphView : GraphView, IDisposable
-	{
-		public delegate void ComputeOrderUpdatedDelegate();
-		public delegate void NodeDuplicatedDelegate(BaseNode duplicatedNode, BaseNode newNode);
-
-		/// <summary>
-		/// Graph that owns of the node
-		/// </summary>
-		public BaseGraph							graph;
-
-		/// <summary>
-		/// Connector listener that will create the edges between ports
-		/// </summary>
-		public BaseEdgeConnectorListener			connectorListener;
-
-		/// <summary>
-		/// List of all node views in the graph
-		/// </summary>
-		/// <typeparam name="BaseNodeView"></typeparam>
-		/// <returns></returns>
-		public List< BaseNodeView >					nodeViews = new List< BaseNodeView >();
-
-		/// <summary>
-		/// Dictionary of the node views accessed view the node instance, faster than a Find in the node view list
-		/// </summary>
-		/// <typeparam name="BaseNode"></typeparam>
-		/// <typeparam name="BaseNodeView"></typeparam>
-		/// <returns></returns>
-		public Dictionary< BaseNode, BaseNodeView >	nodeViewsPerNode = new Dictionary< BaseNode, BaseNodeView >();
-
-		/// <summary>
-		/// List of all edge views in the graph
-		/// </summary>
-		/// <typeparam name="EdgeView"></typeparam>
-		/// <returns></returns>
-		public List< EdgeView >						edgeViews = new List< EdgeView >();
-
-		/// <summary>
-		/// List of all group views in the graph
-		/// </summary>
-		/// <typeparam name="GroupView"></typeparam>
-		/// <returns></returns>
-        public List< GroupView >         			groupViews = new List< GroupView >();
+    /// <summary>
+    /// Base class to write a custom view for a node
+    /// </summary>
+    public class BaseGraphView : GraphView, IDisposable
+    {
+        public delegate void ComputeOrderUpdatedDelegate();
+        public delegate void NodeDuplicatedDelegate(BaseNode duplicatedNode, BaseNode newNode);
+
+        /// <summary>
+        /// Graph that owns of the node
+        /// </summary>
+        public BaseGraph graph;
+
+        /// <summary>
+        /// Connector listener that will create the edges between ports
+        /// </summary>
+        public BaseEdgeConnectorListener connectorListener;
+
+        /// <summary>
+        /// List of all node views in the graph
+        /// </summary>
+        /// <typeparam name="BaseNodeView"></typeparam>
+        /// <returns></returns>
+        public List<BaseNodeView> nodeViews = new List<BaseNodeView>();
+
+        /// <summary>
+        /// Dictionary of the node views accessed view the node instance, faster than a Find in the node view list
+        /// </summary>
+        /// <typeparam name="BaseNode"></typeparam>
+        /// <typeparam name="BaseNodeView"></typeparam>
+        /// <returns></returns>
+        public Dictionary<BaseNode, BaseNodeView> nodeViewsPerNode = new Dictionary<BaseNode, BaseNodeView>();
+
+        /// <summary>
+        /// List of all edge views in the graph
+        /// </summary>
+        /// <typeparam name="EdgeView"></typeparam>
+        /// <returns></returns>
+        public List<EdgeView> edgeViews = new List<EdgeView>();
+
+        /// <summary>
+        /// List of all group views in the graph
+        /// </summary>
+        /// <typeparam name="GroupView"></typeparam>
+        /// <returns></returns>
+        public List<GroupView> groupViews = new List<GroupView>();
 
 #if UNITY_2020_1_OR_NEWER
-		/// <summary>
-		/// List of all sticky note views in the graph
-		/// </summary>
-		/// <typeparam name="StickyNoteView"></typeparam>
-		/// <returns></returns>
-		public List< StickyNoteView >				stickyNoteViews = new List<StickyNoteView>();
+        /// <summary>
+        /// List of all sticky note views in the graph
+        /// </summary>
+        /// <typeparam name="StickyNoteView"></typeparam>
+        /// <returns></returns>
+        public List<StickyNoteView> stickyNoteViews = new List<StickyNoteView>();
 #endif
 
-		/// <summary>
-		/// List of all stack node views in the graph
-		/// </summary>
-		/// <typeparam name="BaseStackNodeView"></typeparam>
-		/// <returns></returns>
-		public List< BaseStackNodeView >			stackNodeViews = new List< BaseStackNodeView >();
-
-		Dictionary< Type, PinnedElementView >		pinnedElements = new Dictionary< Type, PinnedElementView >();
-
-		CreateNodeMenuWindow						createNodeMenu;
-
-		/// <summary>
-		/// Triggered just after the graph is initialized
-		/// </summary>
-		public event Action							initialized;
-
-		/// <summary>
-		/// Triggered just after the compute order of the graph is updated
-		/// </summary>
-		public event ComputeOrderUpdatedDelegate	computeOrderUpdated;
-
-		// Safe event relay from BaseGraph (safe because you are sure to always point on a valid BaseGraph
-		// when one of these events is called), a graph switch can occur between two call tho
-		/// <summary>
-		/// Same event than BaseGraph.onExposedParameterListChanged
-		/// Safe event (not triggered in case the graph is null).
-		/// </summary>
-		public event Action				onExposedParameterListChanged;
-
-		/// <summary>
-		/// Same event than BaseGraph.onExposedParameterModified
-		/// Safe event (not triggered in case the graph is null).
-		/// </summary>
-		public event Action< ExposedParameter >	onExposedParameterModified;
-
-		/// <summary>
-		/// Triggered when a node is duplicated (crt-d) or copy-pasted (crtl-c/crtl-v)
-		/// </summary>
-		public event NodeDuplicatedDelegate	nodeDuplicated;
-
-		/// <summary>
-		/// Object to handle nodes that shows their UI in the inspector.
-		/// </summary>
-		[SerializeField]
-		protected NodeInspectorObject		nodeInspector
-		{
-			get
-			{
-
-				if (graph.nodeInspectorReference == null)
-					graph.nodeInspectorReference = CreateNodeInspectorObject();
-				return graph.nodeInspectorReference as NodeInspectorObject;
-			}
-		}
-
-		/// <summary>
-		/// Workaround object for creating exposed parameter property fields.
-		/// </summary>
-		public ExposedParameterFieldFactory exposedParameterFactory { get; private set; }
-
-		public SerializedObject		serializedGraph { get; private set; }
-
-		Dictionary<Type, (Type nodeType, MethodInfo initalizeNodeFromObject)> nodeTypePerCreateAssetType = new Dictionary<Type, (Type, MethodInfo)>();
-
-		public BaseGraphView(EditorWindow window)
-		{
-			serializeGraphElements = SerializeGraphElementsCallback;
-			canPasteSerializedData = CanPasteSerializedDataCallback;
-			unserializeAndPaste = UnserializeAndPasteCallback;
+        /// <summary>
+        /// List of all stack node views in the graph
+        /// </summary>
+        /// <typeparam name="BaseStackNodeView"></typeparam>
+        /// <returns></returns>
+        public List<BaseStackNodeView> stackNodeViews = new List<BaseStackNodeView>();
+
+        Dictionary<Type, PinnedElementView> pinnedElements = new Dictionary<Type, PinnedElementView>();
+
+        CreateNodeMenuWindow createNodeMenu;
+
+        /// <summary>
+        /// Triggered just after the graph is initialized
+        /// </summary>
+        public event Action initialized;
+
+        /// <summary>
+        /// Triggered just after the compute order of the graph is updated
+        /// </summary>
+        public event ComputeOrderUpdatedDelegate computeOrderUpdated;
+
+        // Safe event relay from BaseGraph (safe because you are sure to always point on a valid BaseGraph
+        // when one of these events is called), a graph switch can occur between two call tho
+        /// <summary>
+        /// Same event than BaseGraph.onExposedParameterListChanged
+        /// Safe event (not triggered in case the graph is null).
+        /// </summary>
+        public event Action onExposedParameterListChanged;
+
+        /// <summary>
+        /// Same event than BaseGraph.onExposedParameterModified
+        /// Safe event (not triggered in case the graph is null).
+        /// </summary>
+        public event Action<ExposedParameter> onExposedParameterModified;
+
+        /// <summary>
+        /// Triggered when a node is duplicated (crt-d) or copy-pasted (crtl-c/crtl-v)
+        /// </summary>
+        public event NodeDuplicatedDelegate nodeDuplicated;
+
+        /// <summary>
+        /// Object to handle nodes that shows their UI in the inspector.
+        /// </summary>
+        [SerializeField]
+        protected NodeInspectorObject nodeInspector
+        {
+            get
+            {
+
+                if (graph.nodeInspectorReference == null)
+                    graph.nodeInspectorReference = CreateNodeInspectorObject();
+                return graph.nodeInspectorReference as NodeInspectorObject;
+            }
+        }
+
+        /// <summary>
+        /// Property that can be overridden to change the Node created when Drag&Drop a Parameter into the Graph.
+        /// </summary>
+        protected virtual Type DefaultParameterNode => typeof(ParameterNode);
+
+        /// <summary>
+        /// Workaround object for creating exposed parameter property fields.
+        /// </summary>
+        public ExposedParameterFieldFactory exposedParameterFactory { get; private set; }
+
+        public SerializedObject serializedGraph { get; private set; }
+
+        Dictionary<Type, (Type nodeType, MethodInfo initalizeNodeFromObject)> nodeTypePerCreateAssetType = new Dictionary<Type, (Type, MethodInfo)>();
+
+        public BaseGraphView(EditorWindow window)
+        {
+            serializeGraphElements = SerializeGraphElementsCallback;
+            canPasteSerializedData = CanPasteSerializedDataCallback;
+            unserializeAndPaste = UnserializeAndPasteCallback;
             graphViewChanged = GraphViewChangedCallback;
-			viewTransformChanged = ViewTransformChangedCallback;
+            viewTransformChanged = ViewTransformChangedCallback;
             elementResized = ElementResizedCallback;
 
-			RegisterCallback< KeyDownEvent >(KeyDownCallback);
-			RegisterCallback< DragPerformEvent >(DragPerformedCallback);
-			RegisterCallback< DragUpdatedEvent >(DragUpdatedCallback);
-			RegisterCallback< MouseDownEvent >(MouseDownCallback);
-			RegisterCallback< MouseUpEvent >(MouseUpCallback);
+            RegisterCallback<KeyDownEvent>(KeyDownCallback);
+            RegisterCallback<DragPerformEvent>(DragPerformedCallback);
+            RegisterCallback<DragUpdatedEvent>(DragUpdatedCallback);
+            RegisterCallback<MouseDownEvent>(MouseDownCallback);
+            RegisterCallback<MouseUpEvent>(MouseUpCallback);
 
-			InitializeManipulators();
+            InitializeManipulators();
 
-			SetupZoom(0.05f, 2f);
+            SetupZoom(0.05f, 2f);
 
-			Undo.undoRedoPerformed += ReloadView;
+            Undo.undoRedoPerformed += ReloadView;
 
-			createNodeMenu = ScriptableObject.CreateInstance< CreateNodeMenuWindow >();
-			createNodeMenu.Initialize(this, window);
+            createNodeMenu = ScriptableObject.CreateInstance<CreateNodeMenuWindow>();
+            createNodeMenu.Initialize(this, window);
 
-			this.StretchToParentSize();
-		}
+            this.StretchToParentSize();
+        }
 
-		protected virtual NodeInspectorObject CreateNodeInspectorObject()
-		{
-			var inspector = ScriptableObject.CreateInstance<NodeInspectorObject>();
-			inspector.name = "Node Inspector";
-			inspector.hideFlags = HideFlags.HideAndDontSave ^ HideFlags.NotEditable;
+        protected virtual NodeInspectorObject CreateNodeInspectorObject()
+        {
+            var inspector = ScriptableObject.CreateInstance<NodeInspectorObject>();
+            inspector.name = "Node Inspector";
+            inspector.hideFlags = HideFlags.HideAndDontSave ^ HideFlags.NotEditable;
 
-			return inspector;
-		}
+            return inspector;
+        }
 
-		#region Callbacks
+        #region Callbacks
 
-		protected override bool canCopySelection
-		{
+        protected override bool canCopySelection
+        {
             get { return selection.Any(e => e is BaseNodeView || e is GroupView); }
-		}
+        }
 
-		protected override bool canCutSelection
-		{
+        protected override bool canCutSelection
+        {
             get { return selection.Any(e => e is BaseNodeView || e is GroupView); }
-		}
-
-		string SerializeGraphElementsCallback(IEnumerable<GraphElement> elements)
-		{
-			var data = new CopyPasteHelper();
-
-			foreach (BaseNodeView nodeView in elements.Where(e => e is BaseNodeView))
-			{
-				data.copiedNodes.Add(JsonSerializer.SerializeNode(nodeView.nodeTarget));
-				foreach (var port in nodeView.nodeTarget.GetAllPorts())
-				{
-					if (port.portData.vertical)
-					{
-						foreach (var edge in port.GetEdges())
-							data.copiedEdges.Add(JsonSerializer.Serialize(edge));
-					}
-				}
-			}
-
-			foreach (GroupView groupView in elements.Where(e => e is GroupView))
-				data.copiedGroups.Add(JsonSerializer.Serialize(groupView.group));
-
-			foreach (EdgeView edgeView in elements.Where(e => e is EdgeView))
-				data.copiedEdges.Add(JsonSerializer.Serialize(edgeView.serializedEdge));
-
-			ClearSelection();
-
-			return JsonUtility.ToJson(data, true);
-		}
-
-		bool CanPasteSerializedDataCallback(string serializedData)
-		{
-			try {
-				return JsonUtility.FromJson(serializedData, typeof(CopyPasteHelper)) != null;
-			} catch {
-				return false;
-			}
-		}
-
-		void UnserializeAndPasteCallback(string operationName, string serializedData)
-		{
-			var data = JsonUtility.FromJson< CopyPasteHelper >(serializedData);
+        }
+
+        string SerializeGraphElementsCallback(IEnumerable<GraphElement> elements)
+        {
+            var data = new CopyPasteHelper();
+
+            foreach (BaseNodeView nodeView in elements.Where(e => e is BaseNodeView))
+            {
+                data.copiedNodes.Add(JsonSerializer.SerializeNode(nodeView.nodeTarget));
+                foreach (var port in nodeView.nodeTarget.GetAllPorts())
+                {
+                    if (port.portData.vertical)
+                    {
+                        foreach (var edge in port.GetEdges())
+                            data.copiedEdges.Add(JsonSerializer.Serialize(edge));
+                    }
+                }
+            }
+
+            foreach (GroupView groupView in elements.Where(e => e is GroupView))
+                data.copiedGroups.Add(JsonSerializer.Serialize(groupView.group));
+
+            foreach (EdgeView edgeView in elements.Where(e => e is EdgeView))
+                data.copiedEdges.Add(JsonSerializer.Serialize(edgeView.serializedEdge));
+
+            ClearSelection();
+
+            return JsonUtility.ToJson(data, true);
+        }
+
+        bool CanPasteSerializedDataCallback(string serializedData)
+        {
+            try
+            {
+                return JsonUtility.FromJson(serializedData, typeof(CopyPasteHelper)) != null;
+            }
+            catch
+            {
+                return false;
+            }
+        }
+
+        void UnserializeAndPasteCallback(string operationName, string serializedData)
+        {
+            var data = JsonUtility.FromJson<CopyPasteHelper>(serializedData);
 
             RegisterCompleteObjectUndo(operationName);
 
-			Dictionary<string, BaseNode> copiedNodesMap = new Dictionary<string, BaseNode>();
+            Dictionary<string, BaseNode> copiedNodesMap = new Dictionary<string, BaseNode>();
 
-			var unserializedGroups = data.copiedGroups.Select(g => JsonSerializer.Deserialize<Group>(g)).ToList();
+            var unserializedGroups = data.copiedGroups.Select(g => JsonSerializer.Deserialize<Group>(g)).ToList();
 
-			foreach (var serializedNode in data.copiedNodes)
-			{
-				var node = JsonSerializer.DeserializeNode(serializedNode);
+            foreach (var serializedNode in data.copiedNodes)
+            {
+                var node = JsonSerializer.DeserializeNode(serializedNode);
 
-				if (node == null)
-					continue ;
+                if (node == null)
+                    continue;
 
-				string sourceGUID = node.GUID;
-				graph.nodesPerGUID.TryGetValue(sourceGUID, out var sourceNode);
-				//Call OnNodeCreated on the new fresh copied node
-				node.createdFromDuplication = true;
-				node.createdWithinGroup = unserializedGroups.Any(g => g.innerNodeGUIDs.Contains(sourceGUID));
-				node.OnNodeCreated();
-				//And move a bit the new node
-				node.position.position += new Vector2(20, 20);
+                string sourceGUID = node.GUID;
+                graph.nodesPerGUID.TryGetValue(sourceGUID, out var sourceNode);
+                //Call OnNodeCreated on the new fresh copied node
+                node.createdFromDuplication = true;
+                node.createdWithinGroup = unserializedGroups.Any(g => g.innerNodeGUIDs.Contains(sourceGUID));
+                node.OnNodeCreated();
+                //And move a bit the new node
+                node.position.position += new Vector2(20, 20);
 
-				var newNodeView = AddNode(node);
+                var newNodeView = AddNode(node);
 
-				// If the nodes were copied from another graph, then the source is null
-				if (sourceNode != null)
-					nodeDuplicated?.Invoke(sourceNode, node);
-				copiedNodesMap[sourceGUID] = node;
+                // If the nodes were copied from another graph, then the source is null
+                if (sourceNode != null)
+                    nodeDuplicated?.Invoke(sourceNode, node);
+                copiedNodesMap[sourceGUID] = node;
 
-				//Select the new node
-				AddToSelection(nodeViewsPerNode[node]);
-			}
+                //Select the new node
+                AddToSelection(nodeViewsPerNode[node]);
+            }
 
             foreach (var group in unserializedGroups)
             {
                 //Same than for node
                 group.OnCreated();
 
-				// try to centre the created node in the screen
+                // try to centre the created node in the screen
                 group.position.position += new Vector2(20, 20);
 
-				var oldGUIDList = group.innerNodeGUIDs.ToList();
-				group.innerNodeGUIDs.Clear();
-				foreach (var guid in oldGUIDList)
-				{
-					graph.nodesPerGUID.TryGetValue(guid, out var node);
-					
-					// In case group was copied from another graph
-					if (node == null)
-					{
-						copiedNodesMap.TryGetValue(guid, out node);
-						group.innerNodeGUIDs.Add(node.GUID);
-					}
-					else
-					{
-						group.innerNodeGUIDs.Add(copiedNodesMap[guid].GUID);
-					}
-				}
+                var oldGUIDList = group.innerNodeGUIDs.ToList();
+                group.innerNodeGUIDs.Clear();
+                foreach (var guid in oldGUIDList)
+                {
+                    graph.nodesPerGUID.TryGetValue(guid, out var node);
+
+                    // In case group was copied from another graph
+                    if (node == null)
+                    {
+                        copiedNodesMap.TryGetValue(guid, out node);
+                        group.innerNodeGUIDs.Add(node.GUID);
+                    }
+                    else
+                    {
+                        group.innerNodeGUIDs.Add(copiedNodesMap[guid].GUID);
+                    }
+                }
 
                 AddGroup(group);
             }
 
             foreach (var serializedEdge in data.copiedEdges)
-			{
-				var edge = JsonSerializer.Deserialize<SerializableEdge>(serializedEdge);
+            {
+                var edge = JsonSerializer.Deserialize<SerializableEdge>(serializedEdge);
 
-				edge.Deserialize();
+                edge.Deserialize();
 
-				// Find port of new nodes:
-				copiedNodesMap.TryGetValue(edge.inputNode.GUID, out var oldInputNode);
-				copiedNodesMap.TryGetValue(edge.outputNode.GUID, out var oldOutputNode);
+                // Find port of new nodes:
+                copiedNodesMap.TryGetValue(edge.inputNode.GUID, out var oldInputNode);
+                copiedNodesMap.TryGetValue(edge.outputNode.GUID, out var oldOutputNode);
 
-				// We avoid to break the graph by replacing unique connections:
-				if (oldInputNode == null && !edge.inputPort.portData.acceptMultipleEdges || !edge.outputPort.portData.acceptMultipleEdges)
-					continue;
+                // We avoid to break the graph by replacing unique connections:
+                if (oldInputNode == null && !edge.inputPort.portData.acceptMultipleEdges || !edge.outputPort.portData.acceptMultipleEdges)
+                    continue;
 
-				oldInputNode = oldInputNode ?? edge.inputNode;
-				oldOutputNode = oldOutputNode ?? edge.outputNode;
+                oldInputNode = oldInputNode ?? edge.inputNode;
+                oldOutputNode = oldOutputNode ?? edge.outputNode;
 
-				var inputPort = oldInputNode.GetPort(edge.inputPort.fieldName, edge.inputPortIdentifier);
-				var outputPort = oldOutputNode.GetPort(edge.outputPort.fieldName, edge.outputPortIdentifier);
+                var inputPort = oldInputNode.GetPort(edge.inputPort.fieldName, edge.inputPortIdentifier);
+                var outputPort = oldOutputNode.GetPort(edge.outputPort.fieldName, edge.outputPortIdentifier);
 
-				var newEdge = SerializableEdge.CreateNewEdge(graph, inputPort, outputPort);
+                var newEdge = SerializableEdge.CreateNewEdge(graph, inputPort, outputPort);
 
-				if (nodeViewsPerNode.ContainsKey(oldInputNode) && nodeViewsPerNode.ContainsKey(oldOutputNode))
-				{
-					var edgeView = CreateEdgeView();
-					edgeView.userData = newEdge;
-					edgeView.input = nodeViewsPerNode[oldInputNode].GetPortViewFromFieldName(newEdge.inputFieldName, newEdge.inputPortIdentifier);
-					edgeView.output = nodeViewsPerNode[oldOutputNode].GetPortViewFromFieldName(newEdge.outputFieldName, newEdge.outputPortIdentifier);
+                if (nodeViewsPerNode.ContainsKey(oldInputNode) && nodeViewsPerNode.ContainsKey(oldOutputNode))
+                {
+                    var edgeView = CreateEdgeView();
+                    edgeView.userData = newEdge;
+                    edgeView.input = nodeViewsPerNode[oldInputNode].GetPortViewFromFieldName(newEdge.inputFieldName, newEdge.inputPortIdentifier);
+                    edgeView.output = nodeViewsPerNode[oldOutputNode].GetPortViewFromFieldName(newEdge.outputFieldName, newEdge.outputPortIdentifier);
 
-					Connect(edgeView);
-				}
-			}
-		}
+                    Connect(edgeView);
+                }
+            }
+        }
 
         public virtual EdgeView CreateEdgeView()
         {
-			return new EdgeView();
+            return new EdgeView();
         }
 
         GraphViewChange GraphViewChangedCallback(GraphViewChange changes)
-		{
-			if (changes.elementsToRemove != null)
-			{
-				RegisterCompleteObjectUndo("Remove Graph Elements");
-
-				// Destroy priority of objects
-				// We need nodes to be destroyed first because we can have a destroy operation that uses node connections
-				changes.elementsToRemove.Sort((e1, e2) => {
-					int GetPriority(GraphElement e)
-					{
-						if (e is BaseNodeView)
-							return 0;
-						else
-							return 1;
-					}
-					return GetPriority(e1).CompareTo(GetPriority(e2));
-				});
-
-				//Handle ourselves the edge and node remove
-				changes.elementsToRemove.RemoveAll(e => {
-
-					switch (e)
-					{
-						case EdgeView edge:
-							Disconnect(edge);
-							return true;
-						case BaseNodeView nodeView:
-							// For vertical nodes, we need to delete them ourselves as it's not handled by GraphView
-							foreach (var pv in nodeView.inputPortViews.Concat(nodeView.outputPortViews))
-								if (pv.orientation == Orientation.Vertical)
-									foreach (var edge in pv.GetEdges().ToList())
-										Disconnect(edge);
-
-							nodeInspector.NodeViewRemoved(nodeView);
-							ExceptionToLog.Call(() => nodeView.OnRemoved());
-							graph.RemoveNode(nodeView.nodeTarget);
-							UpdateSerializedProperties();
-							RemoveElement(nodeView);
-							if (Selection.activeObject == nodeInspector)
-								UpdateNodeInspectorSelection();
-
-							SyncSerializedPropertyPathes();
-							return true;
-						case GroupView group:
-							graph.RemoveGroup(group.group);
-							UpdateSerializedProperties();
-							RemoveElement(group);
-							return true;
-						case ExposedParameterFieldView blackboardField:
-							graph.RemoveExposedParameter(blackboardField.parameter);
-							UpdateSerializedProperties();
-							return true;
-						case BaseStackNodeView stackNodeView:
-							graph.RemoveStackNode(stackNodeView.stackNode);
-							UpdateSerializedProperties();
-							RemoveElement(stackNodeView);
-							return true;
+        {
+            if (changes.elementsToRemove != null)
+            {
+                RegisterCompleteObjectUndo("Remove Graph Elements");
+
+                // Destroy priority of objects
+                // We need nodes to be destroyed first because we can have a destroy operation that uses node connections
+                changes.elementsToRemove.Sort((e1, e2) =>
+                {
+                    int GetPriority(GraphElement e)
+                    {
+                        if (e is BaseNodeView)
+                            return 0;
+                        else
+                            return 1;
+                    }
+                    return GetPriority(e1).CompareTo(GetPriority(e2));
+                });
+
+                //Handle ourselves the edge and node remove
+                changes.elementsToRemove.RemoveAll(e =>
+                {
+
+                    switch (e)
+                    {
+                        case EdgeView edge:
+                            Disconnect(edge);
+                            return true;
+                        case BaseNodeView nodeView:
+                            // For vertical nodes, we need to delete them ourselves as it's not handled by GraphView
+                            foreach (var pv in nodeView.inputPortViews.Concat(nodeView.outputPortViews))
+                                if (pv.orientation == Orientation.Vertical)
+                                    foreach (var edge in pv.GetEdges().ToList())
+                                        Disconnect(edge);
+
+                            nodeInspector.NodeViewRemoved(nodeView);
+                            ExceptionToLog.Call(() => nodeView.OnRemoved());
+                            graph.RemoveNode(nodeView.nodeTarget);
+                            UpdateSerializedProperties();
+                            RemoveElement(nodeView);
+                            if (Selection.activeObject == nodeInspector)
+                                UpdateNodeInspectorSelection();
+
+                            SyncSerializedPropertyPathes();
+                            return true;
+                        case GroupView group:
+                            graph.RemoveGroup(group.group);
+                            UpdateSerializedProperties();
+                            RemoveElement(group);
+                            return true;
+                        case ExposedParameterFieldView blackboardField:
+                            graph.RemoveExposedParameter(blackboardField.parameter);
+                            UpdateSerializedProperties();
+                            return true;
+                        case BaseStackNodeView stackNodeView:
+                            graph.RemoveStackNode(stackNodeView.stackNode);
+                            UpdateSerializedProperties();
+                            RemoveElement(stackNodeView);
+                            return true;
 #if UNITY_2020_1_OR_NEWER
-						case StickyNoteView stickyNoteView:
-							graph.RemoveStickyNote(stickyNoteView.note);
-							UpdateSerializedProperties();
-							RemoveElement(stickyNoteView);
-							return true;
+                        case StickyNoteView stickyNoteView:
+                            graph.RemoveStickyNote(stickyNoteView.note);
+                            UpdateSerializedProperties();
+                            RemoveElement(stickyNoteView);
+                            return true;
 #endif
-					}
-
-					return false;
-				});
-			}
-
-			return changes;
-		}
-
-		void GraphChangesCallback(GraphChanges changes)
-		{
-			if (changes.removedEdge != null)
-			{
-				var edge = edgeViews.FirstOrDefault(e => e.serializedEdge == changes.removedEdge);
-
-				DisconnectView(edge);
-			}
-		}
-
-		void ViewTransformChangedCallback(GraphView view)
-		{
-			if (graph != null)
-			{
-				graph.position = viewTransform.position;
-				graph.scale = viewTransform.scale;
-			}
-		}
+                    }
+
+                    return false;
+                });
+            }
+
+            return changes;
+        }
+
+        void GraphChangesCallback(GraphChanges changes)
+        {
+            if (changes.removedEdge != null)
+            {
+                var edge = edgeViews.FirstOrDefault(e => e.serializedEdge == changes.removedEdge);
+
+                DisconnectView(edge);
+            }
+        }
+
+        void ViewTransformChangedCallback(GraphView view)
+        {
+            if (graph != null)
+            {
+                graph.position = viewTransform.position;
+                graph.scale = viewTransform.scale;
+            }
+        }
 
         void ElementResizedCallback(VisualElement elem)
         {
@@ -426,458 +436,465 @@ void ElementResizedCallback(VisualElement elem)
                 groupView.group.size = groupView.GetPosition().size;
         }
 
-		public override List< Port > GetCompatiblePorts(Port startPort, NodeAdapter nodeAdapter)
-		{
-			var compatiblePorts = new List< Port >();
-
-			compatiblePorts.AddRange(ports.ToList().Where(p => {
-				var portView = p as PortView;
-
-				if (portView.owner == (startPort as PortView).owner)
-					return false;
-
-				if (p.direction == startPort.direction)
-					return false;
-
-				//Check for type assignability
-				if (!BaseGraph.TypesAreConnectable(startPort.portType, p.portType))
-					return false;
-
-				//Check if the edge already exists
-				if (portView.GetEdges().Any(e => e.input == startPort || e.output == startPort))
-					return false;
-
-				return true;
-			}));
-
-			return compatiblePorts;
-		}
-
-		/// <summary>
-		/// Build the contextual menu shown when right clicking inside the graph view
-		/// </summary>
-		/// <param name="evt"></param>
-		public override void BuildContextualMenu(ContextualMenuPopulateEvent evt)
-		{
-			base.BuildContextualMenu(evt);
-			BuildGroupContextualMenu(evt, 1);
-			BuildStickyNoteContextualMenu(evt, 2);
-			BuildViewContextualMenu(evt);
-			BuildSelectAssetContextualMenu(evt);
-			BuildSaveAssetContextualMenu(evt);
-			BuildHelpContextualMenu(evt);
-		}
-
-		/// <summary>
-		/// Add the New Group entry to the context menu
-		/// </summary>
-		/// <param name="evt"></param>
-		protected virtual void BuildGroupContextualMenu(ContextualMenuPopulateEvent evt, int menuPosition = -1)
-		{
-			if (menuPosition == -1)
-				menuPosition = evt.menu.MenuItems().Count;
-			Vector2 position = (evt.currentTarget as VisualElement).ChangeCoordinatesTo(contentViewContainer, evt.localMousePosition);
+        public override List<Port> GetCompatiblePorts(Port startPort, NodeAdapter nodeAdapter)
+        {
+            var compatiblePorts = new List<Port>();
+
+            compatiblePorts.AddRange(ports.Where(p =>
+            {
+                var portView = p as PortView;
+
+                if (portView.owner == (startPort as PortView).owner)
+                    return false;
+
+                if (p.direction == startPort.direction)
+                    return false;
+
+                //Check for type assignability
+                if (!BaseGraph.TypesAreConnectable(startPort.portType, p.portType))
+                    return false;
+
+                //Check if the edge already exists
+                if (portView.GetEdges().Any(e => e.input == startPort || e.output == startPort))
+                    return false;
+
+                return true;
+            }));
+
+            return compatiblePorts;
+        }
+
+        /// <summary>
+        /// Build the contextual menu shown when right clicking inside the graph view
+        /// </summary>
+        /// <param name="evt"></param>
+        public override void BuildContextualMenu(ContextualMenuPopulateEvent evt)
+        {
+            base.BuildContextualMenu(evt);
+            BuildGroupContextualMenu(evt, 1);
+            BuildStickyNoteContextualMenu(evt, 2);
+            BuildViewContextualMenu(evt);
+            BuildSelectAssetContextualMenu(evt);
+            BuildSaveAssetContextualMenu(evt);
+            BuildHelpContextualMenu(evt);
+        }
+
+        /// <summary>
+        /// Add the New Group entry to the context menu
+        /// </summary>
+        /// <param name="evt"></param>
+        protected virtual void BuildGroupContextualMenu(ContextualMenuPopulateEvent evt, int menuPosition = -1)
+        {
+            if (menuPosition == -1)
+                menuPosition = evt.menu.MenuItems().Count;
+            Vector2 position = (evt.currentTarget as VisualElement).ChangeCoordinatesTo(contentViewContainer, evt.localMousePosition);
             evt.menu.InsertAction(menuPosition, "Create Group", (e) => AddSelectionsToGroup(AddGroup(new Group("Create Group", position))), DropdownMenuAction.AlwaysEnabled);
-		}
-
-		/// <summary>
-		/// -Add the New Sticky Note entry to the context menu
-		/// </summary>
-		/// <param name="evt"></param>
-		protected virtual void BuildStickyNoteContextualMenu(ContextualMenuPopulateEvent evt, int menuPosition = -1)
-		{
-			if (menuPosition == -1)
-				menuPosition = evt.menu.MenuItems().Count;
+        }
+
+        /// <summary>
+        /// -Add the New Sticky Note entry to the context menu
+        /// </summary>
+        /// <param name="evt"></param>
+        protected virtual void BuildStickyNoteContextualMenu(ContextualMenuPopulateEvent evt, int menuPosition = -1)
+        {
+            if (menuPosition == -1)
+                menuPosition = evt.menu.MenuItems().Count;
 #if UNITY_2020_1_OR_NEWER
-			Vector2 position = (evt.currentTarget as VisualElement).ChangeCoordinatesTo(contentViewContainer, evt.localMousePosition);
+            Vector2 position = (evt.currentTarget as VisualElement).ChangeCoordinatesTo(contentViewContainer, evt.localMousePosition);
             evt.menu.InsertAction(menuPosition, "Create Sticky Note", (e) => AddStickyNote(new StickyNote("Create Note", position)), DropdownMenuAction.AlwaysEnabled);
 #endif
-		}
-
-		/// <summary>
-		/// Add the View entry to the context menu
-		/// </summary>
-		/// <param name="evt"></param>
-		protected virtual void BuildViewContextualMenu(ContextualMenuPopulateEvent evt)
-		{
-			evt.menu.AppendAction("View/Processor", (e) => ToggleView< ProcessorView >(), (e) => GetPinnedElementStatus< ProcessorView >());
-		}
-
-		/// <summary>
-		/// Add the Select Asset entry to the context menu
-		/// </summary>
-		/// <param name="evt"></param>
-		protected virtual void BuildSelectAssetContextualMenu(ContextualMenuPopulateEvent evt)
-		{
-			evt.menu.AppendAction("Select Asset", (e) => EditorGUIUtility.PingObject(graph), DropdownMenuAction.AlwaysEnabled);
-		}
-
-		/// <summary>
-		/// Add the Save Asset entry to the context menu
-		/// </summary>
-		/// <param name="evt"></param>
-		protected virtual void BuildSaveAssetContextualMenu(ContextualMenuPopulateEvent evt)
-		{
-			evt.menu.AppendAction("Save Asset", (e) => {
-				EditorUtility.SetDirty(graph);
-				AssetDatabase.SaveAssets();
-			}, DropdownMenuAction.AlwaysEnabled);
-		}
-
-		/// <summary>
-		/// Add the Help entry to the context menu
-		/// </summary>
-		/// <param name="evt"></param>
-		protected void BuildHelpContextualMenu(ContextualMenuPopulateEvent evt)
-		{
-			evt.menu.AppendAction("Help/Reset Pinned Windows", e => {
-				foreach (var kp in pinnedElements)
-					kp.Value.ResetPosition();
-			});
-		}
-
-		protected virtual void KeyDownCallback(KeyDownEvent e)
-		{
-			if (e.keyCode == KeyCode.S && e.commandKey)
-			{
-				SaveGraphToDisk();
-				e.StopPropagation();
-			}
-			else if(nodeViews.Count > 0 && e.commandKey && e.altKey)
-			{
-				//	Node Aligning shortcuts
-				switch(e.keyCode)
-				{
-					case KeyCode.LeftArrow:
-						nodeViews[0].AlignToLeft();
-						e.StopPropagation();
-						break;
-					case KeyCode.RightArrow:
-						nodeViews[0].AlignToRight();
-						e.StopPropagation();
-						break;
-					case KeyCode.UpArrow:
-						nodeViews[0].AlignToTop();
-						e.StopPropagation();
-						break;
-					case KeyCode.DownArrow:
-						nodeViews[0].AlignToBottom();
-						e.StopPropagation();
-						break;
-					case KeyCode.C:
-						nodeViews[0].AlignToCenter();
-						e.StopPropagation();
-						break;
-					case KeyCode.M:
-						nodeViews[0].AlignToMiddle();
-						e.StopPropagation();
-						break;
-				}
-			}
-		}
-
-		void MouseUpCallback(MouseUpEvent e)
-		{
-			schedule.Execute(() => {
-				if (DoesSelectionContainsInspectorNodes())
-					UpdateNodeInspectorSelection();
-			}).ExecuteLater(1);
-		}
-
-		void MouseDownCallback(MouseDownEvent e)
-		{
-			// When left clicking on the graph (not a node or something else)
-			if (e.button == 0)
-			{
-				// Close all settings windows:
-				nodeViews.ForEach(v => v.CloseSettings());
-			}
-
-			if (DoesSelectionContainsInspectorNodes())
-				UpdateNodeInspectorSelection();
-		}
-
-		bool DoesSelectionContainsInspectorNodes()
-		{
-			var selectedNodes = selection.Where(s => s is BaseNodeView).ToList();
-			var selectedNodesNotInInspector = selectedNodes.Except(nodeInspector.selectedNodes).ToList();
-			var nodeInInspectorWithoutSelectedNodes = nodeInspector.selectedNodes.Except(selectedNodes).ToList();
-
-			return selectedNodesNotInInspector.Any() || nodeInInspectorWithoutSelectedNodes.Any();
-		}
-
-		void DragPerformedCallback(DragPerformEvent e)
-		{
-			var mousePos = (e.currentTarget as VisualElement).ChangeCoordinatesTo(contentViewContainer, e.localMousePosition);
-			var dragData = DragAndDrop.GetGenericData("DragSelection") as List< ISelectable >;
-
-			// Drag and Drop for elements inside the graph
-			if (dragData != null)
-			{
-				var exposedParameterFieldViews = dragData.OfType<ExposedParameterFieldView>();
-				if (exposedParameterFieldViews.Any())
-				{
-					foreach (var paramFieldView in exposedParameterFieldViews)
-					{
-						RegisterCompleteObjectUndo("Create Parameter Node");
-						var paramNode = BaseNode.CreateFromType< ParameterNode >(mousePos);
-						paramNode.parameterGUID = paramFieldView.parameter.guid;
-						AddNode(paramNode);
-					}
-				}
-			}
-
-			// External objects drag and drop
-			if (DragAndDrop.objectReferences.Length > 0)
-			{
-				RegisterCompleteObjectUndo("Create Node From Object(s)");
-				foreach (var obj in DragAndDrop.objectReferences)
-				{
-					var objectType = obj.GetType();
-
-					foreach (var kp in nodeTypePerCreateAssetType)
-					{
-						if (kp.Key.IsAssignableFrom(objectType))
-						{
-							try
-							{
-								var node = BaseNode.CreateFromType(kp.Value.nodeType, mousePos);
-								if ((bool)kp.Value.initalizeNodeFromObject.Invoke(node, new []{obj}))
-								{
-									AddNode(node);
-									break;
-								}
-							}
-							catch (Exception exception)
-							{
-								Debug.LogException(exception);
-							}
-						}
-					}
-				}
-			}
-		}
-
-		void DragUpdatedCallback(DragUpdatedEvent e)
+        }
+
+        /// <summary>
+        /// Add the View entry to the context menu
+        /// </summary>
+        /// <param name="evt"></param>
+        protected virtual void BuildViewContextualMenu(ContextualMenuPopulateEvent evt)
         {
+            evt.menu.AppendAction("View/Processor", (e) => ToggleView<ProcessorView>(), (e) => GetPinnedElementStatus<ProcessorView>());
+        }
+
+        /// <summary>
+        /// Add the Select Asset entry to the context menu
+        /// </summary>
+        /// <param name="evt"></param>
+        protected virtual void BuildSelectAssetContextualMenu(ContextualMenuPopulateEvent evt)
+        {
+            evt.menu.AppendAction("Select Asset", (e) => EditorGUIUtility.PingObject(graph), DropdownMenuAction.AlwaysEnabled);
+        }
+
+        /// <summary>
+        /// Add the Save Asset entry to the context menu
+        /// </summary>
+        /// <param name="evt"></param>
+        protected virtual void BuildSaveAssetContextualMenu(ContextualMenuPopulateEvent evt)
+        {
+            evt.menu.AppendAction("Save Asset", (e) =>
+            {
+                EditorUtility.SetDirty(graph);
+                AssetDatabase.SaveAssets();
+            }, DropdownMenuAction.AlwaysEnabled);
+        }
+
+        /// <summary>
+        /// Add the Help entry to the context menu
+        /// </summary>
+        /// <param name="evt"></param>
+        protected void BuildHelpContextualMenu(ContextualMenuPopulateEvent evt)
+        {
+            evt.menu.AppendAction("Help/Reset Pinned Windows", e =>
+            {
+                foreach (var kp in pinnedElements)
+                    kp.Value.ResetPosition();
+            });
+        }
+
+        protected virtual void KeyDownCallback(KeyDownEvent e)
+        {
+            if (e.keyCode == KeyCode.S && e.commandKey)
+            {
+                SaveGraphToDisk();
+                e.StopPropagation();
+            }
+            else if (nodeViews.Count > 0 && e.commandKey && e.altKey)
+            {
+                //	Node Aligning shortcuts
+                switch (e.keyCode)
+                {
+                    case KeyCode.LeftArrow:
+                        nodeViews[0].AlignToLeft();
+                        e.StopPropagation();
+                        break;
+                    case KeyCode.RightArrow:
+                        nodeViews[0].AlignToRight();
+                        e.StopPropagation();
+                        break;
+                    case KeyCode.UpArrow:
+                        nodeViews[0].AlignToTop();
+                        e.StopPropagation();
+                        break;
+                    case KeyCode.DownArrow:
+                        nodeViews[0].AlignToBottom();
+                        e.StopPropagation();
+                        break;
+                    case KeyCode.C:
+                        nodeViews[0].AlignToCenter();
+                        e.StopPropagation();
+                        break;
+                    case KeyCode.M:
+                        nodeViews[0].AlignToMiddle();
+                        e.StopPropagation();
+                        break;
+                }
+            }
+        }
+
+        void MouseUpCallback(MouseUpEvent e)
+        {
+            schedule.Execute(() =>
+            {
+                if (DoesSelectionContainsInspectorNodes())
+                    UpdateNodeInspectorSelection();
+            }).ExecuteLater(1);
+        }
+
+        void MouseDownCallback(MouseDownEvent e)
+        {
+            // When left clicking on the graph (not a node or something else)
+            if (e.button == 0)
+            {
+                // Close all settings windows:
+                nodeViews.ForEach(v => v.CloseSettings());
+            }
+
+            if (DoesSelectionContainsInspectorNodes())
+                UpdateNodeInspectorSelection();
+        }
+
+        bool DoesSelectionContainsInspectorNodes()
+        {
+            var selectedNodes = selection.Where(s => s is BaseNodeView).ToList();
+            var selectedNodesNotInInspector = selectedNodes.Except(nodeInspector.selectedNodes).ToList();
+            var nodeInInspectorWithoutSelectedNodes = nodeInspector.selectedNodes.Except(selectedNodes).ToList();
+
+            return selectedNodesNotInInspector.Any() || nodeInInspectorWithoutSelectedNodes.Any();
+        }
+
+        void DragPerformedCallback(DragPerformEvent e)
+        {
+            var mousePos = (e.currentTarget as VisualElement).ChangeCoordinatesTo(contentViewContainer, e.localMousePosition);
             var dragData = DragAndDrop.GetGenericData("DragSelection") as List<ISelectable>;
-			var dragObjects = DragAndDrop.objectReferences;
+
+            // Drag and Drop for elements inside the graph
+            if (dragData != null)
+            {
+                var exposedParameterFieldViews = dragData.OfType<ExposedParameterFieldView>();
+                if (exposedParameterFieldViews.Any())
+                {
+                    foreach (var paramFieldView in exposedParameterFieldViews)
+                    {
+                        RegisterCompleteObjectUndo("Create Parameter Node");
+                        Type parameterNodeType = paramFieldView.parameter.CustomParameterNodeType ?? DefaultParameterNode;
+                        var paramNode = BaseNode.CreateFromType(parameterNodeType, mousePos) as ParameterNode;
+                        paramNode.parameterGUID = paramFieldView.parameter.guid;
+                        AddNode(paramNode);
+                    }
+                }
+            }
+
+            // External objects drag and drop
+            if (DragAndDrop.objectReferences.Length > 0)
+            {
+                RegisterCompleteObjectUndo("Create Node From Object(s)");
+                foreach (var obj in DragAndDrop.objectReferences)
+                {
+                    var objectType = obj.GetType();
+
+                    foreach (var kp in nodeTypePerCreateAssetType)
+                    {
+                        if (kp.Key.IsAssignableFrom(objectType))
+                        {
+                            try
+                            {
+                                var node = BaseNode.CreateFromType(kp.Value.nodeType, mousePos);
+                                if ((bool)kp.Value.initalizeNodeFromObject.Invoke(node, new[] { obj }))
+                                {
+                                    AddNode(node);
+                                    break;
+                                }
+                            }
+                            catch (Exception exception)
+                            {
+                                Debug.LogException(exception);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        void DragUpdatedCallback(DragUpdatedEvent e)
+        {
+            var dragData = DragAndDrop.GetGenericData("DragSelection") as List<ISelectable>;
+            var dragObjects = DragAndDrop.objectReferences;
             bool dragging = false;
 
             if (dragData != null)
             {
                 // Handle drag from exposed parameter view
                 if (dragData.OfType<ExposedParameterFieldView>().Any())
-				{
+                {
                     dragging = true;
-				}
+                }
             }
 
-			if (dragObjects.Length > 0)
-				dragging = true;
+            if (dragObjects.Length > 0)
+                dragging = true;
 
             if (dragging)
                 DragAndDrop.visualMode = DragAndDropVisualMode.Generic;
 
-			UpdateNodeInspectorSelection();
+            UpdateNodeInspectorSelection();
         }
 
-		#endregion
+        #endregion
 
-		#region Initialization
+        #region Initialization
 
-		void ReloadView()
-		{
-			// Force the graph to reload his data (Undo have updated the serialized properties of the graph
-			// so the one that are not serialized need to be synchronized)
-			graph.Deserialize();
+        void ReloadView()
+        {
+            // Force the graph to reload his data (Undo have updated the serialized properties of the graph
+            // so the one that are not serialized need to be synchronized)
+            graph.Deserialize();
 
-			// Get selected nodes
-			var selectedNodeGUIDs = new List<string>();
-			foreach (var e in selection)
-			{
-				if (e is BaseNodeView v && this.Contains(v))
-					selectedNodeGUIDs.Add(v.nodeTarget.GUID);
-			}
+            // Get selected nodes
+            var selectedNodeGUIDs = new List<string>();
+            foreach (var e in selection)
+            {
+                if (e is BaseNodeView v && this.Contains(v))
+                    selectedNodeGUIDs.Add(v.nodeTarget.GUID);
+            }
 
-			// Remove everything
-			RemoveNodeViews();
-			RemoveEdges();
-			RemoveGroups();
+            // Remove everything
+            RemoveNodeViews();
+            RemoveEdges();
+            RemoveGroups();
 #if UNITY_2020_1_OR_NEWER
-			RemoveStrickyNotes();
+            RemoveStrickyNotes();
 #endif
-			RemoveStackNodeViews();
+            RemoveStackNodeViews();
 
-			UpdateSerializedProperties();
+            UpdateSerializedProperties();
 
-			// And re-add with new up to date datas
-			InitializeNodeViews();
-			InitializeEdgeViews();
+            // And re-add with new up to date datas
+            InitializeNodeViews();
+            InitializeEdgeViews();
             InitializeGroups();
-			InitializeStickyNotes();
-			InitializeStackNodes();
+            InitializeStickyNotes();
+            InitializeStackNodes();
 
-			Reload();
+            Reload();
 
-			UpdateComputeOrder();
+            UpdateComputeOrder();
 
-			// Restore selection after re-creating all views
-			// selection = nodeViews.Where(v => selectedNodeGUIDs.Contains(v.nodeTarget.GUID)).Select(v => v as ISelectable).ToList();
-			foreach (var guid in selectedNodeGUIDs)
-			{
-				AddToSelection(nodeViews.FirstOrDefault(n => n.nodeTarget.GUID == guid));
-			}
+            // Restore selection after re-creating all views
+            // selection = nodeViews.Where(v => selectedNodeGUIDs.Contains(v.nodeTarget.GUID)).Select(v => v as ISelectable).ToList();
+            foreach (var guid in selectedNodeGUIDs)
+            {
+                AddToSelection(nodeViews.FirstOrDefault(n => n.nodeTarget.GUID == guid));
+            }
 
-			UpdateNodeInspectorSelection();
-		}
+            UpdateNodeInspectorSelection();
+        }
 
-		public void Initialize(BaseGraph graph)
-		{
-			if (this.graph != null)
-			{
-				SaveGraphToDisk();
-				// Close pinned windows from old graph:
-				ClearGraphElements();
-				NodeProvider.UnloadGraph(graph);
-			}
+        public void Initialize(BaseGraph graph)
+        {
+            if (this.graph != null)
+            {
+                SaveGraphToDisk();
+                // Close pinned windows from old graph:
+                ClearGraphElements();
+                NodeProvider.UnloadGraph(graph);
+            }
 
-			this.graph = graph;
+            this.graph = graph;
 
-			exposedParameterFactory = new ExposedParameterFieldFactory(graph);
+            exposedParameterFactory = new ExposedParameterFieldFactory(graph);
 
-			UpdateSerializedProperties();
+            UpdateSerializedProperties();
 
             connectorListener = CreateEdgeConnectorListener();
 
-			// When pressing ctrl-s, we save the graph
-			EditorSceneManager.sceneSaved += _ => SaveGraphToDisk();
-			RegisterCallback<KeyDownEvent>(e => {
-				if (e.keyCode == KeyCode.S && e.actionKey)
-					SaveGraphToDisk();
-			});
+            // When pressing ctrl-s, we save the graph
+            EditorSceneManager.sceneSaved += _ => SaveGraphToDisk();
+            RegisterCallback<KeyDownEvent>(e =>
+            {
+                if (e.keyCode == KeyCode.S && e.actionKey)
+                    SaveGraphToDisk();
+            });
 
-			ClearGraphElements();
+            ClearGraphElements();
 
-			InitializeGraphView();
-			InitializeNodeViews();
-			InitializeEdgeViews();
-			InitializeViews();
+            InitializeGraphView();
+            InitializeNodeViews();
+            InitializeEdgeViews();
+            InitializeViews();
             InitializeGroups();
-			InitializeStickyNotes();
-			InitializeStackNodes();
+            InitializeStickyNotes();
+            InitializeStackNodes();
 
-			initialized?.Invoke();
-			UpdateComputeOrder();
+            initialized?.Invoke();
+            UpdateComputeOrder();
 
-			InitializeView();
+            InitializeView();
 
-			NodeProvider.LoadGraph(graph);
+            NodeProvider.LoadGraph(graph);
 
-			// Register the nodes that can be created from assets
-			foreach (var nodeInfo in NodeProvider.GetNodeMenuEntries(graph))
-			{
-				var interfaces = nodeInfo.type.GetInterfaces();
+            // Register the nodes that can be created from assets
+            foreach (var nodeInfo in NodeProvider.GetNodeMenuEntries(graph))
+            {
+                var interfaces = nodeInfo.type.GetInterfaces();
                 var exceptInheritedInterfaces = interfaces.Except(interfaces.SelectMany(t => t.GetInterfaces()));
-				foreach (var i in interfaces)
-				{
-					if (i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICreateNodeFrom<>))
-					{
-						var genericArgumentType = i.GetGenericArguments()[0];
-						var initializeFunction = nodeInfo.type.GetMethod(
-							nameof(ICreateNodeFrom<Object>.InitializeNodeFromObject),
-							BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance,
-							null, new Type[]{ genericArgumentType}, null
-						);
-
-						// We only add the type that implements the interface, not it's children
-						if (initializeFunction.DeclaringType == nodeInfo.type)
-							nodeTypePerCreateAssetType[genericArgumentType] = (nodeInfo.type, initializeFunction);
-					}
-				}
-			}
-		}
-
-		public void ClearGraphElements()
-		{
-			RemoveGroups();
-			RemoveNodeViews();
-			RemoveEdges();
-			RemoveStackNodeViews();
-			RemovePinnedElementViews();
+                foreach (var i in exceptInheritedInterfaces)
+                {
+                    if (i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICreateNodeFrom<>))
+                    {
+                        var genericArgumentType = i.GetGenericArguments()[0];
+                        var initializeFunction = nodeInfo.type.GetMethod(
+                            nameof(ICreateNodeFrom<Object>.InitializeNodeFromObject),
+                            BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance,
+                            null, new Type[] { genericArgumentType }, null
+                        );
+
+                        // We only add the type that implements the interface, not it's children
+                        if (initializeFunction.DeclaringType == nodeInfo.type)
+                            nodeTypePerCreateAssetType[genericArgumentType] = (nodeInfo.type, initializeFunction);
+                    }
+                }
+            }
+        }
+
+        public void ClearGraphElements()
+        {
+            RemoveGroups();
+            RemoveNodeViews();
+            RemoveEdges();
+            RemoveStackNodeViews();
+            RemovePinnedElementViews();
 #if UNITY_2020_1_OR_NEWER
-			RemoveStrickyNotes();
+            RemoveStrickyNotes();
 #endif
-		}
-
-		void UpdateSerializedProperties()
-		{
-			serializedGraph = new SerializedObject(graph);
-		}
-
-		/// <summary>
-		/// Allow you to create your own edge connector listener
-		/// </summary>
-		/// <returns></returns>
-		protected virtual BaseEdgeConnectorListener CreateEdgeConnectorListener()
-		 => new BaseEdgeConnectorListener(this);
-
-		void InitializeGraphView()
-		{
-			graph.onExposedParameterListChanged += OnExposedParameterListChanged;
-			graph.onExposedParameterModified += (s) => onExposedParameterModified?.Invoke(s);
-			graph.onGraphChanges += GraphChangesCallback;
-			viewTransform.position = graph.position;
-			viewTransform.scale = graph.scale;
-			nodeCreationRequest = (c) => SearchWindow.Open(new SearchWindowContext(c.screenMousePosition), createNodeMenu);
-		}
-
-		void OnExposedParameterListChanged()
-		{
-			UpdateSerializedProperties();
-			onExposedParameterListChanged?.Invoke();
-		}
-
-		void InitializeNodeViews()
-		{
-			graph.nodes.RemoveAll(n => n == null);
-
-			foreach (var node in graph.nodes)
-			{
-				var v = AddNodeView(node);
-			}
-		}
-
-		void InitializeEdgeViews()
-		{
-			// Sanitize edges in case a node broke something while loading
-			graph.edges.RemoveAll(edge => edge == null || edge.inputNode == null || edge.outputNode == null);
-
-			foreach (var serializedEdge in graph.edges)
-			{
-				nodeViewsPerNode.TryGetValue(serializedEdge.inputNode, out var inputNodeView);
-				nodeViewsPerNode.TryGetValue(serializedEdge.outputNode, out var outputNodeView);
-				if (inputNodeView == null || outputNodeView == null)
-					continue;
-
-				var edgeView = CreateEdgeView();
-				edgeView.userData = serializedEdge;
-				edgeView.input = inputNodeView.GetPortViewFromFieldName(serializedEdge.inputFieldName, serializedEdge.inputPortIdentifier);
-				edgeView.output = outputNodeView.GetPortViewFromFieldName(serializedEdge.outputFieldName, serializedEdge.outputPortIdentifier);
-
-
-				ConnectView(edgeView);
-			}
-		}
-
-		void InitializeViews()
-		{
-			foreach (var pinnedElement in graph.pinnedElements)
-			{
-				if (pinnedElement.opened)
-					OpenPinned(pinnedElement.editorType.type);
-			}
-		}
+        }
+
+        void UpdateSerializedProperties()
+        {
+            if (graph != null)
+                serializedGraph = new SerializedObject(graph);
+        }
+
+        /// <summary>
+        /// Allow you to create your own edge connector listener
+        /// </summary>
+        /// <returns></returns>
+        protected virtual BaseEdgeConnectorListener CreateEdgeConnectorListener()
+         => new BaseEdgeConnectorListener(this);
+
+        void InitializeGraphView()
+        {
+            graph.onExposedParameterListChanged += OnExposedParameterListChanged;
+            graph.onExposedParameterModified += (s) => onExposedParameterModified?.Invoke(s);
+            graph.onGraphChanges += GraphChangesCallback;
+            viewTransform.position = graph.position;
+            viewTransform.scale = graph.scale;
+            nodeCreationRequest = (c) => SearchWindow.Open(new SearchWindowContext(c.screenMousePosition), createNodeMenu);
+        }
+
+        void OnExposedParameterListChanged()
+        {
+            UpdateSerializedProperties();
+            onExposedParameterListChanged?.Invoke();
+        }
+
+        void InitializeNodeViews()
+        {
+            graph.nodes.RemoveAll(n => n == null);
+
+            foreach (var node in graph.nodes)
+            {
+                var v = AddNodeView(node);
+            }
+        }
+
+        void InitializeEdgeViews()
+        {
+            // Sanitize edges in case a node broke something while loading
+            graph.edges.RemoveAll(edge => edge == null || edge.inputNode == null || edge.outputNode == null);
+
+            foreach (var serializedEdge in graph.edges)
+            {
+                nodeViewsPerNode.TryGetValue(serializedEdge.inputNode, out var inputNodeView);
+                nodeViewsPerNode.TryGetValue(serializedEdge.outputNode, out var outputNodeView);
+                if (inputNodeView == null || outputNodeView == null)
+                    continue;
+
+                var edgeView = CreateEdgeView();
+                edgeView.userData = serializedEdge;
+                edgeView.input = inputNodeView.GetPortViewFromFieldName(serializedEdge.inputFieldName, serializedEdge.inputPortIdentifier);
+                edgeView.output = outputNodeView.GetPortViewFromFieldName(serializedEdge.outputFieldName, serializedEdge.outputPortIdentifier);
+
+
+                ConnectView(edgeView);
+            }
+        }
+
+        void InitializeViews()
+        {
+            foreach (var pinnedElement in graph.pinnedElements)
+            {
+                if (pinnedElement.opened)
+                    OpenPinned(pinnedElement.editorType.type);
+            }
+        }
 
         void InitializeGroups()
         {
@@ -885,123 +902,123 @@ void InitializeGroups()
                 AddGroupView(group);
         }
 
-		void InitializeStickyNotes()
-		{
+        void InitializeStickyNotes()
+        {
 #if UNITY_2020_1_OR_NEWER
             foreach (var group in graph.stickyNotes)
                 AddStickyNoteView(group);
 #endif
-		}
-
-		void InitializeStackNodes()
-		{
-			foreach (var stackNode in graph.stackNodes)
-				AddStackNodeView(stackNode);
-		}
-
-		protected virtual void InitializeManipulators()
-		{
-			this.AddManipulator(new ContentDragger());
-			this.AddManipulator(new SelectionDragger());
-			this.AddManipulator(new RectangleSelector());
-		}
-
-		protected virtual void Reload() {}
-
-		#endregion
-
-		#region Graph content modification
-
-		public void UpdateNodeInspectorSelection()
-		{
-			if (nodeInspector.previouslySelectedObject != Selection.activeObject)
-				nodeInspector.previouslySelectedObject = Selection.activeObject;
-
-			HashSet<BaseNodeView> selectedNodeViews = new HashSet<BaseNodeView>();
-			nodeInspector.selectedNodes.Clear();
-			foreach (var e in selection)
-			{
-				if (e is BaseNodeView v && this.Contains(v) && v.nodeTarget.needsInspector)
-					selectedNodeViews.Add(v);
-			}
-
-			nodeInspector.UpdateSelectedNodes(selectedNodeViews);
-			if (Selection.activeObject != nodeInspector && selectedNodeViews.Count > 0)
-				Selection.activeObject = nodeInspector;
-		}
-
-		public BaseNodeView AddNode(BaseNode node)
-		{
-			// This will initialize the node using the graph instance
-			graph.AddNode(node);
-
-			UpdateSerializedProperties();
-
-			var view = AddNodeView(node);
-
-			// Call create after the node have been initialized
-			ExceptionToLog.Call(() => view.OnCreated());
-
-			UpdateComputeOrder();
-
-			return view;
-		}
-
-		public BaseNodeView AddNodeView(BaseNode node)
-		{
-			var viewType = NodeProvider.GetNodeViewTypeFromType(node.GetType());
-
-			if (viewType == null)
-				viewType = typeof(BaseNodeView);
-
-			var baseNodeView = Activator.CreateInstance(viewType) as BaseNodeView;
-			baseNodeView.Initialize(this, node);
-			AddElement(baseNodeView);
-
-			nodeViews.Add(baseNodeView);
-			nodeViewsPerNode[node] = baseNodeView;
-
-			return baseNodeView;
-		}
-
-		public void RemoveNode(BaseNode node)
-		{
-			var view = nodeViewsPerNode[node];
-			RemoveNodeView(view);
-			graph.RemoveNode(node);
-		}
-
-		public void RemoveNodeView(BaseNodeView nodeView)
-		{
-			RemoveElement(nodeView);
-			nodeViews.Remove(nodeView);
-			nodeViewsPerNode.Remove(nodeView.nodeTarget);
-		}
-
-		void RemoveNodeViews()
-		{
-			foreach (var nodeView in nodeViews)
-				RemoveElement(nodeView);
-			nodeViews.Clear();
-			nodeViewsPerNode.Clear();
-		}
-
-		void RemoveStackNodeViews()
-		{
-			foreach (var stackView in stackNodeViews)
-				RemoveElement(stackView);
-			stackNodeViews.Clear();
-		}
-
-		void RemovePinnedElementViews()
-		{
-			foreach (var pinnedView in pinnedElements.Values)
-			{
-				if (Contains(pinnedView))
-					Remove(pinnedView);
-			}
-			pinnedElements.Clear();
-		}
+        }
+
+        void InitializeStackNodes()
+        {
+            foreach (var stackNode in graph.stackNodes)
+                AddStackNodeView(stackNode);
+        }
+
+        protected virtual void InitializeManipulators()
+        {
+            this.AddManipulator(new ContentDragger());
+            this.AddManipulator(new SelectionDragger());
+            this.AddManipulator(new RectangleSelector());
+        }
+
+        protected virtual void Reload() { }
+
+        #endregion
+
+        #region Graph content modification
+
+        public void UpdateNodeInspectorSelection()
+        {
+            if (nodeInspector.previouslySelectedObject != Selection.activeObject)
+                nodeInspector.previouslySelectedObject = Selection.activeObject;
+
+            HashSet<BaseNodeView> selectedNodeViews = new HashSet<BaseNodeView>();
+            nodeInspector.selectedNodes.Clear();
+            foreach (var e in selection)
+            {
+                if (e is BaseNodeView v && this.Contains(v) && v.nodeTarget.needsInspector)
+                    selectedNodeViews.Add(v);
+            }
+
+            nodeInspector.UpdateSelectedNodes(selectedNodeViews);
+            if (Selection.activeObject != nodeInspector && selectedNodeViews.Count > 0)
+                Selection.activeObject = nodeInspector;
+        }
+
+        public BaseNodeView AddNode(BaseNode node)
+        {
+            // This will initialize the node using the graph instance
+            graph.AddNode(node);
+
+            UpdateSerializedProperties();
+
+            var view = AddNodeView(node);
+
+            // Call create after the node have been initialized
+            ExceptionToLog.Call(() => view.OnCreated());
+
+            UpdateComputeOrder();
+
+            return view;
+        }
+
+        public BaseNodeView AddNodeView(BaseNode node)
+        {
+            var viewType = NodeProvider.GetNodeViewTypeFromType(node.GetType());
+
+            if (viewType == null)
+                viewType = typeof(BaseNodeView);
+
+            var baseNodeView = Activator.CreateInstance(viewType) as BaseNodeView;
+            baseNodeView.Initialize(this, node);
+            AddElement(baseNodeView);
+
+            nodeViews.Add(baseNodeView);
+            nodeViewsPerNode[node] = baseNodeView;
+
+            return baseNodeView;
+        }
+
+        public void RemoveNode(BaseNode node)
+        {
+            var view = nodeViewsPerNode[node];
+            RemoveNodeView(view);
+            graph.RemoveNode(node);
+        }
+
+        public void RemoveNodeView(BaseNodeView nodeView)
+        {
+            RemoveElement(nodeView);
+            nodeViews.Remove(nodeView);
+            nodeViewsPerNode.Remove(nodeView.nodeTarget);
+        }
+
+        void RemoveNodeViews()
+        {
+            foreach (var nodeView in nodeViews)
+                RemoveElement(nodeView);
+            nodeViews.Clear();
+            nodeViewsPerNode.Clear();
+        }
+
+        void RemoveStackNodeViews()
+        {
+            foreach (var stackView in stackNodeViews)
+                RemoveElement(stackView);
+            stackNodeViews.Clear();
+        }
+
+        void RemovePinnedElementViews()
+        {
+            foreach (var pinnedView in pinnedElements.Values)
+            {
+                if (Contains(pinnedView))
+                    Remove(pinnedView);
+            }
+            pinnedElements.Clear();
+        }
 
         public GroupView AddGroup(Group block)
         {
@@ -1010,42 +1027,42 @@ public GroupView AddGroup(Group block)
             return AddGroupView(block);
         }
 
-		public GroupView AddGroupView(Group block)
-		{
-			var c = new GroupView();
+        public GroupView AddGroupView(Group block)
+        {
+            var c = new GroupView();
 
-			c.Initialize(this, block);
+            c.Initialize(this, block);
 
-			AddElement(c);
+            AddElement(c);
 
             groupViews.Add(c);
             return c;
-		}
+        }
 
-		public BaseStackNodeView AddStackNode(BaseStackNode stackNode)
-		{
-			graph.AddStackNode(stackNode);
-			return AddStackNodeView(stackNode);
-		}
+        public BaseStackNodeView AddStackNode(BaseStackNode stackNode)
+        {
+            graph.AddStackNode(stackNode);
+            return AddStackNodeView(stackNode);
+        }
 
-		public BaseStackNodeView AddStackNodeView(BaseStackNode stackNode)
-		{
-			var viewType = StackNodeViewProvider.GetStackNodeCustomViewType(stackNode.GetType()) ?? typeof(BaseStackNodeView);
-			var stackView = Activator.CreateInstance(viewType, stackNode) as BaseStackNodeView;
+        public BaseStackNodeView AddStackNodeView(BaseStackNode stackNode)
+        {
+            var viewType = StackNodeViewProvider.GetStackNodeCustomViewType(stackNode.GetType()) ?? typeof(BaseStackNodeView);
+            var stackView = Activator.CreateInstance(viewType, stackNode) as BaseStackNodeView;
 
-			AddElement(stackView);
-			stackNodeViews.Add(stackView);
+            AddElement(stackView);
+            stackNodeViews.Add(stackView);
 
-			stackView.Initialize(this);
+            stackView.Initialize(this);
 
-			return stackView;
-		}
+            return stackView;
+        }
 
-		public void RemoveStackNodeView(BaseStackNodeView stackNodeView)
-		{
-			stackNodeViews.Remove(stackNodeView);
-			RemoveElement(stackNodeView);
-		}
+        public void RemoveStackNodeView(BaseStackNodeView stackNodeView)
+        {
+            stackNodeViews.Remove(stackNodeView);
+            RemoveElement(stackNodeView);
+        }
 
 #if UNITY_2020_1_OR_NEWER
         public StickyNoteView AddStickyNote(StickyNote note)
@@ -1054,30 +1071,30 @@ public StickyNoteView AddStickyNote(StickyNote note)
             return AddStickyNoteView(note);
         }
 
-		public StickyNoteView AddStickyNoteView(StickyNote note)
-		{
-			var c = new StickyNoteView();
+        public StickyNoteView AddStickyNoteView(StickyNote note)
+        {
+            var c = new StickyNoteView();
 
-			c.Initialize(this, note);
+            c.Initialize(this, note);
 
-			AddElement(c);
+            AddElement(c);
 
             stickyNoteViews.Add(c);
             return c;
-		}
-
-		public void RemoveStickyNoteView(StickyNoteView view)
-		{
-			stickyNoteViews.Remove(view);
-			RemoveElement(view);
-		}
-
-		public void RemoveStrickyNotes()
-		{
-			foreach (var stickyNodeView in stickyNoteViews)
-				RemoveElement(stickyNodeView);
-			stickyNoteViews.Clear();
-		}
+        }
+
+        public void RemoveStickyNoteView(StickyNoteView view)
+        {
+            stickyNoteViews.Remove(view);
+            RemoveElement(view);
+        }
+
+        public void RemoveStrickyNotes()
+        {
+            foreach (var stickyNodeView in stickyNoteViews)
+                RemoveElement(stickyNodeView);
+            stickyNoteViews.Clear();
+        }
 #endif
 
         public void AddSelectionsToGroup(GroupView view)
@@ -1094,324 +1111,378 @@ public void AddSelectionsToGroup(GroupView view)
             }
         }
 
-		public void RemoveGroups()
-		{
-			foreach (var groupView in groupViews)
-				RemoveElement(groupView);
-			groupViews.Clear();
-		}
-
-		public bool CanConnectEdge(EdgeView e, bool autoDisconnectInputs = true)
-		{
-			if (e.input == null || e.output == null)
-				return false;
-
-			var inputPortView = e.input as PortView;
-			var outputPortView = e.output as PortView;
-			var inputNodeView = inputPortView.node as BaseNodeView;
-			var outputNodeView = outputPortView.node as BaseNodeView;
-
-			if (inputNodeView == null || outputNodeView == null)
-			{
-				Debug.LogError("Connect aborted !");
-				return false;
-			}
-
-			return true;
-		}
-
-		public bool ConnectView(EdgeView e, bool autoDisconnectInputs = true)
-		{
-			if (!CanConnectEdge(e, autoDisconnectInputs))
-				return false;
-			
-			var inputPortView = e.input as PortView;
-			var outputPortView = e.output as PortView;
-			var inputNodeView = inputPortView.node as BaseNodeView;
-			var outputNodeView = outputPortView.node as BaseNodeView;
-
-			//If the input port does not support multi-connection, we remove them
-			if (autoDisconnectInputs && !(e.input as PortView).portData.acceptMultipleEdges)
-			{
-				foreach (var edge in edgeViews.Where(ev => ev.input == e.input).ToList())
-				{
-					// TODO: do not disconnect them if the connected port is the same than the old connected
-					DisconnectView(edge);
-				}
-			}
-			// same for the output port:
-			if (autoDisconnectInputs && !(e.output as PortView).portData.acceptMultipleEdges)
-			{
-				foreach (var edge in edgeViews.Where(ev => ev.output == e.output).ToList())
-				{
-					// TODO: do not disconnect them if the connected port is the same than the old connected
-					DisconnectView(edge);
-				}
-			}
-
-			AddElement(e);
-
-			e.input.Connect(e);
-			e.output.Connect(e);
-
-			// If the input port have been removed by the custom port behavior
-			// we try to find if it's still here
-			if (e.input == null)
-				e.input = inputNodeView.GetPortViewFromFieldName(inputPortView.fieldName, inputPortView.portData.identifier);
-			if (e.output == null)
-				e.output = inputNodeView.GetPortViewFromFieldName(outputPortView.fieldName, outputPortView.portData.identifier);
-
-			edgeViews.Add(e);
-
-			inputNodeView.RefreshPorts();
-			outputNodeView.RefreshPorts();
-
-			// In certain cases the edge color is wrong so we patch it
-			schedule.Execute(() => {
-				e.UpdateEdgeControl();
-			}).ExecuteLater(1);
-
-			e.isConnected = true;
-
-			return true;
-		}
-
-		public bool Connect(PortView inputPortView, PortView outputPortView, bool autoDisconnectInputs = true)
-		{
-			var inputPort = inputPortView.owner.nodeTarget.GetPort(inputPortView.fieldName, inputPortView.portData.identifier);
-			var outputPort = outputPortView.owner.nodeTarget.GetPort(outputPortView.fieldName, outputPortView.portData.identifier);
-
-			// Checks that the node we are connecting still exists
-			if (inputPortView.owner.parent == null || outputPortView.owner.parent == null)
-				return false;
-
-			var newEdge = SerializableEdge.CreateNewEdge(graph, inputPort, outputPort);
-
-			var edgeView = CreateEdgeView();
-			edgeView.userData = newEdge;
-			edgeView.input = inputPortView;
-			edgeView.output = outputPortView;
-
-
-			return Connect(edgeView);
-		}
-
-		public bool Connect(EdgeView e, bool autoDisconnectInputs = true)
-		{
-			if (!CanConnectEdge(e, autoDisconnectInputs))
-				return false;
-
-			var inputPortView = e.input as PortView;
-			var outputPortView = e.output as PortView;
-			var inputNodeView = inputPortView.node as BaseNodeView;
-			var outputNodeView = outputPortView.node as BaseNodeView;
-			var inputPort = inputNodeView.nodeTarget.GetPort(inputPortView.fieldName, inputPortView.portData.identifier);
-			var outputPort = outputNodeView.nodeTarget.GetPort(outputPortView.fieldName, outputPortView.portData.identifier);
-
-			e.userData = graph.Connect(inputPort, outputPort, autoDisconnectInputs);
-
-			ConnectView(e, autoDisconnectInputs);
-
-			UpdateComputeOrder();
-
-			return true;
-		}
-
-		public void DisconnectView(EdgeView e, bool refreshPorts = true)
-		{
-			if (e == null)
-				return ;
-
-			RemoveElement(e);
-
-			if (e?.input?.node is BaseNodeView inputNodeView)
-			{
-				e.input.Disconnect(e);
-				if (refreshPorts)
-					inputNodeView.RefreshPorts();
-			}
-			if (e?.output?.node is BaseNodeView outputNodeView)
-			{
-				e.output.Disconnect(e);
-				if (refreshPorts)
-					outputNodeView.RefreshPorts();
-			}
-
-			edgeViews.Remove(e);
-		}
-
-		public void Disconnect(EdgeView e, bool refreshPorts = true)
-		{
-			// Remove the serialized edge if there is one
-			if (e.userData is SerializableEdge serializableEdge)
-				graph.Disconnect(serializableEdge.GUID);
-
-			DisconnectView(e, refreshPorts);
-
-			UpdateComputeOrder();
-		}
-
-		public void RemoveEdges()
-		{
-			foreach (var edge in edgeViews)
-				RemoveElement(edge);
-			edgeViews.Clear();
-		}
-
-		public void UpdateComputeOrder()
-		{
-			graph.UpdateComputeOrder();
-
-			computeOrderUpdated?.Invoke();
-		}
-
-		public void RegisterCompleteObjectUndo(string name)
-		{
-			Undo.RegisterCompleteObjectUndo(graph, name);
-		}
-
-		public void SaveGraphToDisk()
-		{
-			if (graph == null)
-				return ;
-
-			EditorUtility.SetDirty(graph);
-		}
-
-		public void ToggleView< T >() where T : PinnedElementView
-		{
-			ToggleView(typeof(T));
-		}
-
-		public void ToggleView(Type type)
-		{
-			PinnedElementView view;
-			pinnedElements.TryGetValue(type, out view);
-
-			if (view == null)
-				OpenPinned(type);
-			else
-				ClosePinned(type, view);
-		}
-
-		public void OpenPinned< T >() where T : PinnedElementView
-		{
-			OpenPinned(typeof(T));
-		}
-
-		public void OpenPinned(Type type)
-		{
-			PinnedElementView view;
-
-			if (type == null)
-				return ;
-
-			PinnedElement elem = graph.OpenPinned(type);
-
-			if (!pinnedElements.ContainsKey(type))
-			{
-				view = Activator.CreateInstance(type) as PinnedElementView;
-				if (view == null)
-					return ;
-				pinnedElements[type] = view;
-				view.InitializeGraphView(elem, this);
-			}
-			view = pinnedElements[type];
-
-			if (!Contains(view))
-				Add(view);
-		}
-
-		public void ClosePinned< T >(PinnedElementView view) where T : PinnedElementView
-		{
-			ClosePinned(typeof(T), view);
-		}
-
-		public void ClosePinned(Type type, PinnedElementView elem)
-		{
-			pinnedElements.Remove(type);
-			Remove(elem);
-			graph.ClosePinned(type);
-		}
-
-		public Status GetPinnedElementStatus< T >() where T : PinnedElementView
-		{
-			return GetPinnedElementStatus(typeof(T));
-		}
-
-		public Status GetPinnedElementStatus(Type type)
-		{
-			var pinned = graph.pinnedElements.Find(p => p.editorType.type == type);
-
-			if (pinned != null && pinned.opened)
-				return Status.Normal;
-			else
-				return Status.Hidden;
-		}
-
-		public void ResetPositionAndZoom()
-		{
-			graph.position = Vector3.zero;
-			graph.scale = Vector3.one;
-
-			UpdateViewTransform(graph.position, graph.scale);
-		}
-
-		/// <summary>
-		/// Deletes the selected content, can be called form an IMGUI container
-		/// </summary>
-		public void DelayedDeleteSelection() => this.schedule.Execute(() => DeleteSelectionOperation("Delete", AskUser.DontAskUser)).ExecuteLater(0);
-
-		protected virtual void InitializeView() {}
-
-		public virtual IEnumerable<(string path, Type type)> FilterCreateNodeMenuEntries()
-		{
-			// By default we don't filter anything
-			foreach (var nodeMenuItem in NodeProvider.GetNodeMenuEntries(graph))
-				yield return nodeMenuItem;
-
-			// TODO: add exposed properties to this list
-		}
-
-		public RelayNodeView AddRelayNode(PortView inputPort, PortView outputPort, Vector2 position)
-		{
-			var relayNode = BaseNode.CreateFromType<RelayNode>(position);
-			var view = AddNode(relayNode) as RelayNodeView;
-
-			if (outputPort != null)
-				Connect(view.inputPortViews[0], outputPort);
-			if (inputPort != null)
-				Connect(inputPort, view.outputPortViews[0]);
-
-			return view;
-		}
-
-		/// <summary>
-		/// Update all the serialized property bindings (in case a node was deleted / added, the property pathes needs to be updated)
-		/// </summary>
-		public void SyncSerializedPropertyPathes()
-		{
-			foreach (var nodeView in nodeViews)
-				nodeView.SyncSerializedPropertyPathes();
-			nodeInspector.RefreshNodes();
-		}
-
-		/// <summary>
-		/// Call this function when you want to remove this view
-		/// </summary>
+        public void RemoveGroups()
+        {
+            foreach (var groupView in groupViews)
+                RemoveElement(groupView);
+            groupViews.Clear();
+        }
+
+        public bool CanConnectEdge(EdgeView e, bool autoDisconnectInputs = true)
+        {
+            if (e.input == null || e.output == null)
+                return false;
+
+            var inputPortView = e.input as PortView;
+            var outputPortView = e.output as PortView;
+            var inputNodeView = inputPortView.node as BaseNodeView;
+            var outputNodeView = outputPortView.node as BaseNodeView;
+
+            if (inputNodeView == null || outputNodeView == null)
+            {
+                Debug.LogError("Connect aborted !");
+                return false;
+            }
+
+            return true;
+        }
+
+        public bool ConnectView(EdgeView e, bool autoDisconnectInputs = true)
+        {
+            if (!CanConnectEdge(e, autoDisconnectInputs))
+                return false;
+
+            var inputPortView = e.input as PortView;
+            var outputPortView = e.output as PortView;
+            var inputNodeView = inputPortView.node as BaseNodeView;
+            var outputNodeView = outputPortView.node as BaseNodeView;
+
+            //If the input port does not support multi-connection, we remove them
+            if (autoDisconnectInputs && !(e.input as PortView).portData.acceptMultipleEdges)
+            {
+                foreach (var edge in edgeViews.Where(ev => ev.input == e.input).ToList())
+                {
+                    // TODO: do not disconnect them if the connected port is the same than the old connected
+                    DisconnectView(edge);
+                }
+            }
+            // same for the output port:
+            if (autoDisconnectInputs && !(e.output as PortView).portData.acceptMultipleEdges)
+            {
+                foreach (var edge in edgeViews.Where(ev => ev.output == e.output).ToList())
+                {
+                    // TODO: do not disconnect them if the connected port is the same than the old connected
+                    DisconnectView(edge);
+                }
+            }
+
+            AddElement(e);
+
+            e.input.Connect(e);
+            e.output.Connect(e);
+
+            // If the input port have been removed by the custom port behavior
+            // we try to find if it's still here
+            if (e.input == null)
+                e.input = inputNodeView.GetPortViewFromFieldName(inputPortView.fieldName, inputPortView.portData.identifier);
+            if (e.output == null)
+                e.output = inputNodeView.GetPortViewFromFieldName(outputPortView.fieldName, outputPortView.portData.identifier);
+
+            edgeViews.Add(e);
+
+            inputNodeView.RefreshPorts();
+            outputNodeView.RefreshPorts();
+
+            // In certain cases the edge color is wrong so we patch it
+            schedule.Execute(() =>
+            {
+                e.UpdateEdgeControl();
+            }).ExecuteLater(1);
+
+            e.isConnected = true;
+
+            return true;
+        }
+
+        public bool Connect(PortView inputPortView, PortView outputPortView, bool autoDisconnectInputs = true)
+        {
+            var inputPort = inputPortView.owner.nodeTarget.GetPort(inputPortView.fieldName, inputPortView.portData.identifier);
+            var outputPort = outputPortView.owner.nodeTarget.GetPort(outputPortView.fieldName, outputPortView.portData.identifier);
+
+            // Checks that the node we are connecting still exists
+            if (inputPortView.owner.parent == null || outputPortView.owner.parent == null)
+                return false;
+
+            var newEdge = SerializableEdge.CreateNewEdge(graph, inputPort, outputPort);
+
+            var edgeView = CreateEdgeView();
+            edgeView.userData = newEdge;
+            edgeView.input = inputPortView;
+            edgeView.output = outputPortView;
+
+            if (ConversionNodeAdapter.AreAssignable(outputPort.portData.displayType, inputPort.portData.displayType))
+            {
+                return ConnectConvertable(edgeView, autoDisconnectInputs);
+            }
+            else
+            {
+                return Connect(edgeView);
+            }
+        }
+
+        /// <summary>
+        /// Same as connect, but also adds custom conversion nodes inbetween the edges input/output, if neccessary
+        /// </summary>
+        /// <param name="e"></param>
+        /// <param name="autoDisconnectInputs"></param>
+        /// <returns></returns>
+        public bool ConnectConvertable(EdgeView e, bool autoDisconnectInputs = true)
+        {
+            if (!CanConnectEdge(e, autoDisconnectInputs))
+                return false;
+
+            var inputPortView = e.input as PortView;
+            var outputPortView = e.output as PortView;
+            var inputNodeView = inputPortView.node as BaseNodeView;
+            var outputNodeView = outputPortView.node as BaseNodeView;
+            var inputPort = inputNodeView.nodeTarget.GetPort(inputPortView.fieldName, inputPortView.portData.identifier);
+            var outputPort = outputNodeView.nodeTarget.GetPort(outputPortView.fieldName, outputPortView.portData.identifier);
+
+            Type conversionNodeType = ConversionNodeAdapter.GetConversionNode(outputPort.portData.displayType, inputPort.portData.displayType);
+            if (conversionNodeType != null)
+            {
+                var nodePosition = (inputPort.owner.position.center + outputPort.owner.position.center) / 2.0f;
+                BaseNode converterNode = BaseNode.CreateFromType(conversionNodeType, nodePosition);
+                IConversionNode conversion = (IConversionNode)converterNode;
+                var converterView = AddNode(converterNode);
+
+                // set nodes center position to be in the middle of the input/output ports
+                converterNode.position.center = nodePosition - new Vector2(converterNode.position.width / 2.0f, 0);
+                converterView.SetPosition(converterNode.position);
+
+
+                var conversionInputName = conversion.GetConversionInput();
+                var converterInput = converterView.inputPortViews.Find(view => view.fieldName == conversionInputName);
+                var conversionOutputName = conversion.GetConversionOutput();
+                var converterOutput = converterView.outputPortViews.Find(view => view.fieldName == conversionOutputName);
+
+                Connect(inputPortView, converterOutput, autoDisconnectInputs);
+
+                e.input = converterInput; // change from original input to use the converter node
+                return Connect(e, autoDisconnectInputs);
+            }
+            else
+            {
+                return Connect(e, autoDisconnectInputs);
+            }
+        }
+
+        public bool Connect(EdgeView e, bool autoDisconnectInputs = true)
+        {
+            if (!CanConnectEdge(e, autoDisconnectInputs))
+                return false;
+
+            var inputPortView = e.input as PortView;
+            var outputPortView = e.output as PortView;
+            var inputNodeView = inputPortView.node as BaseNodeView;
+            var outputNodeView = outputPortView.node as BaseNodeView;
+            var inputPort = inputNodeView.nodeTarget.GetPort(inputPortView.fieldName, inputPortView.portData.identifier);
+            var outputPort = outputNodeView.nodeTarget.GetPort(outputPortView.fieldName, outputPortView.portData.identifier);
+
+            e.userData = graph.Connect(inputPort, outputPort, autoDisconnectInputs);
+
+            ConnectView(e, autoDisconnectInputs);
+
+            UpdateComputeOrder();
+
+            return true;
+        }
+
+        public void DisconnectView(EdgeView e, bool refreshPorts = true)
+        {
+            if (e == null)
+                return;
+
+            RemoveElement(e);
+
+            if (e?.input?.node is BaseNodeView inputNodeView)
+            {
+                e.input.Disconnect(e);
+                if (refreshPorts)
+                    inputNodeView.RefreshPorts();
+            }
+            if (e?.output?.node is BaseNodeView outputNodeView)
+            {
+                e.output.Disconnect(e);
+                if (refreshPorts)
+                    outputNodeView.RefreshPorts();
+            }
+
+            edgeViews.Remove(e);
+        }
+
+        public void Disconnect(EdgeView e, bool refreshPorts = true)
+        {
+            // Remove the serialized edge if there is one
+            if (e.userData is SerializableEdge serializableEdge)
+                graph.Disconnect(serializableEdge.GUID);
+
+            DisconnectView(e, refreshPorts);
+
+            UpdateComputeOrder();
+        }
+
+        public void RemoveEdges()
+        {
+            foreach (var edge in edgeViews)
+                RemoveElement(edge);
+            edgeViews.Clear();
+        }
+
+        public void UpdateComputeOrder()
+        {
+            graph.UpdateComputeOrder();
+
+            computeOrderUpdated?.Invoke();
+        }
+
+        public void RegisterCompleteObjectUndo(string name)
+        {
+            Undo.RegisterCompleteObjectUndo(graph, name);
+        }
+
+        public void SaveGraphToDisk()
+        {
+            if (graph == null)
+                return;
+
+            EditorUtility.SetDirty(graph);
+        }
+
+        public void ToggleView<T>() where T : PinnedElementView
+        {
+            ToggleView(typeof(T));
+        }
+
+        public void ToggleView(Type type)
+        {
+            PinnedElementView view;
+            pinnedElements.TryGetValue(type, out view);
+
+            if (view == null)
+                OpenPinned(type);
+            else
+                ClosePinned(type, view);
+        }
+
+        public void OpenPinned<T>() where T : PinnedElementView
+        {
+            OpenPinned(typeof(T));
+        }
+
+        public void OpenPinned(Type type)
+        {
+            PinnedElementView view;
+
+            if (type == null)
+                return;
+
+            PinnedElement elem = graph.OpenPinned(type);
+
+            if (!pinnedElements.ContainsKey(type))
+            {
+                view = Activator.CreateInstance(type) as PinnedElementView;
+                if (view == null)
+                    return;
+                pinnedElements[type] = view;
+                view.InitializeGraphView(elem, this);
+            }
+            view = pinnedElements[type];
+
+            if (!Contains(view))
+                Add(view);
+        }
+
+        public void ClosePinned<T>(PinnedElementView view) where T : PinnedElementView
+        {
+            ClosePinned(typeof(T), view);
+        }
+
+        public void ClosePinned(Type type, PinnedElementView elem)
+        {
+            pinnedElements.Remove(type);
+            Remove(elem);
+            graph.ClosePinned(type);
+        }
+
+        public Status GetPinnedElementStatus<T>() where T : PinnedElementView
+        {
+            return GetPinnedElementStatus(typeof(T));
+        }
+
+        public Status GetPinnedElementStatus(Type type)
+        {
+            var pinned = graph.pinnedElements.Find(p => p.editorType.type == type);
+
+            if (pinned != null && pinned.opened)
+                return Status.Normal;
+            else
+                return Status.Hidden;
+        }
+
+        public void ResetPositionAndZoom()
+        {
+            graph.position = Vector3.zero;
+            graph.scale = Vector3.one;
+
+            UpdateViewTransform(graph.position, graph.scale);
+        }
+
+        /// <summary>
+        /// Deletes the selected content, can be called form an IMGUI container
+        /// </summary>
+        public void DelayedDeleteSelection() => this.schedule.Execute(() => DeleteSelectionOperation("Delete", AskUser.DontAskUser)).ExecuteLater(0);
+
+        protected virtual void InitializeView() { }
+
+        public virtual IEnumerable<(string path, Type type)> FilterCreateNodeMenuEntries()
+        {
+            // By default we don't filter anything
+            foreach (var nodeMenuItem in NodeProvider.GetNodeMenuEntries(graph))
+                yield return nodeMenuItem;
+
+            // TODO: add exposed properties to this list
+        }
+
+        public RelayNodeView AddRelayNode(PortView inputPort, PortView outputPort, Vector2 position)
+        {
+            var relayNode = BaseNode.CreateFromType<RelayNode>(position);
+            var view = AddNode(relayNode) as RelayNodeView;
+
+            if (outputPort != null)
+                Connect(view.inputPortViews[0], outputPort);
+            if (inputPort != null)
+                Connect(inputPort, view.outputPortViews[0]);
+
+            return view;
+        }
+
+        /// <summary>
+        /// Update all the serialized property bindings (in case a node was deleted / added, the property pathes needs to be updated)
+        /// </summary>
+        public void SyncSerializedPropertyPathes()
+        {
+            foreach (var nodeView in nodeViews)
+                nodeView.SyncSerializedPropertyPathes();
+            nodeInspector.RefreshNodes();
+        }
+
+        /// <summary>
+        /// Call this function when you want to remove this view
+        /// </summary>
         public void Dispose()
         {
-			ClearGraphElements();
-			RemoveFromHierarchy();
-			Undo.undoRedoPerformed -= ReloadView;
-			Object.DestroyImmediate(nodeInspector);
-			NodeProvider.UnloadGraph(graph);
-			exposedParameterFactory.Dispose();
-			exposedParameterFactory = null;
-
-			graph.onExposedParameterListChanged -= OnExposedParameterListChanged;
-			graph.onExposedParameterModified += (s) => onExposedParameterModified?.Invoke(s);
-			graph.onGraphChanges -= GraphChangesCallback;
+            ClearGraphElements();
+            RemoveFromHierarchy();
+            Undo.undoRedoPerformed -= ReloadView;
+            Object.DestroyImmediate(nodeInspector);
+            NodeProvider.UnloadGraph(graph);
+            exposedParameterFactory.Dispose();
+            exposedParameterFactory = null;
+
+            graph.onExposedParameterListChanged -= OnExposedParameterListChanged;
+            graph.onExposedParameterModified += (s) => onExposedParameterModified?.Invoke(s);
+            graph.onGraphChanges -= GraphChangesCallback;
         }
 
         #endregion
diff --git a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs
index 5f3dfdad..551e4601 100644
--- a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs
+++ b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs
@@ -15,1000 +15,1043 @@
 
 namespace GraphProcessor
 {
-	[NodeCustomEditor(typeof(BaseNode))]
-	public class BaseNodeView : NodeView
-	{
-		public BaseNode							nodeTarget;
-
-		public List< PortView >					inputPortViews = new List< PortView >();
-		public List< PortView >					outputPortViews = new List< PortView >();
-
-		public BaseGraphView					owner { private set; get; }
-
-		protected Dictionary< string, List< PortView > > portsPerFieldName = new Dictionary< string, List< PortView > >();
-
-        public VisualElement 					controlsContainer;
-		protected VisualElement					debugContainer;
-		protected VisualElement					rightTitleContainer;
-		protected VisualElement					topPortContainer;
-		protected VisualElement					bottomPortContainer;
-		private VisualElement 					inputContainerElement;
-
-		VisualElement							settings;
-		NodeSettingsView						settingsContainer;
-		Button									settingButton;
-		TextField								titleTextField;
-
-		Label									computeOrderLabel = new Label();
-
-		public event Action< PortView >			onPortConnected;
-		public event Action< PortView >			onPortDisconnected;
-
-		protected virtual bool					hasSettings { get; set; }
-
-        public bool								initializing = false; //Used for applying SetPosition on locked node at init.
-
-        readonly string							baseNodeStyle = "GraphProcessorStyles/BaseNodeView";
-
-		bool									settingsExpanded = false;
-
-		[System.NonSerialized]
-		List< IconBadge >						badges = new List< IconBadge >();
-
-		private List<Node> selectedNodes = new List<Node>();
-		private float      selectedNodesFarLeft;
-		private float      selectedNodesNearLeft;
-		private float      selectedNodesFarRight;
-		private float      selectedNodesNearRight;
-		private float      selectedNodesFarTop;
-		private float      selectedNodesNearTop;
-		private float      selectedNodesFarBottom;
-		private float      selectedNodesNearBottom;
-		private float      selectedNodesAvgHorizontal;
-		private float      selectedNodesAvgVertical;
-		
-		#region  Initialization
-		
-		public void Initialize(BaseGraphView owner, BaseNode node)
-		{
-			nodeTarget = node;
-			this.owner = owner;
-
-			if (!node.deletable)
-				capabilities &= ~Capabilities.Deletable;
-			// Note that the Renamable capability is useless right now as it haven't been implemented in Graphview
-			if (node.isRenamable)
-				capabilities |= Capabilities.Renamable;
-
-			owner.computeOrderUpdated += ComputeOrderUpdatedCallback;
-			node.onMessageAdded += AddMessageView;
-			node.onMessageRemoved += RemoveMessageView;
-			node.onPortsUpdated += a => schedule.Execute(_ => UpdatePortsForField(a)).ExecuteLater(0);
+    [NodeCustomEditor(typeof(BaseNode))]
+    public class BaseNodeView : NodeView
+    {
+        public BaseNode nodeTarget;
+
+        public List<PortView> inputPortViews = new List<PortView>();
+        public List<PortView> outputPortViews = new List<PortView>();
+
+        public BaseGraphView owner { private set; get; }
+
+        protected Dictionary<string, List<PortView>> portsPerFieldName = new Dictionary<string, List<PortView>>();
+
+        public VisualElement controlsContainer;
+        protected VisualElement debugContainer;
+        protected VisualElement rightTitleContainer;
+        protected VisualElement topPortContainer;
+        protected VisualElement bottomPortContainer;
+        private VisualElement inputContainerElement;
+
+        VisualElement settings;
+        NodeSettingsView settingsContainer;
+        Button settingButton;
+        TextField titleTextField;
+
+        Label computeOrderLabel = new Label();
+
+        public event Action<PortView> onPortConnected;
+        public event Action<PortView> onPortDisconnected;
+
+        protected virtual bool hasSettings { get; set; }
+
+        public bool initializing = false; //Used for applying SetPosition on locked node at init.
+
+        readonly string baseNodeStyle = "GraphProcessorStyles/BaseNodeView";
+
+        bool settingsExpanded = false;
+
+        [System.NonSerialized]
+        List<IconBadge> badges = new List<IconBadge>();
+
+        private List<Node> selectedNodes = new List<Node>();
+        private float selectedNodesFarLeft;
+        private float selectedNodesNearLeft;
+        private float selectedNodesFarRight;
+        private float selectedNodesNearRight;
+        private float selectedNodesFarTop;
+        private float selectedNodesNearTop;
+        private float selectedNodesFarBottom;
+        private float selectedNodesNearBottom;
+        private float selectedNodesAvgHorizontal;
+        private float selectedNodesAvgVertical;
+
+        #region  Initialization
+
+        public void Initialize(BaseGraphView owner, BaseNode node)
+        {
+            nodeTarget = node;
+            this.owner = owner;
+
+            if (!node.deletable)
+                capabilities &= ~Capabilities.Deletable;
+            // Note that the Renamable capability is useless right now as it haven't been implemented in Graphview
+            if (node.isRenamable)
+                capabilities |= Capabilities.Renamable;
+
+            owner.computeOrderUpdated += ComputeOrderUpdatedCallback;
+            node.onMessageAdded += AddMessageView;
+            node.onMessageRemoved += RemoveMessageView;
+            node.onPortsUpdated += a => schedule.Execute(_ => UpdatePortsForField(a)).ExecuteLater(0);
 
             styleSheets.Add(Resources.Load<StyleSheet>(baseNodeStyle));
 
             if (!string.IsNullOrEmpty(node.layoutStyle))
                 styleSheets.Add(Resources.Load<StyleSheet>(node.layoutStyle));
 
-			InitializeView();
-			InitializePorts();
-			InitializeDebug();
-
-			// If the standard Enable method is still overwritten, we call it
-			if (GetType().GetMethod(nameof(Enable), new Type[]{}).DeclaringType != typeof(BaseNodeView))
-				ExceptionToLog.Call(() => Enable());
-			else
-				ExceptionToLog.Call(() => Enable(false));
-
-			InitializeSettings();
-
-			RefreshExpandedState();
-
-			this.RefreshPorts();
-
-			RegisterCallback<GeometryChangedEvent>(OnGeometryChanged);
-			RegisterCallback<DetachFromPanelEvent>(e => ExceptionToLog.Call(Disable));
-			OnGeometryChanged(null);
-		}
-
-		void InitializePorts()
-		{
-			var listener = owner.connectorListener;
-
-			foreach (var inputPort in nodeTarget.inputPorts)
-			{
-				AddPort(inputPort.fieldInfo, Direction.Input, listener, inputPort.portData);
-			}
-
-			foreach (var outputPort in nodeTarget.outputPorts)
-			{
-				AddPort(outputPort.fieldInfo, Direction.Output, listener, outputPort.portData);
-			}
-		}
-
-		void InitializeView()
-		{
-            controlsContainer = new VisualElement{ name = "controls" };
-			controlsContainer.AddToClassList("NodeControls");
-			mainContainer.Add(controlsContainer);
-
-			rightTitleContainer = new VisualElement{ name = "RightTitleContainer" };
-			titleContainer.Add(rightTitleContainer);
-
-			topPortContainer = new VisualElement { name = "TopPortContainer" };
-			this.Insert(0, topPortContainer);
-
-			bottomPortContainer = new VisualElement { name = "BottomPortContainer" };
-			this.Add(bottomPortContainer);
-
-			if (nodeTarget.showControlsOnHover)
-			{
-				bool mouseOverControls = false;
-				controlsContainer.style.display = DisplayStyle.None;
-				RegisterCallback<MouseOverEvent>(e => {
-					controlsContainer.style.display = DisplayStyle.Flex;
-					mouseOverControls = true;
-				});
-				RegisterCallback<MouseOutEvent>(e => {
-					var rect = GetPosition();
-					var graphMousePosition = owner.contentViewContainer.WorldToLocal(e.mousePosition);
-					if (rect.Contains(graphMousePosition) || !nodeTarget.showControlsOnHover)
-						return;
-					mouseOverControls = false;
-					schedule.Execute(_ => {
-						if (!mouseOverControls)
-							controlsContainer.style.display = DisplayStyle.None;
-					}).ExecuteLater(500);
-				});
-			}
-
-			Undo.undoRedoPerformed += UpdateFieldValues;
-
-			debugContainer = new VisualElement{ name = "debug" };
-			if (nodeTarget.debug)
-				mainContainer.Add(debugContainer);
-
-			initializing = true;
-
-			UpdateTitle();
+            InitializeView();
+            InitializePorts();
+            InitializeDebug();
+
+            // If the standard Enable method is still overwritten, we call it
+            if (GetType().GetMethod(nameof(Enable), new Type[] { }).DeclaringType != typeof(BaseNodeView))
+                ExceptionToLog.Call(() => Enable());
+            else
+                ExceptionToLog.Call(() => Enable(false));
+
+            InitializeSettings();
+
+            RefreshExpandedState();
+
+            this.RefreshPorts();
+
+            RegisterCallback<GeometryChangedEvent>(OnGeometryChanged);
+            RegisterCallback<DetachFromPanelEvent>(e => ExceptionToLog.Call(Disable));
+            OnGeometryChanged(null);
+        }
+
+        void InitializePorts()
+        {
+            var listener = owner.connectorListener;
+
+            foreach (var inputPort in nodeTarget.inputPorts)
+            {
+                AddPort(inputPort.fieldInfo, Direction.Input, listener, inputPort.portData);
+            }
+
+            foreach (var outputPort in nodeTarget.outputPorts)
+            {
+                AddPort(outputPort.fieldInfo, Direction.Output, listener, outputPort.portData);
+            }
+        }
+
+        void InitializeView()
+        {
+            controlsContainer = new VisualElement { name = "controls" };
+            controlsContainer.AddToClassList("NodeControls");
+            mainContainer.Add(controlsContainer);
+
+            rightTitleContainer = new VisualElement { name = "RightTitleContainer" };
+            titleContainer.Add(rightTitleContainer);
+
+            topPortContainer = new VisualElement { name = "TopPortContainer" };
+            this.Insert(0, topPortContainer);
+
+            bottomPortContainer = new VisualElement { name = "BottomPortContainer" };
+            this.Add(bottomPortContainer);
+
+            if (nodeTarget.showControlsOnHover)
+            {
+                bool mouseOverControls = false;
+                controlsContainer.style.display = DisplayStyle.None;
+                RegisterCallback<MouseOverEvent>(e =>
+                {
+                    controlsContainer.style.display = DisplayStyle.Flex;
+                    mouseOverControls = true;
+                });
+                RegisterCallback<MouseOutEvent>(e =>
+                {
+                    var rect = GetPosition();
+                    var graphMousePosition = owner.contentViewContainer.WorldToLocal(e.mousePosition);
+                    if (rect.Contains(graphMousePosition) || !nodeTarget.showControlsOnHover)
+                        return;
+                    mouseOverControls = false;
+                    schedule.Execute(_ =>
+                    {
+                        if (!mouseOverControls)
+                            controlsContainer.style.display = DisplayStyle.None;
+                    }).ExecuteLater(500);
+                });
+            }
+
+            Undo.undoRedoPerformed += UpdateFieldValues;
+
+            debugContainer = new VisualElement { name = "debug" };
+            if (nodeTarget.debug)
+                mainContainer.Add(debugContainer);
+
+            initializing = true;
+
+            UpdateTitle();
             SetPosition(nodeTarget.position);
-			SetNodeColor(nodeTarget.color);
-            
-			AddInputContainer();
-
-			// Add renaming capability
-			if ((capabilities & Capabilities.Renamable) != 0)
-				SetupRenamableTitle();
-		}
-
-		void SetupRenamableTitle()
-		{
-			var titleLabel = this.Q("title-label") as Label;
-
-			titleTextField = new TextField{ isDelayed = true };
-			titleTextField.style.display = DisplayStyle.None;
-			titleLabel.parent.Insert(0, titleTextField);
-
-			titleLabel.RegisterCallback<MouseDownEvent>(e => {
-				if (e.clickCount == 2 && e.button == (int)MouseButton.LeftMouse)
-					OpenTitleEditor();
-			});
-
-			titleTextField.RegisterValueChangedCallback(e => CloseAndSaveTitleEditor(e.newValue));
-
-			titleTextField.RegisterCallback<MouseDownEvent>(e => {
-				if (e.clickCount == 2 && e.button == (int)MouseButton.LeftMouse)
-					CloseAndSaveTitleEditor(titleTextField.value);
-			});
-
-			titleTextField.RegisterCallback<FocusOutEvent>(e => CloseAndSaveTitleEditor(titleTextField.value));
-
-			void OpenTitleEditor()
-			{
-				// show title textbox
-				titleTextField.style.display = DisplayStyle.Flex;
-				titleLabel.style.display = DisplayStyle.None;
-				titleTextField.focusable = true;
-
-				titleTextField.SetValueWithoutNotify(title);
-				titleTextField.Focus();
-				titleTextField.SelectAll();
-			}
-
-			void CloseAndSaveTitleEditor(string newTitle)
-			{
-				owner.RegisterCompleteObjectUndo("Renamed node " + newTitle);
-				nodeTarget.SetCustomName(newTitle);
-
-				// hide title TextBox
-				titleTextField.style.display = DisplayStyle.None;
-				titleLabel.style.display = DisplayStyle.Flex;
-				titleTextField.focusable = false;
-
-				UpdateTitle();
-			}
-		}
-
-		void UpdateTitle()
-		{
-			title = (nodeTarget.GetCustomName() == null) ? nodeTarget.GetType().Name : nodeTarget.GetCustomName();
-		}
-
-		void InitializeSettings()
-		{
-			// Initialize settings button:
-			if (hasSettings)
-			{
-				CreateSettingButton();
-				settingsContainer = new NodeSettingsView();
-				settingsContainer.visible = false;
-				settings = new VisualElement();
-				// Add Node type specific settings
-				settings.Add(CreateSettingsView());
-				settingsContainer.Add(settings);
-				Add(settingsContainer);
-				
-				var fields = nodeTarget.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
-
-				foreach(var field in fields)
-					if(field.GetCustomAttribute(typeof(SettingAttribute)) != null) 
-						AddSettingField(field);
-			}
-		}
-
-		void OnGeometryChanged(GeometryChangedEvent evt)
-		{
-			if (settingButton != null)
-			{
-				var settingsButtonLayout = settingButton.ChangeCoordinatesTo(settingsContainer.parent, settingButton.layout);
-				settingsContainer.style.top = settingsButtonLayout.yMax - 18f;
-				settingsContainer.style.left = settingsButtonLayout.xMin - layout.width + 20f;
-			}
-		}
-
-		// Workaround for bug in GraphView that makes the node selection border way too big
-		VisualElement selectionBorder, nodeBorder;
-		internal void EnableSyncSelectionBorderHeight()
-		{
-			if (selectionBorder == null || nodeBorder == null)
-			{
-				selectionBorder = this.Q("selection-border");
-				nodeBorder = this.Q("node-border");
-
-				schedule.Execute(() => {
-					selectionBorder.style.height = nodeBorder.localBound.height;
-				}).Every(17);
-			}
-		}
-		
-		void CreateSettingButton()
-		{
-			settingButton = new Button(ToggleSettings){name = "settings-button"};
-			settingButton.Add(new Image { name = "icon", scaleMode = ScaleMode.ScaleToFit });
-
-			titleContainer.Add(settingButton);
-		}
-
-		void ToggleSettings()
-		{
-			settingsExpanded = !settingsExpanded;
-			if (settingsExpanded)
-				OpenSettings();
-			else
-				CloseSettings();
-		}
-
-		public void OpenSettings()
-		{
-			if (settingsContainer != null)
-			{
-				owner.ClearSelection();
-				owner.AddToSelection(this);
-
-				settingButton.AddToClassList("clicked");
-				settingsContainer.visible = true;
-				settingsExpanded = true;
-			}
-		}
-
-		public void CloseSettings()
-		{
-			if (settingsContainer != null)
-			{
-				settingButton.RemoveFromClassList("clicked");
-				settingsContainer.visible = false;
-				settingsExpanded = false;
-			}
-		}
-
-		void InitializeDebug()
-		{
-			ComputeOrderUpdatedCallback();
-			debugContainer.Add(computeOrderLabel);
-		}
-
-		#endregion
-
-		#region API
-
-		public List< PortView > GetPortViewsFromFieldName(string fieldName)
-		{
-			List< PortView >	ret;
-
-			portsPerFieldName.TryGetValue(fieldName, out ret);
-
-			return ret;
-		}
-
-		public PortView GetFirstPortViewFromFieldName(string fieldName)
-		{
-			return GetPortViewsFromFieldName(fieldName)?.First();
-		}
-
-		public PortView GetPortViewFromFieldName(string fieldName, string identifier)
-		{
-			return GetPortViewsFromFieldName(fieldName)?.FirstOrDefault(pv => {
-				return (pv.portData.identifier == identifier) || (String.IsNullOrEmpty(pv.portData.identifier) && String.IsNullOrEmpty(identifier));
-			});
-		}
-
-
-		public PortView AddPort(FieldInfo fieldInfo, Direction direction, BaseEdgeConnectorListener listener, PortData portData)
-		{
-			PortView p = CreatePortView(direction, fieldInfo, portData, listener);
-
-			if (p.direction == Direction.Input)
-			{
-				inputPortViews.Add(p);
-
-				if (portData.vertical)
-					topPortContainer.Add(p);
-				else
-					inputContainer.Add(p);
-			}
-			else
-			{
-				outputPortViews.Add(p);
-
-				if (portData.vertical)
-					bottomPortContainer.Add(p);
-				else
-					outputContainer.Add(p);
-			}
-
-			p.Initialize(this, portData?.displayName);
-
-			List< PortView > ports;
-			portsPerFieldName.TryGetValue(p.fieldName, out ports);
-			if (ports == null)
-			{
-				ports = new List< PortView >();
-				portsPerFieldName[p.fieldName] = ports;
-			}
-			ports.Add(p);
-
-			return p;
-		}
+            SetNodeColor(nodeTarget.color);
+
+            AddInputContainer();
+
+            // Add renaming capability
+            if ((capabilities & Capabilities.Renamable) != 0)
+                SetupRenamableTitle();
+        }
+
+        void SetupRenamableTitle()
+        {
+            var titleLabel = this.Q("title-label") as Label;
+
+            titleTextField = new TextField { isDelayed = true };
+            titleTextField.style.display = DisplayStyle.None;
+            titleLabel.parent.Insert(0, titleTextField);
+
+            titleLabel.RegisterCallback<MouseDownEvent>(e =>
+            {
+                if (e.clickCount == 2 && e.button == (int)MouseButton.LeftMouse)
+                    OpenTitleEditor();
+            });
+
+            titleTextField.RegisterValueChangedCallback(e => CloseAndSaveTitleEditor(e.newValue));
+
+            titleTextField.RegisterCallback<MouseDownEvent>(e =>
+            {
+                if (e.clickCount == 2 && e.button == (int)MouseButton.LeftMouse)
+                    CloseAndSaveTitleEditor(titleTextField.value);
+            });
+
+            titleTextField.RegisterCallback<FocusOutEvent>(e => CloseAndSaveTitleEditor(titleTextField.value));
+
+            void OpenTitleEditor()
+            {
+                // show title textbox
+                titleTextField.style.display = DisplayStyle.Flex;
+                titleLabel.style.display = DisplayStyle.None;
+                titleTextField.focusable = true;
+
+                titleTextField.SetValueWithoutNotify(title);
+                titleTextField.Focus();
+                titleTextField.SelectAll();
+            }
+
+            void CloseAndSaveTitleEditor(string newTitle)
+            {
+                owner.RegisterCompleteObjectUndo("Renamed node " + newTitle);
+                nodeTarget.SetCustomName(newTitle);
+
+                // hide title TextBox
+                titleTextField.style.display = DisplayStyle.None;
+                titleLabel.style.display = DisplayStyle.Flex;
+                titleTextField.focusable = false;
+
+                UpdateTitle();
+            }
+        }
+
+        void UpdateTitle()
+        {
+            title = (nodeTarget.GetCustomName() == null) ? nodeTarget.GetType().Name : nodeTarget.GetCustomName();
+        }
+
+        void InitializeSettings()
+        {
+            // Initialize settings button:
+            if (hasSettings)
+            {
+                CreateSettingButton();
+                settingsContainer = new NodeSettingsView();
+                settingsContainer.visible = false;
+                settings = new VisualElement();
+                // Add Node type specific settings
+                settings.Add(CreateSettingsView());
+                settingsContainer.Add(settings);
+                Add(settingsContainer);
+
+                var fields = nodeTarget.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
+
+                foreach (var field in fields)
+                    if (field.HasCustomAttribute<SettingAttribute>())
+                        AddSettingField(field);
+            }
+        }
+
+        void OnGeometryChanged(GeometryChangedEvent evt)
+        {
+            if (settingButton != null)
+            {
+                var settingsButtonLayout = settingButton.ChangeCoordinatesTo(settingsContainer.parent, settingButton.layout);
+                settingsContainer.style.top = settingsButtonLayout.yMax - 18f;
+                settingsContainer.style.left = settingsButtonLayout.xMin - layout.width + 20f;
+            }
+        }
+
+        // Workaround for bug in GraphView that makes the node selection border way too big
+        VisualElement selectionBorder, nodeBorder;
+        internal void EnableSyncSelectionBorderHeight()
+        {
+            if (selectionBorder == null || nodeBorder == null)
+            {
+                selectionBorder = this.Q("selection-border");
+                nodeBorder = this.Q("node-border");
+
+                schedule.Execute(() =>
+                {
+                    selectionBorder.style.height = nodeBorder.localBound.height;
+                }).Every(17);
+            }
+        }
+
+        void CreateSettingButton()
+        {
+            settingButton = new Button(ToggleSettings) { name = "settings-button" };
+            settingButton.Add(new Image { name = "icon", scaleMode = ScaleMode.ScaleToFit });
+
+            titleContainer.Add(settingButton);
+        }
+
+        void ToggleSettings()
+        {
+            settingsExpanded = !settingsExpanded;
+            if (settingsExpanded)
+                OpenSettings();
+            else
+                CloseSettings();
+        }
+
+        public void OpenSettings()
+        {
+            if (settingsContainer != null)
+            {
+                owner.ClearSelection();
+                owner.AddToSelection(this);
+
+                settingButton.AddToClassList("clicked");
+                settingsContainer.visible = true;
+                settingsExpanded = true;
+            }
+        }
+
+        public void CloseSettings()
+        {
+            if (settingsContainer != null)
+            {
+                settingButton.RemoveFromClassList("clicked");
+                settingsContainer.visible = false;
+                settingsExpanded = false;
+            }
+        }
+
+        void InitializeDebug()
+        {
+            ComputeOrderUpdatedCallback();
+            debugContainer.Add(computeOrderLabel);
+        }
+
+        #endregion
+
+        #region API
+
+        public List<PortView> GetPortViewsFromFieldName(string fieldName)
+        {
+            List<PortView> ret;
+
+            portsPerFieldName.TryGetValue(fieldName, out ret);
+
+            return ret;
+        }
+
+        public PortView GetFirstPortViewFromFieldName(string fieldName)
+        {
+            return GetPortViewsFromFieldName(fieldName)?.First();
+        }
+
+        public PortView GetPortViewFromFieldName(string fieldName, string identifier)
+        {
+            return GetPortViewsFromFieldName(fieldName)?.FirstOrDefault(pv =>
+            {
+                return (pv.portData.identifier == identifier) || (String.IsNullOrEmpty(pv.portData.identifier) && String.IsNullOrEmpty(identifier));
+            });
+        }
+
+
+        public PortView AddPort(FieldInfo fieldInfo, Direction direction, BaseEdgeConnectorListener listener, PortData portData)
+        {
+            PortView p = CreatePortView(direction, fieldInfo, portData, listener);
+
+            if (p.direction == Direction.Input)
+            {
+                inputPortViews.Add(p);
+
+                if (portData.vertical)
+                    topPortContainer.Add(p);
+                else
+                    inputContainer.Add(p);
+            }
+            else
+            {
+                outputPortViews.Add(p);
+
+                if (portData.vertical)
+                    bottomPortContainer.Add(p);
+                else
+                    outputContainer.Add(p);
+            }
+
+            p.Initialize(this, portData?.displayName);
+
+            List<PortView> ports;
+            portsPerFieldName.TryGetValue(p.fieldName, out ports);
+            if (ports == null)
+            {
+                ports = new List<PortView>();
+                portsPerFieldName[p.fieldName] = ports;
+            }
+            ports.Add(p);
+
+            return p;
+        }
 
         protected virtual PortView CreatePortView(Direction direction, FieldInfo fieldInfo, PortData portData, BaseEdgeConnectorListener listener)
-        	=> PortView.CreatePortView(direction, fieldInfo, portData, listener);
+            => PortView.CreatePortView(direction, fieldInfo, portData, listener);
 
         public void InsertPort(PortView portView, int index)
-		{
-			if (portView.direction == Direction.Input)
-			{
-				if (portView.portData.vertical)
-					topPortContainer.Insert(index, portView);
-				else
-					inputContainer.Insert(index, portView);
-			}
-			else
-			{
-				if (portView.portData.vertical)
-					bottomPortContainer.Insert(index, portView);
-				else
-					outputContainer.Insert(index, portView);
-			}
-		}
-
-		public void RemovePort(PortView p)
-		{
-			// Remove all connected edges:
-			var edgesCopy = p.GetEdges().ToList();
-			foreach (var e in edgesCopy)
-				owner.Disconnect(e, refreshPorts: false);
-
-			if (p.direction == Direction.Input)
-			{
-				if (inputPortViews.Remove(p))
-					p.RemoveFromHierarchy();
-			}
-			else
-			{
-				if (outputPortViews.Remove(p))
-					p.RemoveFromHierarchy();
-			}
-
-			List< PortView > ports;
-			portsPerFieldName.TryGetValue(p.fieldName, out ports);
-			ports.Remove(p);
-		}
-		
-		private void SetValuesForSelectedNodes()
-		{
-			selectedNodes = new List<Node>();
-			owner.nodes.ForEach(node =>
-			{
-				if(node.selected) selectedNodes.Add(node);
-			});
-
-			if(selectedNodes.Count < 2) return; //	No need for any of the calculations below
-
-			selectedNodesFarLeft   = int.MinValue;
-			selectedNodesFarRight  = int.MinValue;
-			selectedNodesFarTop    = int.MinValue;
-			selectedNodesFarBottom = int.MinValue;
-
-			selectedNodesNearLeft   = int.MaxValue;
-			selectedNodesNearRight  = int.MaxValue;
-			selectedNodesNearTop    = int.MaxValue;
-			selectedNodesNearBottom = int.MaxValue;
-
-			foreach(var selectedNode in selectedNodes)
-			{
-				var nodeStyle  = selectedNode.style;
-				var nodeWidth  = selectedNode.localBound.size.x;
-				var nodeHeight = selectedNode.localBound.size.y;
-
-				if(nodeStyle.left.value.value > selectedNodesFarLeft) selectedNodesFarLeft                 = nodeStyle.left.value.value;
-				if(nodeStyle.left.value.value + nodeWidth > selectedNodesFarRight) selectedNodesFarRight   = nodeStyle.left.value.value + nodeWidth;
-				if(nodeStyle.top.value.value > selectedNodesFarTop) selectedNodesFarTop                    = nodeStyle.top.value.value;
-				if(nodeStyle.top.value.value + nodeHeight > selectedNodesFarBottom) selectedNodesFarBottom = nodeStyle.top.value.value + nodeHeight;
-
-				if(nodeStyle.left.value.value < selectedNodesNearLeft) selectedNodesNearLeft                 = nodeStyle.left.value.value;
-				if(nodeStyle.left.value.value + nodeWidth < selectedNodesNearRight) selectedNodesNearRight   = nodeStyle.left.value.value + nodeWidth;
-				if(nodeStyle.top.value.value < selectedNodesNearTop) selectedNodesNearTop                    = nodeStyle.top.value.value;
-				if(nodeStyle.top.value.value + nodeHeight < selectedNodesNearBottom) selectedNodesNearBottom = nodeStyle.top.value.value + nodeHeight;
-			}
-
-			selectedNodesAvgHorizontal = (selectedNodesNearLeft + selectedNodesFarRight) / 2f;
-			selectedNodesAvgVertical   = (selectedNodesNearTop + selectedNodesFarBottom) / 2f;
-		}
-
-		public static Rect GetNodeRect(Node node, float left = int.MaxValue, float top = int.MaxValue)
-		{
-			return new Rect(
-				new Vector2(left != int.MaxValue ? left : node.style.left.value.value, top != int.MaxValue ? top : node.style.top.value.value),
-				new Vector2(node.style.width.value.value, node.style.height.value.value)
-			);
-		}
-
-		public void AlignToLeft()
-		{
-			SetValuesForSelectedNodes();
-			if(selectedNodes.Count < 2) return;
-
-			foreach(var selectedNode in selectedNodes)
-			{
-				selectedNode.SetPosition(GetNodeRect(selectedNode, selectedNodesNearLeft));
-			}
-		}
-
-		public void AlignToCenter()
-		{
-			SetValuesForSelectedNodes();
-			if(selectedNodes.Count < 2) return;
-
-			foreach(var selectedNode in selectedNodes)
-			{
-				selectedNode.SetPosition(GetNodeRect(selectedNode, selectedNodesAvgHorizontal - selectedNode.localBound.size.x / 2f));
-			}
-		}
-
-		public void AlignToRight()
-		{
-			SetValuesForSelectedNodes();
-			if(selectedNodes.Count < 2) return;
-
-			foreach(var selectedNode in selectedNodes)
-			{
-				selectedNode.SetPosition(GetNodeRect(selectedNode, selectedNodesFarRight - selectedNode.localBound.size.x));
-			}
-		}
-
-		public void AlignToTop()
-		{
-			SetValuesForSelectedNodes();
-			if(selectedNodes.Count < 2) return;
-
-			foreach(var selectedNode in selectedNodes)
-			{
-				selectedNode.SetPosition(GetNodeRect(selectedNode, top: selectedNodesNearTop));
-			}
-		}
-
-		public void AlignToMiddle()
-		{
-			SetValuesForSelectedNodes();
-			if(selectedNodes.Count < 2) return;
-
-			foreach(var selectedNode in selectedNodes)
-			{
-				selectedNode.SetPosition(GetNodeRect(selectedNode, top: selectedNodesAvgVertical - selectedNode.localBound.size.y / 2f));
-			}
-		}
-
-		public void AlignToBottom()
-		{
-			SetValuesForSelectedNodes();
-			if(selectedNodes.Count < 2) return;
-
-			foreach(var selectedNode in selectedNodes)
-			{
-				selectedNode.SetPosition(GetNodeRect(selectedNode, top: selectedNodesFarBottom - selectedNode.localBound.size.y));
-			}
-		}
-
-		public void OpenNodeViewScript()
-		{
-			var script = NodeProvider.GetNodeViewScript(GetType());
-
-			if (script != null)
-				AssetDatabase.OpenAsset(script.GetInstanceID(), 0, 0);
-		}
-
-		public void OpenNodeScript()
-		{
-			var script = NodeProvider.GetNodeScript(nodeTarget.GetType());
-
-			if (script != null)
-				AssetDatabase.OpenAsset(script.GetInstanceID(), 0, 0);
-		}
-
-		public void ToggleDebug()
-		{
-			nodeTarget.debug = !nodeTarget.debug;
-			UpdateDebugView();
-		}
-
-		public void UpdateDebugView()
-		{
-			if (nodeTarget.debug)
-				mainContainer.Add(debugContainer);
-			else
-				mainContainer.Remove(debugContainer);
-		}
-
-		public void AddMessageView(string message, Texture icon, Color color)
-			=> AddBadge(new NodeBadgeView(message, icon, color));
-
-		public void AddMessageView(string message, NodeMessageType messageType)
-		{
-			IconBadge	badge = null;
-			switch (messageType)
-			{
-				case NodeMessageType.Warning:
-					badge = new NodeBadgeView(message, EditorGUIUtility.IconContent("Collab.Warning").image, Color.yellow);
-					break ;
-				case NodeMessageType.Error:	
-					badge = IconBadge.CreateError(message);
-					break ;
-				case NodeMessageType.Info:
-					badge = IconBadge.CreateComment(message);
-					break ;
-				default:
-				case NodeMessageType.None:
-					badge = new NodeBadgeView(message, null, Color.grey);
-					break ;
-			}
-			
-			AddBadge(badge);
-		}
-
-		void AddBadge(IconBadge badge)
-		{
-			Add(badge);
-			badges.Add(badge);
-			badge.AttachTo(topContainer, SpriteAlignment.TopRight);
-		}
-
-		void RemoveBadge(Func<IconBadge, bool> callback)
-		{
-			badges.RemoveAll(b => {
-				if (callback(b))
-				{
-					b.Detach();
-					b.RemoveFromHierarchy();
-					return true;
-				}
-				return false;
-			});
-		}
-
-		public void RemoveMessageViewContains(string message) => RemoveBadge(b => b.badgeText.Contains(message));
-		
-		public void RemoveMessageView(string message) => RemoveBadge(b => b.badgeText == message);
-
-		public void Highlight()
-		{
-			AddToClassList("Highlight");
-		}
-
-		public void UnHighlight()
-		{
-			RemoveFromClassList("Highlight");
-		}
-
-		#endregion
-
-		#region Callbacks & Overrides
-
-		void ComputeOrderUpdatedCallback()
-		{
-			//Update debug compute order
-			computeOrderLabel.text = "Compute order: " + nodeTarget.computeOrder;
-		}
-
-		public virtual void Enable(bool fromInspector = false) => DrawDefaultInspector(fromInspector);
-		public virtual void Enable() => DrawDefaultInspector(false);
-
-		public virtual void Disable() {}
-
-		Dictionary<string, List<(object value, VisualElement target)>> visibleConditions = new Dictionary<string, List<(object value, VisualElement target)>>();
-		Dictionary<string, VisualElement>  hideElementIfConnected = new Dictionary<string, VisualElement>();
-		Dictionary<FieldInfo, List<VisualElement>> fieldControlsMap = new Dictionary<FieldInfo, List<VisualElement>>();
-
-		protected void AddInputContainer()
-		{
-			inputContainerElement = new VisualElement {name = "input-container"};
-			mainContainer.parent.Add(inputContainerElement);
-			inputContainerElement.SendToBack();
-			inputContainerElement.pickingMode = PickingMode.Ignore;
-		}
-
-		protected virtual void DrawDefaultInspector(bool fromInspector = false)
-		{
-			var fields = nodeTarget.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
-				// Filter fields from the BaseNode type since we are only interested in user-defined fields
-				// (better than BindingFlags.DeclaredOnly because we keep any inherited user-defined fields) 
-				.Where(f => f.DeclaringType != typeof(BaseNode));
-
-			fields = nodeTarget.OverrideFieldOrder(fields).Reverse();
-
-			foreach (var field in fields)
-			{
-				//skip if the field is a node setting
-				if(field.GetCustomAttribute(typeof(SettingAttribute)) != null)
-				{
-					hasSettings = true;
-					continue;
-				}
-
-				//skip if the field is not serializable
-				bool serializeField = field.GetCustomAttribute(typeof(SerializeField)) != null;
-				if((!field.IsPublic && !serializeField) || field.IsNotSerialized)
-				{
-					AddEmptyField(field, fromInspector);
-					continue;
-				}
-
-				//skip if the field is an input/output and not marked as SerializedField
-				bool hasInputAttribute         = field.GetCustomAttribute(typeof(InputAttribute)) != null;
-				bool hasInputOrOutputAttribute = hasInputAttribute || field.GetCustomAttribute(typeof(OutputAttribute)) != null;
-				bool showAsDrawer			   = !fromInspector && field.GetCustomAttribute(typeof(ShowAsDrawer)) != null;
-				if (!serializeField && hasInputOrOutputAttribute && !showAsDrawer)
-				{
-					AddEmptyField(field, fromInspector);
-					continue;
-				}
-
-				//skip if marked with NonSerialized or HideInInspector
-				if (field.GetCustomAttribute(typeof(System.NonSerializedAttribute)) != null || field.GetCustomAttribute(typeof(HideInInspector)) != null)
-				{
-					AddEmptyField(field, fromInspector);
-					continue;
-				}
-
-				// Hide the field if we want to display in in the inspector
-				var showInInspector = field.GetCustomAttribute<ShowInInspector>();
-				if (!serializeField && showInInspector != null && !showInInspector.showInNode && !fromInspector)
-				{
-					AddEmptyField(field, fromInspector);
-					continue;
-				}
-
-				var showInputDrawer = field.GetCustomAttribute(typeof(InputAttribute)) != null && field.GetCustomAttribute(typeof(SerializeField)) != null;
-				showInputDrawer |= field.GetCustomAttribute(typeof(InputAttribute)) != null && field.GetCustomAttribute(typeof(ShowAsDrawer)) != null;
-				showInputDrawer &= !fromInspector; // We can't show a drawer in the inspector
-				showInputDrawer &= !typeof(IList).IsAssignableFrom(field.FieldType);
-
-				string displayName = ObjectNames.NicifyVariableName(field.Name);
-
-				var inspectorNameAttribute = field.GetCustomAttribute<InspectorNameAttribute>();
-				if (inspectorNameAttribute != null)
-					displayName = inspectorNameAttribute.displayName;
-
-				var elem = AddControlField(field, displayName, showInputDrawer);
-				if (hasInputAttribute)
-				{
-					hideElementIfConnected[field.Name] = elem;
-
-					// Hide the field right away if there is already a connection:
-					if (portsPerFieldName.TryGetValue(field.Name, out var pvs))
-						if (pvs.Any(pv => pv.GetEdges().Count > 0))
-							elem.style.display = DisplayStyle.None;
-				}
-			}
-		}
-
-		protected virtual void SetNodeColor(Color color)
-		{
-			titleContainer.style.borderBottomColor = new StyleColor(color);
-			titleContainer.style.borderBottomWidth = new StyleFloat(color.a > 0 ? 5f : 0f);
-		}
-		
-		private void AddEmptyField(FieldInfo field, bool fromInspector)
-		{
-			if (field.GetCustomAttribute(typeof(InputAttribute)) == null || fromInspector)
-				return;
-
-			if (field.GetCustomAttribute<VerticalAttribute>() != null)
-				return;
-			
-			var box = new VisualElement {name = field.Name};
-			box.AddToClassList("port-input-element");
-			box.AddToClassList("empty");
-			inputContainerElement.Add(box);
-		}
-
-		void UpdateFieldVisibility(string fieldName, object newValue)
-		{
-			if (newValue == null)
-				return;
-			if (visibleConditions.TryGetValue(fieldName, out var list))
-			{
-				foreach (var elem in list)
-				{
-					if (newValue.Equals(elem.value))
-						elem.target.style.display = DisplayStyle.Flex;
-					else
-						elem.target.style.display = DisplayStyle.None;
-				}
-			}
-		}
-
-		void UpdateOtherFieldValueSpecific<T>(FieldInfo field, object newValue)
-		{
-			foreach (var inputField in fieldControlsMap[field])
-			{
-				var notify = inputField as INotifyValueChanged<T>;
-				if (notify != null)
-					notify.SetValueWithoutNotify((T)newValue);
-			}
-		}
-
-		static MethodInfo specificUpdateOtherFieldValue = typeof(BaseNodeView).GetMethod(nameof(UpdateOtherFieldValueSpecific), BindingFlags.NonPublic | BindingFlags.Instance);
-		void UpdateOtherFieldValue(FieldInfo info, object newValue)
-		{
-			// Warning: Keep in sync with FieldFactory CreateField
-			var fieldType = info.FieldType.IsSubclassOf(typeof(UnityEngine.Object)) ? typeof(UnityEngine.Object) : info.FieldType;
-			var genericUpdate = specificUpdateOtherFieldValue.MakeGenericMethod(fieldType);
-
-			genericUpdate.Invoke(this, new object[]{info, newValue});
-		}
-
-		object GetInputFieldValueSpecific<T>(FieldInfo field)
-		{
-			if (fieldControlsMap.TryGetValue(field, out var list))
-			{
-				foreach (var inputField in list)
-				{
-					if (inputField is INotifyValueChanged<T> notify)
-						return notify.value;
-				}
-			}
-			return null;
-		}
-
-		static MethodInfo specificGetValue = typeof(BaseNodeView).GetMethod(nameof(GetInputFieldValueSpecific), BindingFlags.NonPublic | BindingFlags.Instance);
-		object GetInputFieldValue(FieldInfo info)
-		{
-			// Warning: Keep in sync with FieldFactory CreateField
-			var fieldType = info.FieldType.IsSubclassOf(typeof(UnityEngine.Object)) ? typeof(UnityEngine.Object) : info.FieldType;
-			var genericUpdate = specificGetValue.MakeGenericMethod(fieldType);
-
-			return genericUpdate.Invoke(this, new object[]{info});
-		}
-
-		protected VisualElement AddControlField(string fieldName, string label = null, bool showInputDrawer = false, Action valueChangedCallback = null)
-			=> AddControlField(nodeTarget.GetType().GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance), label, showInputDrawer, valueChangedCallback);
-
-		Regex s_ReplaceNodeIndexPropertyPath = new Regex(@"(^nodes.Array.data\[)(\d+)(\])");
-		internal void SyncSerializedPropertyPathes()
-		{
-			int nodeIndex = owner.graph.nodes.FindIndex(n => n == nodeTarget);
-
-			// If the node is not found, then it means that it has been deleted from serialized data.
-			if (nodeIndex == -1)
-				return;
-
-			var nodeIndexString = nodeIndex.ToString();
-			foreach (var propertyField in this.Query<PropertyField>().ToList())
-			{
-				propertyField.Unbind();
-				// The property path look like this: nodes.Array.data[x].fieldName
-				// And we want to update the value of x with the new node index:
-				propertyField.bindingPath = s_ReplaceNodeIndexPropertyPath.Replace(propertyField.bindingPath, m => m.Groups[1].Value + nodeIndexString + m.Groups[3].Value);
-				propertyField.Bind(owner.serializedGraph);
-			}
-		}
-
-		protected SerializedProperty FindSerializedProperty(string fieldName)
-		{
-			int i = owner.graph.nodes.FindIndex(n => n == nodeTarget);
-			return owner.serializedGraph.FindProperty("nodes").GetArrayElementAtIndex(i).FindPropertyRelative(fieldName);
-		}
-
-		protected VisualElement AddControlField(FieldInfo field, string label = null, bool showInputDrawer = false, Action valueChangedCallback = null)
-		{
-			if (field == null)
-				return null;
-
-			var element = new PropertyField(FindSerializedProperty(field.Name), showInputDrawer ? "" : label);
-			element.Bind(owner.serializedGraph);
+        {
+            if (portView.direction == Direction.Input)
+            {
+                if (portView.portData.vertical)
+                    topPortContainer.Insert(index, portView);
+                else
+                    inputContainer.Insert(index, portView);
+            }
+            else
+            {
+                if (portView.portData.vertical)
+                    bottomPortContainer.Insert(index, portView);
+                else
+                    outputContainer.Insert(index, portView);
+            }
+        }
+
+        public void RemovePort(PortView p)
+        {
+            // Remove all connected edges:
+            var edgesCopy = p.GetEdges().ToList();
+            foreach (var e in edgesCopy)
+                owner.Disconnect(e, refreshPorts: false);
+
+            if (p.direction == Direction.Input)
+            {
+                if (inputPortViews.Remove(p))
+                    p.RemoveFromHierarchy();
+            }
+            else
+            {
+                if (outputPortViews.Remove(p))
+                    p.RemoveFromHierarchy();
+            }
+
+            List<PortView> ports;
+            portsPerFieldName.TryGetValue(p.fieldName, out ports);
+            ports.Remove(p);
+        }
+
+        private void SetValuesForSelectedNodes()
+        {
+            selectedNodes = new List<Node>();
+            owner.nodes.ForEach(node =>
+            {
+                if (node.selected) selectedNodes.Add(node);
+            });
+
+            if (selectedNodes.Count < 2) return; //	No need for any of the calculations below
+
+            selectedNodesFarLeft = int.MinValue;
+            selectedNodesFarRight = int.MinValue;
+            selectedNodesFarTop = int.MinValue;
+            selectedNodesFarBottom = int.MinValue;
+
+            selectedNodesNearLeft = int.MaxValue;
+            selectedNodesNearRight = int.MaxValue;
+            selectedNodesNearTop = int.MaxValue;
+            selectedNodesNearBottom = int.MaxValue;
+
+            foreach (var selectedNode in selectedNodes)
+            {
+                var nodeStyle = selectedNode.style;
+                var nodeWidth = selectedNode.localBound.size.x;
+                var nodeHeight = selectedNode.localBound.size.y;
+
+                if (nodeStyle.left.value.value > selectedNodesFarLeft) selectedNodesFarLeft = nodeStyle.left.value.value;
+                if (nodeStyle.left.value.value + nodeWidth > selectedNodesFarRight) selectedNodesFarRight = nodeStyle.left.value.value + nodeWidth;
+                if (nodeStyle.top.value.value > selectedNodesFarTop) selectedNodesFarTop = nodeStyle.top.value.value;
+                if (nodeStyle.top.value.value + nodeHeight > selectedNodesFarBottom) selectedNodesFarBottom = nodeStyle.top.value.value + nodeHeight;
+
+                if (nodeStyle.left.value.value < selectedNodesNearLeft) selectedNodesNearLeft = nodeStyle.left.value.value;
+                if (nodeStyle.left.value.value + nodeWidth < selectedNodesNearRight) selectedNodesNearRight = nodeStyle.left.value.value + nodeWidth;
+                if (nodeStyle.top.value.value < selectedNodesNearTop) selectedNodesNearTop = nodeStyle.top.value.value;
+                if (nodeStyle.top.value.value + nodeHeight < selectedNodesNearBottom) selectedNodesNearBottom = nodeStyle.top.value.value + nodeHeight;
+            }
+
+            selectedNodesAvgHorizontal = (selectedNodesNearLeft + selectedNodesFarRight) / 2f;
+            selectedNodesAvgVertical = (selectedNodesNearTop + selectedNodesFarBottom) / 2f;
+        }
+
+        public static Rect GetNodeRect(Node node, float left = int.MaxValue, float top = int.MaxValue)
+        {
+            return new Rect(
+                new Vector2(left != int.MaxValue ? left : node.style.left.value.value, top != int.MaxValue ? top : node.style.top.value.value),
+                new Vector2(node.style.width.value.value, node.style.height.value.value)
+            );
+        }
+
+        public void AlignToLeft()
+        {
+            SetValuesForSelectedNodes();
+            if (selectedNodes.Count < 2) return;
+
+            foreach (var selectedNode in selectedNodes)
+            {
+                selectedNode.SetPosition(GetNodeRect(selectedNode, selectedNodesNearLeft));
+            }
+        }
+
+        public void AlignToCenter()
+        {
+            SetValuesForSelectedNodes();
+            if (selectedNodes.Count < 2) return;
+
+            foreach (var selectedNode in selectedNodes)
+            {
+                selectedNode.SetPosition(GetNodeRect(selectedNode, selectedNodesAvgHorizontal - selectedNode.localBound.size.x / 2f));
+            }
+        }
+
+        public void AlignToRight()
+        {
+            SetValuesForSelectedNodes();
+            if (selectedNodes.Count < 2) return;
+
+            foreach (var selectedNode in selectedNodes)
+            {
+                selectedNode.SetPosition(GetNodeRect(selectedNode, selectedNodesFarRight - selectedNode.localBound.size.x));
+            }
+        }
+
+        public void AlignToTop()
+        {
+            SetValuesForSelectedNodes();
+            if (selectedNodes.Count < 2) return;
+
+            foreach (var selectedNode in selectedNodes)
+            {
+                selectedNode.SetPosition(GetNodeRect(selectedNode, top: selectedNodesNearTop));
+            }
+        }
+
+        public void AlignToMiddle()
+        {
+            SetValuesForSelectedNodes();
+            if (selectedNodes.Count < 2) return;
+
+            foreach (var selectedNode in selectedNodes)
+            {
+                selectedNode.SetPosition(GetNodeRect(selectedNode, top: selectedNodesAvgVertical - selectedNode.localBound.size.y / 2f));
+            }
+        }
+
+        public void AlignToBottom()
+        {
+            SetValuesForSelectedNodes();
+            if (selectedNodes.Count < 2) return;
+
+            foreach (var selectedNode in selectedNodes)
+            {
+                selectedNode.SetPosition(GetNodeRect(selectedNode, top: selectedNodesFarBottom - selectedNode.localBound.size.y));
+            }
+        }
+
+        public void OpenNodeViewScript()
+        {
+            var script = NodeProvider.GetNodeViewScript(GetType());
+
+            if (script != null)
+                AssetDatabase.OpenAsset(script.GetInstanceID(), 0, 0);
+        }
+
+        public void OpenNodeScript()
+        {
+            var script = NodeProvider.GetNodeScript(nodeTarget.GetType());
+
+            if (script != null)
+                AssetDatabase.OpenAsset(script.GetInstanceID(), 0, 0);
+        }
+
+        public void ToggleDebug()
+        {
+            nodeTarget.debug = !nodeTarget.debug;
+            UpdateDebugView();
+        }
+
+        public void UpdateDebugView()
+        {
+            if (nodeTarget.debug)
+                mainContainer.Add(debugContainer);
+            else
+                mainContainer.Remove(debugContainer);
+        }
+
+        public void AddMessageView(string message, Texture icon, Color color)
+            => AddBadge(new NodeBadgeView(message, icon, color));
+
+        public void AddMessageView(string message, NodeMessageType messageType)
+        {
+            IconBadge badge = null;
+            switch (messageType)
+            {
+                case NodeMessageType.Warning:
+                    badge = new NodeBadgeView(message, EditorGUIUtility.IconContent("Collab.Warning").image, Color.yellow);
+                    break;
+                case NodeMessageType.Error:
+                    badge = IconBadge.CreateError(message);
+                    break;
+                case NodeMessageType.Info:
+                    badge = IconBadge.CreateComment(message);
+                    break;
+                default:
+                case NodeMessageType.None:
+                    badge = new NodeBadgeView(message, null, Color.grey);
+                    break;
+            }
+
+            AddBadge(badge);
+        }
+
+        void AddBadge(IconBadge badge)
+        {
+            Add(badge);
+            badges.Add(badge);
+            badge.AttachTo(topContainer, SpriteAlignment.TopRight);
+        }
+
+        void RemoveBadge(Func<IconBadge, bool> callback)
+        {
+            badges.RemoveAll(b =>
+            {
+                if (callback(b))
+                {
+                    b.Detach();
+                    b.RemoveFromHierarchy();
+                    return true;
+                }
+                return false;
+            });
+        }
+
+        public void RemoveMessageViewContains(string message) => RemoveBadge(b => b.badgeText.Contains(message));
+
+        public void RemoveMessageView(string message) => RemoveBadge(b => b.badgeText == message);
+
+        public void Highlight()
+        {
+            AddToClassList("Highlight");
+        }
+
+        public void UnHighlight()
+        {
+            RemoveFromClassList("Highlight");
+        }
+
+        #endregion
+
+        #region Callbacks & Overrides
+
+        void ComputeOrderUpdatedCallback()
+        {
+            //Update debug compute order
+            computeOrderLabel.text = "Compute order: " + nodeTarget.computeOrder;
+        }
+
+        public virtual void Enable(bool fromInspector = false) => DrawDefaultInspector(fromInspector);
+        public virtual void Enable() => DrawDefaultInspector(false);
+
+        public virtual void Disable() { }
+
+        Dictionary<string, List<(object value, VisualElement target)>> visibleConditions = new Dictionary<string, List<(object value, VisualElement target)>>();
+        Dictionary<string, VisualElement> hideElementIfConnected = new Dictionary<string, VisualElement>();
+        Dictionary<FieldInfoWithPath, List<VisualElement>> fieldControlsMap = new Dictionary<FieldInfoWithPath, List<VisualElement>>();
+
+        protected void AddInputContainer()
+        {
+            inputContainerElement = new VisualElement { name = "input-container" };
+            mainContainer.parent.Add(inputContainerElement);
+            inputContainerElement.SendToBack();
+            inputContainerElement.pickingMode = PickingMode.Ignore;
+        }
+
+        protected virtual void DrawDefaultInspector(bool fromInspector = false)
+        {
+            var fields = nodeTarget.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
+                // Filter fields from the BaseNode type since we are only interested in user-defined fields
+                // (better than BindingFlags.DeclaredOnly because we keep any inherited user-defined fields) 
+                .Where(f => f.DeclaringType != typeof(BaseNode)).ToList();
+
+            fields = nodeTarget.OverrideFieldOrder(fields).Reverse().ToList();
+
+
+            for (int i = 0; i < fields.Count; i++)
+            {
+                FieldInfo field = fields[i];
+                if (field.HasCustomAttribute<InputAttribute>() && portsPerFieldName.ContainsKey(field.Name))
+                {
+                    foreach (var port in portsPerFieldName[field.Name])
+                    {
+                        string fieldPath = port.portData.IsProxied ? port.portData.proxiedFieldPath : port.fieldName;
+                        DrawField(new FieldInfoWithPath(FieldInfoWithPath.GetFieldInfoPath(fieldPath, nodeTarget)), fromInspector, port.portData.IsProxied);
+                    }
+                }
+                else
+                {
+                    DrawField(new FieldInfoWithPath(field), fromInspector);
+                }
+            }
+        }
+
+        protected virtual void DrawField(FieldInfoWithPath fieldInfoWithPath, bool fromInspector, bool isProxied = false)
+        {
+            FieldInfo field = fieldInfoWithPath.Field;
+            string fieldPath = fieldInfoWithPath.Path;
+
+            //skip if the field is a node setting
+            if (field.HasCustomAttribute<SettingAttribute>())
+            {
+                hasSettings = true;
+                return;
+            }
+
+            //skip if the field is not serializable
+            bool serializeField = field.HasCustomAttribute<SerializeField>();
+            if ((!field.IsPublic && !serializeField) || field.IsNotSerialized)
+            {
+                AddEmptyField(field, fromInspector);
+                return;
+            }
+
+            //skip if the field is an input/output and not marked as SerializedField
+            InputAttribute inputAttribute = field.GetCustomAttribute<InputAttribute>();
+            bool hasInputAttribute = inputAttribute != null;
+            bool hasInputOrOutputAttribute = hasInputAttribute || field.HasCustomAttribute<OutputAttribute>();
+            bool showAsDrawer = !fromInspector && hasInputAttribute && (inputAttribute.showAsDrawer || field.HasCustomAttribute<ShowAsDrawer>());
+            if ((!serializeField || isProxied) && hasInputOrOutputAttribute && !showAsDrawer)
+            {
+                AddEmptyField(field, fromInspector);
+                return;
+            }
+
+            //skip if marked with NonSerialized or HideInInspector
+            if (field.HasCustomAttribute<System.NonSerializedAttribute>() || field.HasCustomAttribute<HideInInspector>())
+            {
+                AddEmptyField(field, fromInspector);
+                return;
+            }
+
+            // Hide the field if we want to display in in the inspector
+            var showInInspector = field.GetCustomAttribute<ShowInInspector>();
+            if (!serializeField && showInInspector != null && !showInInspector.showInNode && !fromInspector)
+            {
+                AddEmptyField(field, fromInspector);
+                return;
+            }
+
+
+            var showInputDrawer = hasInputAttribute && serializeField;
+            showInputDrawer |= showAsDrawer;
+            showInputDrawer &= !fromInspector; // We can't show a drawer in the inspector
+            showInputDrawer &= !typeof(IList).IsAssignableFrom(field.FieldType);
+
+            string displayName = ObjectNames.NicifyVariableName(field.Name);
+
+            var inspectorNameAttribute = field.GetCustomAttribute<InspectorNameAttribute>();
+            if (inspectorNameAttribute != null)
+                displayName = inspectorNameAttribute.displayName;
+
+            var elem = AddControlField(fieldPath, displayName, showInputDrawer);
+            if (hasInputAttribute)
+            {
+                hideElementIfConnected[fieldPath] = elem;
+
+                // Hide the field right away if there is already a connection:
+                if (portsPerFieldName.TryGetValue(fieldPath, out var pvs))
+                    if (pvs.Any(pv => pv.GetEdges().Count > 0))
+                        elem.style.display = DisplayStyle.None;
+            }
+        }
+
+        protected virtual void SetNodeColor(Color color)
+        {
+            titleContainer.style.borderBottomColor = new StyleColor(color);
+            titleContainer.style.borderBottomWidth = new StyleFloat(color.a > 0 ? 5f : 0f);
+        }
+
+        private void AddEmptyField(FieldInfo field, bool fromInspector)
+        {
+            if (!field.HasCustomAttribute<InputAttribute>() || fromInspector)
+                return;
+
+            if (field.HasCustomAttribute<VerticalAttribute>())
+                return;
+
+            var box = new VisualElement { name = field.Name };
+            box.AddToClassList("port-input-element");
+            box.AddToClassList("empty");
+            inputContainerElement.Add(box);
+        }
+
+        void UpdateFieldVisibility(string fieldName, object newValue)
+        {
+            if (newValue == null)
+                return;
+            if (visibleConditions.TryGetValue(fieldName, out var list))
+            {
+                foreach (var elem in list)
+                {
+                    if (newValue.Equals(elem.value))
+                        elem.target.style.display = DisplayStyle.Flex;
+                    else
+                        elem.target.style.display = DisplayStyle.None;
+                }
+            }
+        }
+
+        void UpdateOtherFieldValueSpecific<T>(FieldInfoWithPath field, object newValue)
+        {
+            foreach (var inputField in fieldControlsMap[field])
+            {
+                var notify = inputField as INotifyValueChanged<T>;
+                if (notify != null)
+                    notify.SetValueWithoutNotify((T)newValue);
+            }
+        }
+
+        static MethodInfo specificUpdateOtherFieldValue = typeof(BaseNodeView).GetMethod(nameof(UpdateOtherFieldValueSpecific), BindingFlags.NonPublic | BindingFlags.Instance);
+        void UpdateOtherFieldValue(FieldInfoWithPath info, object newValue)
+        {
+            // Warning: Keep in sync with FieldFactory CreateField
+            var fieldType = info.Field.FieldType.IsSubclassOf(typeof(UnityEngine.Object)) ? typeof(UnityEngine.Object) : info.Field.FieldType;
+            var genericUpdate = specificUpdateOtherFieldValue.MakeGenericMethod(fieldType);
+
+            genericUpdate.Invoke(this, new object[] { info, newValue });
+        }
+
+        object GetInputFieldValueSpecific<T>(FieldInfoWithPath field)
+        {
+            if (fieldControlsMap.TryGetValue(field, out var list))
+            {
+                foreach (var inputField in list)
+                {
+                    if (inputField is INotifyValueChanged<T> notify)
+                        return notify.value;
+                }
+            }
+            return null;
+        }
+
+        static MethodInfo specificGetValue = typeof(BaseNodeView).GetMethod(nameof(GetInputFieldValueSpecific), BindingFlags.NonPublic | BindingFlags.Instance);
+        object GetInputFieldValue(FieldInfoWithPath info)
+        {
+            // Warning: Keep in sync with FieldFactory CreateField
+            var fieldType = info.Field.FieldType.IsSubclassOf(typeof(UnityEngine.Object)) ? typeof(UnityEngine.Object) : info.Field.FieldType;
+            var genericUpdate = specificGetValue.MakeGenericMethod(fieldType);
+
+            return genericUpdate.Invoke(this, new object[] { info });
+        }
+
+        protected VisualElement AddControlField(string fieldPath, string label = null, bool showInputDrawer = false, Action valueChangedCallback = null)
+        {
+            List<FieldInfo> fieldInfoPath = FieldInfoWithPath.GetFieldInfoPath(fieldPath, nodeTarget);
+            return AddControlField(new FieldInfoWithPath(fieldInfoPath.Last(), fieldPath), label, showInputDrawer, valueChangedCallback);
+        }
+        Regex s_ReplaceNodeIndexPropertyPath = new Regex(@"(^nodes.Array.data\[)(\d+)(\])");
+        internal void SyncSerializedPropertyPathes()
+        {
+            int nodeIndex = owner.graph.nodes.FindIndex(n => n == nodeTarget);
+
+            // If the node is not found, then it means that it has been deleted from serialized data.
+            if (nodeIndex == -1)
+                return;
+
+            var nodeIndexString = nodeIndex.ToString();
+            foreach (var propertyField in this.Query<PropertyField>().ToList())
+            {
+                if (propertyField.bindingPath == null)
+                    continue;
+
+                propertyField.Unbind();
+                // The property path look like this: nodes.Array.data[x].fieldName
+                // And we want to update the value of x with the new node index:
+                propertyField.bindingPath = s_ReplaceNodeIndexPropertyPath.Replace(propertyField.bindingPath, m => m.Groups[1].Value + nodeIndexString + m.Groups[3].Value);
+                propertyField.Bind(owner.serializedGraph);
+            }
+        }
+
+        protected SerializedProperty FindSerializedProperty(string fieldName)
+        {
+            int i = owner.graph.nodes.FindIndex(n => n == nodeTarget);
+            return owner.serializedGraph.FindProperty("nodes").GetArrayElementAtIndex(i).FindPropertyRelative(fieldName);
+        }
+
+        protected VisualElement AddControlField(FieldInfoWithPath fieldInfoWithPath, string label = null, bool showInputDrawer = false, Action valueChangedCallback = null)
+        {
+            var field = fieldInfoWithPath.Field;
+            var fieldPath = fieldInfoWithPath.Path;
+
+            if (field == null)
+                return null;
+
+            var element = new PropertyField(FindSerializedProperty(fieldPath), showInputDrawer ? "" : label);
+            element.Bind(owner.serializedGraph);
 
 #if UNITY_2020_3 // In Unity 2020.3 the empty label on property field doesn't hide it, so we do it manually
 			if ((showInputDrawer || String.IsNullOrEmpty(label)) && element != null)
 				element.AddToClassList("DrawerField_2020_3");
 #endif
 
-			if (typeof(IList).IsAssignableFrom(field.FieldType))
-				EnableSyncSelectionBorderHeight();
-
-			element.RegisterValueChangeCallback(e => {
-				UpdateFieldVisibility(field.Name, field.GetValue(nodeTarget));
-				valueChangedCallback?.Invoke();
-				NotifyNodeChanged();
-			});
-
-			// Disallow picking scene objects when the graph is not linked to a scene
-			if (element != null && !owner.graph.IsLinkedToScene())
-			{
-				var objectField = element.Q<ObjectField>();
-				if (objectField != null)
-					objectField.allowSceneObjects = false;
-			}
-
-			if (!fieldControlsMap.TryGetValue(field, out var inputFieldList))
-				inputFieldList = fieldControlsMap[field] = new List<VisualElement>();
-			inputFieldList.Add(element);
-
-			if(element != null)
-			{
-				if (showInputDrawer)
-				{
-					var box = new VisualElement {name = field.Name};
-					box.AddToClassList("port-input-element");
-					box.Add(element);
-					inputContainerElement.Add(box);
-				}
-				else
-				{
-					controlsContainer.Add(element);
-				}
-				element.name = field.Name;
-			}
-			else
-			{
-				// Make sure we create an empty placeholder if FieldFactory can not provide a drawer
-				if (showInputDrawer) AddEmptyField(field, false);
-			}
-
-			var visibleCondition = field.GetCustomAttribute(typeof(VisibleIf)) as VisibleIf;
-			if (visibleCondition != null)
-			{
-				// Check if target field exists:
-				var conditionField = nodeTarget.GetType().GetField(visibleCondition.fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
-				if (conditionField == null)
-					Debug.LogError($"[VisibleIf] Field {visibleCondition.fieldName} does not exists in node {nodeTarget.GetType()}");
-				else
-				{
-					visibleConditions.TryGetValue(visibleCondition.fieldName, out var list);
-					if (list == null)
-						list = visibleConditions[visibleCondition.fieldName] = new List<(object value, VisualElement target)>();
-					list.Add((visibleCondition.value, element));
-					UpdateFieldVisibility(visibleCondition.fieldName, conditionField.GetValue(nodeTarget));
-				}
-			}
-
-			return element;
-		}
-
-		void UpdateFieldValues()
-		{
-			foreach (var kp in fieldControlsMap)
-				UpdateOtherFieldValue(kp.Key, kp.Key.GetValue(nodeTarget));
-		}
-		
-		protected void AddSettingField(FieldInfo field)
-		{
-			if (field == null)
-				return;
-
-			var label = field.GetCustomAttribute<SettingAttribute>().name;
-
-			var element = new PropertyField(FindSerializedProperty(field.Name));
-			element.Bind(owner.serializedGraph);
-
-			if (element != null)
-			{
-				settingsContainer.Add(element);
-				element.name = field.Name;
-			}
-		}
-
-		internal void OnPortConnected(PortView port)
-		{
-			if(port.direction == Direction.Input && inputContainerElement?.Q(port.fieldName) != null)
-				inputContainerElement.Q(port.fieldName).AddToClassList("empty");
-			
-			if (hideElementIfConnected.TryGetValue(port.fieldName, out var elem))
-				elem.style.display = DisplayStyle.None;
-
-			onPortConnected?.Invoke(port);
-		}
-
-		internal void OnPortDisconnected(PortView port)
-		{
-			if (port.direction == Direction.Input && inputContainerElement?.Q(port.fieldName) != null)
-			{
-				inputContainerElement.Q(port.fieldName).RemoveFromClassList("empty");
-
-				if (nodeTarget.nodeFields.TryGetValue(port.fieldName, out var fieldInfo))
-				{
-					var valueBeforeConnection = GetInputFieldValue(fieldInfo.info);
-
-					if (valueBeforeConnection != null)
-					{
-						fieldInfo.info.SetValue(nodeTarget, valueBeforeConnection);
-					}
-				}
-			}
-			
-			if (hideElementIfConnected.TryGetValue(port.fieldName, out var elem))
-				elem.style.display = DisplayStyle.Flex;
-
-			onPortDisconnected?.Invoke(port);
-		}
-
-		// TODO: a function to force to reload the custom behavior ports (if we want to do a button to add ports for example)
-
-		public virtual void OnRemoved() {}
-		public virtual void OnCreated() {}
-
-		public override void SetPosition(Rect newPos)
-		{
+            if (typeof(IList).IsAssignableFrom(field.FieldType))
+                EnableSyncSelectionBorderHeight();
+
+            element.RegisterValueChangeCallback(e =>
+            {
+                UpdateFieldVisibility(field.Name, FieldInfoWithPath.GetFieldInfoPath(fieldPath, nodeTarget).GetFinalValue(nodeTarget));
+                valueChangedCallback?.Invoke();
+                NotifyNodeChanged();
+            });
+
+            // Disallow picking scene objects when the graph is not linked to a scene
+            if (element != null && !owner.graph.IsLinkedToScene())
+            {
+                var objectField = element.Q<ObjectField>();
+                if (objectField != null)
+                    objectField.allowSceneObjects = false;
+            }
+
+            if (!fieldControlsMap.TryGetValue(fieldInfoWithPath, out var inputFieldList))
+                inputFieldList = fieldControlsMap[fieldInfoWithPath] = new List<VisualElement>();
+            inputFieldList.Add(element);
+
+            if (element != null)
+            {
+                if (showInputDrawer)
+                {
+                    var box = new VisualElement { name = field.Name };
+                    box.AddToClassList("port-input-element");
+                    box.Add(element);
+                    inputContainerElement.Add(box);
+                }
+                else
+                {
+                    controlsContainer.Add(element);
+                }
+                element.name = field.Name;
+            }
+            else
+            {
+                // Make sure we create an empty placeholder if FieldFactory can not provide a drawer
+                if (showInputDrawer) AddEmptyField(field, false);
+            }
+
+            var visibleCondition = field.GetCustomAttribute<VisibleIf>();
+            if (visibleCondition != null)
+            {
+                // Check if target field exists:
+                var conditionField = nodeTarget.GetType().GetField(visibleCondition.fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
+                if (conditionField == null)
+                    Debug.LogError($"[VisibleIf] Field {visibleCondition.fieldName} does not exists in node {nodeTarget.GetType()}");
+                else
+                {
+                    visibleConditions.TryGetValue(visibleCondition.fieldName, out var list);
+                    if (list == null)
+                        list = visibleConditions[visibleCondition.fieldName] = new List<(object value, VisualElement target)>();
+                    list.Add((visibleCondition.value, element));
+                    UpdateFieldVisibility(visibleCondition.fieldName, conditionField.GetValue(nodeTarget));
+                }
+            }
+
+            return element;
+        }
+
+        void UpdateFieldValues()
+        {
+            foreach (var kp in fieldControlsMap)
+                UpdateOtherFieldValue(kp.Key, FieldInfoWithPath.GetFieldInfoPath(kp.Key.Path, nodeTarget).GetFinalValue(nodeTarget));
+        }
+
+        protected void AddSettingField(FieldInfo field)
+        {
+            if (field == null)
+                return;
+
+            var label = field.GetCustomAttribute<SettingAttribute>().name;
+
+            var element = new PropertyField(FindSerializedProperty(field.Name));
+            element.Bind(owner.serializedGraph);
+
+            if (element != null)
+            {
+                settingsContainer.Add(element);
+                element.name = field.Name;
+            }
+        }
+
+        internal void OnPortConnected(PortView port)
+        {
+            string fieldName = port.portData.IsProxied ? port.portData.proxiedFieldPath : port.fieldName;
+
+            if (port.direction == Direction.Input && inputContainerElement?.Q(fieldName) != null)
+                inputContainerElement.Q(fieldName).AddToClassList("empty");
+
+            if (hideElementIfConnected.TryGetValue(fieldName, out var elem))
+                elem.style.display = DisplayStyle.None;
+
+            onPortConnected?.Invoke(port);
+        }
+
+        internal void OnPortDisconnected(PortView port) //
+        {
+            bool isProxied = port.portData.IsProxied;
+            string fieldName = isProxied ? port.portData.proxiedFieldPath : port.fieldName;
+
+            if (port.direction == Direction.Input && inputContainerElement?.Q(fieldName) != null)
+            {
+                inputContainerElement.Q(fieldName).RemoveFromClassList("empty");
+                var fieldInfoWithPath = new FieldInfoWithPath(fieldName, nodeTarget);
+
+                var valueBeforeConnection = GetInputFieldValue(fieldInfoWithPath);
+
+                if (valueBeforeConnection != null)
+                {
+                    fieldInfoWithPath.SetValue(nodeTarget, valueBeforeConnection);
+                }
+            }
+
+            if (hideElementIfConnected.TryGetValue(fieldName, out var elem))
+                elem.style.display = DisplayStyle.Flex;
+
+            onPortDisconnected?.Invoke(port);
+        }
+
+        // TODO: a function to force to reload the custom behavior ports (if we want to do a button to add ports for example)
+
+        public virtual void OnRemoved() { }
+        public virtual void OnCreated() { }
+
+        public override void SetPosition(Rect newPos)
+        {
             if (initializing || !nodeTarget.isLocked)
             {
                 base.SetPosition(newPos);
 
-				if (!initializing)
-					owner.RegisterCompleteObjectUndo("Moved graph node");
+                if (!initializing)
+                    owner.RegisterCompleteObjectUndo("Moved graph node");
 
                 nodeTarget.position = newPos;
                 initializing = false;
             }
-		}
-
-		public override bool	expanded
-		{
-			get { return base.expanded; }
-			set
-			{
-				base.expanded = value;
-				nodeTarget.expanded = value;
-			}
-		}
+        }
+
+        public override bool expanded
+        {
+            get { return base.expanded; }
+            set
+            {
+                base.expanded = value;
+                nodeTarget.expanded = value;
+            }
+        }
 
         public void ChangeLockStatus()
         {
@@ -1016,26 +1059,26 @@ public void ChangeLockStatus()
         }
 
         public override void BuildContextualMenu(ContextualMenuPopulateEvent evt)
-		{
-			BuildAlignMenu(evt);
-			evt.menu.AppendAction("Open Node Script", (e) => OpenNodeScript(), OpenNodeScriptStatus);
-			evt.menu.AppendAction("Open Node View Script", (e) => OpenNodeViewScript(), OpenNodeViewScriptStatus);
-			evt.menu.AppendAction("Debug", (e) => ToggleDebug(), DebugStatus);
+        {
+            BuildAlignMenu(evt);
+            evt.menu.AppendAction("Open Node Script", (e) => OpenNodeScript(), OpenNodeScriptStatus);
+            evt.menu.AppendAction("Open Node View Script", (e) => OpenNodeViewScript(), OpenNodeViewScriptStatus);
+            evt.menu.AppendAction("Debug", (e) => ToggleDebug(), DebugStatus);
             if (nodeTarget.unlockable)
                 evt.menu.AppendAction((nodeTarget.isLocked ? "Unlock" : "Lock"), (e) => ChangeLockStatus(), LockStatus);
         }
 
-		protected void BuildAlignMenu(ContextualMenuPopulateEvent evt)
-		{
-			evt.menu.AppendAction("Align/To Left", (e) => AlignToLeft());
-			evt.menu.AppendAction("Align/To Center", (e) => AlignToCenter());
-			evt.menu.AppendAction("Align/To Right", (e) => AlignToRight());
-			evt.menu.AppendSeparator("Align/");
-			evt.menu.AppendAction("Align/To Top", (e) => AlignToTop());
-			evt.menu.AppendAction("Align/To Middle", (e) => AlignToMiddle());
-			evt.menu.AppendAction("Align/To Bottom", (e) => AlignToBottom());
-			evt.menu.AppendSeparator();
-		}
+        protected void BuildAlignMenu(ContextualMenuPopulateEvent evt)
+        {
+            evt.menu.AppendAction("Align/To Left", (e) => AlignToLeft());
+            evt.menu.AppendAction("Align/To Center", (e) => AlignToCenter());
+            evt.menu.AppendAction("Align/To Right", (e) => AlignToRight());
+            evt.menu.AppendSeparator("Align/");
+            evt.menu.AppendAction("Align/To Top", (e) => AlignToTop());
+            evt.menu.AppendAction("Align/To Middle", (e) => AlignToMiddle());
+            evt.menu.AppendAction("Align/To Bottom", (e) => AlignToBottom());
+            evt.menu.AppendSeparator();
+        }
 
         Status LockStatus(DropdownMenuAction action)
         {
@@ -1043,137 +1086,199 @@ Status LockStatus(DropdownMenuAction action)
         }
 
         Status DebugStatus(DropdownMenuAction action)
-		{
-			if (nodeTarget.debug)
-				return Status.Checked;
-			return Status.Normal;
-		}
-
-		Status OpenNodeScriptStatus(DropdownMenuAction action)
-		{
-			if (NodeProvider.GetNodeScript(nodeTarget.GetType()) != null)
-				return Status.Normal;
-			return Status.Disabled;
-		}
-
-		Status OpenNodeViewScriptStatus(DropdownMenuAction action)
-		{
-			if (NodeProvider.GetNodeViewScript(GetType()) != null)
-				return Status.Normal;
-			return Status.Disabled;
-		}
-
-		IEnumerable< PortView > SyncPortCounts(IEnumerable< NodePort > ports, IEnumerable< PortView > portViews)
-		{
-			var listener = owner.connectorListener;
-			var portViewList = portViews.ToList();
-
-			// Maybe not good to remove ports as edges are still connected :/
-			foreach (var pv in portViews.ToList())
-			{
-				// If the port have disappeared from the node data, we remove the view:
-				// We can use the identifier here because this function will only be called when there is a custom port behavior
-				if (!ports.Any(p => p.portData.identifier == pv.portData.identifier))
-				{
-					RemovePort(pv);
-					portViewList.Remove(pv);
-				}
-			}
-
-			foreach (var p in ports)
-			{
-				// Add missing port views
-				if (!portViews.Any(pv => p.portData.identifier == pv.portData.identifier))
-				{
-					Direction portDirection = nodeTarget.IsFieldInput(p.fieldName) ? Direction.Input : Direction.Output;
-					var pv = AddPort(p.fieldInfo, portDirection, listener, p.portData);
-					portViewList.Add(pv);
-				}
-			}
-
-			return portViewList;
-		}
-
-		void SyncPortOrder(IEnumerable< NodePort > ports, IEnumerable< PortView > portViews)
-		{
-			var portViewList = portViews.ToList();
-			var portsList = ports.ToList();
-
-			// Re-order the port views to match the ports order in case a custom behavior re-ordered the ports
-			for (int i = 0; i < portsList.Count; i++)
-			{
-				var id = portsList[i].portData.identifier;
-
-				var pv = portViewList.FirstOrDefault(p => p.portData.identifier == id);
-				if (pv != null)
-					InsertPort(pv, i);
-			}
-		}
-
-		public virtual new bool RefreshPorts()
-		{
-			// If a port behavior was attached to one port, then
-			// the port count might have been updated by the node
-			// so we have to refresh the list of port views.
-			UpdatePortViewWithPorts(nodeTarget.inputPorts, inputPortViews);
-			UpdatePortViewWithPorts(nodeTarget.outputPorts, outputPortViews);
-
-			void UpdatePortViewWithPorts(NodePortContainer ports, List< PortView > portViews)
-			{
-				if (ports.Count == 0 && portViews.Count == 0) // Nothing to update
-					return;
-
-				// When there is no current portviews, we can't zip the list so we just add all
-				if (portViews.Count == 0)
-					SyncPortCounts(ports, new PortView[]{});
-				else if (ports.Count == 0) // Same when there is no ports
-					SyncPortCounts(new NodePort[]{}, portViews);
-				else if (portViews.Count != ports.Count)
-					SyncPortCounts(ports, portViews);
-				else
-				{
-					var p = ports.GroupBy(n => n.fieldName);
-					var pv = portViews.GroupBy(v => v.fieldName);
-					p.Zip(pv, (portPerFieldName, portViewPerFieldName) => {
-						IEnumerable< PortView > portViewsList = portViewPerFieldName;
-						if (portPerFieldName.Count() != portViewPerFieldName.Count())
-							portViewsList = SyncPortCounts(portPerFieldName, portViewPerFieldName);
-						SyncPortOrder(portPerFieldName, portViewsList);
-						// We don't care about the result, we just iterate over port and portView
-						return "";
-					}).ToList();
-				}
-
-				// Here we're sure that we have the same amount of port and portView
-				// so we can update the view with the new port data (if the name of a port have been changed for example)
-
-				for (int i = 0; i < portViews.Count; i++)
-					portViews[i].UpdatePortView(ports[i].portData);
-			}
-
-			return base.RefreshPorts();
-		}
-
-		public void ForceUpdatePorts()
-		{
-			nodeTarget.UpdateAllPorts();
-
-			RefreshPorts();
-		}
-
-		void UpdatePortsForField(string fieldName)
-		{
-			// TODO: actual code
-			RefreshPorts();
-		}
-
-		protected virtual VisualElement CreateSettingsView() => new Label("Settings") {name = "header"};
-
-		/// <summary>
-		/// Send an event to the graph telling that the content of this node have changed
-		/// </summary>
-		public void NotifyNodeChanged() => owner.graph.NotifyNodeChanged(nodeTarget);
-
-		#endregion
+        {
+            if (nodeTarget.debug)
+                return Status.Checked;
+            return Status.Normal;
+        }
+
+        Status OpenNodeScriptStatus(DropdownMenuAction action)
+        {
+            if (NodeProvider.GetNodeScript(nodeTarget.GetType()) != null)
+                return Status.Normal;
+            return Status.Disabled;
+        }
+
+        Status OpenNodeViewScriptStatus(DropdownMenuAction action)
+        {
+            if (NodeProvider.GetNodeViewScript(GetType()) != null)
+                return Status.Normal;
+            return Status.Disabled;
+        }
+
+        IEnumerable<PortView> SyncPortCounts(IEnumerable<NodePort> ports, IEnumerable<PortView> portViews)
+        {
+            var listener = owner.connectorListener;
+            var portViewList = portViews.ToList();
+
+            // Maybe not good to remove ports as edges are still connected :/
+            foreach (var pv in portViews.ToList())
+            {
+                // If the port have disappeared from the node data, we remove the view:
+                // We can use the identifier here because this function will only be called when there is a custom port behavior
+                if (!ports.Any(p => p.portData.identifier == pv.portData.identifier))
+                {
+                    RemovePort(pv);
+                    portViewList.Remove(pv);
+                }
+            }
+
+            foreach (var p in ports)
+            {
+                // Add missing port views
+                if (!portViews.Any(pv => p.portData.identifier == pv.portData.identifier))
+                {
+                    Direction portDirection = nodeTarget.IsFieldInput(p.fieldName) ? Direction.Input : Direction.Output;
+                    var pv = AddPort(p.fieldInfo, portDirection, listener, p.portData);
+                    portViewList.Add(pv);
+                }
+            }
+
+            return portViewList;
+        }
+
+        void SyncPortOrder(IEnumerable<NodePort> ports, IEnumerable<PortView> portViews)
+        {
+            var portViewList = portViews.ToList();
+            var portsList = ports.ToList();
+
+            // Re-order the port views to match the ports order in case a custom behavior re-ordered the ports
+            for (int i = 0; i < portsList.Count; i++)
+            {
+                var id = portsList[i].portData.identifier;
+
+                var pv = portViewList.FirstOrDefault(p => p.portData.identifier == id);
+                if (pv != null)
+                    InsertPort(pv, i);
+            }
+        }
+
+        public virtual new bool RefreshPorts()
+        {
+            // If a port behavior was attached to one port, then
+            // the port count might have been updated by the node
+            // so we have to refresh the list of port views.
+            UpdatePortViewWithPorts(nodeTarget.inputPorts, inputPortViews);
+            UpdatePortViewWithPorts(nodeTarget.outputPorts, outputPortViews);
+
+            void UpdatePortViewWithPorts(NodePortContainer ports, List<PortView> portViews)
+            {
+                if (ports.Count == 0 && portViews.Count == 0) // Nothing to update
+                    return;
+
+                // When there is no current portviews, we can't zip the list so we just add all
+                if (portViews.Count == 0)
+                    SyncPortCounts(ports, new PortView[] { });
+                else if (ports.Count == 0) // Same when there is no ports
+                    SyncPortCounts(new NodePort[] { }, portViews);
+                else if (portViews.Count != ports.Count)
+                    SyncPortCounts(ports, portViews);
+                else
+                {
+                    var p = ports.GroupBy(n => n.fieldName);
+                    var pv = portViews.GroupBy(v => v.fieldName);
+                    p.Zip(pv, (portPerFieldName, portViewPerFieldName) =>
+                    {
+                        IEnumerable<PortView> portViewsList = portViewPerFieldName;
+                        if (portPerFieldName.Count() != portViewPerFieldName.Count())
+                            portViewsList = SyncPortCounts(portPerFieldName, portViewPerFieldName);
+                        SyncPortOrder(portPerFieldName, portViewsList);
+                        // We don't care about the result, we just iterate over port and portView
+                        return "";
+                    }).ToList();
+                }
+
+                // Here we're sure that we have the same amount of port and portView
+                // so we can update the view with the new port data (if the name of a port have been changed for example)
+
+                for (int i = 0; i < portViews.Count; i++)
+                    portViews[i].UpdatePortView(ports[i].portData);
+            }
+
+            return base.RefreshPorts();
+        }
+
+        public void ForceUpdatePorts()
+        {
+            nodeTarget.UpdateAllPorts();
+
+            RefreshPorts();
+        }
+
+        void UpdatePortsForField(string fieldName)
+        {
+            // TODO: actual code
+            RefreshPorts();
+        }
+
+        protected virtual VisualElement CreateSettingsView() => new Label("Settings") { name = "header" };
+
+        /// <summary>
+        /// Send an event to the graph telling that the content of this node have changed
+        /// </summary>
+        public void NotifyNodeChanged() => owner.graph.NotifyNodeChanged(nodeTarget);
+
+        #endregion
+    }
+
+    public class FieldInfoWithPath : IEquatable<FieldInfoWithPath>
+    {
+        private FieldInfo field;
+        public FieldInfo Field => field;
+        private string path;
+        public string Path => path;
+
+        public FieldInfoWithPath(FieldInfo field, string path)
+        {
+            this.field = field;
+            this.path = path;
+        }
+
+        public FieldInfoWithPath(string path, object startingValue)
+        {
+            this.field = GetFieldInfoPath(path, startingValue).Last();
+            this.path = path;
+        }
+
+        public FieldInfoWithPath(List<FieldInfo> fieldInfos)
+        {
+            this.field = fieldInfos.Last();
+            this.path = fieldInfos.GetPath();
+        }
+
+        public FieldInfoWithPath(FieldInfo field)
+        {
+            this.field = field;
+            this.path = field.Name;
+        }
+
+        public bool Equals(FieldInfoWithPath other)
+        {
+            return field == other.field
+                && path == other.path;
+        }
+
+        public void SetValue(object startingValue, object finalValue)
+        {
+            GetFieldInfoPath(path, startingValue).SetValue(startingValue, finalValue);
+        }
+
+        public static List<FieldInfo> GetFieldInfoPath(string path, object startValue)
+        {
+            string[] pathArray = path.Split('.');
+            List<FieldInfo> fieldInfoPath = new List<FieldInfo>();
+            object value = startValue;
+            for (int i = 0; i < pathArray.Length; i++)
+            {
+                // Debug.Log(pathArray[i]);
+                fieldInfoPath.Add(value.GetType().GetField(pathArray[i], BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance));
+                if (i + 1 < pathArray.Length)
+                {
+                    value = fieldInfoPath[i].GetValue(value);
+                }
+            }
+            return fieldInfoPath;
+        }
     }
-}
\ No newline at end of file
+}
+
diff --git a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/PortView.cs b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/PortView.cs
index 293dac83..a714699f 100644
--- a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/PortView.cs
+++ b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/PortView.cs
@@ -8,172 +8,173 @@
 
 namespace GraphProcessor
 {
-	public class PortView : Port
-	{
-		public string				fieldName => fieldInfo.Name;
-		public Type					fieldType => fieldInfo.FieldType;
-		public new Type				portType;
-        public BaseNodeView     	owner { get; private set; }
-		public PortData				portData;
+    public class PortView : Port
+    {
+        public string fieldName => fieldInfo.Name;
+        public Type fieldType => fieldInfo.FieldType;
+        public new Type portType;
+        public BaseNodeView owner { get; private set; }
+        public PortData portData;
 
-		public event Action< PortView, Edge >	OnConnected;
-		public event Action< PortView, Edge >	OnDisconnected;
+        public event Action<PortView, Edge> OnConnected;
+        public event Action<PortView, Edge> OnDisconnected;
 
-		protected FieldInfo		fieldInfo;
-		protected BaseEdgeConnectorListener	listener;
+        protected FieldInfo fieldInfo;
+        protected BaseEdgeConnectorListener listener;
 
-		string userPortStyleFile = "PortViewTypes";
+        string userPortStyleFile = "PortViewTypes";
 
-		List< EdgeView >		edges = new List< EdgeView >();
+        List<EdgeView> edges = new List<EdgeView>();
 
-		public int connectionCount => edges.Count;
+        public int connectionCount => edges.Count;
 
-		readonly string portStyle = "GraphProcessorStyles/PortView";
+        readonly string portStyle = "GraphProcessorStyles/PortView";
 
         protected PortView(Direction direction, FieldInfo fieldInfo, PortData portData, BaseEdgeConnectorListener edgeConnectorListener)
             : base(portData.vertical ? Orientation.Vertical : Orientation.Horizontal, direction, Capacity.Multi, portData.displayType ?? fieldInfo.FieldType)
-		{
-			this.fieldInfo = fieldInfo;
-			this.listener = edgeConnectorListener;
-			this.portType = portData.displayType ?? fieldInfo.FieldType;
-			this.portData = portData;
-			this.portName = fieldName;
-
-			styleSheets.Add(Resources.Load<StyleSheet>(portStyle));
-
-			UpdatePortSize();
-
-			var userPortStyle = Resources.Load<StyleSheet>(userPortStyleFile);
-			if (userPortStyle != null)
-				styleSheets.Add(userPortStyle);
-			
-			if (portData.vertical)
-				AddToClassList("Vertical");
-			
-			this.tooltip = portData.tooltip;
-		}
-
-		public static PortView CreatePortView(Direction direction, FieldInfo fieldInfo, PortData portData, BaseEdgeConnectorListener edgeConnectorListener)
-		{
-			var pv = new PortView(direction, fieldInfo, portData, edgeConnectorListener);
-			pv.m_EdgeConnector = new BaseEdgeConnector(edgeConnectorListener);
-			pv.AddManipulator(pv.m_EdgeConnector);
-
-			// Force picking in the port label to enlarge the edge creation zone
-			var portLabel = pv.Q("type");
-			if (portLabel != null)
-			{
-				portLabel.pickingMode = PickingMode.Position;
-				portLabel.style.flexGrow = 1;
-			}
-
-			// hide label when the port is vertical
-			if (portData.vertical && portLabel != null)
-				portLabel.style.display = DisplayStyle.None;
-			
-			// Fixup picking mode for vertical top ports
-			if (portData.vertical)
-				pv.Q("connector").pickingMode = PickingMode.Position;
-
-			return pv;
-		}
-
-		/// <summary>
-		/// Update the size of the port view (using the portData.sizeInPixel property)
-		/// </summary>
-		public void UpdatePortSize()
-		{
-			int size = portData.sizeInPixel == 0 ? 8 : portData.sizeInPixel;
-			var connector = this.Q("connector");
-			var cap = connector.Q("cap");
-			connector.style.width = size;
-			connector.style.height = size;
-			cap.style.width = size - 4;
-			cap.style.height = size - 4;
-
-			// Update connected edge sizes:
-			edges.ForEach(e => e.UpdateEdgeSize());
-		}
-
-		public virtual void Initialize(BaseNodeView nodeView, string name)
-		{
-			this.owner = nodeView;
-			AddToClassList(fieldName);
-
-			// Correct port type if port accept multiple values (and so is a container)
-			if (direction == Direction.Input && portData.acceptMultipleEdges && portType == fieldType) // If the user haven't set a custom field type
-			{
-				if (fieldType.GetGenericArguments().Length > 0)
-					portType = fieldType.GetGenericArguments()[0];
-			}
-
-			if (name != null)
-				portName = name;
-			visualClass = "Port_" + portType.Name;
-			tooltip = portData.tooltip;
-		}
-
-		public override void Connect(Edge edge)
-		{
-			OnConnected?.Invoke(this, edge);
-
-			base.Connect(edge);
-
-			var inputNode = (edge.input as PortView).owner;
-			var outputNode = (edge.output as PortView).owner;
-
-			edges.Add(edge as EdgeView);
-
-			inputNode.OnPortConnected(edge.input as PortView);
-			outputNode.OnPortConnected(edge.output as PortView);
-		}
-
-		public override void Disconnect(Edge edge)
-		{
-			OnDisconnected?.Invoke(this, edge);
-
-			base.Disconnect(edge);
-
-			if (!(edge as EdgeView).isConnected)
-				return ;
-
-			var inputNode = (edge.input as PortView)?.owner;
-			var outputNode = (edge.output as PortView)?.owner;
-
-			inputNode?.OnPortDisconnected(edge.input as PortView);
-			outputNode?.OnPortDisconnected(edge.output as PortView);
-
-			edges.Remove(edge as EdgeView);
-		}
-
-		public void UpdatePortView(PortData data)
-		{
-			if (data.displayType != null)
-			{
-				base.portType = data.displayType;
-				portType = data.displayType;
-				visualClass = "Port_" + portType.Name;
-			}
-			if (!String.IsNullOrEmpty(data.displayName))
-				base.portName = data.displayName;
-
-			portData = data;
-
-			// Update the edge in case the port color have changed
-			schedule.Execute(() => {
-				foreach (var edge in edges)
-				{
-					edge.UpdateEdgeControl();
-					edge.MarkDirtyRepaint();
-				}
-			}).ExecuteLater(50); // Hummm
-
-			UpdatePortSize();
-		}
-
-		public List< EdgeView >	GetEdges()
-		{
-			return edges;
-		}
-	}
+        {
+            this.fieldInfo = fieldInfo;
+            this.listener = edgeConnectorListener;
+            this.portType = portData.displayType ?? fieldInfo.FieldType;
+            this.portData = portData;
+            this.portName = fieldName;
+
+            styleSheets.Add(Resources.Load<StyleSheet>(portStyle));
+
+            UpdatePortSize();
+
+            var userPortStyle = Resources.Load<StyleSheet>(userPortStyleFile);
+            if (userPortStyle != null)
+                styleSheets.Add(userPortStyle);
+
+            if (portData.vertical)
+                AddToClassList("Vertical");
+
+            this.tooltip = portData.tooltip;
+        }
+
+        public static PortView CreatePortView(Direction direction, FieldInfo fieldInfo, PortData portData, BaseEdgeConnectorListener edgeConnectorListener)
+        {
+            var pv = new PortView(direction, fieldInfo, portData, edgeConnectorListener);
+            pv.m_EdgeConnector = new BaseEdgeConnector(edgeConnectorListener);
+            pv.AddManipulator(pv.m_EdgeConnector);
+
+            // Force picking in the port label to enlarge the edge creation zone
+            var portLabel = pv.Q("type");
+            if (portLabel != null)
+            {
+                portLabel.pickingMode = PickingMode.Position;
+                portLabel.style.flexGrow = 1;
+            }
+
+            // hide label when the port is vertical
+            if (portData.vertical && portLabel != null)
+                portLabel.style.display = DisplayStyle.None;
+
+            // Fixup picking mode for vertical top ports
+            if (portData.vertical)
+                pv.Q("connector").pickingMode = PickingMode.Position;
+
+            return pv;
+        }
+
+        /// <summary>
+        /// Update the size of the port view (using the portData.sizeInPixel property)
+        /// </summary>
+        public void UpdatePortSize()
+        {
+            int size = portData.sizeInPixel == 0 ? 8 : portData.sizeInPixel;
+            var connector = this.Q("connector");
+            var cap = connector.Q("cap");
+            connector.style.width = size;
+            connector.style.height = size;
+            cap.style.width = size - 4;
+            cap.style.height = size - 4;
+
+            // Update connected edge sizes:
+            edges.ForEach(e => e.UpdateEdgeSize());
+        }
+
+        public virtual void Initialize(BaseNodeView nodeView, string name)
+        {
+            this.owner = nodeView;
+            AddToClassList(fieldName);
+
+            // Correct port type if port accept multiple values (and so is a container)
+            if (direction == Direction.Input && portData.acceptMultipleEdges && portType == fieldType) // If the user haven't set a custom field type
+            {
+                if (fieldType.GetGenericArguments().Length > 0)
+                    portType = fieldType.GetGenericArguments()[0];
+            }
+
+            if (name != null)
+                portName = name;
+            visualClass = "Port_" + portType.Name;
+            tooltip = portData.tooltip;
+        }
+
+        public override void Connect(Edge edge)
+        {
+            OnConnected?.Invoke(this, edge);
+
+            base.Connect(edge);
+
+            var inputNode = (edge.input as PortView).owner;
+            var outputNode = (edge.output as PortView).owner;
+
+            edges.Add(edge as EdgeView);
+
+            inputNode.OnPortConnected(edge.input as PortView);
+            outputNode.OnPortConnected(edge.output as PortView);
+        }
+
+        public override void Disconnect(Edge edge)
+        {
+            OnDisconnected?.Invoke(this, edge);
+
+            base.Disconnect(edge);
+
+            if (!(edge as EdgeView).isConnected)
+                return;
+
+            var inputNode = (edge.input as PortView)?.owner;
+            var outputNode = (edge.output as PortView)?.owner;
+
+            inputNode?.OnPortDisconnected(edge.input as PortView);
+            outputNode?.OnPortDisconnected(edge.output as PortView);
+
+            edges.Remove(edge as EdgeView);
+        }
+
+        public void UpdatePortView(PortData data)
+        {
+            if (data.displayType != null)
+            {
+                base.portType = data.displayType;
+                portType = data.displayType;
+                visualClass = "Port_" + portType.Name;
+            }
+            if (!String.IsNullOrEmpty(data.displayName))
+                base.portName = data.displayName;
+
+            portData = data;
+
+            // Update the edge in case the port color have changed
+            schedule.Execute(() =>
+            {
+                foreach (var edge in edges)
+                {
+                    edge.UpdateEdgeControl();
+                    edge.MarkDirtyRepaint();
+                }
+            }).ExecuteLater(50); // Hummm
+
+            UpdatePortSize();
+        }
+
+        public List<EdgeView> GetEdges()
+        {
+            return edges;
+        }
+    }
 }
\ No newline at end of file
diff --git a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/BaseNode.cs b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/BaseNode.cs
index 0338c14b..6a8dadca 100644
--- a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/BaseNode.cs
+++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/BaseNode.cs
@@ -8,858 +8,885 @@
 
 namespace GraphProcessor
 {
-	public delegate IEnumerable< PortData > CustomPortBehaviorDelegate(List< SerializableEdge > edges);
-	public delegate IEnumerable< PortData > CustomPortTypeBehaviorDelegate(string fieldName, string displayName, object value);
-
-	[Serializable]
-	public abstract class BaseNode
-	{
-		[SerializeField]
-		internal string nodeCustomName = null; // The name of the node in case it was renamed by a user
-
-		/// <summary>
-		/// Name of the node, it will be displayed in the title section
-		/// </summary>
-		/// <returns></returns>
-		public virtual string       name => GetType().Name;
-		
-		/// <summary>
-		/// The accent color of the node
-		/// </summary>
-		public virtual Color color => Color.clear;
-		
-		/// <summary>
-		/// Set a custom uss file for the node. We use a Resources.Load to get the stylesheet so be sure to put the correct resources path
-		/// https://docs.unity3d.com/ScriptReference/Resources.Load.html
-		/// </summary>
-        public virtual string       layoutStyle => string.Empty;
-
-		/// <summary>
-		/// If the node can be locked or not
-		/// </summary>
-        public virtual bool         unlockable => true; 
-
-		/// <summary>
-		/// Is the node is locked (if locked it can't be moved)
-		/// </summary>
-        public virtual bool         isLocked => nodeLock; 
+    public delegate IEnumerable<PortData> CustomPortBehaviorDelegate(List<SerializableEdge> edges);
+    public delegate IEnumerable<PortData> CustomPortTypeBehaviorDelegate(string fieldName, string displayName, object value);
+
+    [Serializable]
+    public abstract class BaseNode
+    {
+        [SerializeField]
+        internal string nodeCustomName = null; // The name of the node in case it was renamed by a user
+
+        /// <summary>
+        /// Name of the node, it will be displayed in the title section
+        /// </summary>
+        /// <returns></returns>
+        public virtual string name => GetType().Name;
+
+        /// <summary>
+        /// The accent color of the node
+        /// </summary>
+        public virtual Color color => Color.clear;
+
+        /// <summary>
+        /// Set a custom uss file for the node. We use a Resources.Load to get the stylesheet so be sure to put the correct resources path
+        /// https://docs.unity3d.com/ScriptReference/Resources.Load.html
+        /// </summary>
+        public virtual string layoutStyle => string.Empty;
+
+        /// <summary>
+        /// If the node can be locked or not
+        /// </summary>
+        public virtual bool unlockable => true;
+
+        /// <summary>
+        /// Is the node is locked (if locked it can't be moved)
+        /// </summary>
+        public virtual bool isLocked => nodeLock;
 
         //id
-        public string				GUID;
-
-		public int					computeOrder = -1;
-
-		/// <summary>Tell wether or not the node can be processed. Do not check anything from inputs because this step happens before inputs are sent to the node</summary>
-		public virtual bool			canProcess => true;
-
-		/// <summary>Show the node controlContainer only when the mouse is over the node</summary>
-		public virtual bool			showControlsOnHover => false;
-
-		/// <summary>True if the node can be deleted, false otherwise</summary>
-		public virtual bool			deletable => true;
-
-		/// <summary>
-		/// Container of input ports
-		/// </summary>
-		[NonSerialized]
-		public readonly NodeInputPortContainer	inputPorts;
-		/// <summary>
-		/// Container of output ports
-		/// </summary>
-		[NonSerialized]
-		public readonly NodeOutputPortContainer	outputPorts;
-
-		//Node view datas
-		public Rect					position;
-		/// <summary>
-		/// Is the node expanded
-		/// </summary>
-		public bool					expanded;
-		/// <summary>
-		/// Is debug visible
-		/// </summary>
-		public bool					debug;
-		/// <summary>
-		/// Node locked state
-		/// </summary>
-        public bool                 nodeLock;
-
-        public delegate void		ProcessDelegate();
-
-		/// <summary>
-		/// Triggered when the node is processes
-		/// </summary>
-		public event ProcessDelegate	onProcessed;
-		public event Action< string, NodeMessageType >	onMessageAdded;
-		public event Action< string >					onMessageRemoved;
-		/// <summary>
-		/// Triggered after an edge was connected on the node
-		/// </summary>
-		public event Action< SerializableEdge >			onAfterEdgeConnected;
-		/// <summary>
-		/// Triggered after an edge was disconnected on the node
-		/// </summary>
-		public event Action< SerializableEdge >			onAfterEdgeDisconnected;
-
-		/// <summary>
-		/// Triggered after a single/list of port(s) is updated, the parameter is the field name
-		/// </summary>
-		public event Action< string >					onPortsUpdated;
-
-		[NonSerialized]
-		bool _needsInspector = false;
-
-		/// <summary>
-		/// Does the node needs to be visible in the inspector (when selected).
-		/// </summary>
-		public virtual bool			needsInspector => _needsInspector;
-
-		/// <summary>
-		/// Can the node be renamed in the UI. By default a node can be renamed by double clicking it's name.
-		/// </summary>
-		public virtual bool			isRenamable => false;
-
-		/// <summary>
-		/// Is the node created from a duplicate operation (either ctrl-D or copy/paste).
-		/// </summary>
-		public bool					createdFromDuplication {get; internal set; } = false;
-
-		/// <summary>
-		/// True only when the node was created from a duplicate operation and is inside a group that was also duplicated at the same time. 
-		/// </summary>
-		public bool					createdWithinGroup {get; internal set; } = false;
-
-		[NonSerialized]
-		internal Dictionary< string, NodeFieldInformation >	nodeFields = new Dictionary< string, NodeFieldInformation >();
-
-		[NonSerialized]
-		internal Dictionary< Type, CustomPortTypeBehaviorDelegate> customPortTypeBehaviorMap = new Dictionary<Type, CustomPortTypeBehaviorDelegate>();
-
-		[NonSerialized]
-		List< string >				messages = new List<string>();
-
-		[NonSerialized]
-		protected BaseGraph			graph;
-
-		internal class NodeFieldInformation
-		{
-			public string						name;
-			public string						fieldName;
-			public FieldInfo					info;
-			public bool							input;
-			public bool							isMultiple;
-			public string						tooltip;
-			public CustomPortBehaviorDelegate	behavior;
-			public bool							vertical;
-
-			public NodeFieldInformation(FieldInfo info, string name, bool input, bool isMultiple, string tooltip, bool vertical, CustomPortBehaviorDelegate behavior)
-			{
-				this.input = input;
-				this.isMultiple = isMultiple;
-				this.info = info;
-				this.name = name;
-				this.fieldName = info.Name;
-				this.behavior = behavior;
-				this.tooltip = tooltip;
-				this.vertical = vertical;
-			}
-		}
-
-		struct PortUpdate
-		{
-			public List<string>	fieldNames;
-			public BaseNode		node;
-
-			public void Deconstruct(out List<string> fieldNames, out BaseNode node)
-			{
-				fieldNames = this.fieldNames;
-				node = this.node;
-			}
-		}
-
-		// Used in port update algorithm
-		Stack<PortUpdate> fieldsToUpdate = new Stack<PortUpdate>();
-		HashSet<PortUpdate> updatedFields = new HashSet<PortUpdate>();
-
-		/// <summary>
-		/// Creates a node of type T at a certain position
-		/// </summary>
-		/// <param name="position">position in the graph in pixels</param>
-		/// <typeparam name="T">type of the node</typeparam>
-		/// <returns>the node instance</returns>
-		public static T CreateFromType< T >(Vector2 position) where T : BaseNode
-		{
-			return CreateFromType(typeof(T), position) as T;
-		}
-
-		/// <summary>
-		/// Creates a node of type nodeType at a certain position
-		/// </summary>
-		/// <param name="position">position in the graph in pixels</param>
-		/// <typeparam name="nodeType">type of the node</typeparam>
-		/// <returns>the node instance</returns>
-		public static BaseNode CreateFromType(Type nodeType, Vector2 position)
-		{
-			if (!nodeType.IsSubclassOf(typeof(BaseNode)))
-				return null;
-
-			var node = Activator.CreateInstance(nodeType) as BaseNode;
-
-			node.position = new Rect(position, new Vector2(100, 100));
-
-			ExceptionToLog.Call(() => node.OnNodeCreated());
-
-			return node;
-		}
-
-		#region Initialization
-
-		// called by the BaseGraph when the node is added to the graph
-		public void Initialize(BaseGraph graph)
-		{
-			this.graph = graph;
-
-			ExceptionToLog.Call(() => Enable());
-
-			InitializePorts();
-		}
-
-		void InitializeCustomPortTypeMethods()
-		{
-			MethodInfo[] methods = new MethodInfo[0];
-			Type baseType = GetType();
-			while (true)
-			{
-				methods = baseType.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance);
-				foreach (var method in methods)
-				{
-					var typeBehaviors = method.GetCustomAttributes<CustomPortTypeBehavior>().ToArray();
-
-					if (typeBehaviors.Length == 0)
-						continue;
-
-					CustomPortTypeBehaviorDelegate deleg = null;
-					try
-					{
-						deleg = Delegate.CreateDelegate(typeof(CustomPortTypeBehaviorDelegate), this, method) as CustomPortTypeBehaviorDelegate;
-					} catch (Exception e)
-					{
-						Debug.LogError(e);
-						Debug.LogError($"Cannot convert method {method} to a delegate of type {typeof(CustomPortTypeBehaviorDelegate)}");
-					}
-
-					foreach (var typeBehavior in typeBehaviors)
-						customPortTypeBehaviorMap[typeBehavior.type] = deleg;
-				}
-
-				// Try to also find private methods in the base class
-				baseType = baseType.BaseType;
-				if (baseType == null)
-					break;
-			}
-		}
-
-		/// <summary>
-		/// Use this function to initialize anything related to ports generation in your node
-		/// This will allow the node creation menu to correctly recognize ports that can be connected between nodes
-		/// </summary>
-		public virtual void InitializePorts()
-		{
-			InitializeCustomPortTypeMethods();
-
-			foreach (var key in OverrideFieldOrder(nodeFields.Values.Select(k => k.info)))
-			{
-				var nodeField = nodeFields[key.Name];
-
-				if (HasCustomBehavior(nodeField))
-				{
-					UpdatePortsForField(nodeField.fieldName, sendPortUpdatedEvent: false);
-				}
-				else
-				{
-					// If we don't have a custom behavior on the node, we just have to create a simple port
-					AddPort(nodeField.input, nodeField.fieldName, new PortData { acceptMultipleEdges = nodeField.isMultiple, displayName = nodeField.name, tooltip = nodeField.tooltip, vertical = nodeField.vertical });
-				}
-			}
-		}
-
-		/// <summary>
-		/// Override the field order inside the node. It allows to re-order all the ports and field in the UI.
-		/// </summary>
-		/// <param name="fields">List of fields to sort</param>
-		/// <returns>Sorted list of fields</returns>
-		public virtual IEnumerable<FieldInfo> OverrideFieldOrder(IEnumerable<FieldInfo> fields)
-		{
-			long GetFieldInheritanceLevel(FieldInfo f)
-			{
-				int level = 0;
-				var t = f.DeclaringType;
-				while (t != null)
-				{
-					t = t.BaseType;
-					level++;
-				}
-
-				return level;
-			}
-
-			// Order by MetadataToken and inheritance level to sync the order with the port order (make sure FieldDrawers are next to the correct port)
-			return fields.OrderByDescending(f => (long)(((GetFieldInheritanceLevel(f) << 32)) | (long)f.MetadataToken));
-		}
-
-		protected BaseNode()
-		{
+        public string GUID;
+
+        public int computeOrder = -1;
+
+        /// <summary>Tell wether or not the node can be processed. Do not check anything from inputs because this step happens before inputs are sent to the node</summary>
+        public virtual bool canProcess => true;
+
+        /// <summary>Show the node controlContainer only when the mouse is over the node</summary>
+        public virtual bool showControlsOnHover => false;
+
+        /// <summary>True if the node can be deleted, false otherwise</summary>
+        public virtual bool deletable => true;
+
+        /// <summary>
+        /// Container of input ports
+        /// </summary>
+        [NonSerialized]
+        public readonly NodeInputPortContainer inputPorts;
+        /// <summary>
+        /// Container of output ports
+        /// </summary>
+        [NonSerialized]
+        public readonly NodeOutputPortContainer outputPorts;
+
+        //Node view datas
+        public Rect position;
+        /// <summary>
+        /// Is the node expanded
+        /// </summary>
+        public bool expanded;
+        /// <summary>
+        /// Is debug visible
+        /// </summary>
+        public bool debug;
+        /// <summary>
+        /// Node locked state
+        /// </summary>
+        public bool nodeLock;
+
+        public delegate void ProcessDelegate();
+
+        /// <summary>
+        /// Triggered when the node is processes
+        /// </summary>
+        public event ProcessDelegate onProcessed;
+        public event Action<string, NodeMessageType> onMessageAdded;
+        public event Action<string> onMessageRemoved;
+        /// <summary>
+        /// Triggered after an edge was connected on the node
+        /// </summary>
+        public event Action<SerializableEdge> onAfterEdgeConnected;
+        /// <summary>
+        /// Triggered after an edge was disconnected on the node
+        /// </summary>
+        public event Action<SerializableEdge> onAfterEdgeDisconnected;
+
+        /// <summary>
+        /// Triggered after a single/list of port(s) is updated, the parameter is the field name
+        /// </summary>
+        public event Action<string> onPortsUpdated;
+
+        [NonSerialized]
+        bool _needsInspector = false;
+
+        /// <summary>
+        /// Does the node needs to be visible in the inspector (when selected).
+        /// </summary>
+        public virtual bool needsInspector => _needsInspector;
+
+        /// <summary>
+        /// Can the node be renamed in the UI. By default a node can be renamed by double clicking it's name.
+        /// </summary>
+        public virtual bool isRenamable => false;
+
+        /// <summary>
+        /// Is the node created from a duplicate operation (either ctrl-D or copy/paste).
+        /// </summary>
+        public bool createdFromDuplication { get; internal set; } = false;
+
+        /// <summary>
+        /// True only when the node was created from a duplicate operation and is inside a group that was also duplicated at the same time. 
+        /// </summary>
+        public bool createdWithinGroup { get; internal set; } = false;
+
+        [NonSerialized]
+        internal Dictionary<string, NodeFieldInformation> nodeFields = new Dictionary<string, NodeFieldInformation>();
+
+        [NonSerialized]
+        internal Dictionary<Type, CustomPortTypeBehaviorDelegate> customPortTypeBehaviorMap = new Dictionary<Type, CustomPortTypeBehaviorDelegate>();
+
+        [NonSerialized]
+        List<string> messages = new List<string>();
+
+        [NonSerialized]
+        protected BaseGraph graph;
+
+        internal class NodeFieldInformation
+        {
+            public string name;
+            public string fieldName;
+            public FieldInfo info;
+            public bool input;
+            public bool isMultiple;
+            public string tooltip;
+            public bool showAsDrawer;
+            public CustomPortBehaviorDelegate behavior;
+            public bool vertical;
+
+            public NodeFieldInformation(FieldInfo info, string name, bool input, bool isMultiple, string tooltip, bool showAsDrawer, bool vertical, CustomPortBehaviorDelegate behavior)
+            {
+                this.input = input;
+                this.isMultiple = isMultiple;
+                this.info = info;
+                this.name = name;
+                this.fieldName = info.Name;
+                this.behavior = behavior;
+                this.tooltip = tooltip;
+                this.showAsDrawer = showAsDrawer;
+                this.vertical = vertical;
+            }
+        }
+
+        struct PortUpdate
+        {
+            public List<string> fieldNames;
+            public BaseNode node;
+
+            public void Deconstruct(out List<string> fieldNames, out BaseNode node)
+            {
+                fieldNames = this.fieldNames;
+                node = this.node;
+            }
+        }
+
+        // Used in port update algorithm
+        Stack<PortUpdate> fieldsToUpdate = new Stack<PortUpdate>();
+        HashSet<PortUpdate> updatedFields = new HashSet<PortUpdate>();
+
+        /// <summary>
+        /// Creates a node of type T at a certain position
+        /// </summary>
+        /// <param name="position">position in the graph in pixels</param>
+        /// <typeparam name="T">type of the node</typeparam>
+        /// <returns>the node instance</returns>
+        public static T CreateFromType<T>(Vector2 position) where T : BaseNode
+        {
+            return CreateFromType(typeof(T), position) as T;
+        }
+
+        /// <summary>
+        /// Creates a node of type nodeType at a certain position
+        /// </summary>
+        /// <param name="position">position in the graph in pixels</param>
+        /// <typeparam name="nodeType">type of the node</typeparam>
+        /// <returns>the node instance</returns>
+        public static BaseNode CreateFromType(Type nodeType, Vector2 position)
+        {
+            if (!nodeType.IsSubclassOf(typeof(BaseNode)))
+                return null;
+
+            var node = Activator.CreateInstance(nodeType) as BaseNode;
+
+            node.position = new Rect(position, new Vector2(100, 100));
+
+            ExceptionToLog.Call(() => node.OnNodeCreated());
+
+            return node;
+        }
+
+        #region Initialization
+
+        // called by the BaseGraph when the node is added to the graph
+        public void Initialize(BaseGraph graph)
+        {
+            this.graph = graph;
+
+            ExceptionToLog.Call(() => Enable());
+
+            InitializePorts();
+        }
+
+        void InitializeCustomPortTypeMethods()
+        {
+            MethodInfo[] methods = new MethodInfo[0];
+            Type baseType = GetType();
+            while (true)
+            {
+                methods = baseType.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance);
+                foreach (var method in methods)
+                {
+                    var typeBehaviors = method.GetCustomAttributes<CustomPortTypeBehavior>().ToArray();
+
+                    if (typeBehaviors.Length == 0)
+                        continue;
+
+                    CustomPortTypeBehaviorDelegate deleg = null;
+                    try
+                    {
+                        deleg = Delegate.CreateDelegate(typeof(CustomPortTypeBehaviorDelegate), this, method) as CustomPortTypeBehaviorDelegate;
+                    }
+                    catch (Exception e)
+                    {
+                        Debug.LogError(e);
+                        Debug.LogError($"Cannot convert method {method} to a delegate of type {typeof(CustomPortTypeBehaviorDelegate)}");
+                    }
+
+                    foreach (var typeBehavior in typeBehaviors)
+                        customPortTypeBehaviorMap[typeBehavior.type] = deleg;
+                }
+
+                // Try to also find private methods in the base class
+                baseType = baseType.BaseType;
+                if (baseType == null)
+                    break;
+            }
+        }
+
+        /// <summary>
+        /// Use this function to initialize anything related to ports generation in your node
+        /// This will allow the node creation menu to correctly recognize ports that can be connected between nodes
+        /// </summary>
+        public virtual void InitializePorts()
+        {
+            InitializeCustomPortTypeMethods();
+
+            foreach (var key in OverrideFieldOrder(nodeFields.Values.Select(k => k.info)))
+            {
+                var nodeField = nodeFields[key.Name];
+
+                if (HasCustomBehavior(nodeField))
+                {
+                    UpdatePortsForField(nodeField.fieldName, sendPortUpdatedEvent: false);
+                }
+                else
+                {
+                    // If we don't have a custom behavior on the node, we just have to create a simple port
+                    AddPort(
+                        nodeField.input,
+                        nodeField.fieldName,
+                        new PortData
+                        {
+                            acceptMultipleEdges = nodeField.isMultiple,
+                            displayName = nodeField.name,
+                            tooltip = nodeField.tooltip,
+                            vertical = nodeField.vertical,
+                            showAsDrawer = nodeField.showAsDrawer
+                        }
+                    );
+                }
+            }
+        }
+
+        /// <summary>
+        /// Override the field order inside the node. It allows to re-order all the ports and field in the UI.
+        /// </summary>
+        /// <param name="fields">List of fields to sort</param>
+        /// <returns>Sorted list of fields</returns>
+        public virtual IEnumerable<FieldInfo> OverrideFieldOrder(IEnumerable<FieldInfo> fields)
+        {
+            long GetFieldInheritanceLevel(FieldInfo f)
+            {
+                int level = 0;
+                var t = f.DeclaringType;
+                while (t != null)
+                {
+                    t = t.BaseType;
+                    level++;
+                }
+
+                return level;
+            }
+
+            // Order by MetadataToken and inheritance level to sync the order with the port order (make sure FieldDrawers are next to the correct port)
+            return fields.OrderByDescending(f => (long)(((GetFieldInheritanceLevel(f) << 32)) | (long)f.MetadataToken));
+        }
+
+        protected BaseNode()
+        {
             inputPorts = new NodeInputPortContainer(this);
             outputPorts = new NodeOutputPortContainer(this);
 
-			InitializeInOutDatas();
-		}
-
-		/// <summary>
-		/// Update all ports of the node
-		/// </summary>
-		public bool UpdateAllPorts()
-		{
-			bool changed = false;
-
-			foreach (var key in OverrideFieldOrder(nodeFields.Values.Select(k => k.info)))
-			{
-				var field = nodeFields[key.Name];
-				changed |= UpdatePortsForField(field.fieldName);
-			}
-
-			return changed;
-		}
-
-		/// <summary>
-		/// Update all ports of the node without updating the connected ports. Only use this method when you need to update all the nodes ports in your graph.
-		/// </summary>
-		public bool UpdateAllPortsLocal()
-		{
-			bool changed = false;
-
-			foreach (var key in OverrideFieldOrder(nodeFields.Values.Select(k => k.info)))
-			{
-				var field = nodeFields[key.Name];
-				changed |= UpdatePortsForFieldLocal(field.fieldName);
-			}
-
-			return changed;
-		}
-
-
-		/// <summary>
-		/// Update the ports related to one C# property field (only for this node)
-		/// </summary>
-		/// <param name="fieldName"></param>
-		public bool UpdatePortsForFieldLocal(string fieldName, bool sendPortUpdatedEvent = true)
-		{
-			bool changed = false;
-
-			if (!nodeFields.ContainsKey(fieldName))
-				return false;
-
-			var fieldInfo = nodeFields[fieldName];
-
-			if (!HasCustomBehavior(fieldInfo))
-				return false;
-
-			List< string > finalPorts = new List< string >();
-
-			var portCollection = fieldInfo.input ? (NodePortContainer)inputPorts : outputPorts;
-
-			// Gather all fields for this port (before to modify them)
-			var nodePorts = portCollection.Where(p => p.fieldName == fieldName);
-			// Gather all edges connected to these fields:
-			var edges = nodePorts.SelectMany(n => n.GetEdges()).ToList();
-
-			if (fieldInfo.behavior != null)
-			{
-				foreach (var portData in fieldInfo.behavior(edges))
-					AddPortData(portData);
-			}
-			else
-			{
-				var customPortTypeBehavior = customPortTypeBehaviorMap[fieldInfo.info.FieldType];
-
-				foreach (var portData in customPortTypeBehavior(fieldName, fieldInfo.name, fieldInfo.info.GetValue(this)))
-					AddPortData(portData);
-			}
-
-			void AddPortData(PortData portData)
-			{
-				var port = nodePorts.FirstOrDefault(n => n.portData.identifier == portData.identifier);
-				// Guard using the port identifier so we don't duplicate identifiers
-				if (port == null)
-				{
-					AddPort(fieldInfo.input, fieldName, portData);
-					changed = true;
-				}
-				else
-				{
-					// in case the port type have changed for an incompatible type, we disconnect all the edges attached to this port
-					if (!BaseGraph.TypesAreConnectable(port.portData.displayType, portData.displayType))
-					{
-						foreach (var edge in port.GetEdges().ToList())
-							graph.Disconnect(edge.GUID);
-					}
-
-					// patch the port data
-					if (port.portData != portData)
-					{
-						port.portData.CopyFrom(portData);
-						changed = true;
-					}
-				}
-
-				finalPorts.Add(portData.identifier);
-			}
-
-			// TODO
-			// Remove only the ports that are no more in the list
-			if (nodePorts != null)
-			{
-				var currentPortsCopy = nodePorts.ToList();
-				foreach (var currentPort in currentPortsCopy)
-				{
-					// If the current port does not appear in the list of final ports, we remove it
-					if (!finalPorts.Any(id => id == currentPort.portData.identifier))
-					{
-						RemovePort(fieldInfo.input, currentPort);
-						changed = true;
-					}
-				}
-			}
-
-			// Make sure the port order is correct:
-			portCollection.Sort((p1, p2) => {
-				int p1Index = finalPorts.FindIndex(id => p1.portData.identifier == id);
-				int p2Index = finalPorts.FindIndex(id => p2.portData.identifier == id);
-
-				if (p1Index == -1 || p2Index == -1)
-					return 0;
-
-				return p1Index.CompareTo(p2Index);
-			});
-
-			if (sendPortUpdatedEvent)
-				onPortsUpdated?.Invoke(fieldName);
-
-			return changed;
-		}
-
-		bool HasCustomBehavior(NodeFieldInformation info)
-		{
-			if (info.behavior != null)
-				return true;
-
-			if (customPortTypeBehaviorMap.ContainsKey(info.info.FieldType))
-				return true;
-			
-			return false;
-		}
-
-		/// <summary>
-		/// Update the ports related to one C# property field and all connected nodes in the graph
-		/// </summary>
-		/// <param name="fieldName"></param>
-		public bool UpdatePortsForField(string fieldName, bool sendPortUpdatedEvent = true)
-		{
-			bool changed  = false;
-
-			fieldsToUpdate.Clear();
-			updatedFields.Clear();
-
-			fieldsToUpdate.Push(new PortUpdate{fieldNames = new List<string>(){fieldName}, node = this});
-
-			// Iterate through all the ports that needs to be updated, following graph connection when the 
-			// port is updated. This is required ton have type propagation multiple nodes that changes port types
-			// are connected to each other (i.e. the relay node)
-			while (fieldsToUpdate.Count != 0)
-			{
-				var (fields, node) = fieldsToUpdate.Pop();
-
-				// Avoid updating twice a port
-				if (updatedFields.Any((t) => t.node == node && fields.SequenceEqual(t.fieldNames)))
-					continue;
-				updatedFields.Add(new PortUpdate{fieldNames = fields, node = node});
-
-				foreach (var field in fields)
-				{
-					if (node.UpdatePortsForFieldLocal(field, sendPortUpdatedEvent))
-					{
-						foreach (var port in node.IsFieldInput(field) ? (NodePortContainer)node.inputPorts : node.outputPorts)
-						{
-							if (port.fieldName != field)
-								continue;
-
-							foreach(var edge in port.GetEdges())
-							{
-								var edgeNode = (node.IsFieldInput(field)) ? edge.outputNode : edge.inputNode;
-								var fieldsWithBehavior = edgeNode.nodeFields.Values.Where(f => HasCustomBehavior(f)).Select(f => f.fieldName).ToList();
-								fieldsToUpdate.Push(new PortUpdate{fieldNames = fieldsWithBehavior, node = edgeNode});
-							}
-						}
-						changed = true;
-					}
-				}
-			}
-
-			return changed;
-		}
-
-		HashSet<BaseNode> portUpdateHashSet = new HashSet<BaseNode>();
-
-		internal void DisableInternal()
-		{
-			// port containers are initialized in the OnEnable
-			inputPorts.Clear();
-			outputPorts.Clear();
-
-			ExceptionToLog.Call(() => Disable());
-		}
-
-		internal void DestroyInternal() => ExceptionToLog.Call(() => Destroy());
-
-		/// <summary>
-		/// Called only when the node is created, not when instantiated
-		/// </summary>
-		public virtual void	OnNodeCreated() => GUID = Guid.NewGuid().ToString();
-
-		public virtual FieldInfo[] GetNodeFields()
-			=> GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
-
-		void InitializeInOutDatas()
-		{
-			var fields = GetNodeFields();
-			var methods = GetType().GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
-
-			foreach (var field in fields)
-			{
-				var inputAttribute = field.GetCustomAttribute< InputAttribute >();
-				var outputAttribute = field.GetCustomAttribute< OutputAttribute >();
-				var tooltipAttribute = field.GetCustomAttribute< TooltipAttribute >();
-				var showInInspector = field.GetCustomAttribute< ShowInInspector >();
-				var vertical = field.GetCustomAttribute< VerticalAttribute >();
-				bool isMultiple = false;
-				bool input = false;
-				string name = field.Name;
-				string tooltip = null;
-
-				if (showInInspector != null)
-					_needsInspector = true;
-
-				if (inputAttribute == null && outputAttribute == null)
-					continue ;
-
-				//check if field is a collection type
-				isMultiple = (inputAttribute != null) ? inputAttribute.allowMultiple : (outputAttribute.allowMultiple);
-				input = inputAttribute != null;
-				tooltip = tooltipAttribute?.tooltip;
-
-				if (!String.IsNullOrEmpty(inputAttribute?.name))
-					name = inputAttribute.name;
-				if (!String.IsNullOrEmpty(outputAttribute?.name))
-					name = outputAttribute.name;
-
-				// By default we set the behavior to null, if the field have a custom behavior, it will be set in the loop just below
-				nodeFields[field.Name] = new NodeFieldInformation(field, name, input, isMultiple, tooltip, vertical != null, null);
-			}
-
-			foreach (var method in methods)
-			{
-				var customPortBehaviorAttribute = method.GetCustomAttribute< CustomPortBehaviorAttribute >();
-				CustomPortBehaviorDelegate behavior = null;
-
-				if (customPortBehaviorAttribute == null)
-					continue ;
-
-				// Check if custom port behavior function is valid
-				try {
-					var referenceType = typeof(CustomPortBehaviorDelegate);
-					behavior = (CustomPortBehaviorDelegate)Delegate.CreateDelegate(referenceType, this, method, true);
-				} catch {
-					Debug.LogError("The function " + method + " cannot be converted to the required delegate format: " + typeof(CustomPortBehaviorDelegate));
-				}
-
-				if (nodeFields.ContainsKey(customPortBehaviorAttribute.fieldName))
-					nodeFields[customPortBehaviorAttribute.fieldName].behavior = behavior;
-				else
-					Debug.LogError("Invalid field name for custom port behavior: " + method + ", " + customPortBehaviorAttribute.fieldName);
-			}
-		}
-
-		#endregion
-
-		#region Events and Processing
-
-		public void OnEdgeConnected(SerializableEdge edge)
-		{
-			bool input = edge.inputNode == this;
-			NodePortContainer portCollection = (input) ? (NodePortContainer)inputPorts : outputPorts;
-
-			portCollection.Add(edge);
-
-			UpdateAllPorts();
-
-			onAfterEdgeConnected?.Invoke(edge);
-		}
-
-		protected virtual bool CanResetPort(NodePort port) => true;
-
-		public void OnEdgeDisconnected(SerializableEdge edge)
-		{
-			if (edge == null)
-				return ;
-
-			bool input = edge.inputNode == this;
-			NodePortContainer portCollection = (input) ? (NodePortContainer)inputPorts : outputPorts;
-
-			portCollection.Remove(edge);
-
-			// Reset default values of input port:
-			bool haveConnectedEdges = edge.inputNode.inputPorts.Where(p => p.fieldName == edge.inputFieldName).Any(p => p.GetEdges().Count != 0);
-			if (edge.inputNode == this && !haveConnectedEdges && CanResetPort(edge.inputPort))
-				edge.inputPort?.ResetToDefault();
-
-			UpdateAllPorts();
-
-			onAfterEdgeDisconnected?.Invoke(edge);
-		}
-
-		public void OnProcess()
-		{
-			inputPorts.PullDatas();
-
-			ExceptionToLog.Call(() => Process());
-
-			InvokeOnProcessed();
-
-			outputPorts.PushDatas();
-		}
-
-		public void InvokeOnProcessed() => onProcessed?.Invoke();
-
-		/// <summary>
-		/// Called when the node is enabled
-		/// </summary>
-		protected virtual void Enable() {}
-		/// <summary>
-		/// Called when the node is disabled
-		/// </summary>
-		protected virtual void Disable() {}
-		/// <summary>
-		/// Called when the node is removed
-		/// </summary>
-		protected virtual void Destroy() {}
-
-		/// <summary>
-		/// Override this method to implement custom processing
-		/// </summary>
-		protected virtual void Process() {}
-
-		#endregion
-
-		#region API and utils
-
-		/// <summary>
-		/// Add a port
-		/// </summary>
-		/// <param name="input">is input port</param>
-		/// <param name="fieldName">C# field name</param>
-		/// <param name="portData">Data of the port</param>
-		public void AddPort(bool input, string fieldName, PortData portData)
-		{
-			// Fixup port data info if needed:
-			if (portData.displayType == null)
-				portData.displayType = nodeFields[fieldName].info.FieldType;
-
-			if (input)
-				inputPorts.Add(new NodePort(this, fieldName, portData));
-			else
-				outputPorts.Add(new NodePort(this, fieldName, portData));
-		}
-
-		/// <summary>
-		/// Remove a port
-		/// </summary>
-		/// <param name="input">is input port</param>
-		/// <param name="port">the port to delete</param>
-		public void RemovePort(bool input, NodePort port)
-		{
-			if (input)
-				inputPorts.Remove(port);
-			else
-				outputPorts.Remove(port);
-		}
-
-		/// <summary>
-		/// Remove port(s) from field name
-		/// </summary>
-		/// <param name="input">is input</param>
-		/// <param name="fieldName">C# field name</param>
-		public void RemovePort(bool input, string fieldName)
-		{
-			if (input)
-				inputPorts.RemoveAll(p => p.fieldName == fieldName);
-			else
-				outputPorts.RemoveAll(p => p.fieldName == fieldName);
-		}
-
-		/// <summary>
-		/// Get all the nodes connected to the input ports of this node
-		/// </summary>
-		/// <returns>an enumerable of node</returns>
-		public IEnumerable< BaseNode > GetInputNodes()
-		{
-			foreach (var port in inputPorts)
-				foreach (var edge in port.GetEdges())
-					yield return edge.outputNode;
-		}
-
-		/// <summary>
-		/// Get all the nodes connected to the output ports of this node
-		/// </summary>
-		/// <returns>an enumerable of node</returns>
-		public IEnumerable< BaseNode > GetOutputNodes()
-		{
-			foreach (var port in outputPorts)
-				foreach (var edge in port.GetEdges())
-					yield return edge.inputNode;
-		}
-
-		/// <summary>
-		/// Return a node matching the condition in the dependencies of the node
-		/// </summary>
-		/// <param name="condition">Condition to choose the node</param>
-		/// <returns>Matched node or null</returns>
-		public BaseNode FindInDependencies(Func<BaseNode, bool> condition)
-		{
-			Stack<BaseNode> dependencies = new Stack<BaseNode>();
-
-			dependencies.Push(this);
-
-			int depth = 0;
-			while (dependencies.Count > 0)
-			{
-				var node = dependencies.Pop();
-
-				// Guard for infinite loop (faster than a HashSet based solution)
-				depth++;
-				if (depth > 2000)
-					break;
-
-				if (condition(node))
-					return node;
-				
-				foreach (var dep in node.GetInputNodes())
-					dependencies.Push(dep);
-			}
-			return null;
-		}
-
-		/// <summary>
-		/// Get the port from field name and identifier
-		/// </summary>
-		/// <param name="fieldName">C# field name</param>
-		/// <param name="identifier">Unique port identifier</param>
-		/// <returns></returns>
-		public NodePort	GetPort(string fieldName, string identifier)
-		{
-			return inputPorts.Concat(outputPorts).FirstOrDefault(p => {
-				var bothNull = String.IsNullOrEmpty(identifier) && String.IsNullOrEmpty(p.portData.identifier);
-				return p.fieldName == fieldName && (bothNull || identifier == p.portData.identifier);
-			});
-		}
-
-		/// <summary>
-		/// Return all the ports of the node
-		/// </summary>
-		/// <returns></returns>
-		public IEnumerable<NodePort> GetAllPorts()
-		{
-			foreach (var port in inputPorts)
-				yield return port;
-			foreach (var port in outputPorts)
-				yield return port;
-		}
-
-		/// <summary>
-		/// Return all the connected edges of the node
-		/// </summary>
-		/// <returns></returns>
-		public IEnumerable<SerializableEdge> GetAllEdges()
-		{
-			foreach (var port in GetAllPorts())
-				foreach (var edge in port.GetEdges())
-					yield return edge;
-		}
-
-		/// <summary>
-		/// Is the port an input
-		/// </summary>
-		/// <param name="fieldName"></param>
-		/// <returns></returns>
-		public bool IsFieldInput(string fieldName) => nodeFields[fieldName].input;
-
-		/// <summary>
-		/// Add a message on the node
-		/// </summary>
-		/// <param name="message"></param>
-		/// <param name="messageType"></param>
-		public void AddMessage(string message, NodeMessageType messageType)
-		{
-			if (messages.Contains(message))
-				return;
-
-			onMessageAdded?.Invoke(message, messageType);
-			messages.Add(message);
-		}
-
-		/// <summary>
-		/// Remove a message on the node
-		/// </summary>
-		/// <param name="message"></param>
-		public void RemoveMessage(string message)
-		{
-			onMessageRemoved?.Invoke(message);
-			messages.Remove(message);
-		}
-
-		/// <summary>
-		/// Remove a message that contains
-		/// </summary>
-		/// <param name="subMessage"></param>
-		public void RemoveMessageContains(string subMessage)
-		{
-			string toRemove = messages.Find(m => m.Contains(subMessage));
-			messages.Remove(toRemove);
-			onMessageRemoved?.Invoke(toRemove);
-		}
-
-		/// <summary>
-		/// Remove all messages on the node
-		/// </summary>
-		public void ClearMessages()
-		{
-			foreach (var message in messages)
-				onMessageRemoved?.Invoke(message);
-			messages.Clear();
-		}
-
-		/// <summary>
-		/// Set the custom name of the node. This is intended to be used by renamable nodes.
-		/// This custom name will be serialized inside the node.
-		/// </summary>
-		/// <param name="customNodeName">New name of the node.</param>
-		public void SetCustomName(string customName) => nodeCustomName = customName;
-
-		/// <summary>
-		/// Get the name of the node. If the node have a custom name (set using the UI by double clicking on the node title) then it will return this name first, otherwise it returns the value of the name field.
-		/// </summary>
-		/// <returns>The name of the node as written in the title</returns>
-		public string GetCustomName() => String.IsNullOrEmpty(nodeCustomName) ? name : nodeCustomName; 
-
-		#endregion
-	}
+            InitializeInOutDatas();
+        }
+
+        /// <summary>
+        /// Update all ports of the node
+        /// </summary>
+        public bool UpdateAllPorts()
+        {
+            bool changed = false;
+
+            foreach (var key in OverrideFieldOrder(nodeFields.Values.Select(k => k.info)))
+            {
+                var field = nodeFields[key.Name];
+                changed |= UpdatePortsForField(field.fieldName);
+            }
+
+            return changed;
+        }
+
+        /// <summary>
+        /// Update all ports of the node without updating the connected ports. Only use this method when you need to update all the nodes ports in your graph.
+        /// </summary>
+        public bool UpdateAllPortsLocal()
+        {
+            bool changed = false;
+
+            foreach (var key in OverrideFieldOrder(nodeFields.Values.Select(k => k.info)))
+            {
+                var field = nodeFields[key.Name];
+                changed |= UpdatePortsForFieldLocal(field.fieldName);
+            }
+
+            return changed;
+        }
+
+
+        /// <summary>
+        /// Update the ports related to one C# property field (only for this node)
+        /// </summary>
+        /// <param name="fieldName"></param>
+        public bool UpdatePortsForFieldLocal(string fieldName, bool sendPortUpdatedEvent = true)
+        {
+            bool changed = false;
+
+            if (!nodeFields.ContainsKey(fieldName))
+                return false;
+
+            var fieldInfo = nodeFields[fieldName];
+
+            if (!HasCustomBehavior(fieldInfo))
+                return false;
+
+            List<string> finalPorts = new List<string>();
+
+            var portCollection = fieldInfo.input ? (NodePortContainer)inputPorts : outputPorts;
+
+            // Gather all fields for this port (before to modify them)
+            var nodePorts = portCollection.Where(p => p.fieldName == fieldName);
+            // Gather all edges connected to these fields:
+            var edges = nodePorts.SelectMany(n => n.GetEdges()).ToList();
+
+            if (fieldInfo.behavior != null)
+            {
+                foreach (var portData in fieldInfo.behavior(edges))
+                {
+                    if (portData != null)
+                        AddPortData(portData);
+                }
+            }
+            else
+            {
+                var customPortTypeBehavior = customPortTypeBehaviorMap[fieldInfo.info.FieldType];
+
+                foreach (var portData in customPortTypeBehavior(fieldName, fieldInfo.name, fieldInfo.info.GetValue(this)))
+                    AddPortData(portData);
+            }
+
+            void AddPortData(PortData portData)
+            {
+                var port = nodePorts.FirstOrDefault(n => n.portData.identifier == portData.identifier);
+                // Guard using the port identifier so we don't duplicate identifiers
+                if (port == null)
+                {
+                    AddPort(fieldInfo.input, fieldName, portData);
+                    changed = true;
+                }
+                else
+                {
+                    // in case the port type have changed for an incompatible type, we disconnect all the edges attached to this port
+                    if (!BaseGraph.TypesAreConnectable(port.portData.displayType, portData.displayType))
+                    {
+                        foreach (var edge in port.GetEdges().ToList())
+                            graph.Disconnect(edge.GUID);
+                    }
+
+                    // patch the port data
+                    if (port.portData != portData)
+                    {
+                        port.portData.CopyFrom(portData);
+                        changed = true;
+                    }
+                }
+
+                finalPorts.Add(portData.identifier);
+            }
+
+            // TODO
+            // Remove only the ports that are no more in the list
+            if (nodePorts != null)
+            {
+                var currentPortsCopy = nodePorts.ToList();
+                foreach (var currentPort in currentPortsCopy)
+                {
+                    // If the current port does not appear in the list of final ports, we remove it
+                    if (!finalPorts.Any(id => id == currentPort.portData.identifier))
+                    {
+                        RemovePort(fieldInfo.input, currentPort);
+                        changed = true;
+                    }
+                }
+            }
+
+            // Make sure the port order is correct:
+            portCollection.Sort((p1, p2) =>
+            {
+                int p1Index = finalPorts.FindIndex(id => p1.portData.identifier == id);
+                int p2Index = finalPorts.FindIndex(id => p2.portData.identifier == id);
+
+                if (p1Index == -1 || p2Index == -1)
+                    return 0;
+
+                return p1Index.CompareTo(p2Index);
+            });
+
+            if (sendPortUpdatedEvent)
+                onPortsUpdated?.Invoke(fieldName);
+
+            return changed;
+        }
+
+        bool HasCustomBehavior(NodeFieldInformation info)
+        {
+            if (info.behavior != null)
+                return true;
+
+            if (customPortTypeBehaviorMap.ContainsKey(info.info.FieldType))
+                return true;
+
+            return false;
+        }
+
+        /// <summary>
+        /// Update the ports related to one C# property field and all connected nodes in the graph
+        /// </summary>
+        /// <param name="fieldName"></param>
+        public bool UpdatePortsForField(string fieldName, bool sendPortUpdatedEvent = true)
+        {
+            bool changed = false;
+
+            fieldsToUpdate.Clear();
+            updatedFields.Clear();
+
+            fieldsToUpdate.Push(new PortUpdate { fieldNames = new List<string>() { fieldName }, node = this });
+
+            // Iterate through all the ports that needs to be updated, following graph connection when the 
+            // port is updated. This is required ton have type propagation multiple nodes that changes port types
+            // are connected to each other (i.e. the relay node)
+            while (fieldsToUpdate.Count != 0)
+            {
+                var (fields, node) = fieldsToUpdate.Pop();
+
+                // Avoid updating twice a port
+                if (updatedFields.Any((t) => t.node == node && fields.SequenceEqual(t.fieldNames)))
+                    continue;
+                updatedFields.Add(new PortUpdate { fieldNames = fields, node = node });
+
+                foreach (var field in fields)
+                {
+                    if (node.UpdatePortsForFieldLocal(field, sendPortUpdatedEvent))
+                    {
+                        foreach (var port in node.IsFieldInput(field) ? (NodePortContainer)node.inputPorts : node.outputPorts)
+                        {
+                            if (port.fieldName != field)
+                                continue;
+
+                            foreach (var edge in port.GetEdges())
+                            {
+                                var edgeNode = (node.IsFieldInput(field)) ? edge.outputNode : edge.inputNode;
+                                var fieldsWithBehavior = edgeNode.nodeFields.Values.Where(f => HasCustomBehavior(f)).Select(f => f.fieldName).ToList();
+                                fieldsToUpdate.Push(new PortUpdate { fieldNames = fieldsWithBehavior, node = edgeNode });
+                            }
+                        }
+                        changed = true;
+                    }
+                }
+            }
+
+            return changed;
+        }
+
+        HashSet<BaseNode> portUpdateHashSet = new HashSet<BaseNode>();
+
+        internal void DisableInternal()
+        {
+            // port containers are initialized in the OnEnable
+            inputPorts.Clear();
+            outputPorts.Clear();
+
+            ExceptionToLog.Call(() => Disable());
+        }
+
+        internal void DestroyInternal() => ExceptionToLog.Call(() => Destroy());
+
+        /// <summary>
+        /// Called only when the node is created, not when instantiated
+        /// </summary>
+        public virtual void OnNodeCreated() => GUID = Guid.NewGuid().ToString();
+
+        public virtual FieldInfo[] GetNodeFields()
+            => GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
+
+        void InitializeInOutDatas()
+        {
+            var fields = GetNodeFields();
+            var methods = GetType().GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
+
+            foreach (var field in fields)
+            {
+                var inputAttribute = field.GetCustomAttribute<InputAttribute>();
+                var outputAttribute = field.GetCustomAttribute<OutputAttribute>();
+                var tooltipAttribute = field.GetCustomAttribute<TooltipAttribute>();
+                var showInInspector = field.GetCustomAttribute<ShowInInspector>();
+                var vertical = field.GetCustomAttribute<VerticalAttribute>();
+                bool isMultiple = false;
+                bool input = false;
+                string name = field.Name;
+                string tooltip = null;
+                bool showAsDrawer = false;
+
+                if (showInInspector != null)
+                    _needsInspector = true;
+
+                if (inputAttribute == null && outputAttribute == null)
+                    continue;
+
+                //check if field is a collection type
+                isMultiple = (inputAttribute != null) ? inputAttribute.allowMultiple : (outputAttribute.allowMultiple);
+                input = inputAttribute != null;
+
+                if (input)
+                    showAsDrawer = inputAttribute.showAsDrawer;
+
+                tooltip = tooltipAttribute?.tooltip;
+
+                if (!String.IsNullOrEmpty(inputAttribute?.name))
+                    name = inputAttribute.name;
+                if (!String.IsNullOrEmpty(outputAttribute?.name))
+                    name = outputAttribute.name;
+
+                // By default we set the behavior to null, if the field have a custom behavior, it will be set in the loop just below
+                nodeFields[field.Name] = new NodeFieldInformation(field, name, input, isMultiple, tooltip, showAsDrawer, vertical != null, null);
+            }
+
+            foreach (var method in methods)
+            {
+                var customPortBehaviorAttribute = method.GetCustomAttribute<CustomPortBehaviorAttribute>();
+                CustomPortBehaviorDelegate behavior = null;
+
+                if (customPortBehaviorAttribute == null)
+                    continue;
+
+                // Check if custom port behavior function is valid
+                try
+                {
+                    var referenceType = typeof(CustomPortBehaviorDelegate);
+                    behavior = (CustomPortBehaviorDelegate)Delegate.CreateDelegate(referenceType, this, method, true);
+                }
+                catch
+                {
+                    Debug.LogError("The function " + method + " cannot be converted to the required delegate format: " + typeof(CustomPortBehaviorDelegate));
+                }
+
+                if (nodeFields.ContainsKey(customPortBehaviorAttribute.fieldName))
+                    nodeFields[customPortBehaviorAttribute.fieldName].behavior = behavior;
+                else
+                    Debug.LogError("Invalid field name for custom port behavior: " + method + ", " + customPortBehaviorAttribute.fieldName);
+            }
+        }
+
+        #endregion
+
+        #region Events and Processing
+
+        public void OnEdgeConnected(SerializableEdge edge)
+        {
+            bool input = edge.inputNode == this;
+            NodePortContainer portCollection = (input) ? (NodePortContainer)inputPorts : outputPorts;
+
+            portCollection.Add(edge);
+
+            UpdateAllPorts();
+
+            onAfterEdgeConnected?.Invoke(edge);
+        }
+
+        protected virtual bool CanResetPort(NodePort port) => true;
+
+        public void OnEdgeDisconnected(SerializableEdge edge)
+        {
+            if (edge == null)
+                return;
+
+            bool input = edge.inputNode == this;
+            NodePortContainer portCollection = (input) ? (NodePortContainer)inputPorts : outputPorts;
+
+            portCollection.Remove(edge);
+
+            // Reset default values of input port:
+            bool haveConnectedEdges = edge.inputNode.inputPorts.Where(p => p.fieldName == edge.inputFieldName).Any(p => p.GetEdges().Count != 0);
+            if (edge.inputNode == this && !haveConnectedEdges && CanResetPort(edge.inputPort))
+                edge.inputPort?.ResetToDefault();
+
+            UpdateAllPorts();
+
+            onAfterEdgeDisconnected?.Invoke(edge);
+        }
+
+        public void OnProcess()
+        {
+            inputPorts.PullDatas();
+
+            ExceptionToLog.Call(() => Process());
+
+            InvokeOnProcessed();
+
+            outputPorts.PushDatas();
+        }
+
+        public void InvokeOnProcessed() => onProcessed?.Invoke();
+
+        /// <summary>
+        /// Called when the node is enabled
+        /// </summary>
+        protected virtual void Enable() { }
+        /// <summary>
+        /// Called when the node is disabled
+        /// </summary>
+        protected virtual void Disable() { }
+        /// <summary>
+        /// Called when the node is removed
+        /// </summary>
+        protected virtual void Destroy() { }
+
+        /// <summary>
+        /// Override this method to implement custom processing
+        /// </summary>
+        protected virtual void Process() { }
+
+        #endregion
+
+        #region API and utils
+
+        /// <summary>
+        /// Add a port
+        /// </summary>
+        /// <param name="input">is input port</param>
+        /// <param name="fieldName">C# field name</param>
+        /// <param name="portData">Data of the port</param>
+        public void AddPort(bool input, string fieldName, PortData portData)
+        {
+            // Fixup port data info if needed:
+            if (portData.displayType == null)
+                portData.displayType = nodeFields[fieldName].info.FieldType;
+
+            if (input)
+                inputPorts.Add(new NodePort(this, fieldName, portData));
+            else
+                outputPorts.Add(new NodePort(this, fieldName, portData));
+        }
+
+        /// <summary>
+        /// Remove a port
+        /// </summary>
+        /// <param name="input">is input port</param>
+        /// <param name="port">the port to delete</param>
+        public void RemovePort(bool input, NodePort port)
+        {
+            if (input)
+                inputPorts.Remove(port);
+            else
+                outputPorts.Remove(port);
+        }
+
+        /// <summary>
+        /// Remove port(s) from field name
+        /// </summary>
+        /// <param name="input">is input</param>
+        /// <param name="fieldName">C# field name</param>
+        public void RemovePort(bool input, string fieldName)
+        {
+            if (input)
+                inputPorts.RemoveAll(p => p.fieldName == fieldName);
+            else
+                outputPorts.RemoveAll(p => p.fieldName == fieldName);
+        }
+
+        /// <summary>
+        /// Get all the nodes connected to the input ports of this node
+        /// </summary>
+        /// <returns>an enumerable of node</returns>
+        public IEnumerable<BaseNode> GetInputNodes()
+        {
+            foreach (var port in inputPorts)
+                foreach (var edge in port.GetEdges())
+                    yield return edge.outputNode;
+        }
+
+        /// <summary>
+        /// Get all the nodes connected to the output ports of this node
+        /// </summary>
+        /// <returns>an enumerable of node</returns>
+        public IEnumerable<BaseNode> GetOutputNodes()
+        {
+            foreach (var port in outputPorts)
+                foreach (var edge in port.GetEdges())
+                    yield return edge.inputNode;
+        }
+
+        /// <summary>
+        /// Return a node matching the condition in the dependencies of the node
+        /// </summary>
+        /// <param name="condition">Condition to choose the node</param>
+        /// <returns>Matched node or null</returns>
+        public BaseNode FindInDependencies(Func<BaseNode, bool> condition)
+        {
+            Stack<BaseNode> dependencies = new Stack<BaseNode>();
+
+            dependencies.Push(this);
+
+            int depth = 0;
+            while (dependencies.Count > 0)
+            {
+                var node = dependencies.Pop();
+
+                // Guard for infinite loop (faster than a HashSet based solution)
+                depth++;
+                if (depth > 2000)
+                    break;
+
+                if (condition(node))
+                    return node;
+
+                foreach (var dep in node.GetInputNodes())
+                    dependencies.Push(dep);
+            }
+            return null;
+        }
+
+        /// <summary>
+        /// Get the port from field name and identifier
+        /// </summary>
+        /// <param name="fieldName">C# field name</param>
+        /// <param name="identifier">Unique port identifier</param>
+        /// <returns></returns>
+        public NodePort GetPort(string fieldName, string identifier)
+        {
+            return inputPorts.Concat(outputPorts).FirstOrDefault(p =>
+            {
+                var bothNull = String.IsNullOrEmpty(identifier) && String.IsNullOrEmpty(p.portData.identifier);
+                return p.fieldName == fieldName && (bothNull || identifier == p.portData.identifier);
+            });
+        }
+
+        /// <summary>
+        /// Return all the ports of the node
+        /// </summary>
+        /// <returns></returns>
+        public IEnumerable<NodePort> GetAllPorts()
+        {
+            foreach (var port in inputPorts)
+                yield return port;
+            foreach (var port in outputPorts)
+                yield return port;
+        }
+
+        /// <summary>
+        /// Return all the connected edges of the node
+        /// </summary>
+        /// <returns></returns>
+        public IEnumerable<SerializableEdge> GetAllEdges()
+        {
+            foreach (var port in GetAllPorts())
+                foreach (var edge in port.GetEdges())
+                    yield return edge;
+        }
+
+        /// <summary>
+        /// Is the port an input
+        /// </summary>
+        /// <param name="fieldName"></param>
+        /// <returns></returns>
+        public bool IsFieldInput(string fieldName) => nodeFields[fieldName].input;
+
+        /// <summary>
+        /// Add a message on the node
+        /// </summary>
+        /// <param name="message"></param>
+        /// <param name="messageType"></param>
+        public void AddMessage(string message, NodeMessageType messageType)
+        {
+            if (messages.Contains(message))
+                return;
+
+            onMessageAdded?.Invoke(message, messageType);
+            messages.Add(message);
+        }
+
+        /// <summary>
+        /// Remove a message on the node
+        /// </summary>
+        /// <param name="message"></param>
+        public void RemoveMessage(string message)
+        {
+            onMessageRemoved?.Invoke(message);
+            messages.Remove(message);
+        }
+
+        /// <summary>
+        /// Remove a message that contains
+        /// </summary>
+        /// <param name="subMessage"></param>
+        public void RemoveMessageContains(string subMessage)
+        {
+            string toRemove = messages.Find(m => m.Contains(subMessage));
+            messages.Remove(toRemove);
+            onMessageRemoved?.Invoke(toRemove);
+        }
+
+        /// <summary>
+        /// Remove all messages on the node
+        /// </summary>
+        public void ClearMessages()
+        {
+            foreach (var message in messages)
+                onMessageRemoved?.Invoke(message);
+            messages.Clear();
+        }
+
+        /// <summary>
+        /// Set the custom name of the node. This is intended to be used by renamable nodes.
+        /// This custom name will be serialized inside the node.
+        /// </summary>
+        /// <param name="customNodeName">New name of the node.</param>
+        public void SetCustomName(string customName) => nodeCustomName = customName;
+
+        /// <summary>
+        /// Get the name of the node. If the node have a custom name (set using the UI by double clicking on the node title) then it will return this name first, otherwise it returns the value of the name field.
+        /// </summary>
+        /// <returns>The name of the node as written in the title</returns>
+        public string GetCustomName() => String.IsNullOrEmpty(nodeCustomName) ? name : nodeCustomName;
+
+        #endregion
+    }
 }
diff --git a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/ExposedParameter.cs b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/ExposedParameter.cs
index 75adaa6e..861ccdad 100644
--- a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/ExposedParameter.cs
+++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/ExposedParameter.cs
@@ -4,9 +4,9 @@
 
 namespace GraphProcessor
 {
-	[Serializable]
-	public class ExposedParameter : ISerializationCallbackReceiver
-	{
+    [Serializable]
+    public class ExposedParameter : ISerializationCallbackReceiver
+    {
         [Serializable]
         public class Settings
         {
@@ -30,43 +30,44 @@ public virtual bool Equals(Settings param)
             public override int GetHashCode() => base.GetHashCode();
         }
 
-		public string				guid; // unique id to keep track of the parameter
-		public string				name;
-		[Obsolete("Use GetValueType()")]
-		public string				type;
-		[Obsolete("Use value instead")]
-		public SerializableObject	serializedValue;
-		public bool					input = true;
+        public string guid; // unique id to keep track of the parameter
+        public string name;
+        [Obsolete("Use GetValueType()")]
+        public string type;
+        [Obsolete("Use value instead")]
+        public SerializableObject serializedValue;
+        public bool input = true;
         [SerializeReference]
-		public Settings             settings;
-		public string shortType => GetValueType()?.Name;
+        public Settings settings;
+        public string shortType => GetValueType()?.Name;
 
         public void Initialize(string name, object value)
         {
-			guid = Guid.NewGuid().ToString(); // Generated once and unique per parameter
+            guid = Guid.NewGuid().ToString(); // Generated once and unique per parameter
             settings = CreateSettings();
             settings.guid = guid;
-			this.name = name;
-			this.value = value;
+            this.name = name;
+            this.value = value;
         }
 
-		void ISerializationCallbackReceiver.OnAfterDeserialize()
-		{
-			// SerializeReference migration step:
+        void ISerializationCallbackReceiver.OnAfterDeserialize()
+        {
+            // SerializeReference migration step:
 #pragma warning disable CS0618
-			if (serializedValue?.value != null) // old serialization system can't serialize null values
-			{
-				value = serializedValue.value;
-				Debug.Log("Migrated: " + serializedValue.value + " | " + serializedValue.serializedName);
-				serializedValue.value = null;
-			}
+            if (serializedValue?.value != null) // old serialization system can't serialize null values
+            {
+                value = serializedValue.value;
+                Debug.Log("Migrated: " + serializedValue.value + " | " + serializedValue.serializedName);
+                serializedValue.value = null;
+            }
 #pragma warning restore CS0618
-		}
+        }
 
-		void ISerializationCallbackReceiver.OnBeforeSerialize() {}
+        void ISerializationCallbackReceiver.OnBeforeSerialize() { }
 
         protected virtual Settings CreateSettings() => new Settings();
 
+        public virtual Type CustomParameterNodeType => null;
         public virtual object value { get; set; }
         public virtual Type GetValueType() => value == null ? typeof(object) : value.GetType();
 
@@ -89,7 +90,7 @@ internal ExposedParameter Migrate()
 #pragma warning restore CS0618
             if (oldType == null || !exposedParameterTypeCache.TryGetValue(oldType, out var newParamType))
                 return null;
-            
+
             var newParam = Activator.CreateInstance(newParamType) as ExposedParameter;
 
             newParam.guid = guid;
@@ -99,7 +100,7 @@ internal ExposedParameter Migrate()
             newParam.settings.guid = guid;
 
             return newParam;
-     
+
         }
 
         public static bool operator ==(ExposedParameter param1, ExposedParameter param2)
@@ -142,7 +143,7 @@ public ExposedParameter Clone()
 
             return clonedParam;
         }
-	}
+    }
 
     // Due to polymorphic constraints with [SerializeReference] we need to explicitly create a class for
     // every parameter type available in the graph (i.e. templating doesn't work)
diff --git a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/NodePort.cs b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/NodePort.cs
index a5cfde55..78e82a80 100644
--- a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/NodePort.cs
+++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/NodePort.cs
@@ -10,168 +10,182 @@
 
 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;
+    /// <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>
+        /// Whether to show a property drawer with this port (only for input)
+        /// </summary>
+		public bool showAsDrawer;
+        /// <summary>
+        /// If the port accept multiple connection
+        /// </summary>
+        public bool acceptMultipleEdges;
+        /// <summary>
+        /// The field the port is proxying if using custombehavior
+        /// </summary>
+        public string proxiedFieldPath;
+        /// <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 IsProxied => !String.IsNullOrEmpty(proxiedFieldPath);
 
         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;
+            return identifier == other.identifier
+                && displayName == other.displayName
+                && displayType == other.displayType
+                && showAsDrawer == other.showAsDrawer
+                && acceptMultipleEdges == other.acceptMultipleEdges
+                && sizeInPixel == other.sizeInPixel
+                && proxiedFieldPath == other.proxiedFieldPath
+                && 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;
-		}
+        public void CopyFrom(PortData other)
+        {
+            identifier = other.identifier;
+            displayName = other.displayName;
+            displayType = other.displayType;
+            showAsDrawer = other.showAsDrawer;
+            acceptMultipleEdges = other.acceptMultipleEdges;
+            sizeInPixel = other.sizeInPixel;
+            proxiedFieldPath = other.proxiedFieldPath;
+            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;
+    /// <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(() => {
@@ -183,214 +197,217 @@ PushDataDelegate CreatePushDataDelegateForEdge(SerializableEdge edge)
 					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 });
+						var conversionMethod = TypeAdapter.GetConversionMethod(outType, inType);
+						Debug.Log("Conversion method: " + conversionMethod.Name);
+						convertedValue = conversionMethod.Invoke(null, new object[]{ outValue });
 					}
 
 					inputField.SetValue(edge.inputNode, convertedValue);
 				});
 #endif
 
-// We keep slow checks inside the editor
+                // 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;
-				}
+                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 conversions");
+                    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());
-		}
-	}
+                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 conversion 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.GetConversionMethod(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 conversion 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 conversion 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 conversion 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());
+        }
+    }
 }
\ No newline at end of file
diff --git a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/ParameterNode.cs b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/ParameterNode.cs
index d86856e2..77b5e7e9 100644
--- a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/ParameterNode.cs
+++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/ParameterNode.cs
@@ -7,113 +7,113 @@
 
 namespace GraphProcessor
 {
-	[System.Serializable]
-	public class ParameterNode : BaseNode
-	{
-		[Input]
-		public object input;
-
-		[Output]
-		public object output;
-
-		public override string name => "Parameter";
-
-		// We serialize the GUID of the exposed parameter in the graph so we can retrieve the true ExposedParameter from the graph
-		[SerializeField, HideInInspector]
-		public string parameterGUID;
-
-		public ExposedParameter parameter { get; private set; }
-
-		public event Action onParameterChanged;
-
-		public ParameterAccessor accessor;
-
-		protected override void Enable()
-		{
-			// load the parameter
-			LoadExposedParameter();
-
-			graph.onExposedParameterModified += OnParamChanged;
-			if (onParameterChanged != null)
-				onParameterChanged?.Invoke();
-		}
-
-		void LoadExposedParameter()
-		{
-			parameter = graph.GetExposedParameterFromGUID(parameterGUID);
-
-			if (parameter == null)
-			{
-				Debug.Log("Property \"" + parameterGUID + "\" Can't be found !");
-
-				// Delete this node as the property can't be found
-				graph.RemoveNode(this);
-				return;
-			}
-
-			output = parameter.value;
-		}
-
-		void OnParamChanged(ExposedParameter modifiedParam)
-		{
-			if (parameter == modifiedParam)
-			{
-				onParameterChanged?.Invoke();
-			}
-		}
-
-		[CustomPortBehavior(nameof(output))]
-		IEnumerable<PortData> GetOutputPort(List<SerializableEdge> edges)
-		{
-			if (accessor == ParameterAccessor.Get)
-			{
-				yield return new PortData
-				{
-					identifier = "output",
-					displayName = "Value",
-					displayType = (parameter == null) ? typeof(object) : parameter.GetValueType(),
-					acceptMultipleEdges = true
-				};
-			}
-		}
-
-		[CustomPortBehavior(nameof(input))]
-		IEnumerable<PortData> GetInputPort(List<SerializableEdge> edges)
-		{
-			if (accessor == ParameterAccessor.Set)
-			{
-				yield return new PortData
-				{
-					identifier = "input",
-					displayName = "Value",
-					displayType = (parameter == null) ? typeof(object) : parameter.GetValueType(),
-				};
-			}
-		}
-
-		protected override void Process()
-		{
+    [System.Serializable]
+    public class ParameterNode : BaseNode
+    {
+        [Input]
+        public object input;
+
+        [Output]
+        public object output;
+
+        public override string name => "Parameter";
+
+        // We serialize the GUID of the exposed parameter in the graph so we can retrieve the true ExposedParameter from the graph
+        [SerializeField, HideInInspector]
+        public string parameterGUID;
+
+        public ExposedParameter parameter { get; private set; }
+
+        public event Action onParameterChanged;
+
+        public ParameterAccessor accessor;
+
+        protected override void Enable()
+        {
+            // load the parameter
+            LoadExposedParameter();
+
+            graph.onExposedParameterModified += OnParamChanged;
+            if (onParameterChanged != null)
+                onParameterChanged?.Invoke();
+        }
+
+        void LoadExposedParameter()
+        {
+            parameter = graph.GetExposedParameterFromGUID(parameterGUID);
+
+            if (parameter == null)
+            {
+                Debug.Log("Property \"" + parameterGUID + "\" Can't be found !");
+
+                // Delete this node as the property can't be found
+                graph.RemoveNode(this);
+                return;
+            }
+
+            output = parameter.value;
+        }
+
+        void OnParamChanged(ExposedParameter modifiedParam)
+        {
+            if (parameter == modifiedParam)
+            {
+                onParameterChanged?.Invoke();
+            }
+        }
+
+        [CustomPortBehavior(nameof(output))]
+        protected virtual IEnumerable<PortData> GetOutputPort(List<SerializableEdge> edges)
+        {
+            if (accessor == ParameterAccessor.Get)
+            {
+                yield return new PortData
+                {
+                    identifier = "output",
+                    displayName = "Value",
+                    displayType = (parameter == null) ? typeof(object) : parameter.GetValueType(),
+                    acceptMultipleEdges = true
+                };
+            }
+        }
+
+        [CustomPortBehavior(nameof(input))]
+        protected virtual IEnumerable<PortData> GetInputPort(List<SerializableEdge> edges)
+        {
+            if (accessor == ParameterAccessor.Set)
+            {
+                yield return new PortData
+                {
+                    identifier = "input",
+                    displayName = "Value",
+                    displayType = (parameter == null) ? typeof(object) : parameter.GetValueType(),
+                };
+            }
+        }
+
+        protected override void Process()
+        {
 #if UNITY_EDITOR // In the editor, an undo/redo can change the parameter instance in the graph, in this case the field in this class will point to the wrong parameter
-			parameter = graph.GetExposedParameterFromGUID(parameterGUID);
+            parameter = graph.GetExposedParameterFromGUID(parameterGUID);
 #endif
 
-			ClearMessages();
-			if (parameter == null)
-			{
-				AddMessage($"Parameter not found: {parameterGUID}", NodeMessageType.Error);
-				return;
-			}
-
-			if (accessor == ParameterAccessor.Get)
-				output = parameter.value;
-			else
-				graph.UpdateExposedParameter(parameter.guid, input);
-		}
-	}
-
-	public enum ParameterAccessor
-	{
-		Get,
-		Set
-	}
+            ClearMessages();
+            if (parameter == null)
+            {
+                AddMessage($"Parameter not found: {parameterGUID}", NodeMessageType.Error);
+                return;
+            }
+
+            if (accessor == ParameterAccessor.Get)
+                output = parameter.value;
+            else
+                graph.UpdateExposedParameter(parameter.guid, input);
+        }
+    }
+
+    public enum ParameterAccessor
+    {
+        Get,
+        Set
+    }
 }
diff --git a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Graph/Attributes.cs b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Graph/Attributes.cs
index effd5610..4dae226b 100644
--- a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Graph/Attributes.cs
+++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Graph/Attributes.cs
@@ -5,242 +5,246 @@
 
 namespace GraphProcessor
 {
-	/// <summary>
-	/// Tell that this field is will generate an input port
-	/// </summary>
-	[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
-	public class InputAttribute : Attribute
-	{
-		public string		name;
-		public bool			allowMultiple = false;
-
-		/// <summary>
-		/// Mark the field as an input port
-		/// </summary>
-		/// <param name="name">display name</param>
-		/// <param name="allowMultiple">is connecting multiple edges allowed</param>
-		public InputAttribute(string name = null, bool allowMultiple = false)
-		{
-			this.name = name;
-			this.allowMultiple = allowMultiple;
-		}
-	}
-
-	/// <summary>
-	/// Tell that this field is will generate an output port
-	/// </summary>
-	[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
-	public class OutputAttribute : Attribute
-	{
-		public string		name;
-		public bool			allowMultiple = true;
-
-		/// <summary>
-		/// Mark the field as an output port
-		/// </summary>
-		/// <param name="name">display name</param>
-		/// <param name="allowMultiple">is connecting multiple edges allowed</param>
-		public OutputAttribute(string name = null, bool allowMultiple = true)
-		{
-			this.name = name;
-			this.allowMultiple = allowMultiple;
-		}
-	}
-
-	/// <summary>
-	/// Creates a vertical port instead of the default horizontal one
-	/// </summary>
-	[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
-	public class VerticalAttribute : Attribute
-	{
-	}
-
-	/// <summary>
-	/// Register the node in the NodeProvider class. The node will also be available in the node creation window.
-	/// </summary>
-	[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
-	public class NodeMenuItemAttribute : Attribute
-	{
-		public string	menuTitle;
-		public Type		onlyCompatibleWithGraph;
-
-		/// <summary>
-		/// Register the node in the NodeProvider class. The node will also be available in the node creation window.
-		/// </summary>
-		/// <param name="menuTitle">Path in the menu, use / as folder separators</param>
-		public NodeMenuItemAttribute(string menuTitle = null, Type onlyCompatibleWithGraph = null)
-		{
-			this.menuTitle = menuTitle;
-			this.onlyCompatibleWithGraph = onlyCompatibleWithGraph;
-		}
-	}
-
-	/// <summary>
-	/// Set a custom drawer for a field. It can then be created using the FieldFactory
-	/// </summary>
-	[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
-	[Obsolete("You can use the standard Unity CustomPropertyDrawer instead.")]
-	public class FieldDrawerAttribute : Attribute
-	{
-		public Type		fieldType;
-
-		/// <summary>
-		/// Register a custom view for a type in the FieldFactory class
-		/// </summary>
-		/// <param name="fieldType"></param>
-		public FieldDrawerAttribute(Type fieldType)
-		{
-			this.fieldType = fieldType;
-		}
-	}
-
-	/// <summary>
-	/// Allow you to customize the input function of a port
-	/// </summary>
-	[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
-	public class CustomPortInputAttribute : Attribute
-	{
-		public string	fieldName;
-		public Type		inputType;
-		public bool		allowCast;
-
-		/// <summary>
-		/// Allow you to customize the input function of a port.
-		/// See CustomPortsNode example in Samples.
-		/// </summary>
-		/// <param name="fieldName">local field of the node</param>
-		/// <param name="inputType">type of input of the port</param>
-		/// <param name="allowCast">if cast is allowed when connecting an edge</param>
-		public CustomPortInputAttribute(string fieldName, Type inputType, bool allowCast = true)
-		{
-			this.fieldName = fieldName;
-			this.inputType = inputType;
-			this.allowCast = allowCast;
-		}
-	}
-
-	/// <summary>
-	/// Allow you to customize the input function of a port
-	/// </summary>
-	[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
-	public class CustomPortOutputAttribute : Attribute
-	{
-		public string	fieldName;
-		public Type		outputType;
-		public bool		allowCast;
-
-		/// <summary>
-		/// Allow you to customize the output function of a port.
-		/// See CustomPortsNode example in Samples.
-		/// </summary>
-		/// <param name="fieldName">local field of the node</param>
-		/// <param name="inputType">type of input of the port</param>
-		/// <param name="allowCast">if cast is allowed when connecting an edge</param>
-		public CustomPortOutputAttribute(string fieldName, Type outputType, bool allowCast = true)
-		{
-			this.fieldName = fieldName;
-			this.outputType = outputType;
-			this.allowCast = allowCast;
-		}
-	}
-
-	/// <summary>
-	/// Allow you to modify the generated port view from a field. Can be used to generate multiple ports from one field.
-	/// </summary>
-	[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
-	public class CustomPortBehaviorAttribute : Attribute
-	{
-		public string		fieldName;
-
-		/// <summary>
-		/// Allow you to modify the generated port view from a field. Can be used to generate multiple ports from one field.
-		/// You must add this attribute on a function of this signature
-		/// <code>
-		/// IEnumerable&lt;PortData&gt; MyCustomPortFunction(List&lt;SerializableEdge&gt; edges);
-		/// </code>
-		/// </summary>
-		/// <param name="fieldName">local node field name</param>
-		public CustomPortBehaviorAttribute(string fieldName)
-		{
-			this.fieldName = fieldName;
-		}
-	}
-
-	/// <summary>
-	/// Allow to bind a method to generate a specific set of ports based on a field type in a node
-	/// </summary>
-	[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
-	public class CustomPortTypeBehavior : Attribute
-	{
-		/// <summary>
-		/// Target type
-		/// </summary>
-		public Type type;
-
-		public CustomPortTypeBehavior(Type type)
-		{
-			this.type = type;
-		}
-	}
-
-	/// <summary>
-	/// Allow you to have a custom view for your stack nodes
-	/// </summary>
-	[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
-	public class CustomStackNodeView : Attribute
-	{
-		public Type	stackNodeType;
-
-		/// <summary>
-		/// Allow you to have a custom view for your stack nodes
-		/// </summary>
-		/// <param name="stackNodeType">The type of the stack node you target</param>
-		public CustomStackNodeView(Type stackNodeType)
-		{
-			this.stackNodeType = stackNodeType;
-		}
-	}
-
-	[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
-	public class VisibleIf : Attribute
-	{
-		public string fieldName;
-		public object value;
-
-		public VisibleIf(string fieldName, object value)
-		{
-			this.fieldName = fieldName;
-			this.value = value;
-		}
-	}
-
-	[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
-	public class ShowInInspector : Attribute
-	{
-		public bool showInNode;
-
-		public ShowInInspector(bool showInNode = false)
-		{
-			this.showInNode = showInNode;
-		}
-	}
-	
-	[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
-	public class ShowAsDrawer : Attribute
-	{
-	}
-	
-	[AttributeUsage(AttributeTargets.Field)]
-	public class SettingAttribute : Attribute
-	{
-		public string name;
-
-		public SettingAttribute(string name = null)
-		{
-			this.name = name;
-		}
-	}
-
-	[AttributeUsage(AttributeTargets.Method)]
-	public class IsCompatibleWithGraph : Attribute {}
+    /// <summary>
+    /// Tell that this field is will generate an input port
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
+    public class InputAttribute : Attribute
+    {
+        public string name;
+        public bool allowMultiple = false;
+        public bool showAsDrawer = false;
+
+        /// <summary>
+        /// Mark the field as an input port
+        /// </summary>
+        /// <param name="name">display name</param>
+        /// <param name="allowMultiple">is connecting multiple edges allowed</param>
+        public InputAttribute(string name = null, bool allowMultiple = false, bool showAsDrawer = false)
+        {
+            this.name = name;
+            this.allowMultiple = allowMultiple;
+            this.showAsDrawer = showAsDrawer;
+        }
+    }
+
+    /// <summary>
+    /// Tell that this field is will generate an output port
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
+    public class OutputAttribute : Attribute
+    {
+        public string name;
+        public bool allowMultiple = true;
+
+        /// <summary>
+        /// Mark the field as an output port
+        /// </summary>
+        /// <param name="name">display name</param>
+        /// <param name="allowMultiple">is connecting multiple edges allowed</param>
+        public OutputAttribute(string name = null, bool allowMultiple = true)
+        {
+            this.name = name;
+            this.allowMultiple = allowMultiple;
+        }
+    }
+
+    /// <summary>
+    /// Creates a vertical port instead of the default horizontal one
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
+    public class VerticalAttribute : Attribute
+    {
+    }
+
+    /// <summary>
+    /// Register the node in the NodeProvider class. The node will also be available in the node creation window.
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
+    public class NodeMenuItemAttribute : Attribute
+    {
+        public string menuTitle;
+        public Type onlyCompatibleWithGraph;
+
+        /// <summary>
+        /// Register the node in the NodeProvider class. The node will also be available in the node creation window.
+        /// </summary>
+        /// <param name="menuTitle">Path in the menu, use / as folder separators</param>
+        public NodeMenuItemAttribute(string menuTitle = null, Type onlyCompatibleWithGraph = null)
+        {
+            this.menuTitle = menuTitle;
+            this.onlyCompatibleWithGraph = onlyCompatibleWithGraph;
+        }
+    }
+
+    /// <summary>
+    /// Set a custom drawer for a field. It can then be created using the FieldFactory
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
+    [Obsolete("You can use the standard Unity CustomPropertyDrawer instead.")]
+    public class FieldDrawerAttribute : Attribute
+    {
+        public Type fieldType;
+
+        /// <summary>
+        /// Register a custom view for a type in the FieldFactory class
+        /// </summary>
+        /// <param name="fieldType"></param>
+        public FieldDrawerAttribute(Type fieldType)
+        {
+            this.fieldType = fieldType;
+        }
+    }
+
+    /// <summary>
+    /// Allow you to customize the input function of a port
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
+    public class CustomPortInputAttribute : Attribute
+    {
+        public string fieldName;
+        public Type inputType;
+        public bool allowCast;
+
+        /// <summary>
+        /// Allow you to customize the input function of a port.
+        /// See CustomPortsNode example in Samples.
+        /// </summary>
+        /// <param name="fieldName">local field of the node</param>
+        /// <param name="inputType">type of input of the port</param>
+        /// <param name="allowCast">if cast is allowed when connecting an edge</param>
+        public CustomPortInputAttribute(string fieldName, Type inputType, bool allowCast = true)
+        {
+            this.fieldName = fieldName;
+            this.inputType = inputType;
+            this.allowCast = allowCast;
+        }
+    }
+
+    /// <summary>
+    /// Allow you to customize the input function of a port
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
+    public class CustomPortOutputAttribute : Attribute
+    {
+        public string fieldName;
+        public Type outputType;
+        public bool allowCast;
+
+        /// <summary>
+        /// Allow you to customize the output function of a port.
+        /// See CustomPortsNode example in Samples.
+        /// </summary>
+        /// <param name="fieldName">local field of the node</param>
+        /// <param name="inputType">type of input of the port</param>
+        /// <param name="allowCast">if cast is allowed when connecting an edge</param>
+        public CustomPortOutputAttribute(string fieldName, Type outputType, bool allowCast = true)
+        {
+            this.fieldName = fieldName;
+            this.outputType = outputType;
+            this.allowCast = allowCast;
+        }
+    }
+
+    /// <summary>
+    /// Allow you to modify the generated port view from a field. Can be used to generate multiple ports from one field.
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
+    public class CustomPortBehaviorAttribute : Attribute
+    {
+        public string fieldName;
+
+        /// <summary>
+        /// Allow you to modify the generated port view from a field. Can be used to generate multiple ports from one field.
+        /// You must add this attribute on a function of this signature
+        /// <code>
+        /// IEnumerable&lt;PortData&gt; MyCustomPortFunction(List&lt;SerializableEdge&gt; edges);
+        /// </code>
+        /// </summary>
+        /// <param name="fieldName">local node field name</param>
+        public CustomPortBehaviorAttribute(string fieldName)
+        {
+            this.fieldName = fieldName;
+        }
+    }
+
+    /// <summary>
+    /// Allow to bind a method to generate a specific set of ports based on a field type in a node
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
+    public class CustomPortTypeBehavior : Attribute
+    {
+        /// <summary>
+        /// Target type
+        /// </summary>
+        public Type type;
+
+        public CustomPortTypeBehavior(Type type)
+        {
+            this.type = type;
+        }
+    }
+
+    /// <summary>
+    /// Allow you to have a custom view for your stack nodes
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
+    public class CustomStackNodeView : Attribute
+    {
+        public Type stackNodeType;
+
+        /// <summary>
+        /// Allow you to have a custom view for your stack nodes
+        /// </summary>
+        /// <param name="stackNodeType">The type of the stack node you target</param>
+        public CustomStackNodeView(Type stackNodeType)
+        {
+            this.stackNodeType = stackNodeType;
+        }
+    }
+
+    [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
+    public class VisibleIf : Attribute
+    {
+        public string fieldName;
+        public object value;
+
+        public VisibleIf(string fieldName, object value)
+        {
+            this.fieldName = fieldName;
+            this.value = value;
+        }
+    }
+
+    [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
+    public class ShowInInspector : Attribute
+    {
+        public bool showInNode;
+
+        public ShowInInspector(bool showInNode = false)
+        {
+            this.showInNode = showInNode;
+        }
+    }
+
+    // [Obsolete("ShowAsDrawer attribute is deprecated. Please use the InputAttribute showAsDrawer field.")]
+
+    [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
+    public class ShowAsDrawer : Attribute
+    {
+    }
+
+    [AttributeUsage(AttributeTargets.Field)]
+    public class SettingAttribute : Attribute
+    {
+        public string name;
+
+        public SettingAttribute(string name = null)
+        {
+            this.name = name;
+        }
+    }
+
+    [AttributeUsage(AttributeTargets.Method)]
+    public class IsCompatibleWithGraph : Attribute { }
 }
\ No newline at end of file
diff --git a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Graph/BaseGraph.cs b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Graph/BaseGraph.cs
index f7da2f09..46f660f6 100644
--- a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Graph/BaseGraph.cs
+++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Graph/BaseGraph.cs
@@ -823,27 +823,30 @@ void DestroyBrokenGraphElements()
 		/// <summary>
 		/// Tell if two types can be connected in the context of a graph
 		/// </summary>
-		/// <param name="t1"></param>
-		/// <param name="t2"></param>
+		/// <param name="from"></param>
+		/// <param name="to"></param>
 		/// <returns></returns>
-		public static bool TypesAreConnectable(Type t1, Type t2)
+		public static bool TypesAreConnectable(Type from, Type to) // NOTE: Extend this later for adding conversion nodes
 		{
-			if (t1 == null || t2 == null)
+			if (from == null || to == null)
 				return false;
 
-			if (TypeAdapter.AreIncompatible(t1, t2))
+			if (TypeAdapter.AreIncompatible(from, to))
 				return false;
 
 			//Check if there is custom adapters for this assignation
-			if (CustomPortIO.IsAssignable(t1, t2))
+			if (CustomPortIO.IsAssignable(from, to))
 				return true;
 
 			//Check for type assignability
-			if (t2.IsReallyAssignableFrom(t1))
+			if (to.IsReallyAssignableFrom(from))
 				return true;
 
-			// User defined type convertions
-			if (TypeAdapter.AreAssignable(t1, t2))
+			// User defined type conversions
+			if (TypeAdapter.AreAssignable(from, to))
+				return true;
+
+			if (ConversionNodeAdapter.AreAssignable(from, to))
 				return true;
 
 			return false;
diff --git a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Processing/ConversionNodeAdapter.cs b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Processing/ConversionNodeAdapter.cs
new file mode 100644
index 00000000..d8b76d41
--- /dev/null
+++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Processing/ConversionNodeAdapter.cs
@@ -0,0 +1,66 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using UnityEngine;
+
+namespace GraphProcessor
+{
+	[AttributeUsage(AttributeTargets.Class)]
+	public class ConverterNodeAttribute : Attribute
+	{
+		public Type from, to;
+
+		public ConverterNodeAttribute(Type from, Type to)
+		{
+			this.from = from;
+			this.to = to;
+		}
+	}
+
+	public interface IConversionNode
+	{
+		public string GetConversionInput();
+		public string GetConversionOutput();
+	}
+
+	public static class ConversionNodeAdapter
+	{
+		private static bool conversionsLoaded = false;
+
+		static readonly Dictionary<(Type from, Type to), Type> adapters = new Dictionary<(Type from, Type to), Type>();
+
+		static void LoadAllAdapters()
+		{
+			foreach (Type currType in AppDomain.CurrentDomain.GetAllTypes())
+			{
+				var conversionAttrib = currType.GetCustomAttribute<ConverterNodeAttribute>();
+				if (conversionAttrib != null)
+				{
+					Debug.Assert(typeof(IConversionNode).IsAssignableFrom(currType),
+						"Class marked with ConverterNode attribute must implement the IConversionNode interface");
+					Debug.Assert(typeof(BaseNode).IsAssignableFrom(currType), "Class marked with ConverterNode attribute must inherit from BaseNode");
+					
+					adapters.Add((conversionAttrib.from, conversionAttrib.to), currType);
+				}
+			}
+
+			conversionsLoaded = true;
+		}
+
+		public static bool AreAssignable(Type from, Type to)
+		{
+			if (!conversionsLoaded)
+				LoadAllAdapters();
+
+			return adapters.ContainsKey((from, to));
+		}
+
+		public static Type GetConversionNode(Type from, Type to)
+		{
+			if (!conversionsLoaded)
+				LoadAllAdapters();
+
+			return adapters.TryGetValue((from, to), out Type nodeType) ? nodeType : null;
+		}
+	}
+}
\ No newline at end of file
diff --git a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Processing/ConversionNodeAdapter.cs.meta b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Processing/ConversionNodeAdapter.cs.meta
new file mode 100644
index 00000000..f8bb0d29
--- /dev/null
+++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Processing/ConversionNodeAdapter.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 604ecd0dea834136834bf1737ef7a91f
+timeCreated: 1637143540
\ No newline at end of file
diff --git a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Processing/CustomPortIO.cs b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Processing/CustomPortIO.cs
index ba5db93b..4db839f2 100644
--- a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Processing/CustomPortIO.cs
+++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Processing/CustomPortIO.cs
@@ -79,15 +79,15 @@ static void LoadCustomPortMethods()
 					deleg = Expression.Lambda< CustomPortIODelegate >(ex, p1, p2, p3).Compile();
 #endif
 
-					if (deleg == null)
+					string fieldName = (portInputAttr == null) ? portOutputAttr.fieldName : portInputAttr.fieldName;
+					Type customType = (portInputAttr == null) ? portOutputAttr.outputType : portInputAttr.inputType;
+					var field = type.GetField(fieldName, bindingFlags);
+					if (field == null)
 					{
-						Debug.LogWarning("Can't use custom IO port function " + method + ": The method have to respect this format: " + typeof(CustomPortIODelegate));
+						Debug.LogWarning("Can't use custom IO port function '" + method.Name + "' of class '" + type.Name + "': No field named " + fieldName + " found");
 						continue ;
 					}
-
-					string fieldName = (portInputAttr == null) ? portOutputAttr.fieldName : portInputAttr.fieldName;
-					Type customType = (portInputAttr == null) ? portOutputAttr.outputType : portInputAttr.inputType;
-					Type fieldType = type.GetField(fieldName, bindingFlags).FieldType;
+					Type fieldType = field.FieldType;
 
 					AddCustomIOMethod(type, fieldName, deleg);
 
diff --git a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Processing/TypeAdapter.cs b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Processing/TypeAdapter.cs
index 33592e6e..7108adce 100644
--- a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Processing/TypeAdapter.cs
+++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Processing/TypeAdapter.cs
@@ -22,13 +22,18 @@ public abstract class ITypeAdapter // TODO: turn this back into an interface whe
         public virtual IEnumerable<(Type, Type)> GetIncompatibleTypes() { yield break; }
     }
 
+    public class ValueTypeConversion : ITypeAdapter
+    {
+	    public static float ConvertIntToFloat(int from) => from;
+    }
+
     public static class TypeAdapter
     {
         static Dictionary< (Type from, Type to), Func<object, object> > adapters = new Dictionary< (Type, Type), Func<object, object> >();
         static Dictionary< (Type from, Type to), MethodInfo > adapterMethods = new Dictionary< (Type, Type), MethodInfo >();
         static List< (Type from, Type to)> incompatibleTypes = new List<( Type from, Type to) >();
 
-        [System.NonSerialized]
+        [NonSerialized]
         static bool adaptersLoaded = false;
 
 #if !ENABLE_IL2CPP
@@ -67,12 +72,12 @@ static void LoadAllAdapters()
                     {
                         if (method.GetParameters().Length != 1)
                         {
-                            Debug.LogError($"Ignoring convertion method {method} because it does not have exactly one parameter");
+                            Debug.LogError($"Ignoring conversion method {method} because it does not have exactly one parameter");
                             continue;
                         }
                         if (method.ReturnType == typeof(void))
                         {
-                            Debug.LogError($"Ignoring convertion method {method} because it does not returns anything");
+                            Debug.LogError($"Ignoring conversion method {method} because it does not returns anything");
                             continue;
                         }
                         Type from = method.GetParameters()[0].ParameterType;
@@ -81,7 +86,7 @@ static void LoadAllAdapters()
                         try {
 
 #if ENABLE_IL2CPP
-                            // IL2CPP doesn't suport calling generic functions via reflection (AOT can't generate templated code)
+                            // IL2CPP doesn't support calling generic functions via reflection (AOT can't generate templated code)
                             Func<object, object> r = (object param) => { return (object)method.Invoke(null, new object[]{ param }); };
 #else
                             MethodInfo genericHelper = typeof(TypeAdapter).GetMethod("ConvertTypeMethodHelper", 
@@ -97,19 +102,21 @@ static void LoadAllAdapters()
                             adapters.Add((method.GetParameters()[0].ParameterType, method.ReturnType), r);
                             adapterMethods.Add((method.GetParameters()[0].ParameterType, method.ReturnType), method);
                         } catch (Exception e) {
-                            Debug.LogError($"Failed to load the type convertion method: {method}\n{e}");
+                            Debug.LogError($"Failed to load the type conversion method: {method}\n{e}");
                         }
                     }
                 }
             }
 
-            // Ensure that the dictionary contains all the convertions in both ways
+            /*
+            // Ensure that the dictionary contains all the conversions in both ways
             // ex: float to vector but no vector to float
             foreach (var kp in adapters)
             {
                 if (!adapters.ContainsKey((kp.Key.to, kp.Key.from)))
-                    Debug.LogError($"Missing convertion method. There is one for {kp.Key.from} to {kp.Key.to} but not for {kp.Key.to} to {kp.Key.from}");
+                    Debug.LogError($"Missing conversion method. There is one for {kp.Key.from} to {kp.Key.to} but not for {kp.Key.to} to {kp.Key.from}");
             }
+            */
 
             adaptersLoaded = true;
         }
@@ -132,7 +139,7 @@ public static bool AreAssignable(Type from, Type to)
             return adapters.ContainsKey((from, to));
         }
 
-        public static MethodInfo GetConvertionMethod(Type from, Type to) => adapterMethods[(from, to)];
+        public static MethodInfo GetConversionMethod(Type from, Type to) => adapterMethods[(from, to)];
 
         public static object Convert(object from, Type targetType)
         {
diff --git a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/FieldInfoExtension.cs b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/FieldInfoExtension.cs
new file mode 100644
index 00000000..888225e1
--- /dev/null
+++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/FieldInfoExtension.cs
@@ -0,0 +1,72 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+
+namespace GraphProcessor
+{
+    public static class FieldInfoExtension
+    {
+        public static bool HasCustomAttribute<T>(this FieldInfo fieldInfo)
+        {
+            return Attribute.IsDefined(fieldInfo, typeof(T));
+        }
+
+        public static bool HasCustomAttribute(this FieldInfo fieldInfo, Type type)
+        {
+            return Attribute.IsDefined(fieldInfo, type);
+        }
+
+        public static object GetValueAt(this IList<FieldInfo> list, object startingValue, int index)
+        {
+            object currentValue = startingValue;
+            for (int i = 0; i < list.Count; i++)
+            {
+                currentValue = list[i].GetValue(currentValue);
+                if (i == index) break;
+            }
+            return currentValue;
+        }
+
+        public static object GetFinalValue(this IList<FieldInfo> list, object startingValue)
+        {
+            object currentValue = startingValue;
+            for (int i = 0; i < list.Count; i++)
+            {
+                currentValue = list[i].GetValue(currentValue);
+            }
+            return currentValue;
+        }
+
+        public static void SetValue(this IList<FieldInfo> list, object startingValue, object finalValue)
+        {
+            object currentValue = startingValue;
+            for (int i = 0; i < list.Count; i++)
+            {
+                if (i + 1 == list.Count)
+                {
+                    list[i].SetValue(currentValue, finalValue);
+                    break;
+                }
+
+                currentValue = list[i].GetValue(currentValue);
+            }
+        }
+
+        public static string GetPath(this IList<FieldInfo> list)
+        {
+            string path = "";
+            for (int i = 0; i < list.Count; i++)
+            {
+                if (i > 0) path += ".";
+                path += list[i].Name;
+            }
+            return path;
+        }
+
+        public static bool IsValid(this IList<FieldInfo> list)
+        {
+            return list.Any(x => x == null);
+        }
+    }
+}
\ No newline at end of file
diff --git a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/FieldInfoExtension.cs.meta b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/FieldInfoExtension.cs.meta
new file mode 100644
index 00000000..cd26b8f4
--- /dev/null
+++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/FieldInfoExtension.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 6fbc650ecb8ca02faa22f7a9e5d9b4a0
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/SerializedEdgeExtension.cs b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/SerializedEdgeExtension.cs
new file mode 100644
index 00000000..4892283a
--- /dev/null
+++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/SerializedEdgeExtension.cs
@@ -0,0 +1,28 @@
+using System.Collections.Generic;
+
+namespace GraphProcessor
+{
+    public static class SerializedEdgeExtension
+    {
+        public static IList<SerializableEdge> GetNonRelayEdges(this IList<SerializableEdge> edges)
+        {
+            List<SerializableEdge> nonrelayEdges = new List<SerializableEdge>();
+            foreach (var edge in edges)
+            {
+                if (edge.outputNode is RelayNode)
+                {
+                    RelayNode relay = edge.outputNode as RelayNode;
+                    foreach (var relayEdge in relay.GetNonRelayEdges())
+                    {
+                        nonrelayEdges.Add(relayEdge);
+                    }
+                }
+                else
+                {
+                    nonrelayEdges.Add(edge);
+                }
+            }
+            return nonrelayEdges;
+        }
+    }
+}
diff --git a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/SerializedEdgeExtension.cs.meta b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/SerializedEdgeExtension.cs.meta
new file mode 100644
index 00000000..13097a7b
--- /dev/null
+++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/SerializedEdgeExtension.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 1b6986467dd851f8b8153d3bf6b93994
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Packages/manifest.json b/Packages/manifest.json
index ca1a11a9..4ffdacc6 100644
--- a/Packages/manifest.json
+++ b/Packages/manifest.json
@@ -4,13 +4,13 @@
     "com.unity.2d.tilemap": "1.0.0",
     "com.unity.ext.nunit": "1.0.6",
     "com.unity.ide.rider": "3.0.7",
-    "com.unity.ide.visualstudio": "2.0.9",
-    "com.unity.ide.vscode": "1.2.3",
-    "com.unity.test-framework": "1.1.26",
+    "com.unity.ide.visualstudio": "2.0.12",
+    "com.unity.ide.vscode": "1.2.4",
+    "com.unity.test-framework": "1.1.29",
     "com.unity.textmeshpro": "3.0.6",
-    "com.unity.timeline": "1.6.0-pre.5",
+    "com.unity.timeline": "1.6.3",
     "com.unity.ugui": "1.0.0",
-    "com.unity.xr.legacyinputhelpers": "2.1.7",
+    "com.unity.xr.legacyinputhelpers": "2.1.8",
     "com.unity.modules.ai": "1.0.0",
     "com.unity.modules.androidjni": "1.0.0",
     "com.unity.modules.animation": "1.0.0",
diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json
index a27bb1bf..5db5cfa6 100644
--- a/Packages/packages-lock.json
+++ b/Packages/packages-lock.json
@@ -29,7 +29,7 @@
       "url": "https://packages.unity.com"
     },
     "com.unity.ide.visualstudio": {
-      "version": "2.0.9",
+      "version": "2.0.12",
       "depth": 0,
       "source": "registry",
       "dependencies": {
@@ -38,14 +38,14 @@
       "url": "https://packages.unity.com"
     },
     "com.unity.ide.vscode": {
-      "version": "1.2.3",
+      "version": "1.2.4",
       "depth": 0,
       "source": "registry",
       "dependencies": {},
       "url": "https://packages.unity.com"
     },
     "com.unity.test-framework": {
-      "version": "1.1.26",
+      "version": "1.1.29",
       "depth": 0,
       "source": "registry",
       "dependencies": {
@@ -65,7 +65,7 @@
       "url": "https://packages.unity.com"
     },
     "com.unity.timeline": {
-      "version": "1.6.0-pre.5",
+      "version": "1.6.3",
       "depth": 0,
       "source": "registry",
       "dependencies": {
@@ -86,7 +86,7 @@
       }
     },
     "com.unity.xr.legacyinputhelpers": {
-      "version": "2.1.7",
+      "version": "2.1.8",
       "depth": 0,
       "source": "registry",
       "dependencies": {
diff --git a/ProjectSettings/ProjectVersion.txt b/ProjectSettings/ProjectVersion.txt
index 5ab5db38..bbae793c 100644
--- a/ProjectSettings/ProjectVersion.txt
+++ b/ProjectSettings/ProjectVersion.txt
@@ -1,2 +1,2 @@
-m_EditorVersion: 2021.2.0b3
-m_EditorVersionWithRevision: 2021.2.0b3 (40188ccec128)
+m_EditorVersion: 2021.2.7f1
+m_EditorVersionWithRevision: 2021.2.7f1 (6bd9e232123f)
diff --git a/README.md b/README.md
index 9e9e3556..60c58475 100644
--- a/README.md
+++ b/README.md
@@ -89,17 +89,17 @@ Join the [NodeGraphProcessor Discord server](https://discord.gg/XuMd3Z5Rym)!
 - Graph processor which execute node's logic with a dependency order
 - [Documented C# API to add new nodes / graphs](https://github.com/alelievr/NodeGraphProcessor/wiki/Node-scripting-API)
 - Exposed parameters that can be set per-asset to customize the graph processing from scripts or the inspector
-- Parameter set mode, you can now output data from thegraph using exposed parameters. Their values will be updated when the graph is processed
+- Parameter set mode, you can now output data from the graph using exposed parameters. Their values will be updated when the graph is processed
 - Search window to create new nodes
 - Colored groups
 - Node messages (small message with it's icon beside the node)
-- Stack Nodes
+- Stack nodes
 - Relay nodes
 - Display additional settings in the inspector
 - Node creation menu on edge drop
 - Simplified edge connection compared to default GraphView (ShaderGraph and VFX Graph)
 - Multiple graph window workflow (copy/paste)
-- Vertical Ports
+- Vertical ports
 - Sticky notes (requires Unity 2020.1)
 - Renamable nodes