diff --git a/Scripts/Attributes/PortTypeOverrideAttribute.cs b/Scripts/Attributes/PortTypeOverrideAttribute.cs
new file mode 100644
index 0000000..316ca2d
--- /dev/null
+++ b/Scripts/Attributes/PortTypeOverrideAttribute.cs
@@ -0,0 +1,12 @@
+using System;
+/// Overrides the ValueType of the Port, to have a ValueType different from the type of its serializable field
+/// Especially useful in Dynamic Port Lists to create Value-Port Pairs with different type.
+[AttributeUsage(AttributeTargets.Field)]
+public class PortTypeOverrideAttribute : Attribute {
+ public Type type;
+ /// Overrides the ValueType of the Port
+ /// ValueType of the Port
+ public PortTypeOverrideAttribute(Type type) {
+ this.type = type;
+ }
+}
diff --git a/Scripts/Attributes/PortTypeOverrideAttribute.cs.meta b/Scripts/Attributes/PortTypeOverrideAttribute.cs.meta
new file mode 100644
index 0000000..a587759
--- /dev/null
+++ b/Scripts/Attributes/PortTypeOverrideAttribute.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 1410c1437e863ab4fac7a7428aaca35b
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Scripts/Editor/NodeEditorGUI.cs b/Scripts/Editor/NodeEditorGUI.cs
index 35b2e2a..8f3a574 100755
--- a/Scripts/Editor/NodeEditorGUI.cs
+++ b/Scripts/Editor/NodeEditorGUI.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
@@ -12,8 +12,8 @@ namespace XNodeEditor {
/// Contains GUI methods
public partial class NodeEditorWindow {
public NodeGraphEditor graphEditor;
- private List selectionCache;
- private List culledNodes;
+ private readonly HashSet selectionCache = new();
+ private readonly HashSet culledNodes = new();
/// 19 if docked, 22 if not
private int topPadding { get { return isDocked() ? 19 : 22; } }
/// Executed after all other window GUI. Useful if Zoom is ruining your day. Automatically resets after being run.
@@ -399,8 +399,13 @@ namespace XNodeEditor {
private void DrawNodes() {
Event e = Event.current;
+
if (e.type == EventType.Layout) {
- selectionCache = new List(Selection.objects);
+ selectionCache.Clear();
+ var objs = Selection.objects;
+ selectionCache.EnsureCapacity(objs.Length);
+ foreach (var obj in objs)
+ selectionCache.Add(obj);
}
System.Reflection.MethodInfo onValidate = null;
@@ -432,7 +437,7 @@ namespace XNodeEditor {
List removeEntries = new List();
- if (e.type == EventType.Layout) culledNodes = new List();
+ if (e.type == EventType.Layout) culledNodes.Clear();
for (int n = 0; n < graph.nodes.Count; n++) {
// Skip null nodes. The user could be in the process of renaming scripts, so removing them at this point is not advisable.
if (graph.nodes[n] == null) continue;
diff --git a/Scripts/Node.cs b/Scripts/Node.cs
index 2cfd569..bad468d 100644
--- a/Scripts/Node.cs
+++ b/Scripts/Node.cs
@@ -399,6 +399,8 @@ namespace XNode {
public void OnBeforeSerialize() {
keys.Clear();
values.Clear();
+ keys.Capacity = this.Count;
+ values.Capacity = this.Count;
foreach (KeyValuePair pair in this) {
keys.Add(pair.Key);
values.Add(pair.Value);
@@ -407,6 +409,9 @@ namespace XNode {
public void OnAfterDeserialize() {
this.Clear();
+#if UNITY_2021_3_OR_NEWER
+ this.EnsureCapacity(keys.Count);
+#endif
if (keys.Count != values.Count)
throw new System.Exception("there are " + keys.Count + " keys and " + values.Count + " values after deserialization. Make sure that both key and value types are serializable.");
diff --git a/Scripts/NodeDataCache.cs b/Scripts/NodeDataCache.cs
index f28de98..2283fd1 100644
--- a/Scripts/NodeDataCache.cs
+++ b/Scripts/NodeDataCache.cs
@@ -8,13 +8,24 @@ namespace XNode {
public static class NodeDataCache {
private static PortDataCache portDataCache;
private static Dictionary> formerlySerializedAsCache;
+ private static Dictionary typeQualifiedNameCache;
private static bool Initialized { get { return portDataCache != null; } }
+ public static string GetTypeQualifiedName(System.Type type) {
+ if(typeQualifiedNameCache == null) typeQualifiedNameCache = new Dictionary();
+
+ string name;
+ if (!typeQualifiedNameCache.TryGetValue(type, out name)) {
+ name = type.AssemblyQualifiedName;
+ typeQualifiedNameCache.Add(type, name);
+ }
+ return name;
+ }
+
/// Update static ports and dynamic ports managed by DynamicPortLists to reflect class fields.
public static void UpdatePorts(Node node, Dictionary ports) {
if (!Initialized) BuildCache();
- Dictionary staticPorts = new Dictionary();
Dictionary> removedPorts = new Dictionary>();
System.Type nodeType = node.GetType();
@@ -23,17 +34,15 @@ namespace XNode {
List dynamicListPorts = new List();
- List typePortCache;
- if (portDataCache.TryGetValue(nodeType, out typePortCache)) {
- for (int i = 0; i < typePortCache.Count; i++) {
- staticPorts.Add(typePortCache[i].fieldName, portDataCache[nodeType][i]);
- }
- }
+ Dictionary staticPorts;
+ if (!portDataCache.TryGetValue(nodeType, out staticPorts)) {
+ staticPorts = new Dictionary();
+ }
// Cleanup port dict - Remove nonexisting static ports - update static port types
// AND update dynamic ports (albeit only those in lists) too, in order to enforce proper serialisation.
// Loop through current node ports
- foreach (NodePort port in ports.Values.ToList()) {
+ foreach (NodePort port in ports.Values.ToArray()) {
// If port still exists, check it it has been changed
NodePort staticPort;
if (staticPorts.TryGetValue(port.fieldName, out staticPort)) {
@@ -84,7 +93,7 @@ namespace XNode {
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];
+ string backingPortName = listPort.fieldName.Substring(0, listPort.fieldName.IndexOf(' '));
NodePort backingPort = staticPorts[backingPortName];
// Update port constraints. Creating a new port instead will break the editor, mandating the need for setters.
@@ -147,6 +156,7 @@ namespace XNode {
// The following assemblies, and sub-assemblies (eg. UnityEngine.UI) are skipped
case "UnityEditor":
case "UnityEngine":
+ case "Unity":
case "System":
case "mscorlib":
case "Microsoft":
@@ -154,11 +164,11 @@ namespace XNode {
default:
try
{
- nodeTypes.AddRange(assembly.GetTypes().Where(t => !t.IsAbstract && baseType.IsAssignableFrom(t)).ToArray());
- }
+ nodeTypes.AddRange(assembly.GetTypes().Where(t => !t.IsAbstract && baseType.IsAssignableFrom(t)).ToArray());
+ }
catch (System.Exception ex)
{
- Debug.LogError("Catched exception when building " + assemblyName + " caches");
+ Debug.LogError($"Catched exception when building {assemblyName} caches");
Debug.LogException(ex);
}
break;
@@ -203,8 +213,9 @@ namespace XNode {
if (inputAttrib != null && outputAttrib != null) Debug.LogError("Field " + fieldInfo[i].Name + " of type " + nodeType.FullName + " cannot be both input and output.");
else {
- if (!portDataCache.ContainsKey(nodeType)) portDataCache.Add(nodeType, new List());
- portDataCache[nodeType].Add(new NodePort(fieldInfo[i]));
+ if (!portDataCache.ContainsKey(nodeType)) portDataCache.Add(nodeType, new Dictionary());
+ NodePort port = new NodePort(fieldInfo[i]);
+ portDataCache[nodeType].Add(port.fieldName, port);
}
if (formerlySerializedAsAttribute != null) {
@@ -218,30 +229,6 @@ namespace XNode {
}
[System.Serializable]
- private class PortDataCache : Dictionary>, ISerializationCallbackReceiver {
- [SerializeField] private List keys = new List();
- [SerializeField] private List> values = new List>();
-
- // save the dictionary to lists
- public void OnBeforeSerialize() {
- keys.Clear();
- values.Clear();
- foreach (var pair in this) {
- keys.Add(pair.Key);
- values.Add(pair.Value);
- }
- }
-
- // load dictionary from lists
- public void OnAfterDeserialize() {
- this.Clear();
-
- if (keys.Count != values.Count)
- throw new System.Exception(string.Format("there are {0} keys and {1} values after deserialization. Make sure that both key and value types are serializable.", keys.Count, values.Count));
-
- for (int i = 0; i < keys.Count; i++)
- this.Add(keys[i], values[i]);
- }
- }
+ private class PortDataCache : Dictionary> { }
}
}
diff --git a/Scripts/NodeGraph.cs b/Scripts/NodeGraph.cs
index d928f94..347bc8e 100644
--- a/Scripts/NodeGraph.cs
+++ b/Scripts/NodeGraph.cs
@@ -47,7 +47,7 @@ namespace XNode {
public virtual void Clear() {
if (Application.isPlaying) {
for (int i = 0; i < nodes.Count; i++) {
- Destroy(nodes[i]);
+ if (nodes[i] != null) Destroy(nodes[i]);
}
}
nodes.Clear();
diff --git a/Scripts/NodePort.cs b/Scripts/NodePort.cs
index 8e0114f..ea5287f 100644
--- a/Scripts/NodePort.cs
+++ b/Scripts/NodePort.cs
@@ -47,8 +47,9 @@ namespace XNode {
return valueType;
}
set {
+ if (valueType == value) return;
valueType = value;
- if (value != null) _typeQualifiedName = value.AssemblyQualifiedName;
+ if (value != null) _typeQualifiedName = NodeDataCache.GetTypeQualifiedName(value);
}
}
private Type valueType;
@@ -78,6 +79,10 @@ namespace XNode {
_connectionType = (attribs[i] as Node.OutputAttribute).connectionType;
_typeConstraint = (attribs[i] as Node.OutputAttribute).typeConstraint;
}
+ // Override ValueType of the Port
+ if(attribs[i] is PortTypeOverrideAttribute) {
+ ValueType = (attribs[i] as PortTypeOverrideAttribute).type;
+ }
}
}