Skip to content

Commit aad78c4

Browse files
committed
Added control over which node is available in which graph, either based on type of graph settings
1 parent bcfb086 commit aad78c4

File tree

6 files changed

+217
-53
lines changed

6 files changed

+217
-53
lines changed

Assets/Examples/BasicExample.asset

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,16 +49,17 @@ MonoBehaviour:
4949
- type: PortConnectionTests, Assembly-CSharp, Version=0.0.0.0, Culture=neutral,
5050
PublicKeyToken=null
5151
jsonDatas: '{"GUID":"d0c72054-62bf-409c-a49f-4230da5526d5","computeOrder":16,"position":{"serializedVersion":"2","x":1248.0,"y":637.0,"width":127.0,"height":245.0},"expanded":true,"debug":false,"nodeLock":false,"padding":0.0}'
52-
- type: ColorNode, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
53-
jsonDatas: '{"GUID":"9df78625-a020-4242-b1f2-00dab72837f0","computeOrder":17,"position":{"serializedVersion":"2","x":672.1738891601563,"y":765.478271484375,"width":200.0,"height":97.0},"expanded":true,"debug":false,"nodeLock":false,"color":{"r":0.0,"g":0.0,"b":0.0,"a":0.0}}'
5452
- type: PortConnectionTests, Assembly-CSharp, Version=0.0.0.0, Culture=neutral,
5553
PublicKeyToken=null
56-
jsonDatas: '{"GUID":"0bb81f44-19ac-4b15-a7a6-f0f319a6d5c7","computeOrder":18,"position":{"serializedVersion":"2","x":1584.0,"y":639.0,"width":178.0,"height":264.0},"expanded":true,"debug":false,"nodeLock":false,"padding":0.0}'
54+
jsonDatas: '{"GUID":"0bb81f44-19ac-4b15-a7a6-f0f319a6d5c7","computeOrder":17,"position":{"serializedVersion":"2","x":1584.0,"y":639.0,"width":178.0,"height":264.0},"expanded":true,"debug":false,"nodeLock":false,"padding":0.0}'
5755
- type: DrawerFieldTestNode, Assembly-CSharp, Version=0.0.0.0, Culture=neutral,
5856
PublicKeyToken=null
59-
jsonDatas: '{"GUID":"a93ff68f-3bcb-4234-815f-f44501daf144","computeOrder":19,"position":{"serializedVersion":"2","x":716.8695678710938,"y":886.3478393554688,"width":369.0000305175781,"height":501.0},"expanded":false,"debug":false,"nodeLock":false,"vector4":{"x":0.0,"y":0.0,"z":0.0,"w":0.0},"vector3":{"x":0.0,"y":0.0,"z":0.0},"vector2":{"x":0.0,"y":0.0},"floatInput":0.0,"intInput":0,"intInput2":0,"stringInput":"Hello","color":{"r":0.0,"g":0.0,"b":0.0,"a":0.0},"gameObject":{"instanceID":0},"animationCurve":{"serializedVersion":"2","m_Curve":[],"m_PreInfinity":2,"m_PostInfinity":2,"m_RotationOrder":4},"rigidbody":{"instanceID":0},"layerMask":{"serializedVersion":"2","m_Bits":0}}'
57+
jsonDatas: '{"GUID":"a93ff68f-3bcb-4234-815f-f44501daf144","computeOrder":18,"position":{"serializedVersion":"2","x":728.0,"y":893.0,"width":146.0,"height":341.0},"expanded":true,"debug":false,"nodeLock":false,"vector4":{"x":0.0,"y":0.0,"z":0.0,"w":0.0},"vector3":{"x":0.0,"y":0.0,"z":0.0},"vector2":{"x":0.0,"y":0.0},"floatInput":0.0,"intInput":0,"intInput2":0,"stringInput":"Hello","color":{"r":0.0,"g":0.0,"b":0.0,"a":0.0},"gameObject":{"instanceID":0},"animationCurve":{"serializedVersion":"2","m_Curve":[],"m_PreInfinity":2,"m_PostInfinity":2,"m_RotationOrder":4},"rigidbody":{"instanceID":0},"layerMask":{"serializedVersion":"2","m_Bits":0}}'
6058
- type: InspectorNode, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
61-
jsonDatas: '{"GUID":"fc76049e-5e1f-422a-a991-b02e5ed5ee67","computeOrder":20,"position":{"serializedVersion":"2","x":1024.9176025390625,"y":766.064697265625,"width":100.0,"height":100.0},"expanded":false,"debug":false,"nodeLock":false,"input":0.0,"output":0.0,"additionalSettings":false,"additionalParam":""}'
59+
jsonDatas: '{"GUID":"fc76049e-5e1f-422a-a991-b02e5ed5ee67","computeOrder":19,"position":{"serializedVersion":"2","x":1024.9176025390625,"y":766.064697265625,"width":100.0,"height":100.0},"expanded":false,"debug":false,"nodeLock":false,"input":0.0,"output":0.0,"additionalSettings":false,"additionalParam":""}'
60+
- type: DrawerFieldTestNode, Assembly-CSharp, Version=0.0.0.0, Culture=neutral,
61+
PublicKeyToken=null
62+
jsonDatas: '{"GUID":"ac9a5d83-a834-41cb-89cf-eb5239cc0868","computeOrder":20,"position":{"serializedVersion":"2","x":1210.0,"y":995.4347534179688,"width":146.0,"height":341.0},"expanded":false,"debug":false,"nodeLock":false,"vector4":{"x":0.0,"y":0.0,"z":0.0,"w":0.0},"vector3":{"x":0.0,"y":0.0,"z":0.0},"vector2":{"x":0.0,"y":0.0},"floatInput":0.0,"intInput":0,"intInput2":0,"stringInput":"","color":{"r":0.0,"g":0.0,"b":0.0,"a":0.0},"gameObject":{"instanceID":0},"animationCurve":{"serializedVersion":"2","m_Curve":[],"m_PreInfinity":2,"m_PostInfinity":2,"m_RotationOrder":4},"rigidbody":{"instanceID":0},"layerMask":{"serializedVersion":"2","m_Bits":0}}'
6263
edges:
6364
- GUID: 04cee6c7-b233-40e1-b41a-31f6093f1482
6465
owner: {fileID: 11400000}
@@ -263,7 +264,7 @@ MonoBehaviour:
263264
height: 101
264265
title: New Sticky Note
265266
content: Write your text here
266-
position: {x: -688, y: -349, z: 0}
267+
position: {x: -416, y: -575, z: 0}
267268
scale: {x: 1.15, y: 1.15, z: 1}
268269
references:
269270
version: 1

Assets/Examples/Editor/02_CustomContextMenu/CustomContextMenuGraphView.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ public override void BuildContextualMenu(ContextualMenuPopulateEvent evt)
1717
{
1818
var mousePos = (evt.currentTarget as VisualElement).ChangeCoordinatesTo(contentViewContainer, evt.localMousePosition);
1919
Vector2 nodePosition = mousePos;
20-
evt.menu.AppendAction("Create/" + nodeMenuItem.Key,
21-
(e) => CreateNodeOfType(nodeMenuItem.Value, nodePosition),
20+
evt.menu.AppendAction("Create/" + nodeMenuItem.path,
21+
(e) => CreateNodeOfType(nodeMenuItem.type, nodePosition),
2222
DropdownMenuAction.AlwaysEnabled
2323
);
2424
}

Assets/com.alelievr.NodeGraphProcessor/Editor/Utils/NodeProvider.cs

Lines changed: 184 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -21,45 +21,168 @@ public struct PortDescription
2121
public string portDisplayName;
2222
}
2323

24-
static Dictionary< Type, Type > nodeViewPerType = new Dictionary< Type, Type >();
25-
static Dictionary< string, Type > nodePerMenuTitle = new Dictionary< string, Type >();
2624
static Dictionary< Type, MonoScript > nodeViewScripts = new Dictionary< Type, MonoScript >();
2725
static Dictionary< Type, MonoScript > nodeScripts = new Dictionary< Type, MonoScript >();
28-
static List< Type > slotTypes = new List< Type >();
29-
static List< PortDescription > nodeCreatePortDescription = new List<PortDescription>();
26+
static Dictionary< Type, Type > nodeViewPerType = new Dictionary< Type, Type >();
27+
28+
public class NodeDescriptions
29+
{
30+
public Dictionary< string, Type > nodePerMenuTitle = new Dictionary< string, Type >();
31+
public List< Type > slotTypes = new List< Type >();
32+
public List< PortDescription > nodeCreatePortDescription = new List<PortDescription>();
33+
}
34+
35+
public struct NodeSpecificToGraph
36+
{
37+
public Type nodeType;
38+
public List<MethodInfo> isCompatibleWithGraph;
39+
public Type compatibleWithGraphType;
40+
}
41+
42+
static Dictionary<BaseGraph, NodeDescriptions> specificNodeDescriptions = new Dictionary<BaseGraph, NodeDescriptions>();
43+
static List<NodeSpecificToGraph> specificNodes = new List<NodeSpecificToGraph>();
44+
45+
static NodeDescriptions genericNodes = new NodeDescriptions();
3046

3147
static NodeProvider()
48+
{
49+
BuildScriptCache();
50+
BuildGenericNodeCache();
51+
}
52+
53+
public static void LoadGraph(BaseGraph graph)
54+
{
55+
// Clear old graph data in case there was some
56+
specificNodeDescriptions.Remove(graph);
57+
var descriptions = new NodeDescriptions();
58+
specificNodeDescriptions.Add(graph, descriptions);
59+
60+
var graphType = graph.GetType();
61+
foreach (var nodeInfo in specificNodes)
62+
{
63+
bool compatible = nodeInfo.compatibleWithGraphType == null || nodeInfo.compatibleWithGraphType == graphType;
64+
65+
if (nodeInfo.isCompatibleWithGraph != null)
66+
{
67+
foreach (var method in nodeInfo.isCompatibleWithGraph)
68+
compatible &= (bool)method?.Invoke(null, new object[]{ graph });
69+
}
70+
71+
if (compatible)
72+
BuildCacheForNode(nodeInfo.nodeType, descriptions, graph);
73+
}
74+
}
75+
76+
public static void UnloadGraph(BaseGraph graph)
77+
{
78+
specificNodeDescriptions.Remove(graph);
79+
}
80+
81+
static void BuildGenericNodeCache()
3282
{
3383
foreach (var nodeType in TypeCache.GetTypesDerivedFrom<BaseNode>())
3484
{
35-
if (nodeType.IsAbstract)
85+
if (!IsNodeAccessibleFromMenu(nodeType))
3686
continue;
3787

38-
AddNodeType(nodeType);
88+
if (IsNodeSpecificToGraph(nodeType))
89+
continue;
90+
91+
BuildCacheForNode(nodeType, genericNodes);
92+
}
93+
}
94+
95+
static void BuildCacheForNode(Type nodeType, NodeDescriptions targetDescription, BaseGraph graph = null)
96+
{
97+
var attrs = nodeType.GetCustomAttributes(typeof(NodeMenuItemAttribute), false) as NodeMenuItemAttribute[];
98+
99+
if (attrs != null && attrs.Length > 0)
100+
{
101+
foreach (var attr in attrs)
102+
targetDescription.nodePerMenuTitle[attr.menuTitle] = nodeType;
103+
}
104+
105+
foreach (var field in nodeType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
106+
{
107+
if (field.GetCustomAttribute<HideInInspector>() == null && field.GetCustomAttributes().Any(c => c is InputAttribute || c is OutputAttribute))
108+
targetDescription.slotTypes.Add(field.FieldType);
109+
}
110+
111+
ProvideNodePortCreationDescription(nodeType, targetDescription, graph);
112+
}
113+
114+
static bool IsNodeAccessibleFromMenu(Type nodeType)
115+
{
116+
if (nodeType.IsAbstract)
117+
return false;
118+
119+
return nodeType.GetCustomAttributes<NodeMenuItemAttribute>().Count() > 0;
120+
}
121+
122+
// Check if node has anything that depends on the graph type or settings
123+
static bool IsNodeSpecificToGraph(Type nodeType)
124+
{
125+
var isCompatibleWithGraphMethods = nodeType.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy).Where(m => m.GetCustomAttribute<IsCompatibleWithGraph>() != null);
126+
var nodeMenuAttributes = nodeType.GetCustomAttributes<NodeMenuItemAttribute>();
39127

40-
foreach (var field in nodeType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
128+
List<Type> compatibleGraphTypes = nodeMenuAttributes.Where(n => n.onlyCompatibleWithGraph != null).Select(a => a.onlyCompatibleWithGraph).ToList();
129+
130+
List<MethodInfo> compatibleMethods = new List<MethodInfo>();
131+
foreach (var method in isCompatibleWithGraphMethods)
132+
{
133+
// Check if the method is static and have the correct prototype
134+
var p = method.GetParameters();
135+
if (method.ReturnType != typeof(bool) || p.Count() != 1 || p[0].ParameterType != typeof(BaseGraph))
136+
Debug.LogError($"The function '{method.Name}' marked with the IsCompatibleWithGraph attribute either doesn't return a boolean or doesn't take one parameter of BaseGraph type.");
137+
else
138+
compatibleMethods.Add(method);
139+
}
140+
141+
if (compatibleMethods.Count > 0 || compatibleGraphTypes.Count > 0)
142+
{
143+
// We still need to add the element in specificNode even without specific graph
144+
if (compatibleGraphTypes.Count == 0)
145+
compatibleGraphTypes.Add(null);
146+
147+
foreach (var graphType in compatibleGraphTypes)
41148
{
42-
if (field.GetCustomAttribute<HideInInspector>() == null && field.GetCustomAttributes().Any(c => c is InputAttribute || c is OutputAttribute))
43-
slotTypes.Add(field.FieldType);
149+
specificNodes.Add(new NodeSpecificToGraph{
150+
nodeType = nodeType,
151+
isCompatibleWithGraph = compatibleMethods,
152+
compatibleWithGraphType = graphType
153+
});
44154
}
155+
return true;
156+
}
157+
return false;
158+
}
159+
160+
static void BuildScriptCache()
161+
{
162+
foreach (var nodeType in TypeCache.GetTypesDerivedFrom<BaseNode>())
163+
{
164+
if (!IsNodeAccessibleFromMenu(nodeType))
165+
continue;
45166

46-
ProvideNodePortCreationDescription(nodeType);
167+
AddNodeScriptAsset(nodeType);
47168
}
48169

49170
foreach (var nodeViewType in TypeCache.GetTypesDerivedFrom<BaseNodeView>())
50171
{
51172
if (!nodeViewType.IsAbstract)
52-
AddNodeViewType(nodeViewType);
173+
AddNodeViewScriptAsset(nodeViewType);
53174
}
54175
}
55176

56-
static void ProvideNodePortCreationDescription(Type nodeType)
177+
static FieldInfo SetGraph = typeof(BaseNode).GetField("graph", BindingFlags.NonPublic | BindingFlags.Instance);
178+
static void ProvideNodePortCreationDescription(Type nodeType, NodeDescriptions targetDescription, BaseGraph graph = null)
57179
{
58180
var node = Activator.CreateInstance(nodeType) as BaseNode;
59181
try {
182+
SetGraph.SetValue(node, graph);
60183
node.InitializePorts();
61184
node.UpdateAllPorts();
62-
} catch (Exception) {}
185+
} catch (Exception) { }
63186

64187
foreach (var p in node.inputPorts)
65188
AddPort(p, true);
@@ -68,7 +191,7 @@ static void ProvideNodePortCreationDescription(Type nodeType)
68191

69192
void AddPort(NodePort p, bool input)
70193
{
71-
nodeCreatePortDescription.Add(new PortDescription{
194+
targetDescription.nodeCreatePortDescription.Add(new PortDescription{
72195
nodeType = nodeType,
73196
portType = p.portData.displayType ?? p.fieldInfo.FieldType,
74197
isInput = input,
@@ -79,16 +202,8 @@ void AddPort(NodePort p, bool input)
79202
}
80203
}
81204

82-
static void AddNodeType(Type type)
205+
static void AddNodeScriptAsset(Type type)
83206
{
84-
var attrs = type.GetCustomAttributes(typeof(NodeMenuItemAttribute), false) as NodeMenuItemAttribute[];
85-
86-
if (attrs != null && attrs.Length > 0)
87-
{
88-
foreach (var attr in attrs)
89-
nodePerMenuTitle[attr.menuTitle] = type;
90-
}
91-
92207
var nodeScriptAsset = FindScriptFromClassName(type.Name);
93208

94209
// Try find the class name with Node name at the end
@@ -98,7 +213,7 @@ static void AddNodeType(Type type)
98213
nodeScripts[type] = nodeScriptAsset;
99214
}
100215

101-
static void AddNodeViewType(Type type)
216+
static void AddNodeViewScriptAsset(Type type)
102217
{
103218
var attrs = type.GetCustomAttributes(typeof(NodeCustomEditor), false) as NodeCustomEditor[];
104219

@@ -154,9 +269,16 @@ public static Type GetNodeViewTypeFromType(Type nodeType)
154269
return view;
155270
}
156271

157-
public static Dictionary< string, Type > GetNodeMenuEntries()
272+
public static IEnumerable<(string path, Type type)> GetNodeMenuEntries(BaseGraph graph = null)
158273
{
159-
return nodePerMenuTitle;
274+
foreach (var node in genericNodes.nodePerMenuTitle)
275+
yield return (node.Key, node.Value);
276+
277+
if (graph != null && specificNodeDescriptions.TryGetValue(graph, out var specificNodes))
278+
{
279+
foreach (var node in specificNodes.nodePerMenuTitle)
280+
yield return (node.Key, node.Value);
281+
}
160282
}
161283

162284
public static MonoScript GetNodeViewScript(Type type)
@@ -173,19 +295,48 @@ public static MonoScript GetNodeScript(Type type)
173295
return script;
174296
}
175297

176-
public static List<Type> GetSlotTypes() => slotTypes;
298+
public static IEnumerable<Type> GetSlotTypes(BaseGraph graph = null)
299+
{
300+
foreach (var type in genericNodes.slotTypes)
301+
yield return type;
177302

178-
public static List<PortDescription> GetEdgeCreationNodeMenuEntry(PortView portView)
303+
if (graph != null && specificNodeDescriptions.TryGetValue(graph, out var specificNodes))
304+
{
305+
foreach (var type in specificNodes.slotTypes)
306+
yield return type;
307+
}
308+
}
309+
310+
public static IEnumerable<PortDescription> GetEdgeCreationNodeMenuEntry(PortView portView, BaseGraph graph = null)
179311
{
180-
return nodeCreatePortDescription.Where(n => {
181-
if (portView.direction == Direction.Input && n.isInput || portView.direction == Direction.Output && !n.isInput)
312+
foreach (var description in genericNodes.nodeCreatePortDescription)
313+
{
314+
if (!IsPortCompatible(description))
315+
continue;
316+
317+
yield return description;
318+
}
319+
320+
if (graph != null && specificNodeDescriptions.TryGetValue(graph, out var specificNodes))
321+
{
322+
foreach (var description in specificNodes.nodeCreatePortDescription)
323+
{
324+
if (!IsPortCompatible(description))
325+
continue;
326+
yield return description;
327+
}
328+
}
329+
330+
bool IsPortCompatible(PortDescription description)
331+
{
332+
if (portView.direction == Direction.Input && description.isInput || portView.direction == Direction.Output && !description.isInput)
182333
return false;
183334

184-
if (!BaseGraph.TypesAreConnectable(n.portType, portView.portType))
335+
if (!BaseGraph.TypesAreConnectable(description.portType, portView.portType))
185336
return false;
186-
337+
187338
return true;
188-
}).ToList();
339+
}
189340
}
190341
}
191342
}

Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseGraphView.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -650,6 +650,7 @@ public void Initialize(BaseGraph graph)
650650
SaveGraphToDisk();
651651
// Close pinned windows from old graph:
652652
ClearGraphElements();
653+
NodeProvider.UnloadGraph(graph);
653654
}
654655

655656
this.graph = graph;
@@ -673,6 +674,8 @@ public void Initialize(BaseGraph graph)
673674
UpdateComputeOrder();
674675

675676
InitializeView();
677+
678+
NodeProvider.LoadGraph(graph);
676679
}
677680

678681
public void ClearGraphElements()
@@ -1225,7 +1228,7 @@ public void ResetPositionAndZoom()
12251228

12261229
protected virtual void InitializeView() {}
12271230

1228-
public virtual IEnumerable< KeyValuePair< string, Type > > FilterCreateNodeMenuEntries()
1231+
public virtual IEnumerable<(string path, Type type)> FilterCreateNodeMenuEntries()
12291232
{
12301233
// By default we don't filter anything
12311234
foreach (var nodeMenuItem in NodeProvider.GetNodeMenuEntries())
@@ -1256,6 +1259,7 @@ public void Dispose()
12561259
RemoveFromHierarchy();
12571260
Undo.undoRedoPerformed -= ReloadView;
12581261
Object.DestroyImmediate(nodeInspector);
1262+
NodeProvider.UnloadGraph(graph);
12591263
}
12601264

12611265
#endregion

0 commit comments

Comments
 (0)