From 36a1e8891b91770464e20609b9fe06c76e3379ac Mon Sep 17 00:00:00 2001 From: David Pilles Date: Fri, 8 Jul 2022 14:15:20 -0700 Subject: [PATCH 01/10] Fix DynamicPortList reordering operating on old data in NodeEditorGUILayout --- Scripts/Editor/NodeEditorGUILayout.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Scripts/Editor/NodeEditorGUILayout.cs b/Scripts/Editor/NodeEditorGUILayout.cs index 8c93cb2..a0e3358 100644 --- a/Scripts/Editor/NodeEditorGUILayout.cs +++ b/Scripts/Editor/NodeEditorGUILayout.cs @@ -394,6 +394,7 @@ namespace XNodeEditor { }; list.onReorderCallback = (ReorderableList rl) => { + serializedObject.Update(); bool hasRect = false; bool hasNewRect = false; Rect rect = Rect.zero; From b23e769c3b613d107c8bf9ff067e2230edb6883c Mon Sep 17 00:00:00 2001 From: LoomDoom Date: Sat, 27 Aug 2022 08:52:22 +0200 Subject: [PATCH 02/10] added cases to handle Copy and Rename for scenegraphs correctly --- Scripts/Editor/NodeGraphEditor.cs | 2 +- Scripts/Editor/RenamePopup.cs | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Scripts/Editor/NodeGraphEditor.cs b/Scripts/Editor/NodeGraphEditor.cs index a304c2c..0f5b8db 100644 --- a/Scripts/Editor/NodeGraphEditor.cs +++ b/Scripts/Editor/NodeGraphEditor.cs @@ -221,7 +221,7 @@ namespace XNodeEditor { XNode.Node node = target.CopyNode(original); Undo.RegisterCreatedObjectUndo(node, "Duplicate Node"); node.name = original.name; - AssetDatabase.AddObjectToAsset(node, target); + if (!string.IsNullOrEmpty(AssetDatabase.GetAssetPath(target))) AssetDatabase.AddObjectToAsset(node, target); if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); return node; } diff --git a/Scripts/Editor/RenamePopup.cs b/Scripts/Editor/RenamePopup.cs index a43837f..ca1ee15 100644 --- a/Scripts/Editor/RenamePopup.cs +++ b/Scripts/Editor/RenamePopup.cs @@ -53,8 +53,10 @@ namespace XNodeEditor { if (GUILayout.Button("Revert to default") || (e.isKey && e.keyCode == KeyCode.Return)) { target.name = NodeEditorUtilities.NodeDefaultName(target.GetType()); NodeEditor.GetEditor((XNode.Node)target, NodeEditorWindow.current).OnRename(); - AssetDatabase.SetMainObject((target as XNode.Node).graph, AssetDatabase.GetAssetPath(target)); - AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(target)); + if (!string.IsNullOrEmpty(AssetDatabase.GetAssetPath(target))) { + AssetDatabase.SetMainObject((target as XNode.Node).graph, AssetDatabase.GetAssetPath(target)); + AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(target)); + } Close(); target.TriggerOnValidate(); } @@ -64,8 +66,10 @@ namespace XNodeEditor { if (GUILayout.Button("Apply") || (e.isKey && e.keyCode == KeyCode.Return)) { target.name = input; NodeEditor.GetEditor((XNode.Node)target, NodeEditorWindow.current).OnRename(); - AssetDatabase.SetMainObject((target as XNode.Node).graph, AssetDatabase.GetAssetPath(target)); - AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(target)); + if (!string.IsNullOrEmpty(AssetDatabase.GetAssetPath(target))) { + AssetDatabase.SetMainObject((target as XNode.Node).graph, AssetDatabase.GetAssetPath(target)); + AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(target)); + } Close(); target.TriggerOnValidate(); } From d19ea40e383299c2f97afb9b8f1bbe0988272c61 Mon Sep 17 00:00:00 2001 From: Raistlin Wolfe Date: Mon, 12 Sep 2022 18:22:03 -0600 Subject: [PATCH 03/10] Update Node.cs --- Scripts/Node.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Scripts/Node.cs b/Scripts/Node.cs index 704e99d..b705627 100644 --- a/Scripts/Node.cs +++ b/Scripts/Node.cs @@ -52,7 +52,13 @@ namespace XNode { /// Allow connections where output value type is assignable from input value type (eg. Object --> ScriptableObject) InheritedInverse, /// Allow connections where output value type is assignable from input value or input value type is assignable from output value type - InheritedAny + InheritedAny, + /// Allow connections where input value type is castable from output value type. + Castable, + /// Allow connections where output value type is castable from input value type. + CastableInverse, + /// Allow connections where input value type is castable from output value type or output value type is castable from input value type. + CastableAny } #region Obsolete From 7473083b8440d0e2d87215d45d631b6f51e10ca6 Mon Sep 17 00:00:00 2001 From: Raistlin Wolfe Date: Mon, 12 Sep 2022 18:25:14 -0600 Subject: [PATCH 04/10] Update NodePort.cs --- Scripts/NodePort.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Scripts/NodePort.cs b/Scripts/NodePort.cs index 6bcc638..14c3b7d 100644 --- a/Scripts/NodePort.cs +++ b/Scripts/NodePort.cs @@ -279,6 +279,9 @@ namespace XNode { if (output.typeConstraint == XNode.Node.TypeConstraint.Strict && input.ValueType != output.ValueType) return false; if (output.typeConstraint == XNode.Node.TypeConstraint.InheritedInverse && !output.ValueType.IsAssignableFrom(input.ValueType)) return false; if (output.typeConstraint == XNode.Node.TypeConstraint.InheritedAny && !input.ValueType.IsAssignableFrom(output.ValueType) && !output.ValueType.IsAssignableFrom(input.ValueType)) return false; + if (output.typeConstraint == XNode.Node.TypeConstraint.Castable && !input.ValueType.IsCastableFrom(output.ValueType)) return false; + if (output.typeConstraint == XNode.Node.TypeConstraint.CastableInverse && !output.ValueType.IsCastableFrom(input.ValueType)) return false; + if (output.typeConstraint == XNode.Node.TypeConstraint.CastableAny && !input.ValueType.IsCastableFrom(output.ValueType) && !output.ValueType.IsCastableFrom(input.ValueType)) return false; // Success return true; } From 5e3c5c0012025ca09ccc9363cb20318519f20957 Mon Sep 17 00:00:00 2001 From: Raistlin Wolfe Date: Mon, 12 Sep 2022 18:36:45 -0600 Subject: [PATCH 05/10] Create TypeExtensions.cs Provided Implementation for IsCastableFrom --- Scripts/TypeExtensions.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 Scripts/TypeExtensions.cs diff --git a/Scripts/TypeExtensions.cs b/Scripts/TypeExtensions.cs new file mode 100644 index 0000000..5f79664 --- /dev/null +++ b/Scripts/TypeExtensions.cs @@ -0,0 +1,15 @@ +using System.Linq; +using System.Reflection; + +public static class TypeExtensions +{ + public static bool IsCastableFrom(this Type to, Type from) + { + if ( to.IsAssignableFrom ( from ) ) + return true; + return from.GetMethods ( BindingFlags.Public | BindingFlags.Static ).Any ( m => + { + return m.ReturnType == to && ( m.Name == "op_Implicit" || m.Name == "op_Explicit" ); + } ); + } +} From ba988f7f28aaf4a1d97e6ebea76ebed658fdbc9a Mon Sep 17 00:00:00 2001 From: Raistlin Wolfe Date: Mon, 12 Sep 2022 18:43:28 -0600 Subject: [PATCH 06/10] Update TypeExtensions.cs Added xml comment on IsCastableFrom --- Scripts/TypeExtensions.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Scripts/TypeExtensions.cs b/Scripts/TypeExtensions.cs index 5f79664..ae49c31 100644 --- a/Scripts/TypeExtensions.cs +++ b/Scripts/TypeExtensions.cs @@ -3,6 +3,7 @@ using System.Reflection; public static class TypeExtensions { + /// Determines whether an instance of a specified type can be assigned to a variable of the current type. public static bool IsCastableFrom(this Type to, Type from) { if ( to.IsAssignableFrom ( from ) ) From fa62765daa1aa30d5b624d75506129073889a082 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Wed, 21 Sep 2022 11:01:49 +0200 Subject: [PATCH 07/10] Revert "Merge pull request #353 from LupusInferni315/master" This reverts commit 1b64a96d40a0b33b497259bcd680c73c3122c85b, reversing changes made to 75078edd20c6e28d9350b5aacc2a118977262192. --- Scripts/Node.cs | 8 +------- Scripts/NodePort.cs | 3 --- Scripts/TypeExtensions.cs | 16 ---------------- 3 files changed, 1 insertion(+), 26 deletions(-) delete mode 100644 Scripts/TypeExtensions.cs diff --git a/Scripts/Node.cs b/Scripts/Node.cs index b705627..704e99d 100644 --- a/Scripts/Node.cs +++ b/Scripts/Node.cs @@ -52,13 +52,7 @@ namespace XNode { /// Allow connections where output value type is assignable from input value type (eg. Object --> ScriptableObject) InheritedInverse, /// Allow connections where output value type is assignable from input value or input value type is assignable from output value type - InheritedAny, - /// Allow connections where input value type is castable from output value type. - Castable, - /// Allow connections where output value type is castable from input value type. - CastableInverse, - /// Allow connections where input value type is castable from output value type or output value type is castable from input value type. - CastableAny + InheritedAny } #region Obsolete diff --git a/Scripts/NodePort.cs b/Scripts/NodePort.cs index 14c3b7d..6bcc638 100644 --- a/Scripts/NodePort.cs +++ b/Scripts/NodePort.cs @@ -279,9 +279,6 @@ namespace XNode { if (output.typeConstraint == XNode.Node.TypeConstraint.Strict && input.ValueType != output.ValueType) return false; if (output.typeConstraint == XNode.Node.TypeConstraint.InheritedInverse && !output.ValueType.IsAssignableFrom(input.ValueType)) return false; if (output.typeConstraint == XNode.Node.TypeConstraint.InheritedAny && !input.ValueType.IsAssignableFrom(output.ValueType) && !output.ValueType.IsAssignableFrom(input.ValueType)) return false; - if (output.typeConstraint == XNode.Node.TypeConstraint.Castable && !input.ValueType.IsCastableFrom(output.ValueType)) return false; - if (output.typeConstraint == XNode.Node.TypeConstraint.CastableInverse && !output.ValueType.IsCastableFrom(input.ValueType)) return false; - if (output.typeConstraint == XNode.Node.TypeConstraint.CastableAny && !input.ValueType.IsCastableFrom(output.ValueType) && !output.ValueType.IsCastableFrom(input.ValueType)) return false; // Success return true; } diff --git a/Scripts/TypeExtensions.cs b/Scripts/TypeExtensions.cs deleted file mode 100644 index ae49c31..0000000 --- a/Scripts/TypeExtensions.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Linq; -using System.Reflection; - -public static class TypeExtensions -{ - /// Determines whether an instance of a specified type can be assigned to a variable of the current type. - public static bool IsCastableFrom(this Type to, Type from) - { - if ( to.IsAssignableFrom ( from ) ) - return true; - return from.GetMethods ( BindingFlags.Public | BindingFlags.Static ).Any ( m => - { - return m.ReturnType == to && ( m.Name == "op_Implicit" || m.Name == "op_Explicit" ); - } ); - } -} From 0e7e4aa2545621b02f249357a6db68a22f74e61a Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Wed, 21 Sep 2022 13:53:28 +0200 Subject: [PATCH 08/10] Added public overrides for custom NodePort CanConnect conditions in GraphEditor --- Scripts/Editor/NodeEditorAction.cs | 11 ++++------- Scripts/Editor/NodeGraphEditor.cs | 7 +++++++ Scripts/NodeDataCache.cs | 19 +++++++++++-------- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/Scripts/Editor/NodeEditorAction.cs b/Scripts/Editor/NodeEditorAction.cs index 5d8d32b..a9147f2 100644 --- a/Scripts/Editor/NodeEditorAction.cs +++ b/Scripts/Editor/NodeEditorAction.cs @@ -221,7 +221,7 @@ namespace XNodeEditor { //Port drag release if (IsDraggingPort) { // If connection is valid, save it - if (draggedOutputTarget != null && draggedOutput.CanConnectTo(draggedOutputTarget)) { + if (draggedOutputTarget != null && graphEditor.CanConnect(draggedOutput, draggedOutputTarget)) { XNode.Node node = draggedOutputTarget.node; if (graph.nodes.Count != 0) draggedOutput.Connect(draggedOutputTarget); @@ -553,12 +553,9 @@ namespace XNodeEditor { public void AutoConnect(XNode.Node node) { if (autoConnectOutput == null) return; - // Find input port of same type - XNode.NodePort inputPort = node.Ports.FirstOrDefault(x => x.IsInput && x.ValueType == autoConnectOutput.ValueType); - // Fallback to input port - if (inputPort == null) inputPort = node.Ports.FirstOrDefault(x => x.IsInput); - // Autoconnect if connection is compatible - if (inputPort != null && inputPort.CanConnectTo(autoConnectOutput)) autoConnectOutput.Connect(inputPort); + // Find compatible input port + XNode.NodePort inputPort = node.Ports.FirstOrDefault(x => x.IsInput && graphEditor.CanConnect(autoConnectOutput, x)); + if (inputPort != null) autoConnectOutput.Connect(inputPort); // Save changes EditorUtility.SetDirty(graph); diff --git a/Scripts/Editor/NodeGraphEditor.cs b/Scripts/Editor/NodeGraphEditor.cs index 0f5b8db..191d10b 100644 --- a/Scripts/Editor/NodeGraphEditor.cs +++ b/Scripts/Editor/NodeGraphEditor.cs @@ -59,6 +59,13 @@ namespace XNodeEditor { return 0; } + /// + /// Called before connecting two ports in the graph view to see if the output port is compatible with the input port + /// + public virtual bool CanConnect(XNode.NodePort output, XNode.NodePort input) { + return output.CanConnectTo(input); + } + /// /// Add items for the context menu when right-clicking this node. /// Override to add custom menu items. diff --git a/Scripts/NodeDataCache.cs b/Scripts/NodeDataCache.cs index f865ab2..3bc8478 100644 --- a/Scripts/NodeDataCache.cs +++ b/Scripts/NodeDataCache.cs @@ -70,20 +70,23 @@ namespace XNode { for (int i = 0; i < reconnectConnections.Count; i++) { NodePort connection = reconnectConnections[i]; if (connection == null) continue; + // CAVEAT: Ports connected under special conditions defined in graphEditor.CanConnect overrides will not auto-connect. + // To fix this, this code would need to be moved to an editor script and call graphEditor.CanConnect instead of port.CanConnectTo. + // This is only a problem in the rare edge case where user is using non-standard CanConnect overrides and changes port type of an already connected port if (port.CanConnectTo(connection)) port.Connect(connection); } } ports.Add(staticPort.fieldName, port); } } - + // Finally, make sure dynamic list port settings correspond to the settings of their "backing port" foreach (NodePort listPort in dynamicListPorts) { // At this point we know that ports here are dynamic list ports // which have passed name/"backing port" checks, ergo we can proceed more safely. string backingPortName = listPort.fieldName.Split(' ')[0]; NodePort backingPort = staticPorts[backingPortName]; - + // Update port constraints. Creating a new port instead will break the editor, mandating the need for setters. listPort.ValueType = GetBackingValueType(backingPort.ValueType); listPort.direction = backingPort.direction; @@ -95,7 +98,7 @@ namespace XNode { /// /// Extracts the underlying types from arrays and lists, the only collections for dynamic port lists /// currently supported. If the given type is not applicable (i.e. if the dynamic list port was not - /// defined as an array or a list), returns the given type itself. + /// defined as an array or a list), returns the given type itself. /// private static System.Type GetBackingValueType(System.Type portValType) { if (portValType.HasElementType) { @@ -114,10 +117,10 @@ namespace XNode { // Thus, we need to check for attributes... (but at least we don't need to look at all fields this time) string[] fieldNameParts = port.fieldName.Split(' '); if (fieldNameParts.Length != 2) return false; - + FieldInfo backingPortInfo = port.node.GetType().GetField(fieldNameParts[0]); if (backingPortInfo == null) return false; - + object[] attribs = backingPortInfo.GetCustomAttributes(true); return attribs.Any(x => { Node.InputAttribute inputAttribute = x as Node.InputAttribute; @@ -126,7 +129,7 @@ namespace XNode { outputAttribute != null && outputAttribute.dynamicPortList; }); } - + /// Cache node types private static void BuildCache() { portDataCache = new PortDataCache(); @@ -196,10 +199,10 @@ namespace XNode { portDataCache[nodeType].Add(new NodePort(fieldInfo[i])); } - if(formerlySerializedAsAttribute != null) { + if (formerlySerializedAsAttribute != null) { if (formerlySerializedAsCache == null) formerlySerializedAsCache = new Dictionary>(); if (!formerlySerializedAsCache.ContainsKey(nodeType)) formerlySerializedAsCache.Add(nodeType, new Dictionary()); - + if (formerlySerializedAsCache[nodeType].ContainsKey(formerlySerializedAsAttribute.oldName)) Debug.LogError("Another FormerlySerializedAs with value '" + formerlySerializedAsAttribute.oldName + "' already exist on this node."); else formerlySerializedAsCache[nodeType].Add(formerlySerializedAsAttribute.oldName, fieldInfo[i].Name); } From 71a6ba87a4c858af9e3e37a615c480b6bae4bb44 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Wed, 28 Sep 2022 16:13:35 +0200 Subject: [PATCH 09/10] Added KAJed82's Odin fork to readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 3eadd3e..67a0426 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ Support xNode on [Ko-fi](https://ko-fi.com/Z8Z5DYWA) or [Patreon](https://www.patreon.com/thorbrigsted) +For full Odin support, consider using [KAJed82's fork](https://github.com/KAJed82/xNode) + ### xNode Thinking of developing a node-based plugin? Then this is for you. You can download it as an archive and unpack to a new unity project, or connect it as git submodule. From 9ca798957d5616d40ef59be6cd05de22881de3a8 Mon Sep 17 00:00:00 2001 From: Carlos Gonzalez Diaz Date: Tue, 4 Oct 2022 13:19:44 +0100 Subject: [PATCH 10/10] Update NodeGraphEditor.cs - Handled null nodes coming out of AddNode() both in CreateNode() and AddContextMenuItems() --- Scripts/Editor/NodeGraphEditor.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Scripts/Editor/NodeGraphEditor.cs b/Scripts/Editor/NodeGraphEditor.cs index 191d10b..60b0cdb 100644 --- a/Scripts/Editor/NodeGraphEditor.cs +++ b/Scripts/Editor/NodeGraphEditor.cs @@ -103,7 +103,7 @@ namespace XNodeEditor { if (disallowed) menu.AddItem(new GUIContent(path), false, null); else menu.AddItem(new GUIContent(path), false, () => { XNode.Node node = CreateNode(type, pos); - NodeEditorWindow.current.AutoConnect(node); + if (node != null) NodeEditorWindow.current.AutoConnect(node); // handle null nodes to avoid nullref exceptions }); } menu.AddSeparator(""); @@ -213,6 +213,7 @@ namespace XNodeEditor { public virtual XNode.Node CreateNode(Type type, Vector2 position) { Undo.RecordObject(target, "Create Node"); XNode.Node node = target.AddNode(type); + if (node == null) return null; // handle null nodes to avoid nullref exceptions Undo.RegisterCreatedObjectUndo(node, "Create Node"); node.position = position; if (node.name == null || node.name.Trim() == "") node.name = NodeEditorUtilities.NodeDefaultName(type);