From a511e4ad41953e4b1fdb2fb9518079e21bcf17e0 Mon Sep 17 00:00:00 2001 From: Greebling <56256331+Greebling@users.noreply.github.com> Date: Mon, 11 Oct 2021 11:20:54 +0200 Subject: [PATCH 01/25] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9e9e3556..8d150b55 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# NodeGraphProcessor +# NodeGraphProcessor Fork Node graph editor framework focused on data processing using Unity UIElements, GraphView and C# 4.7 [![Discord](https://img.shields.io/discord/823720615965622323.svg)](https://discord.gg/XuMd3Z5Rym) From d94beb1dae8305913ac785c82cde43244294840c Mon Sep 17 00:00:00 2001 From: Greebling <56256331+Greebling@users.noreply.github.com> Date: Tue, 12 Oct 2021 11:48:12 +0200 Subject: [PATCH 02/25] fixed typo in function name --- Assets/Examples/Editor/GraphAssetCallbacks.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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"); From 43e50877b8f61200116a9c489bd8db104193aac1 Mon Sep 17 00:00:00 2001 From: Greebling <56256331+Greebling@users.noreply.github.com> Date: Sat, 29 Jan 2022 23:02:53 +0100 Subject: [PATCH 03/25] Implement conversion nodes --- .../Editor/Logic/EdgeConnectorListener.cs | 2 +- .../Editor/Utils/NodeProvider.cs | 13 ++++- .../Editor/Views/BaseGraphView.cs | 54 ++++++++++++++++++- .../Runtime/Graph/BaseGraph.cs | 21 ++++---- .../Runtime/Processing/TypeAdapter.cs | 23 +++++--- 5 files changed, 92 insertions(+), 21 deletions(-) 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..7df76fb3 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseGraphView.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseGraphView.cs @@ -1192,8 +1192,60 @@ public bool Connect(PortView inputPortView, PortView outputPortView, bool autoDi edgeView.input = inputPortView; edgeView.output = outputPortView; + if (ConversionNodeAdapter.AreAssignable(outputPort.portData.displayType, inputPort.portData.displayType)) + { + return ConnectConvertable(edgeView, autoDisconnectInputs); + } else + { + return Connect(edgeView); + } + } - return Connect(edgeView); + /// + /// Same as connect, but also adds custom conversion nodes inbetween the edges input/output, if neccessary + /// + /// + /// + /// + 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) 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() /// /// Tell if two types can be connected in the context of a graph /// - /// - /// + /// + /// /// - 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/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 > adapters = new Dictionary< (Type, Type), Func >(); 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 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) { From ad613d98dc2b7d3311c49d0183ff796a91afa945 Mon Sep 17 00:00:00 2001 From: Greebling <56256331+Greebling@users.noreply.github.com> Date: Sat, 29 Jan 2022 23:03:28 +0100 Subject: [PATCH 04/25] Fix typos and ordering of TypesAreConnectable call --- .../Runtime/Elements/NodePort.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/NodePort.cs b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/NodePort.cs index a5cfde55..dba87e96 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/NodePort.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/NodePort.cs @@ -194,7 +194,7 @@ PushDataDelegate CreatePushDataDelegateForEdge(SerializableEdge edge) // We keep slow checks inside the editor #if UNITY_EDITOR - if (!BaseGraph.TypesAreConnectable(inputField.FieldType, outputField.FieldType)) + if (!BaseGraph.TypesAreConnectable(outputField.FieldType, inputField.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; @@ -207,14 +207,14 @@ PushDataDelegate CreatePushDataDelegateForEdge(SerializableEdge edge) 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 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.GetConvertionMethod(outType, inType), convertedParam); + 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 convertion method return type is not always assignable directly: + // the conversion method return type is not always assignable directly: outputParamField = Expression.Convert(outputParamField, inputField.FieldType); } else // otherwise we cast From 0db21a39ab5a1ca868da8f89e99bc7a7976ca7de Mon Sep 17 00:00:00 2001 From: Greebling <56256331+Greebling@users.noreply.github.com> Date: Sat, 29 Jan 2022 23:03:54 +0100 Subject: [PATCH 05/25] Cleared up error message for wrong usage of custom io port functions --- .../Runtime/Processing/CustomPortIO.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) 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); From ed98f489922d3e3cf6272281ac22b10f1ed04bff Mon Sep 17 00:00:00 2001 From: Greebling <56256331+Greebling@users.noreply.github.com> Date: Sat, 29 Jan 2022 23:04:31 +0100 Subject: [PATCH 06/25] Add support for user created property fields that are not in a valid state --- .../Editor/Views/BaseNodeView.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs index 5f3dfdad..b32dfebd 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs @@ -835,6 +835,9 @@ internal void SyncSerializedPropertyPathes() var nodeIndexString = nodeIndex.ToString(); foreach (var propertyField in this.Query().ToList()) { + if(!propertyField.enabledSelf || 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: From a525dd21ec53db5ded0474f274fc26abba3ddb0a Mon Sep 17 00:00:00 2001 From: Greebling <56256331+Greebling@users.noreply.github.com> Date: Sat, 29 Jan 2022 23:05:28 +0100 Subject: [PATCH 07/25] Add support for creating no ports from custom port behaviours --- .../Runtime/Elements/BaseNode.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/BaseNode.cs b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/BaseNode.cs index 0338c14b..843dabde 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/BaseNode.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/BaseNode.cs @@ -376,7 +376,10 @@ public bool UpdatePortsForFieldLocal(string fieldName, bool sendPortUpdatedEvent if (fieldInfo.behavior != null) { foreach (var portData in fieldInfo.behavior(edges)) - AddPortData(portData); + { + if (portData != null) + AddPortData(portData); + } } else { From ae7e2cae6d398678bd700768aade6490aec3148d Mon Sep 17 00:00:00 2001 From: Greebling <56256331+Greebling@users.noreply.github.com> Date: Sat, 29 Jan 2022 23:06:00 +0100 Subject: [PATCH 08/25] Small performance optimization --- .../Editor/Views/BaseGraphView.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseGraphView.cs b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseGraphView.cs index 7df76fb3..7d1b968d 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseGraphView.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseGraphView.cs @@ -430,7 +430,7 @@ public override List< Port > GetCompatiblePorts(Port startPort, NodeAdapter node { var compatiblePorts = new List< Port >(); - compatiblePorts.AddRange(ports.ToList().Where(p => { + compatiblePorts.AddRange(ports.Where(p => { var portView = p as PortView; if (portView.owner == (startPort as PortView).owner) From 83c1dcb2e24d374774b358740e4ef82ed873c348 Mon Sep 17 00:00:00 2001 From: Greebling <56256331+Greebling@users.noreply.github.com> Date: Sat, 29 Jan 2022 23:06:38 +0100 Subject: [PATCH 09/25] Fixed node creation menu displaying interfaces --- .../Editor/Views/BaseGraphView.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseGraphView.cs b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseGraphView.cs index 7d1b968d..9f157c81 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseGraphView.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseGraphView.cs @@ -779,7 +779,7 @@ public void Initialize(BaseGraph graph) { var interfaces = nodeInfo.type.GetInterfaces(); var exceptInheritedInterfaces = interfaces.Except(interfaces.SelectMany(t => t.GetInterfaces())); - foreach (var i in interfaces) + foreach (var i in exceptInheritedInterfaces) { if (i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICreateNodeFrom<>)) { From 3747551f37e672e9a6d643e2b11b07a7ca16b4a6 Mon Sep 17 00:00:00 2001 From: Greebling <56256331+Greebling@users.noreply.github.com> Date: Sat, 29 Jan 2022 23:07:26 +0100 Subject: [PATCH 10/25] Fixed nullreference when opening and closing the graph window too fast --- .../Editor/Views/BaseGraphView.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseGraphView.cs b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseGraphView.cs index 9f157c81..dd1239d1 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseGraphView.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseGraphView.cs @@ -812,7 +812,8 @@ public void ClearGraphElements() void UpdateSerializedProperties() { - serializedGraph = new SerializedObject(graph); + if(graph != null) + serializedGraph = new SerializedObject(graph); } /// From 781256693b38786ab9bf66cbcebbabe65e5666eb Mon Sep 17 00:00:00 2001 From: Greebling <56256331+Greebling@users.noreply.github.com> Date: Sat, 29 Jan 2022 23:19:51 +0100 Subject: [PATCH 11/25] Add example conversion node --- .../DefaultNodes/Nodes/FloatToStringNode.cs | 35 +++++++++++++++++++ .../Nodes/FloatToStringNode.cs.meta | 3 ++ 2 files changed, 38 insertions(+) create mode 100644 Assets/Examples/DefaultNodes/Nodes/FloatToStringNode.cs create mode 100644 Assets/Examples/DefaultNodes/Nodes/FloatToStringNode.cs.meta diff --git a/Assets/Examples/DefaultNodes/Nodes/FloatToStringNode.cs b/Assets/Examples/DefaultNodes/Nodes/FloatToStringNode.cs new file mode 100644 index 00000000..e0a8c051 --- /dev/null +++ b/Assets/Examples/DefaultNodes/Nodes/FloatToStringNode.cs @@ -0,0 +1,35 @@ +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() + { + var mult = Mathf.Pow(10, decimalPlaces); + float val = Mathf.Round(input * mult) / mult; + output = val.ToString(CultureInfo.InvariantCulture); + } +} \ No newline at end of file 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 From 5323252e69d8778e146f525dd154bb05ff644f0b Mon Sep 17 00:00:00 2001 From: Greebling <56256331+Greebling@users.noreply.github.com> Date: Sat, 29 Jan 2022 23:21:52 +0100 Subject: [PATCH 12/25] Revert "Update README.md" This reverts commit a511e4ad41953e4b1fdb2fb9518079e21bcf17e0. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8d150b55..9e9e3556 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# NodeGraphProcessor Fork +# NodeGraphProcessor Node graph editor framework focused on data processing using Unity UIElements, GraphView and C# 4.7 [![Discord](https://img.shields.io/discord/823720615965622323.svg)](https://discord.gg/XuMd3Z5Rym) From 8487de68a317bf9b8775baa0f0d75325e4102d4b Mon Sep 17 00:00:00 2001 From: Greebling <56256331+Greebling@users.noreply.github.com> Date: Sat, 29 Jan 2022 23:25:43 +0100 Subject: [PATCH 13/25] Fix typos --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8d150b55..60c58475 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# NodeGraphProcessor Fork +# NodeGraphProcessor Node graph editor framework focused on data processing using Unity UIElements, GraphView and C# 4.7 [![Discord](https://img.shields.io/discord/823720615965622323.svg)](https://discord.gg/XuMd3Z5Rym) @@ -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 From ecc4dc08ed51d6536bf4bfaa39f8767c6ff6a2bd Mon Sep 17 00:00:00 2001 From: Greebling <56256331+Greebling@users.noreply.github.com> Date: Sat, 29 Jan 2022 23:44:44 +0100 Subject: [PATCH 14/25] Relaxed filtering of inactive property fields --- .../Editor/Views/BaseNodeView.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs index b32dfebd..48b5d69c 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs @@ -835,7 +835,7 @@ internal void SyncSerializedPropertyPathes() var nodeIndexString = nodeIndex.ToString(); foreach (var propertyField in this.Query().ToList()) { - if(!propertyField.enabledSelf || propertyField.bindingPath == null) + if(propertyField.bindingPath == null) continue; propertyField.Unbind(); From 5f5c9a4a1fba5ebc5af13c91dafcd80951b7b7a3 Mon Sep 17 00:00:00 2001 From: Greebling <56256331+Greebling@users.noreply.github.com> Date: Mon, 31 Jan 2022 11:47:27 +0100 Subject: [PATCH 15/25] Add conversion adapter --- .../Processing/ConversionNodeAdapter.cs | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 Assets/com.alelievr.NodeGraphProcessor/Runtime/Processing/ConversionNodeAdapter.cs 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(); + 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 From e0a1dde87306cef47e097a74b051d9a184846408 Mon Sep 17 00:00:00 2001 From: Greebling <56256331+Greebling@users.noreply.github.com> Date: Mon, 31 Jan 2022 11:48:36 +0100 Subject: [PATCH 16/25] Add meta data --- .../Runtime/Processing/ConversionNodeAdapter.cs.meta | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Assets/com.alelievr.NodeGraphProcessor/Runtime/Processing/ConversionNodeAdapter.cs.meta 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 From 664e867dbb5c4595b3025d95ca520944bd1dbcc9 Mon Sep 17 00:00:00 2001 From: Greebling <56256331+Greebling@users.noreply.github.com> Date: Mon, 31 Jan 2022 12:50:57 +0100 Subject: [PATCH 17/25] Made float rounding much more robust --- Assets/Examples/DefaultNodes/Nodes/FloatToStringNode.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Assets/Examples/DefaultNodes/Nodes/FloatToStringNode.cs b/Assets/Examples/DefaultNodes/Nodes/FloatToStringNode.cs index e0a8c051..7613d680 100644 --- a/Assets/Examples/DefaultNodes/Nodes/FloatToStringNode.cs +++ b/Assets/Examples/DefaultNodes/Nodes/FloatToStringNode.cs @@ -28,8 +28,7 @@ public string GetConversionOutput() protected override void Process() { - var mult = Mathf.Pow(10, decimalPlaces); - float val = Mathf.Round(input * mult) / mult; + output = input.ToString("F" + decimalPlace, CultureInfo.InvariantCulture); output = val.ToString(CultureInfo.InvariantCulture); } -} \ No newline at end of file +} From e07e1841a1a60f690a5d50119714e18350dca8a1 Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Thu, 3 Feb 2022 02:09:21 +0000 Subject: [PATCH 18/25] Replaced ShowAsDrawer with Input field Added proxiedFieldPath to input for CustomPortBeh ports Added ability to have property fields for dynamic ports. --- .../DefaultNodes/Nodes/DrawerFieldTestNode.cs | 60 +- Assets/Testing.meta | 8 + Assets/Testing/BaseIsConditional.cs | 35 + Assets/Testing/BaseIsConditional.cs.meta | 11 + Assets/Testing/BoolVariable.cs | 12 + Assets/Testing/BoolVariable.cs.meta | 11 + Assets/Testing/ConditionalNameNode.cs | 12 + Assets/Testing/ConditionalNameNode.cs.meta | 11 + .../DelegateEvent.VarObjectEventArgs.cs | 16 + .../DelegateEvent.VarObjectEventArgs.cs.meta | 11 + Assets/Testing/DelegateEvent.cs | 25 + Assets/Testing/DelegateEvent.cs.meta | 11 + Assets/Testing/DynamicNode.cs | 173 ++ Assets/Testing/DynamicNode.cs.meta | 11 + Assets/Testing/DynamicNodeWithOutput.cs | 20 + Assets/Testing/DynamicNodeWithOutput.cs.meta | 11 + Assets/Testing/ExpandableSOAttribute.cs | 15 + Assets/Testing/ExpandableSOAttribute.cs.meta | 11 + Assets/Testing/MyInputAttribute.cs | 22 + Assets/Testing/MyInputAttribute.cs.meta | 11 + Assets/Testing/PortUpdaterNode.cs | 12 + Assets/Testing/PortUpdaterNode.cs.meta | 11 + Assets/Testing/SequenceData.cs | 64 + Assets/Testing/SequenceData.cs.meta | 11 + Assets/Testing/SharedVariable.cs | 33 + Assets/Testing/SharedVariable.cs.meta | 11 + .../Testing/ValueChangedCallbackAttribute.cs | 23 + .../ValueChangedCallbackAttribute.cs.meta | 11 + .../Editor/Views/BaseNodeView.cs | 2338 +++++++++-------- .../Editor/Views/PortView.cs | 319 +-- .../Runtime/Elements/BaseNode.cs | 1722 ++++++------ .../Runtime/Elements/NodePort.cs | 721 ++--- .../Runtime/Graph/Attributes.cs | 480 ++-- Packages/manifest.json | 10 +- Packages/packages-lock.json | 10 +- ProjectSettings/ProjectVersion.txt | 4 +- 36 files changed, 3519 insertions(+), 2758 deletions(-) create mode 100644 Assets/Testing.meta create mode 100644 Assets/Testing/BaseIsConditional.cs create mode 100644 Assets/Testing/BaseIsConditional.cs.meta create mode 100644 Assets/Testing/BoolVariable.cs create mode 100644 Assets/Testing/BoolVariable.cs.meta create mode 100644 Assets/Testing/ConditionalNameNode.cs create mode 100644 Assets/Testing/ConditionalNameNode.cs.meta create mode 100644 Assets/Testing/DelegateEvent.VarObjectEventArgs.cs create mode 100644 Assets/Testing/DelegateEvent.VarObjectEventArgs.cs.meta create mode 100644 Assets/Testing/DelegateEvent.cs create mode 100644 Assets/Testing/DelegateEvent.cs.meta create mode 100644 Assets/Testing/DynamicNode.cs create mode 100644 Assets/Testing/DynamicNode.cs.meta create mode 100644 Assets/Testing/DynamicNodeWithOutput.cs create mode 100644 Assets/Testing/DynamicNodeWithOutput.cs.meta create mode 100644 Assets/Testing/ExpandableSOAttribute.cs create mode 100644 Assets/Testing/ExpandableSOAttribute.cs.meta create mode 100644 Assets/Testing/MyInputAttribute.cs create mode 100644 Assets/Testing/MyInputAttribute.cs.meta create mode 100644 Assets/Testing/PortUpdaterNode.cs create mode 100644 Assets/Testing/PortUpdaterNode.cs.meta create mode 100644 Assets/Testing/SequenceData.cs create mode 100644 Assets/Testing/SequenceData.cs.meta create mode 100644 Assets/Testing/SharedVariable.cs create mode 100644 Assets/Testing/SharedVariable.cs.meta create mode 100644 Assets/Testing/ValueChangedCallbackAttribute.cs create mode 100644 Assets/Testing/ValueChangedCallbackAttribute.cs.meta diff --git a/Assets/Examples/DefaultNodes/Nodes/DrawerFieldTestNode.cs b/Assets/Examples/DefaultNodes/Nodes/DrawerFieldTestNode.cs index b30fd069..2f80e584 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 = true)] + public Vector4 vector4; - [Input(name = "Vector 3"), ShowAsDrawer] - public Vector3 vector3; + [Input(name = "Vector 3", showAsDrawer = true)] + public Vector3 vector3; - [Input(name = "Vector 2"), ShowAsDrawer] - public Vector2 vector2; + [Input(name = "Vector 2", showAsDrawer = true)] + public Vector2 vector2; - [Input(name = "Float"), ShowAsDrawer] - public float floatInput; + [Input(name = "Float", showAsDrawer = true)] + public float floatInput; - [Input(name = "Vector 3 Int"), ShowAsDrawer] - public Vector3Int vector3Int; + [Input(name = "Vector 3 Int", showAsDrawer = true)] + public Vector3Int vector3Int; - [Input(name = "Vector 2 Int"), ShowAsDrawer] - public Vector2Int vector2Int; + [Input(name = "Vector 2 Int", showAsDrawer = true)] + public Vector2Int vector2Int; - [Input(name = "Int"), ShowAsDrawer] - public int intInput; + [Input(name = "Int", showAsDrawer = true)] + 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 = true)] + public string stringInput; - [Input(name = "Color"), ShowAsDrawer] - new public Color color; + [Input(name = "Color", showAsDrawer = true)] + new public Color color; - [Input(name = "Game Object"), ShowAsDrawer] - public GameObject gameObject; + [Input(name = "Game Object", showAsDrawer = true)] + public GameObject gameObject; - [Input(name = "Animation Curve"), ShowAsDrawer] - public AnimationCurve animationCurve; + [Input(name = "Animation Curve", showAsDrawer = true)] + public AnimationCurve animationCurve; - [Input(name = "Rigidbody"), ShowAsDrawer] - public Rigidbody rigidbody; + [Input(name = "Rigidbody", showAsDrawer = true)] + public Rigidbody rigidbody; - [Input("Layer Mask"), ShowAsDrawer] - public LayerMask layerMask; + [Input("Layer Mask", showAsDrawer = true)] + 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/Testing.meta b/Assets/Testing.meta new file mode 100644 index 00000000..56738015 --- /dev/null +++ b/Assets/Testing.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: db98db52a808b6a11bc0439ea50f7d72 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Testing/BaseIsConditional.cs b/Assets/Testing/BaseIsConditional.cs new file mode 100644 index 00000000..5ae6efb3 --- /dev/null +++ b/Assets/Testing/BaseIsConditional.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using GraphProcessor; +using UnityEngine; + +[System.Serializable] +public class BaseIsConditional +{ + [SerializeField, Input("True Checks", false, true)] protected BoolVariable[] trueChecks; + public BoolVariable[] TrueChecks { get => trueChecks; } + + [SerializeField, Input("False Checks", false, true)] protected BoolVariable[] falseChecks; + public BoolVariable[] FalseChecks { get => falseChecks; } + + public bool IsAvailable() + { + foreach (BoolVariable check in trueChecks) + { + if (!check.RuntimeValue) return false; + } + + foreach (BoolVariable check in falseChecks) + { + if (check.RuntimeValue) return false; + } + return true; + } +} + +public interface IIsConditional +{ + List TrueChecks { get; } + List FalseChecks { get; } + + bool IsAvailable(); +} \ No newline at end of file diff --git a/Assets/Testing/BaseIsConditional.cs.meta b/Assets/Testing/BaseIsConditional.cs.meta new file mode 100644 index 00000000..7f07e8bd --- /dev/null +++ b/Assets/Testing/BaseIsConditional.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9947347622c831651bc3a306795104fa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Testing/BoolVariable.cs b/Assets/Testing/BoolVariable.cs new file mode 100644 index 00000000..01b8eb90 --- /dev/null +++ b/Assets/Testing/BoolVariable.cs @@ -0,0 +1,12 @@ +using UnityEngine; + +[CreateAssetMenu(fileName = "BoolVariable", menuName = "Teletext/Variables/Bool")] +public class BoolVariable : SharedVariable +{ + public override bool RuntimeValue { get => GetEvaluation(); set => base.RuntimeValue = value; } + + protected virtual bool GetEvaluation() + { + return base.RuntimeValue; + } +} diff --git a/Assets/Testing/BoolVariable.cs.meta b/Assets/Testing/BoolVariable.cs.meta new file mode 100644 index 00000000..0d4d6ccf --- /dev/null +++ b/Assets/Testing/BoolVariable.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c2ed7d6a9e49992bca5a969a1db59655 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Testing/ConditionalNameNode.cs b/Assets/Testing/ConditionalNameNode.cs new file mode 100644 index 00000000..3eb7cc5f --- /dev/null +++ b/Assets/Testing/ConditionalNameNode.cs @@ -0,0 +1,12 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using GraphProcessor; +using System.Linq; +using static SequenceName; + +[System.Serializable, NodeMenuItem("Custom/ConditionalNameNode")] +public class ConditionalNameNode : DynamicNodeWithOutput +{ + public override string name => "ConditionalNameNode"; +} diff --git a/Assets/Testing/ConditionalNameNode.cs.meta b/Assets/Testing/ConditionalNameNode.cs.meta new file mode 100644 index 00000000..e41b6497 --- /dev/null +++ b/Assets/Testing/ConditionalNameNode.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/Testing/DelegateEvent.VarObjectEventArgs.cs b/Assets/Testing/DelegateEvent.VarObjectEventArgs.cs new file mode 100644 index 00000000..7a5d32ec --- /dev/null +++ b/Assets/Testing/DelegateEvent.VarObjectEventArgs.cs @@ -0,0 +1,16 @@ +using System; + +public partial class DelegateEvent +{ + public class VarObjectEventArgs : EventArgs + { + private T value; + public T Value => value; + + public VarObjectEventArgs(T value) + { + this.value = value; + } + } +} + diff --git a/Assets/Testing/DelegateEvent.VarObjectEventArgs.cs.meta b/Assets/Testing/DelegateEvent.VarObjectEventArgs.cs.meta new file mode 100644 index 00000000..dfe97910 --- /dev/null +++ b/Assets/Testing/DelegateEvent.VarObjectEventArgs.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1d0825e47caa8faf0b509d6413c06002 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Testing/DelegateEvent.cs b/Assets/Testing/DelegateEvent.cs new file mode 100644 index 00000000..392b04e9 --- /dev/null +++ b/Assets/Testing/DelegateEvent.cs @@ -0,0 +1,25 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +public partial class DelegateEvent : ScriptableObject +{ + public delegate void EventHandler(object sender, VarObjectEventArgs args); + public event EventHandler Event; + + public void AddListener(EventHandler listener) + { + Event += listener; + } + + public void RemoveListener(EventHandler listener) + { + Event -= listener; + } + + public void Raise(T argument) + { + if (Event != null) + Event.Invoke(this, new VarObjectEventArgs(argument)); + } +} diff --git a/Assets/Testing/DelegateEvent.cs.meta b/Assets/Testing/DelegateEvent.cs.meta new file mode 100644 index 00000000..61429f8c --- /dev/null +++ b/Assets/Testing/DelegateEvent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b336ffaa1cc79bc929414981fe23fab5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Testing/DynamicNode.cs b/Assets/Testing/DynamicNode.cs new file mode 100644 index 00000000..e5f6a538 --- /dev/null +++ b/Assets/Testing/DynamicNode.cs @@ -0,0 +1,173 @@ +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 : PortUpdaterNode +{ + [Input("Action Data", true)] + public Dictionary> actionData = new Dictionary>(); + + [ExpandableSO, ValueChangedCallback(nameof(actionData), nameof(OnDataChanged))] + public T data; + + public override bool needsInspector => true; + + protected override void Process() + { + UpdateActionWithCustomPortData(); + } + + protected virtual void UpdateActionWithCustomPortData() + { + // We clone due to reference issues + Dictionary> actionDataClone = new Dictionary>(actionData); + + foreach (var field in GetInputFieldsOfType()) + { + if (!actionDataClone.ContainsKey(field.fieldInfo.Name)) + { + if (field.inputAttribute.showAsDrawer) + continue; + + field.fieldInfo.SetValue(data, default); + continue; + } + + if (field.inputAttribute is MyInputAttribute) + { + MyInputAttribute inputAttribute = field.inputAttribute as MyInputAttribute; + if (inputAttribute.InputType != null && field.fieldInfo.FieldType.GetInterfaces().Any(x => x == typeof(IList))) + { + IList list = Activator.CreateInstance(field.fieldInfo.FieldType) as IList; + foreach (var value in actionDataClone[field.fieldInfo.Name]) + list.Add(value); + + field.fieldInfo.SetValue(data, list); + continue; + } + } + + field.fieldInfo.SetValue(data, actionDataClone[field.fieldInfo.Name][0]); + } + + actionData.Clear(); + } + + #region Reflection Generation Of Ports + + private List GetInputFieldsOfType() + { + List foundInputFields = new List(); + + 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(); + + return new FieldPortInfo(fieldInfo, inputAttribute); + } + + [CustomPortInput(nameof(actionData), typeof(object))] + protected void PullInputs(List connectedEdges) + { + if (connectedEdges.Count == 0) return; + + FieldPortInfo field = GetFieldPortInfo(connectedEdges.ElementAt(0).inputPortIdentifier); + + if (actionData == null) actionData = new Dictionary>(); + foreach (var edge in connectedEdges.GetNonRelayEdges().OrderByInputAttribute(field.inputAttribute)) + { + if (!actionData.ContainsKey(field.fieldInfo.Name)) + actionData.Add(field.fieldInfo.Name, new List()); + + actionData[field.fieldInfo.Name].Add(edge.passThroughBuffer); + } + } + + [CustomPortBehavior(nameof(actionData))] + protected IEnumerable ActionDataBehaviour(List edges) // Try changing edge here when ports update + { + foreach (var field in GetInputFieldsOfType()) + { + Type displayType = field.fieldInfo.FieldType; + + if (field.inputAttribute is MyInputAttribute) + { + MyInputAttribute inputAttribute = field.inputAttribute as MyInputAttribute; + if (inputAttribute.InputType != null) + displayType = inputAttribute.InputType; + } + + yield return new PortData + { + displayName = field.inputAttribute.name, + displayType = displayType, + identifier = field.fieldInfo.Name, + showAsDrawer = field.inputAttribute.showAsDrawer, + vertical = false, + proxiedFieldPath = "data." + field.fieldInfo.Name, + acceptMultipleEdges = field.inputAttribute.allowMultiple, + }; + } + + // Debug.Log(this.GetCustomName() + " BEHAVE: " + this.inputPorts.Count); + } + + // public override IEnumerable OverrideFieldOrder(IEnumerable 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/Testing/DynamicNode.cs.meta b/Assets/Testing/DynamicNode.cs.meta new file mode 100644 index 00000000..930590b6 --- /dev/null +++ b/Assets/Testing/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/Testing/DynamicNodeWithOutput.cs b/Assets/Testing/DynamicNodeWithOutput.cs new file mode 100644 index 00000000..b92c3c9a --- /dev/null +++ b/Assets/Testing/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 : DynamicNode +{ + [Output(name = "Out")] + public T dataOutput; + + public override string name => "DynamicNodeWithOutput"; + + protected override void Process() + { + base.Process(); + dataOutput = data; + } +} diff --git a/Assets/Testing/DynamicNodeWithOutput.cs.meta b/Assets/Testing/DynamicNodeWithOutput.cs.meta new file mode 100644 index 00000000..72c0ff3e --- /dev/null +++ b/Assets/Testing/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/Testing/ExpandableSOAttribute.cs b/Assets/Testing/ExpandableSOAttribute.cs new file mode 100644 index 00000000..00c2b219 --- /dev/null +++ b/Assets/Testing/ExpandableSOAttribute.cs @@ -0,0 +1,15 @@ +using System; +using UnityEditor; +using UnityEngine; + +/// +/// Attribute takes the insides of a set ScriptableObject and display it. +/// +public class ExpandableSOAttribute : PropertyAttribute +{ + + /// + /// Required implementation of the interface. + /// + public ExpandableSOAttribute() { } +} diff --git a/Assets/Testing/ExpandableSOAttribute.cs.meta b/Assets/Testing/ExpandableSOAttribute.cs.meta new file mode 100644 index 00000000..198eb13f --- /dev/null +++ b/Assets/Testing/ExpandableSOAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bf7bd1d3b461c28ca869d43343391f92 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Testing/MyInputAttribute.cs b/Assets/Testing/MyInputAttribute.cs new file mode 100644 index 00000000..d9b488d4 --- /dev/null +++ b/Assets/Testing/MyInputAttribute.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using GraphProcessor; +using UnityEngine; + +[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] +public class MyInputAttribute : InputAttribute +{ + public readonly Type InputType; + public readonly InputSortType SortType; + + public MyInputAttribute(string name = null, bool allowMultiple = false, InputSortType sortType = InputSortType.FIRST_IN, Type inputType = null) + { + this.name = name; + this.allowMultiple = allowMultiple; + this.SortType = sortType; + this.InputType = inputType; + } +} + +public enum InputSortType { FIRST_IN, POSITION_Y } diff --git a/Assets/Testing/MyInputAttribute.cs.meta b/Assets/Testing/MyInputAttribute.cs.meta new file mode 100644 index 00000000..0da6a56d --- /dev/null +++ b/Assets/Testing/MyInputAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0189acdd3b39929d6a8b0a57c3e507bd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Testing/PortUpdaterNode.cs b/Assets/Testing/PortUpdaterNode.cs new file mode 100644 index 00000000..5e4cb1ce --- /dev/null +++ b/Assets/Testing/PortUpdaterNode.cs @@ -0,0 +1,12 @@ +using System.Diagnostics; +using System.Reflection; +using GraphProcessor; +using UnityEngine; + +public abstract class PortUpdaterNode : BaseNode +{ + protected virtual void OnDataChanged(FieldInfo originField, UnityEditor.SerializedProperty serializedProperty) + { + UpdatePortsForFieldLocal(originField.Name); + } +} diff --git a/Assets/Testing/PortUpdaterNode.cs.meta b/Assets/Testing/PortUpdaterNode.cs.meta new file mode 100644 index 00000000..6aaa9538 --- /dev/null +++ b/Assets/Testing/PortUpdaterNode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 67b3cddba608059109efbb060f6504fe +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Testing/SequenceData.cs b/Assets/Testing/SequenceData.cs new file mode 100644 index 00000000..4358fc00 --- /dev/null +++ b/Assets/Testing/SequenceData.cs @@ -0,0 +1,64 @@ +using System.Reflection; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using GraphProcessor; +using UnityEngine; + + +[Serializable] +public class SequenceName : List +{ + public string CurrentName => FindLast(x => x.IsAvailable()).Name; + + [Serializable] + public class ConditionalName : BaseIsConditional + { + [SerializeField, Input("Name", true)] string name; + public string Name => name; + } +} + +public static class ListHelpers +{ + public static string GetCurrentName(this List list) + { + return list.FindLast(x => x.IsAvailable()).Name; + } + + public static IList OrderByInputAttribute(this IList edges, InputAttribute inputAttribute) + { + if (inputAttribute is MyInputAttribute) + { + switch ((inputAttribute as MyInputAttribute).SortType) + { + case InputSortType.POSITION_Y: + edges = edges.OrderBy(x => x.outputNode.position.y).ToList(); + break; + } + } + return edges; + } + + public static IList GetNonRelayEdges(this IList edges) + { + List nonrelayEdges = new List(); + 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; + } +} \ No newline at end of file diff --git a/Assets/Testing/SequenceData.cs.meta b/Assets/Testing/SequenceData.cs.meta new file mode 100644 index 00000000..aeebf036 --- /dev/null +++ b/Assets/Testing/SequenceData.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/Testing/SharedVariable.cs b/Assets/Testing/SharedVariable.cs new file mode 100644 index 00000000..47d04810 --- /dev/null +++ b/Assets/Testing/SharedVariable.cs @@ -0,0 +1,33 @@ +using System.Data.SqlTypes; +using UnityEngine; + +/// +/// +/// CallbackObject callback is used as a OnValueChanged event. +/// +/// +public abstract class SharedVariable : DelegateEvent +{ + [SerializeField] protected T InitialValue; + + protected T runtimeValue; + public virtual T RuntimeValue + { + get => runtimeValue; + set + { + if (runtimeValue == null && value == null) return; + else if (runtimeValue != null && runtimeValue.Equals(value)) return; + else if (value != null && value.Equals(runtimeValue)) return; + + runtimeValue = value; + Raise(value); + } + } + + // Initialize runtime value with editor's value + protected virtual void OnEnable() + { + RuntimeValue = InitialValue; + } +} \ No newline at end of file diff --git a/Assets/Testing/SharedVariable.cs.meta b/Assets/Testing/SharedVariable.cs.meta new file mode 100644 index 00000000..a4acfa26 --- /dev/null +++ b/Assets/Testing/SharedVariable.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4ace1829c0b27489fa70482f81ddbed7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Testing/ValueChangedCallbackAttribute.cs b/Assets/Testing/ValueChangedCallbackAttribute.cs new file mode 100644 index 00000000..792f2d0e --- /dev/null +++ b/Assets/Testing/ValueChangedCallbackAttribute.cs @@ -0,0 +1,23 @@ +using System; +using UnityEditor; +using UnityEngine; +/// +/// Check if a given field has changed and if it has calls a method. +/// +/// +public class ValueChangedCallbackAttribute : PropertyAttribute +{ + string fieldName; + public string FieldName => fieldName; + + string methodName; + public string MethodName => methodName; + + /// + /// + public ValueChangedCallbackAttribute(string fieldName, string methodName) + { + this.fieldName = fieldName; + this.methodName = methodName; + } +} \ No newline at end of file diff --git a/Assets/Testing/ValueChangedCallbackAttribute.cs.meta b/Assets/Testing/ValueChangedCallbackAttribute.cs.meta new file mode 100644 index 00000000..f59e492c --- /dev/null +++ b/Assets/Testing/ValueChangedCallbackAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e9b5389fba495b15191a31e041bfedba +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs index 5f3dfdad..bc6e9fc5 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs @@ -15,1000 +15,1062 @@ 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 selectedNodes = new List(); - 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 inputPortViews = new List(); + public List outputPortViews = new List(); + + public BaseGraphView owner { private set; get; } + + protected Dictionary> portsPerFieldName = new Dictionary>(); + + 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 onPortConnected; + public event Action 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 badges = new List(); + + private List selectedNodes = new List(); + 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(baseNodeStyle)); if (!string.IsNullOrEmpty(node.layoutStyle)) styleSheets.Add(Resources.Load(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(OnGeometryChanged); - RegisterCallback(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(e => { - controlsContainer.style.display = DisplayStyle.Flex; - mouseOverControls = true; - }); - RegisterCallback(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(OnGeometryChanged); + RegisterCallback(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(e => + { + controlsContainer.style.display = DisplayStyle.Flex; + mouseOverControls = true; + }); + RegisterCallback(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(e => { - if (e.clickCount == 2 && e.button == (int)MouseButton.LeftMouse) - OpenTitleEditor(); - }); - - titleTextField.RegisterValueChangedCallback(e => CloseAndSaveTitleEditor(e.newValue)); - - titleTextField.RegisterCallback(e => { - if (e.clickCount == 2 && e.button == (int)MouseButton.LeftMouse) - CloseAndSaveTitleEditor(titleTextField.value); - }); - - titleTextField.RegisterCallback(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(e => + { + if (e.clickCount == 2 && e.button == (int)MouseButton.LeftMouse) + OpenTitleEditor(); + }); + + titleTextField.RegisterValueChangedCallback(e => CloseAndSaveTitleEditor(e.newValue)); + + titleTextField.RegisterCallback(e => + { + if (e.clickCount == 2 && e.button == (int)MouseButton.LeftMouse) + CloseAndSaveTitleEditor(titleTextField.value); + }); + + titleTextField.RegisterCallback(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 GetPortViewsFromFieldName(string fieldName) + { + List 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 ports; + portsPerFieldName.TryGetValue(p.fieldName, out ports); + if (ports == null) + { + ports = new List(); + 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(); - 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 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> visibleConditions = new Dictionary>(); - Dictionary hideElementIfConnected = new Dictionary(); - Dictionary> fieldControlsMap = new Dictionary>(); - - 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(); - 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(); - 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() != 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(FieldInfo field, object newValue) - { - foreach (var inputField in fieldControlsMap[field]) - { - var notify = inputField as INotifyValueChanged; - 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(FieldInfo field) - { - if (fieldControlsMap.TryGetValue(field, out var list)) - { - foreach (var inputField in list) - { - if (inputField is INotifyValueChanged 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().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 ports; + portsPerFieldName.TryGetValue(p.fieldName, out ports); + ports.Remove(p); + } + + private void SetValuesForSelectedNodes() + { + selectedNodes = new List(); + 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 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> visibleConditions = new Dictionary>(); + Dictionary hideElementIfConnected = new Dictionary(); + Dictionary> fieldControlsMap = new Dictionary>(); + + 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]; + InputAttribute inputAttribute = field.GetCustomAttribute(); + bool hasInputAttribute = inputAttribute != null; + if (hasInputAttribute && portsPerFieldName.ContainsKey(field.Name)) + { + // IF FIELD IS MULTI_PORT DO THIS LOOP OVER THIS WITH RELATED PORTS AND IGNORE THE BASE PORT + foreach (var port in portsPerFieldName[field.Name]) + { + bool isProxied = !String.IsNullOrEmpty(port.portData.proxiedFieldPath); + string fieldPath = isProxied ? port.portData.proxiedFieldPath : port.fieldName; + DrawField(GetFieldInfoPath(fieldPath), fromInspector, isProxied); + } + } + else + { + DrawField(new List { field }, fromInspector); + } + } + } + + protected virtual void DrawField(List fieldInfoList, bool fromInspector, bool isProxied = false) + { + FieldInfo field = fieldInfoList.Last(); + string fieldPath = fieldInfoList.GetPath(); + + //skip if the field is a node setting + if (field.GetCustomAttribute(typeof(SettingAttribute)) != null) + { + hasSettings = true; + return; + } + + //skip if the field is not serializable + bool serializeField = field.GetCustomAttribute(typeof(SerializeField)) != null; + 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(); + bool hasInputAttribute = inputAttribute != null; + bool hasInputOrOutputAttribute = hasInputAttribute || field.GetCustomAttribute(typeof(OutputAttribute)) != null; + bool showAsDrawer = !fromInspector && hasInputAttribute && inputAttribute.showAsDrawer; + if ((!serializeField || isProxied) && hasInputOrOutputAttribute && !showAsDrawer) + { + Debug.Log("here: " + field.Name); + AddEmptyField(field, fromInspector); + return; + } + + //skip if marked with NonSerialized or HideInInspector + if (field.GetCustomAttribute(typeof(System.NonSerializedAttribute)) != null || field.GetCustomAttribute(typeof(HideInInspector)) != null) + { + AddEmptyField(field, fromInspector); + return; + } + + // Hide the field if we want to display in in the inspector + var showInInspector = field.GetCustomAttribute(); + if (!serializeField && showInInspector != null && !showInInspector.showInNode && !fromInspector) + { + AddEmptyField(field, fromInspector); + return; + } + + + var showInputDrawer = hasInputAttribute && field.GetCustomAttribute(typeof(SerializeField)) != null; + showInputDrawer |= hasInputAttribute && inputAttribute.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(); + 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; + } + } + + private List GetFieldInfoPath(string path) + { + string[] pathArray = path.Split('.'); + List fieldInfoPath = new List(); + object value = nodeTarget; + 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; + } + + 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() != 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(FieldInfo field, object newValue) + { + foreach (var inputField in fieldControlsMap[field]) + { + var notify = inputField as INotifyValueChanged; + 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(FieldInfo field) + { + if (fieldControlsMap.TryGetValue(field, out var list)) + { + foreach (var inputField in list) + { + if (inputField is INotifyValueChanged 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 fieldPath, string label = null, bool showInputDrawer = false, Action valueChangedCallback = null) + { + List fieldInfoPath = GetFieldInfoPath(fieldPath); + return AddControlField(fieldInfoPath, 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().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(List fieldInfoPath, string label = null, bool showInputDrawer = false, Action valueChangedCallback = null) + { + var field = fieldInfoPath.Last(); // + + if (fieldInfoPath == null && fieldInfoPath.IsValid()) + return null; + + var element = new PropertyField(FindSerializedProperty(fieldInfoPath.GetPath()), 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(); - if (objectField != null) - objectField.allowSceneObjects = false; - } - - if (!fieldControlsMap.TryGetValue(field, out var inputFieldList)) - inputFieldList = fieldControlsMap[field] = new List(); - 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().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, fieldInfoPath.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(); + if (objectField != null) + objectField.allowSceneObjects = false; + } + + if (!fieldControlsMap.TryGetValue(field, out var inputFieldList)) + inputFieldList = fieldControlsMap[field] = new List(); + 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().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 = !String.IsNullOrEmpty(port.portData.proxiedFieldPath) ? 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) // + { + string fieldName = !String.IsNullOrEmpty(port.portData.proxiedFieldPath) ? port.portData.proxiedFieldPath : port.fieldName; + + if (port.direction == Direction.Input && inputContainerElement?.Q(fieldName) != null) + { + inputContainerElement.Q(fieldName).RemoveFromClassList("empty"); + + if (nodeTarget.nodeFields.TryGetValue(fieldName, out var fieldInfo)) + { + var valueBeforeConnection = GetInputFieldValue(fieldInfo.info); + + if (valueBeforeConnection != null) + { + fieldInfo.info.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 +1078,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 +1105,179 @@ 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"}; - - /// - /// Send an event to the graph telling that the content of this node have changed - /// - 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 SyncPortCounts(IEnumerable ports, IEnumerable 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 ports, IEnumerable 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 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 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" }; + + /// + /// Send an event to the graph telling that the content of this node have changed + /// + public void NotifyNodeChanged() => owner.graph.NotifyNodeChanged(nodeTarget); + + #endregion + } + + public static class ListHelpers + { + public static object GetValueAt(this IList 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 list, object startingValue) + { + object currentValue = startingValue; + for (int i = 0; i < list.Count; i++) + { + currentValue = list[i].GetValue(currentValue); + } + return currentValue; + + } + + public static string GetPath(this IList 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 list) + { + return list.Any(x => x == null); + } } } \ 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 OnConnected; + public event Action 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 edges = new List(); - 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(portStyle)); - - UpdatePortSize(); - - var userPortStyle = Resources.Load(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; - } - - /// - /// Update the size of the port view (using the portData.sizeInPixel property) - /// - 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(portStyle)); + + UpdatePortSize(); + + var userPortStyle = Resources.Load(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; + } + + /// + /// Update the size of the port view (using the portData.sizeInPixel property) + /// + 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 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..833504c1 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/BaseNode.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/BaseNode.cs @@ -8,858 +8,882 @@ 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 - - /// - /// Name of the node, it will be displayed in the title section - /// - /// - public virtual string name => GetType().Name; - - /// - /// The accent color of the node - /// - public virtual Color color => Color.clear; - - /// - /// 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 - /// - public virtual string layoutStyle => string.Empty; - - /// - /// If the node can be locked or not - /// - public virtual bool unlockable => true; - - /// - /// Is the node is locked (if locked it can't be moved) - /// - public virtual bool isLocked => nodeLock; + public delegate IEnumerable CustomPortBehaviorDelegate(List edges); + public delegate IEnumerable 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 + + /// + /// Name of the node, it will be displayed in the title section + /// + /// + public virtual string name => GetType().Name; + + /// + /// The accent color of the node + /// + public virtual Color color => Color.clear; + + /// + /// 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 + /// + public virtual string layoutStyle => string.Empty; + + /// + /// If the node can be locked or not + /// + public virtual bool unlockable => true; + + /// + /// Is the node is locked (if locked it can't be moved) + /// + public virtual bool isLocked => nodeLock; //id - public string GUID; - - public int computeOrder = -1; - - /// 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 - public virtual bool canProcess => true; - - /// Show the node controlContainer only when the mouse is over the node - public virtual bool showControlsOnHover => false; - - /// True if the node can be deleted, false otherwise - public virtual bool deletable => true; - - /// - /// Container of input ports - /// - [NonSerialized] - public readonly NodeInputPortContainer inputPorts; - /// - /// Container of output ports - /// - [NonSerialized] - public readonly NodeOutputPortContainer outputPorts; - - //Node view datas - public Rect position; - /// - /// Is the node expanded - /// - public bool expanded; - /// - /// Is debug visible - /// - public bool debug; - /// - /// Node locked state - /// - public bool nodeLock; - - public delegate void ProcessDelegate(); - - /// - /// Triggered when the node is processes - /// - public event ProcessDelegate onProcessed; - public event Action< string, NodeMessageType > onMessageAdded; - public event Action< string > onMessageRemoved; - /// - /// Triggered after an edge was connected on the node - /// - public event Action< SerializableEdge > onAfterEdgeConnected; - /// - /// Triggered after an edge was disconnected on the node - /// - public event Action< SerializableEdge > onAfterEdgeDisconnected; - - /// - /// Triggered after a single/list of port(s) is updated, the parameter is the field name - /// - public event Action< string > onPortsUpdated; - - [NonSerialized] - bool _needsInspector = false; - - /// - /// Does the node needs to be visible in the inspector (when selected). - /// - public virtual bool needsInspector => _needsInspector; - - /// - /// Can the node be renamed in the UI. By default a node can be renamed by double clicking it's name. - /// - public virtual bool isRenamable => false; - - /// - /// Is the node created from a duplicate operation (either ctrl-D or copy/paste). - /// - public bool createdFromDuplication {get; internal set; } = false; - - /// - /// True only when the node was created from a duplicate operation and is inside a group that was also duplicated at the same time. - /// - 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(); - - [NonSerialized] - List< string > messages = new List(); - - [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 fieldNames; - public BaseNode node; - - public void Deconstruct(out List fieldNames, out BaseNode node) - { - fieldNames = this.fieldNames; - node = this.node; - } - } - - // Used in port update algorithm - Stack fieldsToUpdate = new Stack(); - HashSet updatedFields = new HashSet(); - - /// - /// Creates a node of type T at a certain position - /// - /// position in the graph in pixels - /// type of the node - /// the node instance - public static T CreateFromType< T >(Vector2 position) where T : BaseNode - { - return CreateFromType(typeof(T), position) as T; - } - - /// - /// Creates a node of type nodeType at a certain position - /// - /// position in the graph in pixels - /// type of the node - /// the node instance - 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().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; - } - } - - /// - /// 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 - /// - 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 }); - } - } - } - - /// - /// Override the field order inside the node. It allows to re-order all the ports and field in the UI. - /// - /// List of fields to sort - /// Sorted list of fields - public virtual IEnumerable OverrideFieldOrder(IEnumerable 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; + + /// 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 + public virtual bool canProcess => true; + + /// Show the node controlContainer only when the mouse is over the node + public virtual bool showControlsOnHover => false; + + /// True if the node can be deleted, false otherwise + public virtual bool deletable => true; + + /// + /// Container of input ports + /// + [NonSerialized] + public readonly NodeInputPortContainer inputPorts; + /// + /// Container of output ports + /// + [NonSerialized] + public readonly NodeOutputPortContainer outputPorts; + + //Node view datas + public Rect position; + /// + /// Is the node expanded + /// + public bool expanded; + /// + /// Is debug visible + /// + public bool debug; + /// + /// Node locked state + /// + public bool nodeLock; + + public delegate void ProcessDelegate(); + + /// + /// Triggered when the node is processes + /// + public event ProcessDelegate onProcessed; + public event Action onMessageAdded; + public event Action onMessageRemoved; + /// + /// Triggered after an edge was connected on the node + /// + public event Action onAfterEdgeConnected; + /// + /// Triggered after an edge was disconnected on the node + /// + public event Action onAfterEdgeDisconnected; + + /// + /// Triggered after a single/list of port(s) is updated, the parameter is the field name + /// + public event Action onPortsUpdated; + + [NonSerialized] + bool _needsInspector = false; + + /// + /// Does the node needs to be visible in the inspector (when selected). + /// + public virtual bool needsInspector => _needsInspector; + + /// + /// Can the node be renamed in the UI. By default a node can be renamed by double clicking it's name. + /// + public virtual bool isRenamable => false; + + /// + /// Is the node created from a duplicate operation (either ctrl-D or copy/paste). + /// + public bool createdFromDuplication { get; internal set; } = false; + + /// + /// True only when the node was created from a duplicate operation and is inside a group that was also duplicated at the same time. + /// + public bool createdWithinGroup { get; internal set; } = false; + + [NonSerialized] + internal Dictionary nodeFields = new Dictionary(); + + [NonSerialized] + internal Dictionary customPortTypeBehaviorMap = new Dictionary(); + + [NonSerialized] + List messages = new List(); + + [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 fieldNames; + public BaseNode node; + + public void Deconstruct(out List fieldNames, out BaseNode node) + { + fieldNames = this.fieldNames; + node = this.node; + } + } + + // Used in port update algorithm + Stack fieldsToUpdate = new Stack(); + HashSet updatedFields = new HashSet(); + + /// + /// Creates a node of type T at a certain position + /// + /// position in the graph in pixels + /// type of the node + /// the node instance + public static T CreateFromType(Vector2 position) where T : BaseNode + { + return CreateFromType(typeof(T), position) as T; + } + + /// + /// Creates a node of type nodeType at a certain position + /// + /// position in the graph in pixels + /// type of the node + /// the node instance + 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().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; + } + } + + /// + /// 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 + /// + 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 + } + ); + } + } + } + + /// + /// Override the field order inside the node. It allows to re-order all the ports and field in the UI. + /// + /// List of fields to sort + /// Sorted list of fields + public virtual IEnumerable OverrideFieldOrder(IEnumerable 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(); - } - - /// - /// Update all ports of the node - /// - 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; - } - - /// - /// 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. - /// - 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; - } - - - /// - /// Update the ports related to one C# property field (only for this node) - /// - /// - 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; - } - - /// - /// Update the ports related to one C# property field and all connected nodes in the graph - /// - /// - public bool UpdatePortsForField(string fieldName, bool sendPortUpdatedEvent = true) - { - bool changed = false; - - fieldsToUpdate.Clear(); - updatedFields.Clear(); - - fieldsToUpdate.Push(new PortUpdate{fieldNames = new List(){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 portUpdateHashSet = new HashSet(); - - internal void DisableInternal() - { - // port containers are initialized in the OnEnable - inputPorts.Clear(); - outputPorts.Clear(); - - ExceptionToLog.Call(() => Disable()); - } - - internal void DestroyInternal() => ExceptionToLog.Call(() => Destroy()); - - /// - /// Called only when the node is created, not when instantiated - /// - 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(); - - /// - /// Called when the node is enabled - /// - protected virtual void Enable() {} - /// - /// Called when the node is disabled - /// - protected virtual void Disable() {} - /// - /// Called when the node is removed - /// - protected virtual void Destroy() {} - - /// - /// Override this method to implement custom processing - /// - protected virtual void Process() {} - - #endregion - - #region API and utils - - /// - /// Add a port - /// - /// is input port - /// C# field name - /// Data of the port - 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)); - } - - /// - /// Remove a port - /// - /// is input port - /// the port to delete - public void RemovePort(bool input, NodePort port) - { - if (input) - inputPorts.Remove(port); - else - outputPorts.Remove(port); - } - - /// - /// Remove port(s) from field name - /// - /// is input - /// C# field name - public void RemovePort(bool input, string fieldName) - { - if (input) - inputPorts.RemoveAll(p => p.fieldName == fieldName); - else - outputPorts.RemoveAll(p => p.fieldName == fieldName); - } - - /// - /// Get all the nodes connected to the input ports of this node - /// - /// an enumerable of node - public IEnumerable< BaseNode > GetInputNodes() - { - foreach (var port in inputPorts) - foreach (var edge in port.GetEdges()) - yield return edge.outputNode; - } - - /// - /// Get all the nodes connected to the output ports of this node - /// - /// an enumerable of node - public IEnumerable< BaseNode > GetOutputNodes() - { - foreach (var port in outputPorts) - foreach (var edge in port.GetEdges()) - yield return edge.inputNode; - } - - /// - /// Return a node matching the condition in the dependencies of the node - /// - /// Condition to choose the node - /// Matched node or null - public BaseNode FindInDependencies(Func condition) - { - Stack dependencies = new Stack(); - - 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; - } - - /// - /// Get the port from field name and identifier - /// - /// C# field name - /// Unique port identifier - /// - 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); - }); - } - - /// - /// Return all the ports of the node - /// - /// - public IEnumerable GetAllPorts() - { - foreach (var port in inputPorts) - yield return port; - foreach (var port in outputPorts) - yield return port; - } - - /// - /// Return all the connected edges of the node - /// - /// - public IEnumerable GetAllEdges() - { - foreach (var port in GetAllPorts()) - foreach (var edge in port.GetEdges()) - yield return edge; - } - - /// - /// Is the port an input - /// - /// - /// - public bool IsFieldInput(string fieldName) => nodeFields[fieldName].input; - - /// - /// Add a message on the node - /// - /// - /// - public void AddMessage(string message, NodeMessageType messageType) - { - if (messages.Contains(message)) - return; - - onMessageAdded?.Invoke(message, messageType); - messages.Add(message); - } - - /// - /// Remove a message on the node - /// - /// - public void RemoveMessage(string message) - { - onMessageRemoved?.Invoke(message); - messages.Remove(message); - } - - /// - /// Remove a message that contains - /// - /// - public void RemoveMessageContains(string subMessage) - { - string toRemove = messages.Find(m => m.Contains(subMessage)); - messages.Remove(toRemove); - onMessageRemoved?.Invoke(toRemove); - } - - /// - /// Remove all messages on the node - /// - public void ClearMessages() - { - foreach (var message in messages) - onMessageRemoved?.Invoke(message); - messages.Clear(); - } - - /// - /// 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. - /// - /// New name of the node. - public void SetCustomName(string customName) => nodeCustomName = customName; - - /// - /// 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. - /// - /// The name of the node as written in the title - public string GetCustomName() => String.IsNullOrEmpty(nodeCustomName) ? name : nodeCustomName; - - #endregion - } + InitializeInOutDatas(); + } + + /// + /// Update all ports of the node + /// + 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; + } + + /// + /// 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. + /// + 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; + } + + + /// + /// Update the ports related to one C# property field (only for this node) + /// + /// + 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 finalPorts = new List(); + + 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; + } + + /// + /// Update the ports related to one C# property field and all connected nodes in the graph + /// + /// + public bool UpdatePortsForField(string fieldName, bool sendPortUpdatedEvent = true) + { + bool changed = false; + + fieldsToUpdate.Clear(); + updatedFields.Clear(); + + fieldsToUpdate.Push(new PortUpdate { fieldNames = new List() { 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 portUpdateHashSet = new HashSet(); + + internal void DisableInternal() + { + // port containers are initialized in the OnEnable + inputPorts.Clear(); + outputPorts.Clear(); + + ExceptionToLog.Call(() => Disable()); + } + + internal void DestroyInternal() => ExceptionToLog.Call(() => Destroy()); + + /// + /// Called only when the node is created, not when instantiated + /// + 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(); + var outputAttribute = field.GetCustomAttribute(); + var tooltipAttribute = field.GetCustomAttribute(); + var showInInspector = field.GetCustomAttribute(); + var vertical = field.GetCustomAttribute(); + 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(); + 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(); + + /// + /// Called when the node is enabled + /// + protected virtual void Enable() { } + /// + /// Called when the node is disabled + /// + protected virtual void Disable() { } + /// + /// Called when the node is removed + /// + protected virtual void Destroy() { } + + /// + /// Override this method to implement custom processing + /// + protected virtual void Process() { } + + #endregion + + #region API and utils + + /// + /// Add a port + /// + /// is input port + /// C# field name + /// Data of the port + 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)); + } + + /// + /// Remove a port + /// + /// is input port + /// the port to delete + public void RemovePort(bool input, NodePort port) + { + if (input) + inputPorts.Remove(port); + else + outputPorts.Remove(port); + } + + /// + /// Remove port(s) from field name + /// + /// is input + /// C# field name + public void RemovePort(bool input, string fieldName) + { + if (input) + inputPorts.RemoveAll(p => p.fieldName == fieldName); + else + outputPorts.RemoveAll(p => p.fieldName == fieldName); + } + + /// + /// Get all the nodes connected to the input ports of this node + /// + /// an enumerable of node + public IEnumerable GetInputNodes() + { + foreach (var port in inputPorts) + foreach (var edge in port.GetEdges()) + yield return edge.outputNode; + } + + /// + /// Get all the nodes connected to the output ports of this node + /// + /// an enumerable of node + public IEnumerable GetOutputNodes() + { + foreach (var port in outputPorts) + foreach (var edge in port.GetEdges()) + yield return edge.inputNode; + } + + /// + /// Return a node matching the condition in the dependencies of the node + /// + /// Condition to choose the node + /// Matched node or null + public BaseNode FindInDependencies(Func condition) + { + Stack dependencies = new Stack(); + + 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; + } + + /// + /// Get the port from field name and identifier + /// + /// C# field name + /// Unique port identifier + /// + 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); + }); + } + + /// + /// Return all the ports of the node + /// + /// + public IEnumerable GetAllPorts() + { + foreach (var port in inputPorts) + yield return port; + foreach (var port in outputPorts) + yield return port; + } + + /// + /// Return all the connected edges of the node + /// + /// + public IEnumerable GetAllEdges() + { + foreach (var port in GetAllPorts()) + foreach (var edge in port.GetEdges()) + yield return edge; + } + + /// + /// Is the port an input + /// + /// + /// + public bool IsFieldInput(string fieldName) => nodeFields[fieldName].input; + + /// + /// Add a message on the node + /// + /// + /// + public void AddMessage(string message, NodeMessageType messageType) + { + if (messages.Contains(message)) + return; + + onMessageAdded?.Invoke(message, messageType); + messages.Add(message); + } + + /// + /// Remove a message on the node + /// + /// + public void RemoveMessage(string message) + { + onMessageRemoved?.Invoke(message); + messages.Remove(message); + } + + /// + /// Remove a message that contains + /// + /// + public void RemoveMessageContains(string subMessage) + { + string toRemove = messages.Find(m => m.Contains(subMessage)); + messages.Remove(toRemove); + onMessageRemoved?.Invoke(toRemove); + } + + /// + /// Remove all messages on the node + /// + public void ClearMessages() + { + foreach (var message in messages) + onMessageRemoved?.Invoke(message); + messages.Clear(); + } + + /// + /// 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. + /// + /// New name of the node. + public void SetCustomName(string customName) => nodeCustomName = customName; + + /// + /// 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. + /// + /// The name of the node as written in the title + public string GetCustomName() => String.IsNullOrEmpty(nodeCustomName) ? name : nodeCustomName; + + #endregion + } } diff --git a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/NodePort.cs b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/NodePort.cs index a5cfde55..c852fa46 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/NodePort.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/NodePort.cs @@ -10,168 +10,180 @@ namespace GraphProcessor { - /// - /// Class that describe port attributes for it's creation - /// - public class PortData : IEquatable< PortData > - { - /// - /// Unique identifier for the port - /// - public string identifier; - /// - /// Display name on the node - /// - public string displayName; - /// - /// The type that will be used for coloring with the type stylesheet - /// - public Type displayType; - /// - /// If the port accept multiple connection - /// - public bool acceptMultipleEdges; - /// - /// Port size, will also affect the size of the connected edge - /// - public int sizeInPixel; - /// - /// Tooltip of the port - /// - public string tooltip; - /// - /// Is the port vertical - /// - public bool vertical; + /// + /// Class that describe port attributes for it's creation + /// + public class PortData : IEquatable + { + /// + /// Unique identifier for the port + /// + public string identifier; + /// + /// Display name on the node + /// + public string displayName; + /// + /// The type that will be used for coloring with the type stylesheet + /// + public Type displayType; + /// + /// Whether to show a property drawer with this port (only for input) + /// + public bool showAsDrawer; + /// + /// If the port accept multiple connection + /// + public bool acceptMultipleEdges; + /// + /// The field the port is proxying if using custombehavior + /// + public string proxiedFieldPath; + /// + /// Port size, will also affect the size of the connected edge + /// + public int sizeInPixel; + /// + /// Tooltip of the port + /// + public string tooltip; + /// + /// Is the port vertical + /// + public bool vertical; 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; + } } - /// - /// Runtime class that stores all info about one port that is needed for the processing - /// - public class NodePort - { - /// - /// The actual name of the property behind the port (must be exact, it is used for Reflection) - /// - public string fieldName; - /// - /// The node on which the port is - /// - public BaseNode owner; - /// - /// The fieldInfo from the fieldName - /// - public FieldInfo fieldInfo; - /// - /// Data of the port - /// - public PortData portData; - List< SerializableEdge > edges = new List< SerializableEdge >(); - Dictionary< SerializableEdge, PushDataDelegate > pushDataDelegates = new Dictionary< SerializableEdge, PushDataDelegate >(); - List< SerializableEdge > edgeWithRemoteCustomIO = new List< SerializableEdge >(); - - /// - /// Owner of the FieldInfo, to be used in case of Get/SetValue - /// - public object fieldOwner; - - CustomPortIODelegate customPortIOMethod; - - /// - /// 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/ - /// - public delegate void PushDataDelegate(); - - /// - /// Constructor - /// - /// owner node - /// the C# property name - /// Data of the port - public NodePort(BaseNode owner, string fieldName, PortData portData) : this(owner, owner, fieldName, portData) {} - - /// - /// Constructor - /// - /// owner node - /// - /// the C# property name - /// Data of the port - 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); - } - - /// - /// Connect an edge to this port - /// - /// - 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; + /// + /// Runtime class that stores all info about one port that is needed for the processing + /// + public class NodePort + { + /// + /// The actual name of the property behind the port (must be exact, it is used for Reflection) + /// + public string fieldName; + /// + /// The node on which the port is + /// + public BaseNode owner; + /// + /// The fieldInfo from the fieldName + /// + public FieldInfo fieldInfo; + /// + /// Data of the port + /// + public PortData portData; + List edges = new List(); + Dictionary pushDataDelegates = new Dictionary(); + List edgeWithRemoteCustomIO = new List(); + + /// + /// Owner of the FieldInfo, to be used in case of Get/SetValue + /// + public object fieldOwner; + + CustomPortIODelegate customPortIOMethod; + + /// + /// 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/ + /// + public delegate void PushDataDelegate(); + + /// + /// Constructor + /// + /// owner node + /// the C# property name + /// Data of the port + public NodePort(BaseNode owner, string fieldName, PortData portData) : this(owner, owner, fieldName, portData) { } + + /// + /// Constructor + /// + /// owner node + /// + /// the C# property name + /// Data of the port + 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); + } + + /// + /// Connect an edge to this port + /// + /// + 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(() => { @@ -192,205 +204,208 @@ PushDataDelegate CreatePushDataDelegateForEdge(SerializableEdge edge) }); #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 convertions"); + 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; - } - } - - /// - /// Disconnect an Edge from this port - /// - /// - public void Remove(SerializableEdge edge) - { - if (!edges.Contains(edge)) - return; - - pushDataDelegates.Remove(edge); - edgeWithRemoteCustomIO.Remove(edge); - edges.Remove(edge); - } - - /// - /// Get all the edges connected to this port - /// - /// - public List< SerializableEdge > GetEdges() => edges; - - /// - /// Push the value of the port through the edges - /// This method can only be called on output ports - /// - 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; - } - - /// - /// Reset the value of the field to default if possible - /// - 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 - } - } - - /// - /// Pull values from the edge (in case of a custom convertion method) - /// This method can only be called on input ports - /// - 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); - } - } - } - - /// - /// Container of ports and the edges connected to these ports - /// - public abstract class NodePortContainer : List< NodePort > - { - protected BaseNode node; - - public NodePortContainer(BaseNode node) - { - this.node = node; - } - - /// - /// Remove an edge that is connected to one of the node in the container - /// - /// - public void Remove(SerializableEdge edge) - { - ForEach(p => p.Remove(edge)); - } - - /// - /// Add an edge that is connected to one of the node in the container - /// - /// - 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); - } - } - - /// - public class NodeInputPortContainer : NodePortContainer - { - public NodeInputPortContainer(BaseNode node) : base(node) {} - - public void PullDatas() - { - ForEach(p => p.PullData()); - } - } - - /// - 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 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(assign).Compile(); + } + catch (Exception e) + { + Debug.LogError(e); + return null; + } + } + + /// + /// Disconnect an Edge from this port + /// + /// + public void Remove(SerializableEdge edge) + { + if (!edges.Contains(edge)) + return; + + pushDataDelegates.Remove(edge); + edgeWithRemoteCustomIO.Remove(edge); + edges.Remove(edge); + } + + /// + /// Get all the edges connected to this port + /// + /// + public List GetEdges() => edges; + + /// + /// Push the value of the port through the edges + /// This method can only be called on output ports + /// + 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; + } + + /// + /// Reset the value of the field to default if possible + /// + 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 + } + } + + /// + /// Pull values from the edge (in case of a custom convertion method) + /// This method can only be called on input ports + /// + 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); + } + } + } + + /// + /// Container of ports and the edges connected to these ports + /// + public abstract class NodePortContainer : List + { + protected BaseNode node; + + public NodePortContainer(BaseNode node) + { + this.node = node; + } + + /// + /// Remove an edge that is connected to one of the node in the container + /// + /// + public void Remove(SerializableEdge edge) + { + ForEach(p => p.Remove(edge)); + } + + /// + /// Add an edge that is connected to one of the node in the container + /// + /// + 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); + } + } + + /// + public class NodeInputPortContainer : NodePortContainer + { + public NodeInputPortContainer(BaseNode node) : base(node) { } + + public void PullDatas() + { + ForEach(p => p.PullData()); + } + } + + /// + 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/Graph/Attributes.cs b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Graph/Attributes.cs index effd5610..4150e1cf 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 { - /// - /// Tell that this field is will generate an input port - /// - [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] - public class InputAttribute : Attribute - { - public string name; - public bool allowMultiple = false; - - /// - /// Mark the field as an input port - /// - /// display name - /// is connecting multiple edges allowed - public InputAttribute(string name = null, bool allowMultiple = false) - { - this.name = name; - this.allowMultiple = allowMultiple; - } - } - - /// - /// Tell that this field is will generate an output port - /// - [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] - public class OutputAttribute : Attribute - { - public string name; - public bool allowMultiple = true; - - /// - /// Mark the field as an output port - /// - /// display name - /// is connecting multiple edges allowed - public OutputAttribute(string name = null, bool allowMultiple = true) - { - this.name = name; - this.allowMultiple = allowMultiple; - } - } - - /// - /// Creates a vertical port instead of the default horizontal one - /// - [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] - public class VerticalAttribute : Attribute - { - } - - /// - /// Register the node in the NodeProvider class. The node will also be available in the node creation window. - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] - public class NodeMenuItemAttribute : Attribute - { - public string menuTitle; - public Type onlyCompatibleWithGraph; - - /// - /// Register the node in the NodeProvider class. The node will also be available in the node creation window. - /// - /// Path in the menu, use / as folder separators - public NodeMenuItemAttribute(string menuTitle = null, Type onlyCompatibleWithGraph = null) - { - this.menuTitle = menuTitle; - this.onlyCompatibleWithGraph = onlyCompatibleWithGraph; - } - } - - /// - /// Set a custom drawer for a field. It can then be created using the FieldFactory - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] - [Obsolete("You can use the standard Unity CustomPropertyDrawer instead.")] - public class FieldDrawerAttribute : Attribute - { - public Type fieldType; - - /// - /// Register a custom view for a type in the FieldFactory class - /// - /// - public FieldDrawerAttribute(Type fieldType) - { - this.fieldType = fieldType; - } - } - - /// - /// Allow you to customize the input function of a port - /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] - public class CustomPortInputAttribute : Attribute - { - public string fieldName; - public Type inputType; - public bool allowCast; - - /// - /// Allow you to customize the input function of a port. - /// See CustomPortsNode example in Samples. - /// - /// local field of the node - /// type of input of the port - /// if cast is allowed when connecting an edge - public CustomPortInputAttribute(string fieldName, Type inputType, bool allowCast = true) - { - this.fieldName = fieldName; - this.inputType = inputType; - this.allowCast = allowCast; - } - } - - /// - /// Allow you to customize the input function of a port - /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] - public class CustomPortOutputAttribute : Attribute - { - public string fieldName; - public Type outputType; - public bool allowCast; - - /// - /// Allow you to customize the output function of a port. - /// See CustomPortsNode example in Samples. - /// - /// local field of the node - /// type of input of the port - /// if cast is allowed when connecting an edge - public CustomPortOutputAttribute(string fieldName, Type outputType, bool allowCast = true) - { - this.fieldName = fieldName; - this.outputType = outputType; - this.allowCast = allowCast; - } - } - - /// - /// Allow you to modify the generated port view from a field. Can be used to generate multiple ports from one field. - /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] - public class CustomPortBehaviorAttribute : Attribute - { - public string fieldName; - - /// - /// 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 - /// - /// IEnumerable<PortData> MyCustomPortFunction(List<SerializableEdge> edges); - /// - /// - /// local node field name - public CustomPortBehaviorAttribute(string fieldName) - { - this.fieldName = fieldName; - } - } - - /// - /// Allow to bind a method to generate a specific set of ports based on a field type in a node - /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - public class CustomPortTypeBehavior : Attribute - { - /// - /// Target type - /// - public Type type; - - public CustomPortTypeBehavior(Type type) - { - this.type = type; - } - } - - /// - /// Allow you to have a custom view for your stack nodes - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] - public class CustomStackNodeView : Attribute - { - public Type stackNodeType; - - /// - /// Allow you to have a custom view for your stack nodes - /// - /// The type of the stack node you target - 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 {} + /// + /// Tell that this field is will generate an input port + /// + [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] + public class InputAttribute : Attribute + { + public string name; + public bool allowMultiple = false; + public bool showAsDrawer = false; + + /// + /// Mark the field as an input port + /// + /// display name + /// is connecting multiple edges allowed + public InputAttribute(string name = null, bool showAsDrawer = false, bool allowMultiple = false) + { + this.name = name; + this.showAsDrawer = showAsDrawer; + this.allowMultiple = allowMultiple; + } + } + + /// + /// Tell that this field is will generate an output port + /// + [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] + public class OutputAttribute : Attribute + { + public string name; + public bool allowMultiple = true; + + /// + /// Mark the field as an output port + /// + /// display name + /// is connecting multiple edges allowed + public OutputAttribute(string name = null, bool allowMultiple = true) + { + this.name = name; + this.allowMultiple = allowMultiple; + } + } + + /// + /// Creates a vertical port instead of the default horizontal one + /// + [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] + public class VerticalAttribute : Attribute + { + } + + /// + /// Register the node in the NodeProvider class. The node will also be available in the node creation window. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public class NodeMenuItemAttribute : Attribute + { + public string menuTitle; + public Type onlyCompatibleWithGraph; + + /// + /// Register the node in the NodeProvider class. The node will also be available in the node creation window. + /// + /// Path in the menu, use / as folder separators + public NodeMenuItemAttribute(string menuTitle = null, Type onlyCompatibleWithGraph = null) + { + this.menuTitle = menuTitle; + this.onlyCompatibleWithGraph = onlyCompatibleWithGraph; + } + } + + /// + /// Set a custom drawer for a field. It can then be created using the FieldFactory + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + [Obsolete("You can use the standard Unity CustomPropertyDrawer instead.")] + public class FieldDrawerAttribute : Attribute + { + public Type fieldType; + + /// + /// Register a custom view for a type in the FieldFactory class + /// + /// + public FieldDrawerAttribute(Type fieldType) + { + this.fieldType = fieldType; + } + } + + /// + /// Allow you to customize the input function of a port + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + public class CustomPortInputAttribute : Attribute + { + public string fieldName; + public Type inputType; + public bool allowCast; + + /// + /// Allow you to customize the input function of a port. + /// See CustomPortsNode example in Samples. + /// + /// local field of the node + /// type of input of the port + /// if cast is allowed when connecting an edge + public CustomPortInputAttribute(string fieldName, Type inputType, bool allowCast = true) + { + this.fieldName = fieldName; + this.inputType = inputType; + this.allowCast = allowCast; + } + } + + /// + /// Allow you to customize the input function of a port + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + public class CustomPortOutputAttribute : Attribute + { + public string fieldName; + public Type outputType; + public bool allowCast; + + /// + /// Allow you to customize the output function of a port. + /// See CustomPortsNode example in Samples. + /// + /// local field of the node + /// type of input of the port + /// if cast is allowed when connecting an edge + public CustomPortOutputAttribute(string fieldName, Type outputType, bool allowCast = true) + { + this.fieldName = fieldName; + this.outputType = outputType; + this.allowCast = allowCast; + } + } + + /// + /// Allow you to modify the generated port view from a field. Can be used to generate multiple ports from one field. + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + public class CustomPortBehaviorAttribute : Attribute + { + public string fieldName; + + /// + /// 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 + /// + /// IEnumerable<PortData> MyCustomPortFunction(List<SerializableEdge> edges); + /// + /// + /// local node field name + public CustomPortBehaviorAttribute(string fieldName) + { + this.fieldName = fieldName; + } + } + + /// + /// Allow to bind a method to generate a specific set of ports based on a field type in a node + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + public class CustomPortTypeBehavior : Attribute + { + /// + /// Target type + /// + public Type type; + + public CustomPortTypeBehavior(Type type) + { + this.type = type; + } + } + + /// + /// Allow you to have a custom view for your stack nodes + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class CustomStackNodeView : Attribute + { + public Type stackNodeType; + + /// + /// Allow you to have a custom view for your stack nodes + /// + /// The type of the stack node you target + 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/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) From 78cf45faf94c4ee725f74b2da3bf1c7776f00c7d Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Thu, 3 Feb 2022 02:11:39 +0000 Subject: [PATCH 19/25] Removed debug --- .../com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs index bc6e9fc5..8b89f357 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs @@ -733,7 +733,6 @@ protected virtual void DrawField(List fieldInfoList, bool fromInspect bool showAsDrawer = !fromInspector && hasInputAttribute && inputAttribute.showAsDrawer; if ((!serializeField || isProxied) && hasInputOrOutputAttribute && !showAsDrawer) { - Debug.Log("here: " + field.Name); AddEmptyField(field, fromInspector); return; } From 5eb25aeb8c51e7a771d9d25b6cdf9ce885f84262 Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Thu, 3 Feb 2022 02:12:31 +0000 Subject: [PATCH 20/25] Minor --- Assets/Testing/DynamicNode.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Assets/Testing/DynamicNode.cs b/Assets/Testing/DynamicNode.cs index e5f6a538..38ea4318 100644 --- a/Assets/Testing/DynamicNode.cs +++ b/Assets/Testing/DynamicNode.cs @@ -127,7 +127,7 @@ protected IEnumerable ActionDataBehaviour(List edges identifier = field.fieldInfo.Name, showAsDrawer = field.inputAttribute.showAsDrawer, vertical = false, - proxiedFieldPath = "data." + field.fieldInfo.Name, + proxiedFieldPath = nameof(data) + '.' + field.fieldInfo.Name, acceptMultipleEdges = field.inputAttribute.allowMultiple, }; } From d16504e87630501dd5ea85c504d68d879db304c7 Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Thu, 3 Feb 2022 15:35:58 +0000 Subject: [PATCH 21/25] Added FieldInfo extensions for HasAttribute --- .../Editor/Views/BaseNodeView.cs | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs index 8b89f357..eb2863b2 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs @@ -256,7 +256,7 @@ void InitializeSettings() var fields = nodeTarget.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); foreach (var field in fields) - if (field.GetCustomAttribute(typeof(SettingAttribute)) != null) + if (field.HasCustomAttribute()) AddSettingField(field); } } @@ -687,9 +687,7 @@ protected virtual void DrawDefaultInspector(bool fromInspector = false) for (int i = 0; i < fields.Count; i++) { FieldInfo field = fields[i]; - InputAttribute inputAttribute = field.GetCustomAttribute(); - bool hasInputAttribute = inputAttribute != null; - if (hasInputAttribute && portsPerFieldName.ContainsKey(field.Name)) + if (field.HasCustomAttribute() && portsPerFieldName.ContainsKey(field.Name)) { // IF FIELD IS MULTI_PORT DO THIS LOOP OVER THIS WITH RELATED PORTS AND IGNORE THE BASE PORT foreach (var port in portsPerFieldName[field.Name]) @@ -712,14 +710,14 @@ protected virtual void DrawField(List fieldInfoList, bool fromInspect string fieldPath = fieldInfoList.GetPath(); //skip if the field is a node setting - if (field.GetCustomAttribute(typeof(SettingAttribute)) != null) + if (field.HasCustomAttribute()) { hasSettings = true; return; } //skip if the field is not serializable - bool serializeField = field.GetCustomAttribute(typeof(SerializeField)) != null; + bool serializeField = field.HasCustomAttribute(); if ((!field.IsPublic && !serializeField) || field.IsNotSerialized) { AddEmptyField(field, fromInspector); @@ -729,7 +727,7 @@ protected virtual void DrawField(List fieldInfoList, bool fromInspect //skip if the field is an input/output and not marked as SerializedField InputAttribute inputAttribute = field.GetCustomAttribute(); bool hasInputAttribute = inputAttribute != null; - bool hasInputOrOutputAttribute = hasInputAttribute || field.GetCustomAttribute(typeof(OutputAttribute)) != null; + bool hasInputOrOutputAttribute = hasInputAttribute || field.HasCustomAttribute(); bool showAsDrawer = !fromInspector && hasInputAttribute && inputAttribute.showAsDrawer; if ((!serializeField || isProxied) && hasInputOrOutputAttribute && !showAsDrawer) { @@ -738,7 +736,7 @@ protected virtual void DrawField(List fieldInfoList, bool fromInspect } //skip if marked with NonSerialized or HideInInspector - if (field.GetCustomAttribute(typeof(System.NonSerializedAttribute)) != null || field.GetCustomAttribute(typeof(HideInInspector)) != null) + if (field.HasCustomAttribute() || field.HasCustomAttribute()) { AddEmptyField(field, fromInspector); return; @@ -753,7 +751,7 @@ protected virtual void DrawField(List fieldInfoList, bool fromInspect } - var showInputDrawer = hasInputAttribute && field.GetCustomAttribute(typeof(SerializeField)) != null; + var showInputDrawer = hasInputAttribute && serializeField; showInputDrawer |= hasInputAttribute && inputAttribute.showAsDrawer; showInputDrawer &= !fromInspector; // We can't show a drawer in the inspector showInputDrawer &= !typeof(IList).IsAssignableFrom(field.FieldType); @@ -801,10 +799,10 @@ protected virtual void SetNodeColor(Color color) private void AddEmptyField(FieldInfo field, bool fromInspector) { - if (field.GetCustomAttribute(typeof(InputAttribute)) == null || fromInspector) + if (!field.HasCustomAttribute() || fromInspector) return; - if (field.GetCustomAttribute() != null) + if (field.HasCustomAttribute()) return; var box = new VisualElement { name = field.Name }; @@ -961,7 +959,7 @@ protected VisualElement AddControlField(List fieldInfoPath, string la if (showInputDrawer) AddEmptyField(field, false); } - var visibleCondition = field.GetCustomAttribute(typeof(VisibleIf)) as VisibleIf; + var visibleCondition = field.GetCustomAttribute(); if (visibleCondition != null) { // Check if target field exists: @@ -1239,8 +1237,18 @@ void UpdatePortsForField(string fieldName) #endregion } - public static class ListHelpers + public static class Extensions { + public static bool HasCustomAttribute(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 list, object startingValue, int index) { object currentValue = startingValue; From eb092684443c6f1b461159c8bfbafaf6c9e5832e Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Thu, 3 Feb 2022 18:19:49 +0000 Subject: [PATCH 22/25] MovedFieldInfoExtensions into its own file --- .../Editor/Views/BaseNodeView.cs | 51 ---------------- .../Runtime/Utils/FieldInfoExtension.cs | 60 +++++++++++++++++++ .../Runtime/Utils/FieldInfoExtension.cs.meta | 11 ++++ 3 files changed, 71 insertions(+), 51 deletions(-) create mode 100644 Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/FieldInfoExtension.cs create mode 100644 Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/FieldInfoExtension.cs.meta diff --git a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs index eb2863b2..11c0acc9 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs @@ -1236,55 +1236,4 @@ void UpdatePortsForField(string fieldName) #endregion } - - public static class Extensions - { - public static bool HasCustomAttribute(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 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 list, object startingValue) - { - object currentValue = startingValue; - for (int i = 0; i < list.Count; i++) - { - currentValue = list[i].GetValue(currentValue); - } - return currentValue; - - } - - public static string GetPath(this IList 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 list) - { - return list.Any(x => x == null); - } - } } \ No newline at end of file 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..4b423dcc --- /dev/null +++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/FieldInfoExtension.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UnityEngine; + +namespace GraphProcessor +{ + public static class FieldInfoExtension + { + public static bool HasCustomAttribute(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 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 list, object startingValue) + { + object currentValue = startingValue; + for (int i = 0; i < list.Count; i++) + { + currentValue = list[i].GetValue(currentValue); + } + return currentValue; + + } + + public static string GetPath(this IList 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 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: From 26dce966cb65ad2e497bc33fd22ca5039762236e Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Thu, 3 Feb 2022 20:05:45 +0000 Subject: [PATCH 23/25] Also use ShowAsDrawer attribute --- .../DefaultNodes/Nodes/DrawerFieldTestNode.cs | 26 +++++++++---------- Assets/Testing/SequenceData.cs | 2 +- .../Editor/Views/BaseNodeView.cs | 4 +-- .../Runtime/Graph/Attributes.cs | 2 +- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Assets/Examples/DefaultNodes/Nodes/DrawerFieldTestNode.cs b/Assets/Examples/DefaultNodes/Nodes/DrawerFieldTestNode.cs index 2f80e584..5753595c 100644 --- a/Assets/Examples/DefaultNodes/Nodes/DrawerFieldTestNode.cs +++ b/Assets/Examples/DefaultNodes/Nodes/DrawerFieldTestNode.cs @@ -8,46 +8,46 @@ public class DrawerFieldTestNode : BaseNode { - [Input(name = "Vector 4", showAsDrawer = true)] + [Input(name = "Vector 4"), ShowAsDrawer] public Vector4 vector4; - [Input(name = "Vector 3", showAsDrawer = true)] + [Input(name = "Vector 3"), ShowAsDrawer] public Vector3 vector3; - [Input(name = "Vector 2", showAsDrawer = true)] + [Input(name = "Vector 2"), ShowAsDrawer] public Vector2 vector2; - [Input(name = "Float", showAsDrawer = true)] + [Input(name = "Float"), ShowAsDrawer] public float floatInput; - [Input(name = "Vector 3 Int", showAsDrawer = true)] + [Input(name = "Vector 3 Int"), ShowAsDrawer] public Vector3Int vector3Int; - [Input(name = "Vector 2 Int", showAsDrawer = true)] + [Input(name = "Vector 2 Int"), ShowAsDrawer] public Vector2Int vector2Int; - [Input(name = "Int", showAsDrawer = true)] + [Input(name = "Int"), ShowAsDrawer] public int intInput; [Input(name = "Empty")] public int intInput2; - [Input(name = "String", showAsDrawer = true)] + [Input(name = "String"), ShowAsDrawer] public string stringInput; - [Input(name = "Color", showAsDrawer = true)] + [Input(name = "Color"), ShowAsDrawer] new public Color color; - [Input(name = "Game Object", showAsDrawer = true)] + [Input(name = "Game Object"), ShowAsDrawer] public GameObject gameObject; - [Input(name = "Animation Curve", showAsDrawer = true)] + [Input(name = "Animation Curve"), ShowAsDrawer] public AnimationCurve animationCurve; - [Input(name = "Rigidbody", showAsDrawer = true)] + [Input(name = "Rigidbody"), ShowAsDrawer] public Rigidbody rigidbody; - [Input("Layer Mask", showAsDrawer = true)] + [Input("Layer Mask"), ShowAsDrawer] public LayerMask layerMask; public override string name => "Drawer Field Test"; diff --git a/Assets/Testing/SequenceData.cs b/Assets/Testing/SequenceData.cs index 4358fc00..2130a6e5 100644 --- a/Assets/Testing/SequenceData.cs +++ b/Assets/Testing/SequenceData.cs @@ -15,7 +15,7 @@ public class SequenceName : List [Serializable] public class ConditionalName : BaseIsConditional { - [SerializeField, Input("Name", true)] string name; + [SerializeField, Input("Name"), ShowAsDrawer] string name; public string Name => name; } } diff --git a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs index 11c0acc9..a5b1a80e 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs @@ -728,7 +728,7 @@ protected virtual void DrawField(List fieldInfoList, bool fromInspect InputAttribute inputAttribute = field.GetCustomAttribute(); bool hasInputAttribute = inputAttribute != null; bool hasInputOrOutputAttribute = hasInputAttribute || field.HasCustomAttribute(); - bool showAsDrawer = !fromInspector && hasInputAttribute && inputAttribute.showAsDrawer; + bool showAsDrawer = !fromInspector && hasInputAttribute && (inputAttribute.showAsDrawer || field.HasCustomAttribute()); if ((!serializeField || isProxied) && hasInputOrOutputAttribute && !showAsDrawer) { AddEmptyField(field, fromInspector); @@ -752,7 +752,7 @@ protected virtual void DrawField(List fieldInfoList, bool fromInspect var showInputDrawer = hasInputAttribute && serializeField; - showInputDrawer |= hasInputAttribute && inputAttribute.showAsDrawer; + showInputDrawer |= showAsDrawer; showInputDrawer &= !fromInspector; // We can't show a drawer in the inspector showInputDrawer &= !typeof(IList).IsAssignableFrom(field.FieldType); diff --git a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Graph/Attributes.cs b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Graph/Attributes.cs index 4150e1cf..339d7a1b 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Graph/Attributes.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Graph/Attributes.cs @@ -227,7 +227,7 @@ public ShowInInspector(bool showInNode = false) } } - [Obsolete("ShowAsDrawer attribute is deprecated. Please use the InputAttribute showAsDrawer field.")] + // [Obsolete("ShowAsDrawer attribute is deprecated. Please use the InputAttribute showAsDrawer field.")] [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] public class ShowAsDrawer : Attribute From e897ff5426e4d349d7ebf5118edf7cdbbc425756 Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Thu, 3 Feb 2022 20:23:33 +0000 Subject: [PATCH 24/25] Created Example Added GetNonRelayEdges List extensions --- .../Nodes/DynamicPortGeneration.meta} | 2 +- .../DynamicPortGeneration}/DynamicNode.cs | 26 +------- .../DynamicNode.cs.meta | 0 .../DynamicNodeWithOutput.cs | 0 .../DynamicNodeWithOutput.cs.meta | 0 .../Nodes/DynamicPortGeneration/Namer.cs | 14 ++++ .../DynamicPortGeneration/Namer.cs.meta} | 0 .../Nodes/DynamicPortGeneration/NamerNode.cs} | 5 +- .../DynamicPortGeneration/NamerNode.cs.meta} | 0 Assets/Testing/BaseIsConditional.cs | 35 ---------- Assets/Testing/BaseIsConditional.cs.meta | 11 ---- Assets/Testing/BoolVariable.cs | 12 ---- Assets/Testing/BoolVariable.cs.meta | 11 ---- .../DelegateEvent.VarObjectEventArgs.cs | 16 ----- .../DelegateEvent.VarObjectEventArgs.cs.meta | 11 ---- Assets/Testing/DelegateEvent.cs | 25 -------- Assets/Testing/ExpandableSOAttribute.cs | 15 ----- Assets/Testing/ExpandableSOAttribute.cs.meta | 11 ---- Assets/Testing/MyInputAttribute.cs | 22 ------- Assets/Testing/MyInputAttribute.cs.meta | 11 ---- Assets/Testing/PortUpdaterNode.cs | 12 ---- Assets/Testing/PortUpdaterNode.cs.meta | 11 ---- Assets/Testing/SequenceData.cs | 64 ------------------- Assets/Testing/SharedVariable.cs | 33 ---------- Assets/Testing/SharedVariable.cs.meta | 11 ---- .../Testing/ValueChangedCallbackAttribute.cs | 23 ------- .../ValueChangedCallbackAttribute.cs.meta | 11 ---- .../Runtime/Utils/FieldInfoExtension.cs | 2 - .../Runtime/Utils/SerializedEdgeExtension.cs | 28 ++++++++ .../Utils/SerializedEdgeExtension.cs.meta} | 2 +- 30 files changed, 48 insertions(+), 376 deletions(-) rename Assets/{Testing.meta => Examples/DefaultNodes/Nodes/DynamicPortGeneration.meta} (77%) rename Assets/{Testing => Examples/DefaultNodes/Nodes/DynamicPortGeneration}/DynamicNode.cs (80%) rename Assets/{Testing => Examples/DefaultNodes/Nodes/DynamicPortGeneration}/DynamicNode.cs.meta (100%) rename Assets/{Testing => Examples/DefaultNodes/Nodes/DynamicPortGeneration}/DynamicNodeWithOutput.cs (100%) rename Assets/{Testing => Examples/DefaultNodes/Nodes/DynamicPortGeneration}/DynamicNodeWithOutput.cs.meta (100%) create mode 100644 Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/Namer.cs rename Assets/{Testing/SequenceData.cs.meta => Examples/DefaultNodes/Nodes/DynamicPortGeneration/Namer.cs.meta} (100%) rename Assets/{Testing/ConditionalNameNode.cs => Examples/DefaultNodes/Nodes/DynamicPortGeneration/NamerNode.cs} (52%) rename Assets/{Testing/ConditionalNameNode.cs.meta => Examples/DefaultNodes/Nodes/DynamicPortGeneration/NamerNode.cs.meta} (100%) delete mode 100644 Assets/Testing/BaseIsConditional.cs delete mode 100644 Assets/Testing/BaseIsConditional.cs.meta delete mode 100644 Assets/Testing/BoolVariable.cs delete mode 100644 Assets/Testing/BoolVariable.cs.meta delete mode 100644 Assets/Testing/DelegateEvent.VarObjectEventArgs.cs delete mode 100644 Assets/Testing/DelegateEvent.VarObjectEventArgs.cs.meta delete mode 100644 Assets/Testing/DelegateEvent.cs delete mode 100644 Assets/Testing/ExpandableSOAttribute.cs delete mode 100644 Assets/Testing/ExpandableSOAttribute.cs.meta delete mode 100644 Assets/Testing/MyInputAttribute.cs delete mode 100644 Assets/Testing/MyInputAttribute.cs.meta delete mode 100644 Assets/Testing/PortUpdaterNode.cs delete mode 100644 Assets/Testing/PortUpdaterNode.cs.meta delete mode 100644 Assets/Testing/SequenceData.cs delete mode 100644 Assets/Testing/SharedVariable.cs delete mode 100644 Assets/Testing/SharedVariable.cs.meta delete mode 100644 Assets/Testing/ValueChangedCallbackAttribute.cs delete mode 100644 Assets/Testing/ValueChangedCallbackAttribute.cs.meta create mode 100644 Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/SerializedEdgeExtension.cs rename Assets/{Testing/DelegateEvent.cs.meta => com.alelievr.NodeGraphProcessor/Runtime/Utils/SerializedEdgeExtension.cs.meta} (83%) diff --git a/Assets/Testing.meta b/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration.meta similarity index 77% rename from Assets/Testing.meta rename to Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration.meta index 56738015..0c62d05a 100644 --- a/Assets/Testing.meta +++ b/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: db98db52a808b6a11bc0439ea50f7d72 +guid: d1aa8d1481699370f82eb69770a82239 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Assets/Testing/DynamicNode.cs b/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/DynamicNode.cs similarity index 80% rename from Assets/Testing/DynamicNode.cs rename to Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/DynamicNode.cs index 38ea4318..a1141f5c 100644 --- a/Assets/Testing/DynamicNode.cs +++ b/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/DynamicNode.cs @@ -7,12 +7,11 @@ using System; [System.Serializable] -public abstract class DynamicNode : PortUpdaterNode +public abstract class DynamicNode : BaseNode { [Input("Action Data", true)] public Dictionary> actionData = new Dictionary>(); - [ExpandableSO, ValueChangedCallback(nameof(actionData), nameof(OnDataChanged))] public T data; public override bool needsInspector => true; @@ -38,20 +37,6 @@ protected virtual void UpdateActionWithCustomPortData() continue; } - if (field.inputAttribute is MyInputAttribute) - { - MyInputAttribute inputAttribute = field.inputAttribute as MyInputAttribute; - if (inputAttribute.InputType != null && field.fieldInfo.FieldType.GetInterfaces().Any(x => x == typeof(IList))) - { - IList list = Activator.CreateInstance(field.fieldInfo.FieldType) as IList; - foreach (var value in actionDataClone[field.fieldInfo.Name]) - list.Add(value); - - field.fieldInfo.SetValue(data, list); - continue; - } - } - field.fieldInfo.SetValue(data, actionDataClone[field.fieldInfo.Name][0]); } @@ -97,7 +82,7 @@ protected void PullInputs(List connectedEdges) FieldPortInfo field = GetFieldPortInfo(connectedEdges.ElementAt(0).inputPortIdentifier); if (actionData == null) actionData = new Dictionary>(); - foreach (var edge in connectedEdges.GetNonRelayEdges().OrderByInputAttribute(field.inputAttribute)) + foreach (var edge in connectedEdges) { if (!actionData.ContainsKey(field.fieldInfo.Name)) actionData.Add(field.fieldInfo.Name, new List()); @@ -113,13 +98,6 @@ protected IEnumerable ActionDataBehaviour(List edges { Type displayType = field.fieldInfo.FieldType; - if (field.inputAttribute is MyInputAttribute) - { - MyInputAttribute inputAttribute = field.inputAttribute as MyInputAttribute; - if (inputAttribute.InputType != null) - displayType = inputAttribute.InputType; - } - yield return new PortData { displayName = field.inputAttribute.name, diff --git a/Assets/Testing/DynamicNode.cs.meta b/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/DynamicNode.cs.meta similarity index 100% rename from Assets/Testing/DynamicNode.cs.meta rename to Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/DynamicNode.cs.meta diff --git a/Assets/Testing/DynamicNodeWithOutput.cs b/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/DynamicNodeWithOutput.cs similarity index 100% rename from Assets/Testing/DynamicNodeWithOutput.cs rename to Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/DynamicNodeWithOutput.cs diff --git a/Assets/Testing/DynamicNodeWithOutput.cs.meta b/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/DynamicNodeWithOutput.cs.meta similarity index 100% rename from Assets/Testing/DynamicNodeWithOutput.cs.meta rename to Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/DynamicNodeWithOutput.cs.meta 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/Testing/SequenceData.cs.meta b/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/Namer.cs.meta similarity index 100% rename from Assets/Testing/SequenceData.cs.meta rename to Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/Namer.cs.meta diff --git a/Assets/Testing/ConditionalNameNode.cs b/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/NamerNode.cs similarity index 52% rename from Assets/Testing/ConditionalNameNode.cs rename to Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/NamerNode.cs index 3eb7cc5f..32a86ea3 100644 --- a/Assets/Testing/ConditionalNameNode.cs +++ b/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/NamerNode.cs @@ -3,10 +3,9 @@ using UnityEngine; using GraphProcessor; using System.Linq; -using static SequenceName; -[System.Serializable, NodeMenuItem("Custom/ConditionalNameNode")] -public class ConditionalNameNode : DynamicNodeWithOutput +[System.Serializable, NodeMenuItem("Custom/ProxiedInputsNode")] +public class NamerNode : DynamicNodeWithOutput { public override string name => "ConditionalNameNode"; } diff --git a/Assets/Testing/ConditionalNameNode.cs.meta b/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/NamerNode.cs.meta similarity index 100% rename from Assets/Testing/ConditionalNameNode.cs.meta rename to Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/NamerNode.cs.meta diff --git a/Assets/Testing/BaseIsConditional.cs b/Assets/Testing/BaseIsConditional.cs deleted file mode 100644 index 5ae6efb3..00000000 --- a/Assets/Testing/BaseIsConditional.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Collections.Generic; -using GraphProcessor; -using UnityEngine; - -[System.Serializable] -public class BaseIsConditional -{ - [SerializeField, Input("True Checks", false, true)] protected BoolVariable[] trueChecks; - public BoolVariable[] TrueChecks { get => trueChecks; } - - [SerializeField, Input("False Checks", false, true)] protected BoolVariable[] falseChecks; - public BoolVariable[] FalseChecks { get => falseChecks; } - - public bool IsAvailable() - { - foreach (BoolVariable check in trueChecks) - { - if (!check.RuntimeValue) return false; - } - - foreach (BoolVariable check in falseChecks) - { - if (check.RuntimeValue) return false; - } - return true; - } -} - -public interface IIsConditional -{ - List TrueChecks { get; } - List FalseChecks { get; } - - bool IsAvailable(); -} \ No newline at end of file diff --git a/Assets/Testing/BaseIsConditional.cs.meta b/Assets/Testing/BaseIsConditional.cs.meta deleted file mode 100644 index 7f07e8bd..00000000 --- a/Assets/Testing/BaseIsConditional.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 9947347622c831651bc3a306795104fa -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Testing/BoolVariable.cs b/Assets/Testing/BoolVariable.cs deleted file mode 100644 index 01b8eb90..00000000 --- a/Assets/Testing/BoolVariable.cs +++ /dev/null @@ -1,12 +0,0 @@ -using UnityEngine; - -[CreateAssetMenu(fileName = "BoolVariable", menuName = "Teletext/Variables/Bool")] -public class BoolVariable : SharedVariable -{ - public override bool RuntimeValue { get => GetEvaluation(); set => base.RuntimeValue = value; } - - protected virtual bool GetEvaluation() - { - return base.RuntimeValue; - } -} diff --git a/Assets/Testing/BoolVariable.cs.meta b/Assets/Testing/BoolVariable.cs.meta deleted file mode 100644 index 0d4d6ccf..00000000 --- a/Assets/Testing/BoolVariable.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: c2ed7d6a9e49992bca5a969a1db59655 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Testing/DelegateEvent.VarObjectEventArgs.cs b/Assets/Testing/DelegateEvent.VarObjectEventArgs.cs deleted file mode 100644 index 7a5d32ec..00000000 --- a/Assets/Testing/DelegateEvent.VarObjectEventArgs.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; - -public partial class DelegateEvent -{ - public class VarObjectEventArgs : EventArgs - { - private T value; - public T Value => value; - - public VarObjectEventArgs(T value) - { - this.value = value; - } - } -} - diff --git a/Assets/Testing/DelegateEvent.VarObjectEventArgs.cs.meta b/Assets/Testing/DelegateEvent.VarObjectEventArgs.cs.meta deleted file mode 100644 index dfe97910..00000000 --- a/Assets/Testing/DelegateEvent.VarObjectEventArgs.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 1d0825e47caa8faf0b509d6413c06002 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Testing/DelegateEvent.cs b/Assets/Testing/DelegateEvent.cs deleted file mode 100644 index 392b04e9..00000000 --- a/Assets/Testing/DelegateEvent.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using UnityEngine; - -public partial class DelegateEvent : ScriptableObject -{ - public delegate void EventHandler(object sender, VarObjectEventArgs args); - public event EventHandler Event; - - public void AddListener(EventHandler listener) - { - Event += listener; - } - - public void RemoveListener(EventHandler listener) - { - Event -= listener; - } - - public void Raise(T argument) - { - if (Event != null) - Event.Invoke(this, new VarObjectEventArgs(argument)); - } -} diff --git a/Assets/Testing/ExpandableSOAttribute.cs b/Assets/Testing/ExpandableSOAttribute.cs deleted file mode 100644 index 00c2b219..00000000 --- a/Assets/Testing/ExpandableSOAttribute.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using UnityEditor; -using UnityEngine; - -/// -/// Attribute takes the insides of a set ScriptableObject and display it. -/// -public class ExpandableSOAttribute : PropertyAttribute -{ - - /// - /// Required implementation of the interface. - /// - public ExpandableSOAttribute() { } -} diff --git a/Assets/Testing/ExpandableSOAttribute.cs.meta b/Assets/Testing/ExpandableSOAttribute.cs.meta deleted file mode 100644 index 198eb13f..00000000 --- a/Assets/Testing/ExpandableSOAttribute.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: bf7bd1d3b461c28ca869d43343391f92 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Testing/MyInputAttribute.cs b/Assets/Testing/MyInputAttribute.cs deleted file mode 100644 index d9b488d4..00000000 --- a/Assets/Testing/MyInputAttribute.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using GraphProcessor; -using UnityEngine; - -[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] -public class MyInputAttribute : InputAttribute -{ - public readonly Type InputType; - public readonly InputSortType SortType; - - public MyInputAttribute(string name = null, bool allowMultiple = false, InputSortType sortType = InputSortType.FIRST_IN, Type inputType = null) - { - this.name = name; - this.allowMultiple = allowMultiple; - this.SortType = sortType; - this.InputType = inputType; - } -} - -public enum InputSortType { FIRST_IN, POSITION_Y } diff --git a/Assets/Testing/MyInputAttribute.cs.meta b/Assets/Testing/MyInputAttribute.cs.meta deleted file mode 100644 index 0da6a56d..00000000 --- a/Assets/Testing/MyInputAttribute.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 0189acdd3b39929d6a8b0a57c3e507bd -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Testing/PortUpdaterNode.cs b/Assets/Testing/PortUpdaterNode.cs deleted file mode 100644 index 5e4cb1ce..00000000 --- a/Assets/Testing/PortUpdaterNode.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Diagnostics; -using System.Reflection; -using GraphProcessor; -using UnityEngine; - -public abstract class PortUpdaterNode : BaseNode -{ - protected virtual void OnDataChanged(FieldInfo originField, UnityEditor.SerializedProperty serializedProperty) - { - UpdatePortsForFieldLocal(originField.Name); - } -} diff --git a/Assets/Testing/PortUpdaterNode.cs.meta b/Assets/Testing/PortUpdaterNode.cs.meta deleted file mode 100644 index 6aaa9538..00000000 --- a/Assets/Testing/PortUpdaterNode.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 67b3cddba608059109efbb060f6504fe -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Testing/SequenceData.cs b/Assets/Testing/SequenceData.cs deleted file mode 100644 index 2130a6e5..00000000 --- a/Assets/Testing/SequenceData.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System.Reflection; -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using GraphProcessor; -using UnityEngine; - - -[Serializable] -public class SequenceName : List -{ - public string CurrentName => FindLast(x => x.IsAvailable()).Name; - - [Serializable] - public class ConditionalName : BaseIsConditional - { - [SerializeField, Input("Name"), ShowAsDrawer] string name; - public string Name => name; - } -} - -public static class ListHelpers -{ - public static string GetCurrentName(this List list) - { - return list.FindLast(x => x.IsAvailable()).Name; - } - - public static IList OrderByInputAttribute(this IList edges, InputAttribute inputAttribute) - { - if (inputAttribute is MyInputAttribute) - { - switch ((inputAttribute as MyInputAttribute).SortType) - { - case InputSortType.POSITION_Y: - edges = edges.OrderBy(x => x.outputNode.position.y).ToList(); - break; - } - } - return edges; - } - - public static IList GetNonRelayEdges(this IList edges) - { - List nonrelayEdges = new List(); - 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; - } -} \ No newline at end of file diff --git a/Assets/Testing/SharedVariable.cs b/Assets/Testing/SharedVariable.cs deleted file mode 100644 index 47d04810..00000000 --- a/Assets/Testing/SharedVariable.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Data.SqlTypes; -using UnityEngine; - -/// -/// -/// CallbackObject callback is used as a OnValueChanged event. -/// -/// -public abstract class SharedVariable : DelegateEvent -{ - [SerializeField] protected T InitialValue; - - protected T runtimeValue; - public virtual T RuntimeValue - { - get => runtimeValue; - set - { - if (runtimeValue == null && value == null) return; - else if (runtimeValue != null && runtimeValue.Equals(value)) return; - else if (value != null && value.Equals(runtimeValue)) return; - - runtimeValue = value; - Raise(value); - } - } - - // Initialize runtime value with editor's value - protected virtual void OnEnable() - { - RuntimeValue = InitialValue; - } -} \ No newline at end of file diff --git a/Assets/Testing/SharedVariable.cs.meta b/Assets/Testing/SharedVariable.cs.meta deleted file mode 100644 index a4acfa26..00000000 --- a/Assets/Testing/SharedVariable.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 4ace1829c0b27489fa70482f81ddbed7 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Testing/ValueChangedCallbackAttribute.cs b/Assets/Testing/ValueChangedCallbackAttribute.cs deleted file mode 100644 index 792f2d0e..00000000 --- a/Assets/Testing/ValueChangedCallbackAttribute.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using UnityEditor; -using UnityEngine; -/// -/// Check if a given field has changed and if it has calls a method. -/// -/// -public class ValueChangedCallbackAttribute : PropertyAttribute -{ - string fieldName; - public string FieldName => fieldName; - - string methodName; - public string MethodName => methodName; - - /// - /// - public ValueChangedCallbackAttribute(string fieldName, string methodName) - { - this.fieldName = fieldName; - this.methodName = methodName; - } -} \ No newline at end of file diff --git a/Assets/Testing/ValueChangedCallbackAttribute.cs.meta b/Assets/Testing/ValueChangedCallbackAttribute.cs.meta deleted file mode 100644 index f59e492c..00000000 --- a/Assets/Testing/ValueChangedCallbackAttribute.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: e9b5389fba495b15191a31e041bfedba -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/FieldInfoExtension.cs b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/FieldInfoExtension.cs index 4b423dcc..b9e975de 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/FieldInfoExtension.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/FieldInfoExtension.cs @@ -1,9 +1,7 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; -using UnityEngine; namespace GraphProcessor { 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 GetNonRelayEdges(this IList edges) + { + List nonrelayEdges = new List(); + 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/Testing/DelegateEvent.cs.meta b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/SerializedEdgeExtension.cs.meta similarity index 83% rename from Assets/Testing/DelegateEvent.cs.meta rename to Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/SerializedEdgeExtension.cs.meta index 61429f8c..13097a7b 100644 --- a/Assets/Testing/DelegateEvent.cs.meta +++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/SerializedEdgeExtension.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: b336ffaa1cc79bc929414981fe23fab5 +guid: 1b6986467dd851f8b8153d3bf6b93994 MonoImporter: externalObjects: {} serializedVersion: 2 From e91597b59a4c2e4066abaf53d0859a61b0ff9659 Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Thu, 3 Feb 2022 20:36:17 +0000 Subject: [PATCH 25/25] Added IsProxied property to portdata --- .../Editor/Views/BaseNodeView.cs | 10 ++++------ .../Runtime/Elements/NodePort.cs | 2 ++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs index a5b1a80e..7519bd5b 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs @@ -689,12 +689,10 @@ protected virtual void DrawDefaultInspector(bool fromInspector = false) FieldInfo field = fields[i]; if (field.HasCustomAttribute() && portsPerFieldName.ContainsKey(field.Name)) { - // IF FIELD IS MULTI_PORT DO THIS LOOP OVER THIS WITH RELATED PORTS AND IGNORE THE BASE PORT foreach (var port in portsPerFieldName[field.Name]) { - bool isProxied = !String.IsNullOrEmpty(port.portData.proxiedFieldPath); - string fieldPath = isProxied ? port.portData.proxiedFieldPath : port.fieldName; - DrawField(GetFieldInfoPath(fieldPath), fromInspector, isProxied); + string fieldPath = port.portData.IsProxied ? port.portData.proxiedFieldPath : port.fieldName; + DrawField(GetFieldInfoPath(fieldPath), fromInspector, port.portData.IsProxied); } } else @@ -1004,7 +1002,7 @@ protected void AddSettingField(FieldInfo field) internal void OnPortConnected(PortView port) { - string fieldName = !String.IsNullOrEmpty(port.portData.proxiedFieldPath) ? port.portData.proxiedFieldPath : port.fieldName; + string fieldName = port.portData.IsProxied ? port.portData.proxiedFieldPath : port.fieldName; if (port.direction == Direction.Input && inputContainerElement?.Q(fieldName) != null) inputContainerElement.Q(fieldName).AddToClassList("empty"); @@ -1017,7 +1015,7 @@ internal void OnPortConnected(PortView port) internal void OnPortDisconnected(PortView port) // { - string fieldName = !String.IsNullOrEmpty(port.portData.proxiedFieldPath) ? port.portData.proxiedFieldPath : port.fieldName; + string fieldName = port.portData.IsProxied ? port.portData.proxiedFieldPath : port.fieldName; if (port.direction == Direction.Input && inputContainerElement?.Q(fieldName) != null) { diff --git a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/NodePort.cs b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/NodePort.cs index c852fa46..0dff9976 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/NodePort.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/NodePort.cs @@ -52,6 +52,8 @@ public class PortData : IEquatable /// public bool vertical; + public bool IsProxied => !String.IsNullOrEmpty(proxiedFieldPath); + public bool Equals(PortData other) { return identifier == other.identifier