From 2fa0be14c15097e191cab5ab54bc28b7fe3f7bc1 Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Mon, 14 Feb 2022 16:28:59 +0000 Subject: [PATCH] Added ability to use Properties as Outputs OuputAttribute now can target properties Replaced most instances of FieldInfo with MemberInfo --- .../Editor/Utils/NodeProvider.cs | 10 +- .../Editor/Views/BaseNodeView.cs | 93 +++-- .../Editor/Views/PortView.cs | 12 +- .../Runtime/Elements/BaseNode.cs | 20 +- .../Runtime/Elements/NodePort.cs | 41 +- .../Runtime/Elements/RelayNode.cs | 378 +++++++++--------- .../Runtime/Graph/Attributes.cs | 2 +- .../Runtime/Utils/FieldInfoExtension.cs | 100 ++++- 8 files changed, 374 insertions(+), 282 deletions(-) diff --git a/Assets/com.alelievr.NodeGraphProcessor/Editor/Utils/NodeProvider.cs b/Assets/com.alelievr.NodeGraphProcessor/Editor/Utils/NodeProvider.cs index 11f64cc..c36722f 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Editor/Utils/NodeProvider.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Editor/Utils/NodeProvider.cs @@ -102,10 +102,13 @@ static void BuildCacheForNode(Type nodeType, NodeDescriptions targetDescription, targetDescription.nodePerMenuTitle[attr.menuTitle] = nodeType; } - foreach (var field in nodeType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) + foreach (var field in nodeType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) + .Cast() + .Concat(nodeType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) + ) { if (field.GetCustomAttribute() == null && field.GetCustomAttributes().Any(c => c is InputAttribute || c is OutputAttribute)) - targetDescription.slotTypes.Add(field.FieldType); + targetDescription.slotTypes.Add(field.GetUnderlyingType()); } ProvideNodePortCreationDescription(nodeType, targetDescription, graph); @@ -197,7 +200,7 @@ void AddPort(NodePort p, bool input) targetDescription.nodeCreatePortDescription.Add(new PortDescription { nodeType = nodeType, - portType = p.portData.displayType ?? p.fieldInfo.FieldType, + portType = p.portData.displayType ?? p.fieldInfo.GetUnderlyingType(), isInput = input, portFieldName = p.fieldName, portDisplayName = p.portData.displayName ?? p.fieldName, @@ -400,6 +403,7 @@ bool IsPortCompatible(PortDescription description) return true; } + } } } diff --git a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs index 551e460..47405b0 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs @@ -360,7 +360,7 @@ public PortView GetPortViewFromFieldName(string fieldName, string identifier) } - public PortView AddPort(FieldInfo fieldInfo, Direction direction, BaseEdgeConnectorListener listener, PortData portData) + public PortView AddPort(MemberInfo fieldInfo, Direction direction, BaseEdgeConnectorListener listener, PortData portData) { PortView p = CreatePortView(direction, fieldInfo, portData, listener); @@ -397,7 +397,7 @@ public PortView AddPort(FieldInfo fieldInfo, Direction direction, BaseEdgeConnec return p; } - protected virtual PortView CreatePortView(Direction direction, FieldInfo fieldInfo, PortData portData, BaseEdgeConnectorListener listener) + protected virtual PortView CreatePortView(Direction direction, MemberInfo fieldInfo, PortData portData, BaseEdgeConnectorListener listener) => PortView.CreatePortView(direction, fieldInfo, portData, listener); public void InsertPort(PortView portView, int index) @@ -664,7 +664,7 @@ public virtual void Disable() { } Dictionary> visibleConditions = new Dictionary>(); Dictionary hideElementIfConnected = new Dictionary(); - Dictionary> fieldControlsMap = new Dictionary>(); + Dictionary> fieldControlsMap = new Dictionary>(); protected void AddInputContainer() { @@ -677,6 +677,8 @@ protected void AddInputContainer() protected virtual void DrawDefaultInspector(bool fromInspector = false) { var fields = nodeTarget.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) + .Cast() + .Concat(nodeTarget.GetType().GetProperties(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(); @@ -686,27 +688,34 @@ protected virtual void DrawDefaultInspector(bool fromInspector = false) for (int i = 0; i < fields.Count; i++) { - FieldInfo field = fields[i]; + MemberInfo field = fields[i]; if (field.HasCustomAttribute() && portsPerFieldName.ContainsKey(field.Name)) { foreach (var port in portsPerFieldName[field.Name]) { string fieldPath = port.portData.IsProxied ? port.portData.proxiedFieldPath : port.fieldName; - DrawField(new FieldInfoWithPath(FieldInfoWithPath.GetFieldInfoPath(fieldPath, nodeTarget)), fromInspector, port.portData.IsProxied); + DrawField(new MemberInfoWithPath(MemberInfoWithPath.GetMemberInfoPath(fieldPath, nodeTarget)), fromInspector, port.portData.IsProxied); } } else { - DrawField(new FieldInfoWithPath(field), fromInspector); + DrawField(new MemberInfoWithPath(field), fromInspector); } } } - protected virtual void DrawField(FieldInfoWithPath fieldInfoWithPath, bool fromInspector, bool isProxied = false) + protected virtual void DrawField(MemberInfoWithPath fieldInfoWithPath, bool fromInspector, bool isProxied = false) { - FieldInfo field = fieldInfoWithPath.Field; + MemberInfo member = fieldInfoWithPath.Member; string fieldPath = fieldInfoWithPath.Path; + if (!member.IsField()) + { + return; + } + + FieldInfo field = member as FieldInfo; + //skip if the field is a node setting if (field.HasCustomAttribute()) { @@ -752,7 +761,7 @@ protected virtual void DrawField(FieldInfoWithPath fieldInfoWithPath, bool fromI var showInputDrawer = hasInputAttribute && serializeField; showInputDrawer |= showAsDrawer; showInputDrawer &= !fromInspector; // We can't show a drawer in the inspector - showInputDrawer &= !typeof(IList).IsAssignableFrom(field.FieldType); + showInputDrawer &= !typeof(IList).IsAssignableFrom(field.GetUnderlyingType()); string displayName = ObjectNames.NicifyVariableName(field.Name); @@ -778,7 +787,7 @@ protected virtual void SetNodeColor(Color color) titleContainer.style.borderBottomWidth = new StyleFloat(color.a > 0 ? 5f : 0f); } - private void AddEmptyField(FieldInfo field, bool fromInspector) + private void AddEmptyField(MemberInfo field, bool fromInspector) { if (!field.HasCustomAttribute() || fromInspector) return; @@ -808,7 +817,7 @@ void UpdateFieldVisibility(string fieldName, object newValue) } } - void UpdateOtherFieldValueSpecific(FieldInfoWithPath field, object newValue) + void UpdateOtherFieldValueSpecific(MemberInfoWithPath field, object newValue) { foreach (var inputField in fieldControlsMap[field]) { @@ -819,16 +828,16 @@ void UpdateOtherFieldValueSpecific(FieldInfoWithPath field, object newValue) } static MethodInfo specificUpdateOtherFieldValue = typeof(BaseNodeView).GetMethod(nameof(UpdateOtherFieldValueSpecific), BindingFlags.NonPublic | BindingFlags.Instance); - void UpdateOtherFieldValue(FieldInfoWithPath info, object newValue) + void UpdateOtherFieldValue(MemberInfoWithPath info, object newValue) { // Warning: Keep in sync with FieldFactory CreateField - var fieldType = info.Field.FieldType.IsSubclassOf(typeof(UnityEngine.Object)) ? typeof(UnityEngine.Object) : info.Field.FieldType; + var fieldType = info.Member.GetUnderlyingType().IsSubclassOf(typeof(UnityEngine.Object)) ? typeof(UnityEngine.Object) : info.Member.GetUnderlyingType(); var genericUpdate = specificUpdateOtherFieldValue.MakeGenericMethod(fieldType); genericUpdate.Invoke(this, new object[] { info, newValue }); } - object GetInputFieldValueSpecific(FieldInfoWithPath field) + object GetInputFieldValueSpecific(MemberInfoWithPath field) { if (fieldControlsMap.TryGetValue(field, out var list)) { @@ -842,10 +851,10 @@ object GetInputFieldValueSpecific(FieldInfoWithPath field) } static MethodInfo specificGetValue = typeof(BaseNodeView).GetMethod(nameof(GetInputFieldValueSpecific), BindingFlags.NonPublic | BindingFlags.Instance); - object GetInputFieldValue(FieldInfoWithPath info) + object GetInputFieldValue(MemberInfoWithPath info) { // Warning: Keep in sync with FieldFactory CreateField - var fieldType = info.Field.FieldType.IsSubclassOf(typeof(UnityEngine.Object)) ? typeof(UnityEngine.Object) : info.Field.FieldType; + var fieldType = info.Member.GetUnderlyingType().IsSubclassOf(typeof(UnityEngine.Object)) ? typeof(UnityEngine.Object) : info.Member.GetUnderlyingType(); var genericUpdate = specificGetValue.MakeGenericMethod(fieldType); return genericUpdate.Invoke(this, new object[] { info }); @@ -853,8 +862,8 @@ object GetInputFieldValue(FieldInfoWithPath info) protected VisualElement AddControlField(string fieldPath, string label = null, bool showInputDrawer = false, Action valueChangedCallback = null) { - List fieldInfoPath = FieldInfoWithPath.GetFieldInfoPath(fieldPath, nodeTarget); - return AddControlField(new FieldInfoWithPath(fieldInfoPath.Last(), fieldPath), label, showInputDrawer, valueChangedCallback); + List fieldInfoPath = MemberInfoWithPath.GetMemberInfoPath(fieldPath, nodeTarget); + return AddControlField(new MemberInfoWithPath(fieldInfoPath.Last(), fieldPath), label, showInputDrawer, valueChangedCallback); } Regex s_ReplaceNodeIndexPropertyPath = new Regex(@"(^nodes.Array.data\[)(\d+)(\])"); internal void SyncSerializedPropertyPathes() @@ -885,9 +894,9 @@ protected SerializedProperty FindSerializedProperty(string fieldName) return owner.serializedGraph.FindProperty("nodes").GetArrayElementAtIndex(i).FindPropertyRelative(fieldName); } - protected VisualElement AddControlField(FieldInfoWithPath fieldInfoWithPath, string label = null, bool showInputDrawer = false, Action valueChangedCallback = null) + protected VisualElement AddControlField(MemberInfoWithPath fieldInfoWithPath, string label = null, bool showInputDrawer = false, Action valueChangedCallback = null) { - var field = fieldInfoWithPath.Field; + var field = fieldInfoWithPath.Member; var fieldPath = fieldInfoWithPath.Path; if (field == null) @@ -901,12 +910,12 @@ protected VisualElement AddControlField(FieldInfoWithPath fieldInfoWithPath, str element.AddToClassList("DrawerField_2020_3"); #endif - if (typeof(IList).IsAssignableFrom(field.FieldType)) + if (typeof(IList).IsAssignableFrom(field.GetUnderlyingType())) EnableSyncSelectionBorderHeight(); element.RegisterValueChangeCallback(e => { - UpdateFieldVisibility(field.Name, FieldInfoWithPath.GetFieldInfoPath(fieldPath, nodeTarget).GetFinalValue(nodeTarget)); + UpdateFieldVisibility(field.Name, MemberInfoWithPath.GetMemberInfoPath(fieldPath, nodeTarget).GetFinalValue(nodeTarget)); valueChangedCallback?.Invoke(); NotifyNodeChanged(); }); @@ -967,7 +976,7 @@ protected VisualElement AddControlField(FieldInfoWithPath fieldInfoWithPath, str void UpdateFieldValues() { foreach (var kp in fieldControlsMap) - UpdateOtherFieldValue(kp.Key, FieldInfoWithPath.GetFieldInfoPath(kp.Key.Path, nodeTarget).GetFinalValue(nodeTarget)); + UpdateOtherFieldValue(kp.Key, MemberInfoWithPath.GetMemberInfoPath(kp.Key.Path, nodeTarget).GetFinalValue(nodeTarget)); } protected void AddSettingField(FieldInfo field) @@ -1008,7 +1017,7 @@ internal void OnPortDisconnected(PortView port) // if (port.direction == Direction.Input && inputContainerElement?.Q(fieldName) != null) { inputContainerElement.Q(fieldName).RemoveFromClassList("empty"); - var fieldInfoWithPath = new FieldInfoWithPath(fieldName, nodeTarget); + var fieldInfoWithPath = new MemberInfoWithPath(fieldName, nodeTarget); var valueBeforeConnection = GetInputFieldValue(fieldInfoWithPath); @@ -1221,57 +1230,57 @@ void UpdatePortsForField(string fieldName) #endregion } - public class FieldInfoWithPath : IEquatable + public class MemberInfoWithPath : IEquatable { - private FieldInfo field; - public FieldInfo Field => field; + private MemberInfo member; + public MemberInfo Member => member; private string path; public string Path => path; - public FieldInfoWithPath(FieldInfo field, string path) + public MemberInfoWithPath(MemberInfo field, string path) { - this.field = field; + this.member = field; this.path = path; } - public FieldInfoWithPath(string path, object startingValue) + public MemberInfoWithPath(string path, object startingValue) { - this.field = GetFieldInfoPath(path, startingValue).Last(); + this.member = GetMemberInfoPath(path, startingValue).Last(); this.path = path; } - public FieldInfoWithPath(List fieldInfos) + public MemberInfoWithPath(List fieldInfos) { - this.field = fieldInfos.Last(); + this.member = fieldInfos.Last(); this.path = fieldInfos.GetPath(); } - public FieldInfoWithPath(FieldInfo field) + public MemberInfoWithPath(MemberInfo field) { - this.field = field; + this.member = field; this.path = field.Name; } - public bool Equals(FieldInfoWithPath other) + public bool Equals(MemberInfoWithPath other) { - return field == other.field + return member == other.member && path == other.path; } public void SetValue(object startingValue, object finalValue) { - GetFieldInfoPath(path, startingValue).SetValue(startingValue, finalValue); + GetMemberInfoPath(path, startingValue).SetValue(startingValue, finalValue); } - public static List GetFieldInfoPath(string path, object startValue) + public static List GetMemberInfoPath(string path, object startValue) { string[] pathArray = path.Split('.'); - List fieldInfoPath = new List(); + List fieldInfoPath = new List(); object value = startValue; 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)); + MemberInfo info = value.GetType().GetMember(pathArray[i], BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)[0]; + fieldInfoPath.Add(info); if (i + 1 < pathArray.Length) { value = fieldInfoPath[i].GetValue(value); diff --git a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/PortView.cs b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/PortView.cs index a714699..856c739 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/PortView.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/PortView.cs @@ -11,7 +11,7 @@ namespace GraphProcessor public class PortView : Port { public string fieldName => fieldInfo.Name; - public Type fieldType => fieldInfo.FieldType; + public Type fieldType => fieldInfo.GetUnderlyingType(); public new Type portType; public BaseNodeView owner { get; private set; } public PortData portData; @@ -19,7 +19,7 @@ public class PortView : Port public event Action OnConnected; public event Action OnDisconnected; - protected FieldInfo fieldInfo; + protected MemberInfo fieldInfo; protected BaseEdgeConnectorListener listener; string userPortStyleFile = "PortViewTypes"; @@ -30,12 +30,12 @@ public class PortView : Port 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) + protected PortView(Direction direction, MemberInfo fieldInfo, PortData portData, BaseEdgeConnectorListener edgeConnectorListener) + : base(portData.vertical ? Orientation.Vertical : Orientation.Horizontal, direction, Capacity.Multi, portData.displayType ?? fieldInfo.GetUnderlyingType()) { this.fieldInfo = fieldInfo; this.listener = edgeConnectorListener; - this.portType = portData.displayType ?? fieldInfo.FieldType; + this.portType = portData.displayType ?? fieldInfo.GetUnderlyingType(); this.portData = portData; this.portName = fieldName; @@ -53,7 +53,7 @@ protected PortView(Direction direction, FieldInfo fieldInfo, PortData portData, this.tooltip = portData.tooltip; } - public static PortView CreatePortView(Direction direction, FieldInfo fieldInfo, PortData portData, BaseEdgeConnectorListener edgeConnectorListener) + public static PortView CreatePortView(Direction direction, MemberInfo fieldInfo, PortData portData, BaseEdgeConnectorListener edgeConnectorListener) { var pv = new PortView(direction, fieldInfo, portData, edgeConnectorListener); pv.m_EdgeConnector = new BaseEdgeConnector(edgeConnectorListener); diff --git a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/BaseNode.cs b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/BaseNode.cs index 6a8dadc..6babdf0 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/BaseNode.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/BaseNode.cs @@ -145,7 +145,7 @@ internal class NodeFieldInformation { public string name; public string fieldName; - public FieldInfo info; + public MemberInfo info; public bool input; public bool isMultiple; public string tooltip; @@ -153,7 +153,7 @@ internal class NodeFieldInformation public CustomPortBehaviorDelegate behavior; public bool vertical; - public NodeFieldInformation(FieldInfo info, string name, bool input, bool isMultiple, string tooltip, bool showAsDrawer, bool vertical, CustomPortBehaviorDelegate behavior) + public NodeFieldInformation(MemberInfo info, string name, bool input, bool isMultiple, string tooltip, bool showAsDrawer, bool vertical, CustomPortBehaviorDelegate behavior) { this.input = input; this.isMultiple = isMultiple; @@ -302,9 +302,9 @@ public virtual void InitializePorts() /// /// List of fields to sort /// Sorted list of fields - public virtual IEnumerable OverrideFieldOrder(IEnumerable fields) + public virtual IEnumerable OverrideFieldOrder(IEnumerable fields) { - long GetFieldInheritanceLevel(FieldInfo f) + long GetFieldInheritanceLevel(MemberInfo f) { int level = 0; var t = f.DeclaringType; @@ -397,7 +397,7 @@ public bool UpdatePortsForFieldLocal(string fieldName, bool sendPortUpdatedEvent } else { - var customPortTypeBehavior = customPortTypeBehaviorMap[fieldInfo.info.FieldType]; + var customPortTypeBehavior = customPortTypeBehaviorMap[fieldInfo.info.GetUnderlyingType()]; foreach (var portData in customPortTypeBehavior(fieldName, fieldInfo.name, fieldInfo.info.GetValue(this))) AddPortData(portData); @@ -471,7 +471,7 @@ bool HasCustomBehavior(NodeFieldInformation info) if (info.behavior != null) return true; - if (customPortTypeBehaviorMap.ContainsKey(info.info.FieldType)) + if (customPortTypeBehaviorMap.ContainsKey(info.info.GetUnderlyingType())) return true; return false; @@ -547,9 +547,12 @@ internal void DisableInternal() public virtual FieldInfo[] GetNodeFields() => GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + public virtual PropertyInfo[] GetNodeProperties() + => GetType().GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + void InitializeInOutDatas() { - var fields = GetNodeFields(); + var fields = GetNodeFields().Cast().Concat(GetNodeProperties()).ToArray(); var methods = GetType().GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); foreach (var field in fields) @@ -698,7 +701,7 @@ 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; + portData.displayType = nodeFields[fieldName].info.GetUnderlyingType(); if (input) inputPorts.Add(new NodePort(this, fieldName, portData)); @@ -890,3 +893,4 @@ public void ClearMessages() #endregion } } + diff --git a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/NodePort.cs b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/NodePort.cs index 78e82a8..6c8d081 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/NodePort.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/NodePort.cs @@ -97,7 +97,7 @@ public class NodePort /// /// The fieldInfo from the fieldName /// - public FieldInfo fieldInfo; + public MemberInfo fieldInfo; /// /// Data of the port /// @@ -145,6 +145,10 @@ public NodePort(BaseNode owner, object fieldOwner, string fieldName, PortData po fieldInfo = fieldOwner.GetType().GetField( fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + if (fieldInfo == null) + fieldInfo = fieldOwner.GetType().GetProperty( + fieldName, + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); customPortIOMethod = CustomPortIO.GetCustomPortMethod(owner.GetType(), fieldName); } @@ -183,8 +187,13 @@ 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); + MemberInfo inputField = edge.inputNode.GetType().GetField(edge.inputFieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + if (inputField == null) + inputField = edge.inputNode.GetType().GetProperty(edge.inputFieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + MemberInfo outputField = edge.outputNode.GetType().GetField(edge.outputFieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + if (outputField == null) + outputField = edge.outputNode.GetType().GetProperty(edge.outputFieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + Type inType, outType; #if DEBUG_LAMBDA @@ -208,18 +217,18 @@ PushDataDelegate CreatePushDataDelegateForEdge(SerializableEdge edge) // We keep slow checks inside the editor #if UNITY_EDITOR - if (!BaseGraph.TypesAreConnectable(inputField.FieldType, outputField.FieldType)) + if (!BaseGraph.TypesAreConnectable(inputField.GetUnderlyingType(), outputField.GetUnderlyingType())) { - 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 conversions"); + Debug.LogError("Can't convert from " + inputField.GetUnderlyingType() + " to " + outputField.GetUnderlyingType() + ", you must specify a custom port function (i.e CustomPortInput or CustomPortOutput) for non-implicit conversions"); return null; } #endif - Expression inputParamField = Expression.Field(Expression.Constant(edge.inputNode), inputField); - Expression outputParamField = Expression.Field(Expression.Constant(edge.outputNode), outputField); + Expression inputParamField = Expression.PropertyOrField(Expression.Constant(edge.inputNode), inputField.Name); + Expression outputParamField = Expression.PropertyOrField(Expression.Constant(edge.outputNode), outputField.Name); - inType = edge.inputPort.portData.displayType ?? inputField.FieldType; - outType = edge.outputPort.portData.displayType ?? outputField.FieldType; + inType = edge.inputPort.portData.displayType ?? inputField.GetUnderlyingType(); + outType = edge.outputPort.portData.displayType ?? outputField.GetUnderlyingType(); // If there is a user defined conversion function, then we call it if (TypeAdapter.AreAssignable(outType, inType)) @@ -229,10 +238,10 @@ PushDataDelegate CreatePushDataDelegateForEdge(SerializableEdge edge) 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 conversion method return type is not always assignable directly: - outputParamField = Expression.Convert(outputParamField, inputField.FieldType); + outputParamField = Expression.Convert(outputParamField, inputField.GetUnderlyingType()); } else // otherwise we cast - outputParamField = Expression.Convert(outputParamField, inputField.FieldType); + outputParamField = Expression.Convert(outputParamField, inputField.GetUnderlyingType()); BinaryExpression assign = Expression.Assign(inputParamField, outputParamField); return Expression.Lambda(assign).Compile(); @@ -294,15 +303,15 @@ public void PushData() public void ResetToDefault() { // Clear lists, set classes to null and struct to default value. - if (typeof(IList).IsAssignableFrom(fieldInfo.FieldType)) + if (typeof(IList).IsAssignableFrom(fieldInfo.GetUnderlyingType())) (fieldInfo.GetValue(fieldOwner) as IList)?.Clear(); - else if (fieldInfo.FieldType.GetTypeInfo().IsClass) + else if (fieldInfo.GetUnderlyingType().GetTypeInfo().IsClass) fieldInfo.SetValue(fieldOwner, null); else { try { - fieldInfo.SetValue(fieldOwner, Activator.CreateInstance(fieldInfo.FieldType)); + fieldInfo.SetValue(fieldOwner, Activator.CreateInstance(fieldInfo.GetUnderlyingType())); } catch { } // Catch types that don't have any constructors } @@ -332,8 +341,8 @@ public void PullData() // We do an extra conversion 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); + if (TypeAdapter.AreAssignable(fieldInfo.GetUnderlyingType(), passThroughObject.GetType())) + passThroughObject = TypeAdapter.Convert(passThroughObject, fieldInfo.GetUnderlyingType()); fieldInfo.SetValue(fieldOwner, passThroughObject); } diff --git a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/RelayNode.cs b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/RelayNode.cs index 45bce43..d195a46 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/RelayNode.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/RelayNode.cs @@ -8,191 +8,195 @@ [System.Serializable, NodeMenuItem("Utils/Relay")] public class RelayNode : BaseNode { - const string packIdentifier = "_Pack"; - - [HideInInspector] - public struct PackedRelayData - { - public List values; - public List names; - public List types; - } - - [Input(name = "In")] - public PackedRelayData input; - - [Output(name = "Out")] - public PackedRelayData output; - - public bool unpackOutput = false; - public bool packInput = false; - public int inputEdgeCount = 0; - [System.NonSerialized] - int outputIndex = 0; - - SerializableType inputType = new SerializableType(typeof(object)); - - const int k_MaxPortSize = 14; - - protected override void Process() - { - outputIndex = 0; - output = input; - } - - public override string layoutStyle => "GraphProcessorStyles/RelayNode"; - - [CustomPortInput(nameof(input), typeof(object), true)] - public void GetInputs(List< SerializableEdge > edges) - { - inputEdgeCount = edges.Count; - - // If the relay is only connected to another relay: - if (edges.Count == 1 && edges.First().outputNode.GetType() == typeof(RelayNode)) - { - if (edges.First().passThroughBuffer != null) - input = (PackedRelayData)edges.First().passThroughBuffer; - } - else - { - input.values = edges.Select(e => e.passThroughBuffer).ToList(); - input.names = edges.Select(e => e.outputPort.portData.displayName).ToList(); - input.types = edges.Select(e => e.outputPort.portData.displayType ?? e.outputPort.fieldInfo.FieldType).ToList(); - } - } - - [CustomPortOutput(nameof(output), typeof(object), true)] - public void PushOutputs(List< SerializableEdge > edges, NodePort outputPort) - { - if (inputPorts.Count == 0) - return; - - var inputPortEdges = inputPorts[0].GetEdges(); - - if (outputPort.portData.identifier != packIdentifier && outputIndex >= 0 && (unpackOutput || inputPortEdges.Count == 1)) - { - if (output.values == null) - return; - - // When we unpack the output, there is one port per type of data in output - // That means that this function will be called the same number of time than the output port count - // Thus we use a class field to keep the index. - object data = output.values[outputIndex++]; - - foreach (var edge in edges) - { - var inputRelay = edge.inputNode as RelayNode; - edge.passThroughBuffer = inputRelay != null && !inputRelay.packInput ? output : data; - } - } - else - { - foreach (var edge in edges) - edge.passThroughBuffer = output; - } - } - - [CustomPortBehavior(nameof(input))] - IEnumerable< PortData > InputPortBehavior(List< SerializableEdge > edges) - { - // When the node is initialized, the input ports is empty because it's this function that generate the ports - int sizeInPixel = 0; - if (inputPorts.Count != 0) - { - // Add the size of all input edges: - var inputEdges = inputPorts[0]?.GetEdges(); - sizeInPixel = inputEdges.Sum(e => Mathf.Max(0, e.outputPort.portData.sizeInPixel - 8)); - } - - if (edges.Count == 1 && !packInput) - inputType.type = edges[0].outputPort.portData.displayType; - else - inputType.type = typeof(object); - - yield return new PortData { - displayName = "", - displayType = inputType.type, - identifier = "0", - acceptMultipleEdges = true, - sizeInPixel = Mathf.Min(k_MaxPortSize, sizeInPixel + 8), - }; - } - - [CustomPortBehavior(nameof(output))] - IEnumerable< PortData > OutputPortBehavior(List< SerializableEdge > edges) - { - if (inputPorts.Count == 0) - { - // Default dummy port to avoid having a relay without any output: - yield return new PortData { - displayName = "", - displayType = typeof(object), - identifier = "0", - acceptMultipleEdges = true, - }; - yield break; - } - - var inputPortEdges = inputPorts[0].GetEdges(); - var underlyingPortData = GetUnderlyingPortDataList(); - if (unpackOutput && inputPortEdges.Count == 1) - { - yield return new PortData - { - displayName = "Pack", - identifier = packIdentifier, - displayType = inputType.type, - acceptMultipleEdges = true, - sizeInPixel = Mathf.Min(k_MaxPortSize, Mathf.Max(underlyingPortData.Count, 1) + 7), // TODO: function - }; - - // We still keep the packed data as output when unpacking just in case we want to continue the relay after unpacking - for (int i = 0; i < underlyingPortData.Count; i++) - { - yield return new PortData { - displayName = underlyingPortData?[i].name ?? "", - displayType = underlyingPortData?[i].type ?? typeof(object), - identifier = i.ToString(), - acceptMultipleEdges = true, - sizeInPixel = 0, - }; - } - } - else - { - yield return new PortData { - displayName = "", - displayType = inputType.type, - identifier = "0", - acceptMultipleEdges = true, - sizeInPixel = Mathf.Min(k_MaxPortSize, Mathf.Max(underlyingPortData.Count, 1) + 7), - }; - } - } - - static List<(Type, string)> s_empty = new List<(Type, string)>(); - public List<(Type type, string name)> GetUnderlyingPortDataList() - { - // get input edges: - if (inputPorts.Count == 0) - return s_empty; - - var inputEdges = GetNonRelayEdges(); - - if (inputEdges != null) - return inputEdges.Select(e => (e.outputPort.portData.displayType ?? e.outputPort.fieldInfo.FieldType, e.outputPort.portData.displayName)).ToList(); - - return s_empty; - } - - public List GetNonRelayEdges() - { - var inputEdges = inputPorts?[0]?.GetEdges(); - - // Iterate until we don't have a relay node in input - while (inputEdges.Count == 1 && inputEdges.First().outputNode.GetType() == typeof(RelayNode)) - inputEdges = inputEdges.First().outputNode.inputPorts[0]?.GetEdges(); - - return inputEdges; - } + const string packIdentifier = "_Pack"; + + [HideInInspector] + public struct PackedRelayData + { + public List values; + public List names; + public List types; + } + + [Input(name = "In")] + public PackedRelayData input; + + [Output(name = "Out")] + public PackedRelayData output; + + public bool unpackOutput = false; + public bool packInput = false; + public int inputEdgeCount = 0; + [System.NonSerialized] + int outputIndex = 0; + + SerializableType inputType = new SerializableType(typeof(object)); + + const int k_MaxPortSize = 14; + + protected override void Process() + { + outputIndex = 0; + output = input; + } + + public override string layoutStyle => "GraphProcessorStyles/RelayNode"; + + [CustomPortInput(nameof(input), typeof(object), true)] + public void GetInputs(List edges) + { + inputEdgeCount = edges.Count; + + // If the relay is only connected to another relay: + if (edges.Count == 1 && edges.First().outputNode.GetType() == typeof(RelayNode)) + { + if (edges.First().passThroughBuffer != null) + input = (PackedRelayData)edges.First().passThroughBuffer; + } + else + { + input.values = edges.Select(e => e.passThroughBuffer).ToList(); + input.names = edges.Select(e => e.outputPort.portData.displayName).ToList(); + input.types = edges.Select(e => e.outputPort.portData.displayType ?? e.outputPort.fieldInfo.GetUnderlyingType()).ToList(); + } + } + + [CustomPortOutput(nameof(output), typeof(object), true)] + public void PushOutputs(List edges, NodePort outputPort) + { + if (inputPorts.Count == 0) + return; + + var inputPortEdges = inputPorts[0].GetEdges(); + + if (outputPort.portData.identifier != packIdentifier && outputIndex >= 0 && (unpackOutput || inputPortEdges.Count == 1)) + { + if (output.values == null) + return; + + // When we unpack the output, there is one port per type of data in output + // That means that this function will be called the same number of time than the output port count + // Thus we use a class field to keep the index. + object data = output.values[outputIndex++]; + + foreach (var edge in edges) + { + var inputRelay = edge.inputNode as RelayNode; + edge.passThroughBuffer = inputRelay != null && !inputRelay.packInput ? output : data; + } + } + else + { + foreach (var edge in edges) + edge.passThroughBuffer = output; + } + } + + [CustomPortBehavior(nameof(input))] + IEnumerable InputPortBehavior(List edges) + { + // When the node is initialized, the input ports is empty because it's this function that generate the ports + int sizeInPixel = 0; + if (inputPorts.Count != 0) + { + // Add the size of all input edges: + var inputEdges = inputPorts[0]?.GetEdges(); + sizeInPixel = inputEdges.Sum(e => Mathf.Max(0, e.outputPort.portData.sizeInPixel - 8)); + } + + if (edges.Count == 1 && !packInput) + inputType.type = edges[0].outputPort.portData.displayType; + else + inputType.type = typeof(object); + + yield return new PortData + { + displayName = "", + displayType = inputType.type, + identifier = "0", + acceptMultipleEdges = true, + sizeInPixel = Mathf.Min(k_MaxPortSize, sizeInPixel + 8), + }; + } + + [CustomPortBehavior(nameof(output))] + IEnumerable OutputPortBehavior(List edges) + { + if (inputPorts.Count == 0) + { + // Default dummy port to avoid having a relay without any output: + yield return new PortData + { + displayName = "", + displayType = typeof(object), + identifier = "0", + acceptMultipleEdges = true, + }; + yield break; + } + + var inputPortEdges = inputPorts[0].GetEdges(); + var underlyingPortData = GetUnderlyingPortDataList(); + if (unpackOutput && inputPortEdges.Count == 1) + { + yield return new PortData + { + displayName = "Pack", + identifier = packIdentifier, + displayType = inputType.type, + acceptMultipleEdges = true, + sizeInPixel = Mathf.Min(k_MaxPortSize, Mathf.Max(underlyingPortData.Count, 1) + 7), // TODO: function + }; + + // We still keep the packed data as output when unpacking just in case we want to continue the relay after unpacking + for (int i = 0; i < underlyingPortData.Count; i++) + { + yield return new PortData + { + displayName = underlyingPortData?[i].name ?? "", + displayType = underlyingPortData?[i].type ?? typeof(object), + identifier = i.ToString(), + acceptMultipleEdges = true, + sizeInPixel = 0, + }; + } + } + else + { + yield return new PortData + { + displayName = "", + displayType = inputType.type, + identifier = "0", + acceptMultipleEdges = true, + sizeInPixel = Mathf.Min(k_MaxPortSize, Mathf.Max(underlyingPortData.Count, 1) + 7), + }; + } + } + + static List<(Type, string)> s_empty = new List<(Type, string)>(); + public List<(Type type, string name)> GetUnderlyingPortDataList() + { + // get input edges: + if (inputPorts.Count == 0) + return s_empty; + + var inputEdges = GetNonRelayEdges(); + + if (inputEdges != null) + return inputEdges.Select(e => (e.outputPort.portData.displayType ?? e.outputPort.fieldInfo.GetUnderlyingType(), e.outputPort.portData.displayName)).ToList(); + + return s_empty; + } + + public List GetNonRelayEdges() + { + var inputEdges = inputPorts?[0]?.GetEdges(); + + // Iterate until we don't have a relay node in input + while (inputEdges.Count == 1 && inputEdges.First().outputNode.GetType() == typeof(RelayNode)) + inputEdges = inputEdges.First().outputNode.inputPorts[0]?.GetEdges(); + + return inputEdges; + } } \ 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 dfd75cb..f0ff030 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Graph/Attributes.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Graph/Attributes.cs @@ -31,7 +31,7 @@ public InputAttribute(string name = null, bool allowMultiple = false, bool showA /// /// Tell that this field is will generate an output port /// - [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)] public class OutputAttribute : Attribute { public string name; diff --git a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/FieldInfoExtension.cs b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/FieldInfoExtension.cs index 888225e..dd6ba4d 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/FieldInfoExtension.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/FieldInfoExtension.cs @@ -5,30 +5,92 @@ namespace GraphProcessor { - public static class FieldInfoExtension + public static class MemberInfoExtension { - public static bool HasCustomAttribute(this FieldInfo fieldInfo) + public static Type GetUnderlyingType(this MemberInfo member) { - return Attribute.IsDefined(fieldInfo, typeof(T)); + switch (member.MemberType) + { + case MemberTypes.Event: + return ((EventInfo)member).EventHandlerType; + case MemberTypes.Field: + return ((FieldInfo)member).FieldType; + case MemberTypes.Method: + return ((MethodInfo)member).ReturnType; + case MemberTypes.Property: + return ((PropertyInfo)member).PropertyType; + default: + throw new ArgumentException + ( + "Input MemberInfo must be if type EventInfo, FieldInfo, MethodInfo, or PropertyInfo" + ); + } } - public static bool HasCustomAttribute(this FieldInfo fieldInfo, Type type) + public static string GetPath(this IList list) { - return Attribute.IsDefined(fieldInfo, type); + string path = ""; + for (int i = 0; i < list.Count; i++) + { + if (i > 0) path += "."; + path += list[i].Name; + } + return path; } - public static object GetValueAt(this IList list, object startingValue, int index) + public static bool HasCustomAttribute(this MemberInfo memberInfo) { - object currentValue = startingValue; - for (int i = 0; i < list.Count; i++) + return Attribute.IsDefined(memberInfo, typeof(T)); + } + + public static object GetValue(this MemberInfo memberInfo, object forObject) + { + switch (memberInfo.MemberType) { - currentValue = list[i].GetValue(currentValue); - if (i == index) break; + case MemberTypes.Field: + return ((FieldInfo)memberInfo).GetValue(forObject); + case MemberTypes.Property: + return ((PropertyInfo)memberInfo).GetValue(forObject); + default: + throw new NotImplementedException(); } - return currentValue; } - public static object GetFinalValue(this IList list, object startingValue) + public static void SetValue(this MemberInfo memberInfo, object forObject, object value) + { + switch (memberInfo.MemberType) + { + case MemberTypes.Field: + ((FieldInfo)memberInfo).SetValue(forObject, value); + break; + case MemberTypes.Property: + if (((PropertyInfo)memberInfo).GetSetMethod(true) == null) break; + ((PropertyInfo)memberInfo).SetValue(forObject, value); + break; + default: + throw new NotImplementedException(); + } + } + + public static bool IsPublic(this MemberInfo memberInfo) + { + switch (memberInfo.MemberType) + { + case MemberTypes.Field: + return ((FieldInfo)memberInfo).IsPublic; + case MemberTypes.Property: + return ((PropertyInfo)memberInfo).GetAccessors().Any(MethodInfo => MethodInfo.IsPublic); + default: + return false; + } + } + + public static bool IsField(this MemberInfo memberInfo) + { + return memberInfo.MemberType == MemberTypes.Field; + } + + public static object GetFinalValue(this IList list, object startingValue) { object currentValue = startingValue; for (int i = 0; i < list.Count; i++) @@ -38,7 +100,7 @@ public static object GetFinalValue(this IList list, object startingVa return currentValue; } - public static void SetValue(this IList list, object startingValue, object finalValue) + public static void SetValue(this IList list, object startingValue, object finalValue) { object currentValue = startingValue; for (int i = 0; i < list.Count; i++) @@ -53,18 +115,18 @@ public static void SetValue(this IList list, object startingValue, ob } } - public static string GetPath(this IList list) + public static object GetValueAt(this IList list, object startingValue, int index) { - string path = ""; + object currentValue = startingValue; for (int i = 0; i < list.Count; i++) { - if (i > 0) path += "."; - path += list[i].Name; + currentValue = list[i].GetValue(currentValue); + if (i == index) break; } - return path; + return currentValue; } - public static bool IsValid(this IList list) + public static bool IsValid(this IList list) { return list.Any(x => x == null); }