From 5e68b6bcdc3ca415aec1a91b7c692a3b47a428b2 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Sat, 14 Oct 2017 16:19:24 +0200 Subject: [PATCH] Big update. Removal of scripts now also clears dependant nodes, to avoid null objects. NodePorts now support fallback values. UI Changes. node.graph is now serialized as well. --- Example/ExampleNodeGraph.asset | Bin 8768 -> 7160 bytes Example/ExampleNodeGraph.asset.meta | 4 +- Example/Nodes/ConstantValue.cs | 13 -- Example/Nodes/DisplayValue.cs | 2 +- Example/Nodes/ExampleNodeBase.cs | 2 +- Example/Nodes/MathNode.cs | 8 +- Example/Nodes/TestNode.cs | 19 +++ ...ConstantValue.cs.meta => TestNode.cs.meta} | 4 +- Scripts/Editor/NodeEditor.cs | 103 +-------------- Scripts/Editor/NodeEditorAction.cs | 1 + Scripts/Editor/NodeEditorAssetModProcessor.cs | 35 ++++++ .../NodeEditorAssetModProcessor.cs.meta | 12 ++ Scripts/Editor/NodeEditorGUI.cs | 4 + Scripts/Editor/NodeEditorGUILayout.cs | 117 +++++++++++++++++- Scripts/Editor/NodeEditorResources.cs | 2 +- Scripts/Node.cs | 8 +- 16 files changed, 204 insertions(+), 130 deletions(-) delete mode 100644 Example/Nodes/ConstantValue.cs create mode 100644 Example/Nodes/TestNode.cs rename Example/Nodes/{ConstantValue.cs.meta => TestNode.cs.meta} (76%) create mode 100644 Scripts/Editor/NodeEditorAssetModProcessor.cs create mode 100644 Scripts/Editor/NodeEditorAssetModProcessor.cs.meta diff --git a/Example/ExampleNodeGraph.asset b/Example/ExampleNodeGraph.asset index d42f7cabc53ed6a7b03612f2df84cbb363ee1940..b09f280fbf868ff10cafd68ec6f5b6d1e1e955e9 100644 GIT binary patch literal 7160 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 C##-qB diff --git a/Example/ExampleNodeGraph.asset.meta b/Example/ExampleNodeGraph.asset.meta index 6ec3d2f..12fe78e 100644 --- a/Example/ExampleNodeGraph.asset.meta +++ b/Example/ExampleNodeGraph.asset.meta @@ -1,6 +1,6 @@ fileFormatVersion: 2 -guid: e8c47bc953732464a9bf3a76273d99ef -timeCreated: 1507916591 +guid: 614b670274b902e44bba59ba861eb1bd +timeCreated: 1507988229 licenseType: Free NativeFormatImporter: mainObjectFileID: 11400000 diff --git a/Example/Nodes/ConstantValue.cs b/Example/Nodes/ConstantValue.cs deleted file mode 100644 index b0b99ce..0000000 --- a/Example/Nodes/ConstantValue.cs +++ /dev/null @@ -1,13 +0,0 @@ -[System.Serializable] -public class ConstantValue : ExampleNodeBase { - public float a; - [Output] public float value; - - protected override void Init() { - name = "Constant Value"; - } - - public override object GetValue(NodePort port) { - return a; - } -} diff --git a/Example/Nodes/DisplayValue.cs b/Example/Nodes/DisplayValue.cs index d19dd21..ef82b56 100644 --- a/Example/Nodes/DisplayValue.cs +++ b/Example/Nodes/DisplayValue.cs @@ -1,6 +1,6 @@ using UnityEngine; public class DisplayValue : ExampleNodeBase { - [Input] public float value; + [Input(false)] public float value; } diff --git a/Example/Nodes/ExampleNodeBase.cs b/Example/Nodes/ExampleNodeBase.cs index c47cff0..c7ad9ff 100644 --- a/Example/Nodes/ExampleNodeBase.cs +++ b/Example/Nodes/ExampleNodeBase.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using UnityEngine; -public class ExampleNodeBase : Node { +public abstract class ExampleNodeBase : Node { public float GetInputFloat(string fieldName) { float result = 0f; diff --git a/Example/Nodes/MathNode.cs b/Example/Nodes/MathNode.cs index 475698d..fef8659 100644 --- a/Example/Nodes/MathNode.cs +++ b/Example/Nodes/MathNode.cs @@ -2,9 +2,9 @@ [System.Serializable] public class MathNode : ExampleNodeBase { - [Input] public float c; - [Input] public float b; - [Output] public float result; + [Input(true)] public float a; + [Input(true)] public float b; + [Output(false)] public float result; public enum MathType { Add, Subtract, Multiply, Divide} public MathType mathType = MathType.Add; @@ -13,7 +13,7 @@ public class MathNode : ExampleNodeBase { } public override object GetValue(NodePort port) { - float a = GetInputFloat("c"); + float a = GetInputFloat("a"); float b = GetInputFloat("b"); switch(port.fieldName) { diff --git a/Example/Nodes/TestNode.cs b/Example/Nodes/TestNode.cs new file mode 100644 index 0000000..900ddf4 --- /dev/null +++ b/Example/Nodes/TestNode.cs @@ -0,0 +1,19 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +public class TestNode : Node { + + public int i; + public float f; + public double d; + public long l; + public bool b; + public string s; + public Rect r; + public Vector2 v2; + public Vector3 v3; + public Vector4 v4; + public Color col; + public AnimationCurve a; +} diff --git a/Example/Nodes/ConstantValue.cs.meta b/Example/Nodes/TestNode.cs.meta similarity index 76% rename from Example/Nodes/ConstantValue.cs.meta rename to Example/Nodes/TestNode.cs.meta index e836841..93b01d2 100644 --- a/Example/Nodes/ConstantValue.cs.meta +++ b/Example/Nodes/TestNode.cs.meta @@ -1,6 +1,6 @@ fileFormatVersion: 2 -guid: 707240ce8955a0240a7c0c4177d83bf5 -timeCreated: 1505937586 +guid: 47bfd6281f41a10459bcb45bb4cd03f3 +timeCreated: 1507990362 licenseType: Free MonoImporter: serializedVersion: 2 diff --git a/Scripts/Editor/NodeEditor.cs b/Scripts/Editor/NodeEditor.cs index 8da947b..e1777eb 100644 --- a/Scripts/Editor/NodeEditor.cs +++ b/Scripts/Editor/NodeEditor.cs @@ -14,8 +14,7 @@ public class NodeEditor { /// Port handle positions need to be returned to the NodeEditorWindow public virtual void OnNodeGUI(out Dictionary portPositions) { DrawDefaultHeaderGUI(); - DrawDefaultNodePortsGUI(out portPositions); - DrawDefaultNodeBodyGUI(); + DrawDefaultNodeBodyGUI(out portPositions); } protected void DrawDefaultHeaderGUI() { @@ -23,40 +22,15 @@ public class NodeEditor { GUILayout.Label(target.name, NodeEditorResources.styles.nodeHeader, GUILayout.Height(30)); } - /// Draws standard editors for all fields marked with or - protected void DrawDefaultNodePortsGUI(out Dictionary portPositions) { + /// Draws standard field editors for all public fields + protected void DrawDefaultNodeBodyGUI(out Dictionary portPositions) { portPositions = new Dictionary(); - Event e = Event.current; - - GUILayout.BeginHorizontal(); - - //Inputs - GUILayout.BeginVertical(); - for (int i = 0; i < target.InputCount; i++) { - Vector2 handlePoint = DrawNodePortGUI(target.inputs[i]); - portPositions.Add(target.inputs[i], handlePoint); - } - GUILayout.EndVertical(); - - //Outputs - GUILayout.BeginVertical(); - for (int i = 0; i < target.OutputCount; i++) { - Vector2 handlePoint = DrawNodePortGUI(target.outputs[i]); - portPositions.Add(target.outputs[i], handlePoint); - } - GUILayout.EndVertical(); - - GUILayout.EndHorizontal(); - } - - /// Draws standard field editors for all public fields - protected void DrawDefaultNodeBodyGUI() { FieldInfo[] fields = GetInspectorFields(target); for (int i = 0; i < fields.Length; i++) { object[] fieldAttribs = fields[i].GetCustomAttributes(false); - if (NodeEditorUtilities.HasAttrib(fieldAttribs) || NodeEditorUtilities.HasAttrib(fieldAttribs)) continue; - DrawFieldInfoDrawerGUI(fields[i]); + if (fields[i].Name == "graph" || fields[i].Name == "rect") continue; + NodeEditorGUILayout.PropertyField(target, fields[i], portPositions); } } @@ -97,73 +71,6 @@ public class NodeEditor { return node.GetType().GetFields().Where(f => f.IsPublic || f.GetCustomAttributes(typeof(SerializeField), false) != null).ToArray(); } - private void DrawFieldInfoDrawerGUI(FieldInfo fieldInfo) { - Type fieldType = fieldInfo.FieldType; - string fieldName = fieldInfo.Name; - string fieldPrettyName = fieldName.PrettifyCamelCase(); - object fieldValue = fieldInfo.GetValue(target); - object[] fieldAttribs = fieldInfo.GetCustomAttributes(false); - - HeaderAttribute headerAttrib; - if (NodeEditorUtilities.GetAttrib(fieldAttribs, out headerAttrib)) { - EditorGUILayout.LabelField(headerAttrib.header); - } - - EditorGUI.BeginChangeCheck(); - if (fieldType == typeof(int)) { - fieldValue = NodeEditorGUILayout.IntField(fieldPrettyName, (int) fieldValue); - } else if (fieldType == typeof(bool)) { - fieldValue = EditorGUILayout.Toggle(fieldPrettyName, (bool) fieldValue); - } else if (fieldType.IsEnum) { - fieldValue = NodeEditorGUILayout.EnumField(fieldPrettyName, (Enum)fieldValue); - } else if (fieldType == typeof(string)) { - - if (fieldName == "name") return; //Ignore 'name' - TextAreaAttribute textAreaAttrib; - if (NodeEditorUtilities.GetAttrib(fieldAttribs, out textAreaAttrib)) { - fieldValue = EditorGUILayout.TextArea(fieldValue != null ? (string) fieldValue : ""); - } else - fieldValue = EditorGUILayout.TextField(fieldPrettyName, fieldValue != null ? (string) fieldValue : ""); - } else if (fieldType == typeof(Rect)) { - if (fieldName == "rect") return; //Ignore 'rect' - fieldValue = EditorGUILayout.RectField(fieldPrettyName, (Rect) fieldValue); - } else if (fieldType == typeof(float)) { - fieldValue = NodeEditorGUILayout.FloatField(fieldPrettyName, (float) fieldValue); - } else if (fieldType == typeof(double)) { - fieldValue = NodeEditorGUILayout.DoubleField(fieldPrettyName, (double) fieldValue); - } else if (fieldType == typeof(long)) { - fieldValue = NodeEditorGUILayout.LongField(fieldPrettyName, (long) fieldValue); - } else if (fieldType == typeof(Vector2)) { - fieldValue = EditorGUILayout.Vector2Field(fieldPrettyName, (Vector2) fieldValue); - } else if (fieldType == typeof(Vector3)) { - fieldValue = EditorGUILayout.Vector3Field(new GUIContent(fieldPrettyName), (Vector3) fieldValue); - } else if (fieldType == typeof(Vector4)) { - fieldValue = EditorGUILayout.Vector4Field(fieldPrettyName, (Vector4) fieldValue); - } else if (fieldType == typeof(Color)) { - Rect rect = EditorGUILayout.GetControlRect(); - rect.width *= 0.5f; - EditorGUI.LabelField(rect, fieldPrettyName); - rect.x += rect.width; - fieldValue = EditorGUI.ColorField(rect, (Color) fieldValue); - } else if (fieldType == typeof(AnimationCurve)) { - Rect rect = EditorGUILayout.GetControlRect(); - rect.width *= 0.5f; - EditorGUI.LabelField(rect, fieldPrettyName); - rect.x += rect.width; - - AnimationCurve curve = fieldValue != null ? (AnimationCurve) fieldValue : new AnimationCurve(); - curve = EditorGUI.CurveField(rect, curve); - if (fieldValue != curve) fieldInfo.SetValue(target, curve); - } else if (fieldType.IsSubclassOf(typeof(UnityEngine.Object)) || fieldType == typeof(UnityEngine.Object)) { - if (fieldName == "graph") return; //Ignore 'graph' - fieldValue = EditorGUILayout.ObjectField(fieldPrettyName, (UnityEngine.Object) fieldValue, fieldType, true); - } - if (EditorGUI.EndChangeCheck()) { - fieldInfo.SetValue(target, fieldValue); - EditorUtility.SetDirty(target); - } - } - public virtual int GetWidth() { return 200; } diff --git a/Scripts/Editor/NodeEditorAction.cs b/Scripts/Editor/NodeEditorAction.cs index aeed708..95b4600 100644 --- a/Scripts/Editor/NodeEditorAction.cs +++ b/Scripts/Editor/NodeEditorAction.cs @@ -137,6 +137,7 @@ public partial class NodeEditorWindow { Vector2 mousePos = Event.current.mousePosition; Node newHoverNode = null; foreach (Node node in graph.nodes) { + if (node == null) return; //Get node position Vector2 nodePos = GridToWindowPosition(node.rect.position); Rect windowRect = new Rect(nodePos, new Vector2(node.rect.size.x / zoom, node.rect.size.y / zoom)); diff --git a/Scripts/Editor/NodeEditorAssetModProcessor.cs b/Scripts/Editor/NodeEditorAssetModProcessor.cs new file mode 100644 index 0000000..a1756f5 --- /dev/null +++ b/Scripts/Editor/NodeEditorAssetModProcessor.cs @@ -0,0 +1,35 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +public class NodeEditorAssetModProcessor : UnityEditor.AssetModificationProcessor { + public static AssetDeleteResult OnWillDeleteAsset(string path, RemoveAssetOptions options) { + UnityEngine.Object obj = AssetDatabase.LoadAssetAtPath(path); + + if (!(obj is UnityEditor.MonoScript)) return AssetDeleteResult.DidNotDelete; + + UnityEditor.MonoScript script = obj as UnityEditor.MonoScript; + System.Type scriptType = script.GetClass(); + + if (scriptType != typeof(Node) && !scriptType.IsSubclassOf(typeof(Node))) return AssetDeleteResult.DidNotDelete; + + //Find ScriptableObjects using this script + string[] guids = AssetDatabase.FindAssets("t:" + scriptType); + for (int i = 0; i < guids.Length; i++) { + string assetpath = AssetDatabase.GUIDToAssetPath(guids[i]); + Object[] objs = AssetDatabase.LoadAllAssetRepresentationsAtPath(assetpath); + for (int k = 0; k < objs.Length; k++) { + Node node = objs[k] as Node; + if (node.GetType() == scriptType) { + if (node != null && node.graph != null) { + Debug.LogWarning(node.name + " of " + node.graph + " depended on deleted script and has been removed automatically.", node.graph); + node.graph.RemoveNode(node); + } + } + } + + } + return AssetDeleteResult.DidNotDelete; + } +} \ No newline at end of file diff --git a/Scripts/Editor/NodeEditorAssetModProcessor.cs.meta b/Scripts/Editor/NodeEditorAssetModProcessor.cs.meta new file mode 100644 index 0000000..057198b --- /dev/null +++ b/Scripts/Editor/NodeEditorAssetModProcessor.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: e515e86efe8160243a68b7c06d730c9c +timeCreated: 1507982232 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/Editor/NodeEditorGUI.cs b/Scripts/Editor/NodeEditorGUI.cs index 74b0a86..cc7fca0 100644 --- a/Scripts/Editor/NodeEditorGUI.cs +++ b/Scripts/Editor/NodeEditorGUI.cs @@ -116,6 +116,8 @@ public partial class NodeEditorWindow { /// Draws all connections public void DrawConnections() { foreach (Node node in graph.nodes) { + //If a null node is found, return. This can happen if the nodes associated script is deleted. It is currently not possible in Unity to delete a null asset. + if (node == null) return; for (int i = 0; i < node.OutputCount; i++) { NodePort output = node.outputs[i]; @@ -126,6 +128,7 @@ public partial class NodeEditorWindow { NodePort input = output.GetConnection(k); if (input == null) return; //If a script has been updated and the port doesn't exist, it is removed and null is returned. If this happens, return. + if (!_portConnectionPoints.ContainsKey(input)) return; Vector2 to = _portConnectionPoints[input].center; DrawConnection(from, to, NodeEditorPreferences.GetTypeColor(output.type)); } @@ -148,6 +151,7 @@ public partial class NodeEditorWindow { BeginZoomed(position, zoom); foreach (Node node in graph.nodes) { + if (node == null) return; NodeEditor nodeEditor = GetNodeEditor(node.GetType()); nodeEditor.target = node; diff --git a/Scripts/Editor/NodeEditorGUILayout.cs b/Scripts/Editor/NodeEditorGUILayout.cs index e4d3b39..9683c70 100644 --- a/Scripts/Editor/NodeEditorGUILayout.cs +++ b/Scripts/Editor/NodeEditorGUILayout.cs @@ -1,6 +1,8 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Linq; +using System.Reflection; using UnityEditor; using UnityEngine; @@ -9,6 +11,115 @@ public class NodeEditorGUILayout { private static double tempValue; + public static object PortField(string label, object value, NodePort port, bool fallback, out Vector2 portPosition) { + if (fallback) value = PropertyField(label, value); + else EditorGUILayout.LabelField(label); + + Rect rect = GUILayoutUtility.GetLastRect(); + if (port.direction == NodePort.IO.Input) rect.position = rect.position - new Vector2(16, 0); + if (port.direction == NodePort.IO.Output) rect.position = rect.position + new Vector2(rect.width, 0); + rect.size = new Vector2(16, 16); + + Color col = GUI.color; + GUI.color = new Color32(90, 97, 105, 255); + GUI.DrawTexture(rect, NodeEditorResources.dotOuter); + GUI.color = NodeEditorPreferences.GetTypeColor(port.type); + GUI.DrawTexture(rect, NodeEditorResources.dot); + GUI.color = col; + portPosition = rect.center; + + return value; + } + + public static object PropertyField(Node target, FieldInfo fieldInfo, Dictionary portPositions) { + Type fieldType = fieldInfo.FieldType; + string fieldName = fieldInfo.Name; + string fieldPrettyName = fieldName.PrettifyCamelCase(); + object fieldValue = fieldInfo.GetValue(target); + object[] fieldAttribs = fieldInfo.GetCustomAttributes(false); + + HeaderAttribute headerAttrib; + if (NodeEditorUtilities.GetAttrib(fieldAttribs, out headerAttrib)) { + EditorGUILayout.LabelField(headerAttrib.header); + } + + Node.OutputAttribute outputAttrib; + Node.InputAttribute inputAttrib; + + EditorGUI.BeginChangeCheck(); + if (NodeEditorUtilities.GetAttrib(fieldAttribs, out inputAttrib)) { + NodePort port = target.GetPortByFieldName(fieldName); + Vector2 portPos; + fieldValue = PortField(fieldPrettyName, fieldValue, port, inputAttrib.fallback, out portPos); + portPositions.Add(port, portPos); + } else if (NodeEditorUtilities.GetAttrib(fieldAttribs, out outputAttrib)) { + NodePort port = target.GetPortByFieldName(fieldName); + Vector2 portPos; + fieldValue = PortField(fieldPrettyName, fieldValue, port, outputAttrib.fallback, out portPos); + portPositions.Add(port, portPos); + } else { + fieldValue = PropertyField(fieldPrettyName, fieldValue); + } + + if (EditorGUI.EndChangeCheck()) { + fieldInfo.SetValue(target, fieldValue); + EditorUtility.SetDirty(target); + } + return fieldValue; + } + + public static object PropertyField(string label, object value) { + if (value is int) return IntField(label, (int) value); + else if (value is float) return FloatField(label, (float) value); + else if (value is double) return DoubleField(label, (double) value); + else if (value is long) return LongField(label, (long) value); + else if (value is bool) return Toggle(label, (bool) value); + else if (value is Enum) return EnumField(label, (Enum) value); + else if (value is string) return TextField(label, (string) value); + else if (value is Rect) return RectField(label, (Rect) value); + else if (value is Vector2) return Vector2Field(label, (Vector2) value); + else if (value is Vector3) return Vector3Field(label, (Vector3) value); + else if (value is Vector4) return Vector4Field(label, (Vector4) value); + else if (value is Color) return ColorField(label, (Color) value); + else if (value is AnimationCurve) return CurveField(label, (AnimationCurve) value); + else if (value == null || value.GetType().IsSubclassOf(typeof(UnityEngine.Object)) || value.GetType() == typeof(UnityEngine.Object)) return ObjectField(label, (UnityEngine.Object) value); + else return value; + } + public static Rect GetRect(string label) { + Rect rect = EditorGUILayout.GetControlRect(); + rect.width *= 0.5f; + EditorGUI.LabelField(rect, label); + rect.x += rect.width; + return rect; + } + public static UnityEngine.Object ObjectField(string label, UnityEngine.Object value) { + return EditorGUI.ObjectField(GetRect(label), value, value.GetType(), true); + } + public static AnimationCurve CurveField(string label, AnimationCurve value) { + if (value == null) value = new AnimationCurve(); + return EditorGUI.CurveField(GetRect(label), value); + } + public static Color ColorField(string label, Color value) { + return EditorGUI.ColorField(GetRect(label), value); + } + public static Vector4 Vector4Field(string label, Vector4 value) { + return EditorGUILayout.Vector4Field(label, value); + } + public static Vector3 Vector3Field(string label, Vector3 value) { + return EditorGUILayout.Vector3Field(label, value); + } + public static Vector2 Vector2Field(string label, Vector2 value) { + return EditorGUILayout.Vector2Field(label, value); + } + public static Rect RectField(string label, Rect value) { + return EditorGUILayout.RectField(label, value); + } + public static string TextField(string label, string value) { + return EditorGUI.TextField(GetRect(label), value); + } + public static bool Toggle(string label, bool value) { + return EditorGUI.Toggle(GetRect(label), value); + } public static int IntField(string label, int value) { GUIUtility.GetControlID(FocusType.Passive); Rect rect = EditorGUILayout.GetControlRect(); @@ -119,10 +230,6 @@ public class NodeEditorGUILayout { } } public static Enum EnumField(string label, Enum value) { - Rect rect = EditorGUILayout.GetControlRect(); - rect.width *= 0.5f; - EditorGUI.LabelField(rect, label); - rect.x += rect.width; - return EditorGUI.EnumPopup(rect, value); + return EditorGUI.EnumPopup(GetRect(label), value); } } \ No newline at end of file diff --git a/Scripts/Editor/NodeEditorResources.cs b/Scripts/Editor/NodeEditorResources.cs index 0b3bf3b..7ec60ad 100644 --- a/Scripts/Editor/NodeEditorResources.cs +++ b/Scripts/Editor/NodeEditorResources.cs @@ -48,7 +48,7 @@ public static class NodeEditorResources { nodeBody = new GUIStyle(); nodeBody.normal.background = NodeEditorResources.nodeBody; nodeBody.border = new RectOffset(32, 32, 32, 32); - nodeBody.padding = new RectOffset(10, 10, 2, 2); + nodeBody.padding = new RectOffset(16, 16, 4, 6); } } diff --git a/Scripts/Node.cs b/Scripts/Node.cs index 69f39e3..fd6e23a 100644 --- a/Scripts/Node.cs +++ b/Scripts/Node.cs @@ -9,7 +9,7 @@ using UnityEngine; public abstract class Node : ScriptableObject { /// Name of the node - [NonSerialized] public NodeGraph graph; + [SerializeField] public NodeGraph graph; [SerializeField] public Rect rect = new Rect(0, 0, 200, 200); /// Input s. It is recommended not to modify these at hand. Instead, see [SerializeField] public List inputs = new List(); @@ -99,12 +99,14 @@ public abstract class Node : ScriptableObject { [AttributeUsage(AttributeTargets.Field, AllowMultiple = true)] public class InputAttribute : Attribute { - public InputAttribute() { } + public bool fallback; + public InputAttribute(bool fallback) { this.fallback = fallback; } } [AttributeUsage(AttributeTargets.Field, AllowMultiple = true)] public class OutputAttribute : Attribute { - public OutputAttribute() { } + public bool fallback; + public OutputAttribute(bool fallback) { this.fallback = fallback; } } private void GetPorts() {