How to Apply Parameter Override to action itself (rather than binding)?

I've got this action:
8368104--1102554--Look.png

And this binding:
8368104--1102557--See.png

They both have a ScaleVector2 Processor. If I call "GetParameterValue" and "SetParameterValue", I would expect to manipulate the Processor on the action itself, but instead it calls them on the binding.

    private void OnEnable()
    {
        slider.onValueChanged.AddListener(Thing);

        slider.value = Mathf.RoundToInt(lookInput.action.GetParameterValue(scaleVector2ProcessorX) ?? default); // Returns 0.05f
    }

    private void OnDisable()
    {
        slider.onValueChanged.RemoveListener(Thing);
    }

    private void Thing(float value)
    {
        textMeshProUGUI.text = slider.value.ToString();
        lookInput.action.ApplyParameterOverride(scaleVector2ProcessorX, value); // Sets the mouse processor
        lookInput.action.ApplyParameterOverride(scaleVector2ProcessorY, value); // Sets the mouse processor
    }

Even more frustrating is the fact that if I change the processor on a binding that does not have its own processor, like gamepad, that still doesn't scale the processor on the mouse.

Is there no way to manipulate the processor on the base action itself?

The issue is that the processor on the action is a lie! The editor lets you add processors to actions but behind the scenes, the processor gets added to each binding individually. In this case, Delta already has a ScaleVector2 processor, so that prevents the one from the action being added. Making this more complicated is the fact that the overload of GetParameterValue you've used here always returns the parameter from the first processor it matches, in this case, the X parameter from the ScaleVector2 processor on the Delta binding.

You can still do what you want here though by using the overloads of GetParameterValue/ApplyParameterOverride that take a binding mask argument, but you have to do it for each binding individually. There's some examples of how to do that here https://docs.unity3d.com/Packages/com.unity.inputsystem@1.4/manual/ActionBindings.html#setting-parameters, using either the InputBinding.MaskByGroup("...") method to choose bindings by control scheme, or new InputBinding("/control") to target a specific binding.

Know that this next part is written out of nothing but love for the Input System - I only wish the best for it. But this API, and there is no nice way of saying this, is completely overcooked spaghetti.

Notes that nobody asked for - caution, entitled douchebag alert

[spoiler]

Trying to establish the problem and see if I could fix it myself, I found the reason for why applying parameter overrides is such an ordeal: it's utter chaos in there! There are internals everywhere locking me out at every turn, in this type-based day-and-age the code still uses strings to instantiate controls (making saving these settings unnecessarily confusing and cumbersome through guesswork string-manipulation), state of 1 object may be held inside the state of a completely different object (as is the case with InputBindings and ActionMaps). I used reflection to solve my use-case, partly, but I never want to go through it again. It gave me a lot of insight into your codebase and it drives me mad with frustration, because if there's one thing I can't understand, it's why.

Let's start with processors. First of, if the processors on the InputAction in the editor are a lie, but you still need InputAction.processors + InputAction.bindings[0].processors (string, btw) to find out what processors binding 0 has, then what does this API even want me to do? The actual processor state is held by the action map... for... some reason... but wouldn't it make much more sense to make InputAction.bindings[0].processors return an actual collection of InputProcessors? It would make everyone's job easier, because now people can just modify that list instead of using an extension method that: is not performant, is unnecessary, takes agency away from the user, frankly doesn't make a whole lot of sense. Because the InputBinding already processes the input, so why can't it actually contain the processors? Nevermind if InputAction.processors is a lie, InputBinding.processors is a lie as well!

Then I tried to do this: action.GetParameterValue(someExpression, action.bindings[0]), to get the parameter value of my first binding. But this doesn't work... because action.bindings[0] isn't a correct InputBinding mask. Now, maybe I'm missing some big picture... but if an InputBinding is fundamentally different from an InputBindingMask, then they should be 2 different types, so then who let this decision slip by unnoticed? It wouldn't be hard to fix too: you can still create an InputBindingMask type that can implicitly convert from InputBinding and just takes its id.

This one doesn't really matter, but GetParameterValue(...) does a completely unnecessary conversion (which is even stated in the comments of the method) where a simple return (TValue)value; would suffice. It's just weird to me that these kinds of decisions pass by code review.

There's just a lot of places where strings are used when they absolutely shouldn't. What's nicer: this?

var action = new Action(processors: "invertVector2(invertX=false)");
action.ApplyParameterOverride("invertX=true"); // tough luck if you have a binding with an invertVector2.

Or this?

var action = new InputAction(inputProcessors: new[] { new InvertVector2() {invertX: false} });
action.inputProcessors[0].invertX = true; // handles its bindings by setting some internal m_ActionInputProcessors in the its InputBindings (or preferably process the input through the action before the binding processes it)

[/spoiler]

My convoluted solution - reflection everywhere

[spoiler]

#nullable enable
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text.RegularExpressions;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Utilities;

public static partial class InputActionRebindingExtensions
{
    private const BindingFlags privateFlags = BindingFlags.Instance | BindingFlags.NonPublic;

    private static readonly MethodInfo GetOrCreateActionMap = typeof(InputAction).GetMethod(nameof(GetOrCreateActionMap), privateFlags);
    private static readonly MethodInfo ResolveBindingsIfNecessary = typeof(InputActionMap).GetMethod(nameof(ResolveBindingsIfNecessary), privateFlags);
    private static readonly MethodInfo ExtractParameterOverride = typeof(UnityEngine.InputSystem.InputActionRebindingExtensions).GetMethod(nameof(ExtractParameterOverride), BindingFlags.Static | BindingFlags.NonPublic);
    private static readonly FieldInfo m_ParameterOverrides = typeof(InputActionMap).GetField(nameof(m_ParameterOverrides), privateFlags);
    private static readonly FieldInfo m_ParameterOverridesCount = typeof(InputActionMap).GetField(nameof(m_ParameterOverridesCount), privateFlags);
    private static readonly Type ArrayHelpers = Type.GetType("UnityEngine.InputSystem.Utilities." + nameof(ArrayHelpers) + ", Unity.InputSystem");
    private static readonly Type ParameterOverride = Type.GetType("UnityEngine.InputSystem.InputActionRebindingExtensions+" + nameof(ParameterOverride) + ", Unity.InputSystem");
    private static MethodInfo? _AppendWithCapacity;
    private static MethodInfo AppendWithCapacity
    {
        get
        {
            if (_AppendWithCapacity == null)
            {
                var ms = Array.FindAll(ArrayHelpers.GetMethods(BindingFlags.Static | BindingFlags.Public), x => x.Name == nameof(AppendWithCapacity)
                    && x.IsGenericMethod
                    && x.GetParameters().Length == 4);

                _AppendWithCapacity = ms.Length == 1
                    ? ms[0]
                    : ArrayHelpers.GetMethod(nameof(AppendWithCapacity), BindingFlags.Static | BindingFlags.Public); // throws to make sure we get an error if more than one AppendWithCapacity method was found.

                _AppendWithCapacity = _AppendWithCapacity.MakeGenericMethod(ParameterOverride);
            }

            return _AppendWithCapacity;
        }
    }
    private static readonly FieldInfo m_State = typeof(InputActionMap).GetField(nameof(m_State), privateFlags);
    private static readonly FieldInfo m_MapIndexInState = typeof(InputActionMap).GetField(nameof(m_MapIndexInState), privateFlags);
    private static readonly Type ParameterEnumerable = Type.GetType("UnityEngine.InputSystem.InputActionRebindingExtensions+" + nameof(ParameterEnumerable) + ", Unity.InputSystem");
    private static readonly Type Parameter = Type.GetType("UnityEngine.InputSystem.InputActionRebindingExtensions+" + nameof(Parameter) + ", Unity.InputSystem");
    private static readonly FieldInfo bindingIndex = Parameter.GetField(nameof(bindingIndex));
    private static readonly FieldInfo field = Parameter.GetField(nameof(field));
    private static readonly FieldInfo instance = Parameter.GetField(nameof(instance));
    private static readonly FieldInfo bindingMask = ParameterOverride.GetField(nameof(bindingMask));
    private static readonly PropertyInfo valuePtr = typeof(PrimitiveValue).GetProperty(nameof(valuePtr), privateFlags);
    private static readonly FieldInfo objectRegistrationName = ParameterOverride.GetField(nameof(objectRegistrationName));
    private static readonly FieldInfo parameter = ParameterOverride.GetField(nameof(parameter));
    private static readonly object s_Processors = typeof(InputProcessor).GetField(nameof(s_Processors), BindingFlags.Static | BindingFlags.NonPublic).GetValue(null);
    private static readonly MethodInfo FindProcessorNameForType = s_Processors.GetType().GetMethod(nameof(FindNameForType));
    private static readonly Type InputInteraction = Type.GetType("UnityEngine.InputSystem.InputInteraction, Unity.InputSystem");
    private static readonly object s_Interactions = InputInteraction.GetField(nameof(s_Interactions), BindingFlags.Static | BindingFlags.Public).GetValue(null);
    private static readonly MethodInfo FindInteractionNameForType = s_Interactions.GetType().GetMethod(nameof(FindNameForType));
    private static readonly object s_Composites = typeof(InputBindingComposite).GetField(nameof(s_Composites), BindingFlags.Static | BindingFlags.NonPublic).GetValue(null);
    private static readonly MethodInfo FindCompositeNameForType = s_Composites.GetType().GetMethod(nameof(FindNameForType));
    private static readonly FieldInfo m_Processors = typeof(InputAction).GetField(nameof(m_Processors), privateFlags);
    private static readonly FieldInfo m_BindingProcessors = typeof(InputBinding).GetField(nameof(m_Processors), privateFlags);

    private static readonly Regex bracketsRegex = new (@"(\(.*\))");
    //private static readonly Regex bracketsRegex = new (@"((?:\(.*\))?,)");

    private static string FindNameForType(Type type)
    {
        string objectRegistrationName;
        if (typeof(InputProcessor).IsAssignableFrom(type))
        {
            objectRegistrationName = (InternedString)FindProcessorNameForType.Invoke(s_Processors, new[] { type });
        }
        else if (typeof(IInputInteraction).IsAssignableFrom(type))
        {
            objectRegistrationName = (InternedString)FindInteractionNameForType.Invoke(s_Interactions, new[] { type });
        }
        else if (typeof(InputBindingComposite).IsAssignableFrom(type))
        {
            objectRegistrationName = (InternedString)FindCompositeNameForType.Invoke(s_Composites, new[] { type });
        }
        else
        {
            throw new ArgumentException($"Given type must be an {nameof(InputProcessor)}, {nameof(IInputInteraction)}, or {nameof(InputBindingComposite)} (was {type.Name})", nameof(type));
        }

        return objectRegistrationName;
    }

    public static TValue GetParameterValueAt<TObject, TValue>(this InputAction action, Expression<Func<TObject, TValue>> expression, Index index)
        where TValue : struct
        => GetParameter<TValue>(action.GetParameters(expression)[index]);

    public static TValue[] GetParameterValueAt<TObject, TValue>(this InputAction action, Expression<Func<TObject, TValue>> expression, Range range)
        where TValue : struct
        => Array.ConvertAll(action.GetParameters(expression)[range], parameter => GetParameter<TValue>(parameter));

    public static TValue GetParameterValueAt<TObject, TValue>(this InputAction action, Expression<Func<TObject, TValue>> expression, Index index, Index bindingIndex)
        where TValue : struct
        => GetParameter<TValue>(action.GetParameters(expression, action.bindings[bindingIndex])[index]);

    public static TValue[] GetParameterValueAt<TObject, TValue>(this InputAction action, Expression<Func<TObject, TValue>> expression, Range range, Index bindingIndex)
        where TValue : struct
        => Array.ConvertAll(action.GetParameters(expression, action.bindings[bindingIndex])[range], parameter => GetParameter<TValue>(parameter));

    public static void ApplyParameterOverrideAt<TObject, TValue>(this InputAction action, Expression<Func<TObject, TValue>> expression, TValue value, Index index)
        where TValue : struct
    {
        var parameters = action.GetApplyableParameters(expression, value);
        var actionParameterCount = action.GetParameterCount<TObject>();

        for (int i = 0; i < parameters.Length; i += actionParameterCount)
        {
            var segment = new ArraySegment<object>(parameters, i, actionParameterCount);
            SetParameter(segment[index], value);
        }
    }

    public static void ApplyParameterOverrideAt<TObject, TValue>(this InputAction action, Expression<Func<TObject, TValue>> expression, TValue value, Range range)
        where TValue : struct
    {
        var parameters = action.GetApplyableParameters(expression, value);
        var actionParameterCount = action.GetParameterCount<TObject>();

        for (int i = 0; i < parameters.Length; i += actionParameterCount)
        {
            var segment = new ArraySegment<object>(parameters, i, actionParameterCount);
            Array.ForEach(segment[range].Array, parameter => SetParameter(parameter, value));
        }
    }

    public static void ApplyParameterOverrideAt<TObject, TValue>(this InputAction action, Expression<Func<TObject, TValue>> expression, TValue value, Index index, Index bindingIndex)
        where TValue : struct
        => SetParameter(action.GetApplyableParameters(expression, value, action.bindings[bindingIndex])[index], value);

    public static void ApplyParameterOverrideAt<TObject, TValue>(this InputAction action, Expression<Func<TObject, TValue>> expression, TValue value, Range range, Index bindingIndex)
        where TValue : struct
        => Array.ForEach(action.GetApplyableParameters(expression, value, action.bindings[bindingIndex])[range], parameter => SetParameter(parameter, value));

    public static int GetParameterCount<TObject>(this InputAction action)
    {
        if (typeof(InputProcessor).IsAssignableFrom(typeof(TObject)))
        {
            var processors = bracketsRegex.Replace(action.processors, string.Empty).Split(',');
            return Array.FindAll(processors, processor => InputSystem.TryGetProcessor(processor) == typeof(TObject)).Length;
        }
        else if (typeof(IInputInteraction).IsAssignableFrom(typeof(TObject)))
        {
            var interactions = bracketsRegex.Replace(action.interactions, string.Empty).Split(',');
            return Array.FindAll(interactions, interaction => InputSystem.TryGetInteraction(interaction) == typeof(TObject)).Length;
        }

        return default;
    }

    public static int GetParameterCount<TObject, TValue>(this InputAction action, Expression<Func<TObject, TValue>> expression, Index bindingIndex)
        where TValue : struct
        => action.GetParameters(expression, action.bindings[bindingIndex]).Length;

    private static object[] GetParameters<TObject>(this InputAction action, InputActionMap actionMap, object parameterOverride, bool returnActionParameters)
    {
        var allParameters = actionMap.GetParameterEnumerable(parameterOverride, out _, out _);
        var actionParameterCount = action.GetParameterCount<TObject>();

        object[] parameters;
        if (returnActionParameters)
        {
            parameters = (from parameter in allParameters
                         group parameter by (int)bindingIndex.GetValue(parameter) into groupedParameters
                         from parameter in groupedParameters.TakeLast(actionParameterCount)
                         select parameter).ToArray();
        }
        else
        {
            parameters = allParameters.SkipLast(actionParameterCount).ToArray();
        }

        return parameters;
    }

    private static object[] GetParameters<TObject, TValue>(this InputAction action, Expression<Func<TObject, TValue>> expression, InputBinding binding = default)
    {
        var returnActionParameters = binding == default;
        var bindingMask = returnActionParameters
            ? new InputBinding(binding.path, action.name)
            : new InputBinding { id = binding.id };

        var actionMap = (InputActionMap)GetOrCreateActionMap.Invoke(action, null);
        ResolveBindingsIfNecessary.Invoke(actionMap, null);

        var ExtractParameterOverride = InputActionRebindingExtensions.ExtractParameterOverride.MakeGenericMethod(typeof(TObject), typeof(TValue));
        var parameterOverride = ExtractParameterOverride.Invoke(null, new object[] { expression, bindingMask, ExtractParameterOverride.GetParameters()[2].DefaultValue });

        return action.GetParameters<TObject>(actionMap, parameterOverride, returnActionParameters);
    }

    private static object[] GetApplyableParameters<TObject, TValue>(this InputAction action, Expression<Func<TObject, TValue>> expression, TValue value, InputBinding binding = default)
        where TValue : struct
    {
        var returnActionParameters = binding == default;
        var bindingMask = returnActionParameters
            ? new InputBinding(binding.path, action.name)
            : new InputBinding { id = binding.id };

        var actionMap = (InputActionMap)GetOrCreateActionMap.Invoke(action, null);
        ResolveBindingsIfNecessary.Invoke(actionMap, null);

        var ExtractParameterOverride = InputActionRebindingExtensions.ExtractParameterOverride.MakeGenericMethod(typeof(TObject), typeof(TValue));
        var parameterOverride = ExtractParameterOverride.Invoke(null, new object[] { expression, bindingMask, PrimitiveValue.From(value) });

        UpdateParameterOverrides(actionMap, parameterOverride);

        return action.GetParameters<TObject>(actionMap, parameterOverride, returnActionParameters);
    }

    private static TValue GetParameter<TValue>(object parameter)
        where TValue : struct
    {
        object instance = InputActionRebindingExtensions.instance.GetValue(parameter);
        var field = (FieldInfo)InputActionRebindingExtensions.field.GetValue(parameter);
        var value = field.GetValue(instance);
        return (TValue)value;
    }

    private static void SetParameter<TValue>(object parameter, TValue value)
        where TValue : struct
    {
        object instance = InputActionRebindingExtensions.instance.GetValue(parameter);
        var field = (FieldInfo)InputActionRebindingExtensions.field.GetValue(parameter);
        field.SetValue(instance, value);
    }

    private static void UpdateParameterOverrides(InputActionMap actionMap, object parameterOverride)
    {
        var parameterOverrides = m_ParameterOverrides.GetValue(actionMap) as Array;
        var parameterOverridesCount = (int)m_ParameterOverridesCount.GetValue(actionMap);

        if (parameterOverrides != null)
        {
            // Try to find existing override and opt out if we find it.
            var objectRegistrationName = (string)InputActionRebindingExtensions.objectRegistrationName.GetValue(parameterOverride);
            var parameter = (string)InputActionRebindingExtensions.parameter.GetValue(parameterOverride);
            var bindingMask = (InputBinding)InputActionRebindingExtensions.bindingMask.GetValue(parameterOverride);
            for (int i = 0; i < parameterOverridesCount; i++)
            {
                var refParameterOverride = parameterOverrides.GetValue(i);
                var refObjectRegistrationName = (string)InputActionRebindingExtensions.objectRegistrationName.GetValue(refParameterOverride);
                var refParameter = (string)InputActionRebindingExtensions.parameter.GetValue(refParameterOverride);
                var refBindingMask = (InputBinding)InputActionRebindingExtensions.bindingMask.GetValue(parameterOverride);

                if (string.Equals(refObjectRegistrationName, objectRegistrationName, StringComparison.OrdinalIgnoreCase)
                    && string.Equals(refParameter, parameter, StringComparison.OrdinalIgnoreCase)
                    && refBindingMask == bindingMask)
                {
                    parameterOverrides.SetValue(parameterOverride, i);
                    m_ParameterOverrides.SetValue(actionMap, parameterOverrides);
                    return;
                }
            }
        }

        // The override doesn't exist, so add a new override.
        var arguments = new object?[] { parameterOverrides, parameterOverridesCount, parameterOverride, AppendWithCapacity.GetParameters()[3].DefaultValue };
        AppendWithCapacity.Invoke(null, arguments);
        m_ParameterOverrides.SetValue(actionMap, arguments[0]);
        m_ParameterOverridesCount.SetValue(actionMap, arguments[1]);
    }

    private static IEnumerable<object> GetParameterEnumerable(this InputActionMap actionMap, object parameterOverride, out object state, out int mapIndex)
    {
        state = m_State.GetValue(actionMap);
        mapIndex = (int)m_MapIndexInState.GetValue(actionMap);
        return ((IEnumerable)Activator.CreateInstance(ParameterEnumerable, new object[] { state, parameterOverride, mapIndex })).OfType<object>();
    }
}

[/spoiler]

2 Likes

[quote=“andrew_oc”, post:2, topic: 891339]
You can still do what you want here though by using the overloads of GetParameterValue/ApplyParameterOverride that take a binding mask argument, but you have to do it for each binding individually. There’s some examples of how to do that here https://docs.unity3d.com/Packages/com.unity.inputsystem@1.4/manual/ActionBindings.html#setting-parameters, using either the InputBinding.MaskByGroup(“…”) method to choose bindings by control scheme, or new InputBinding(“/control”) to target a specific binding.
[/quote]

This was not true, because it will still set the ScaleVector2 that was supplied by the InputAction setting both my mouse(x=0.05,y=0.05) and global(x=2,y=2) to something like mouse(x=3,y=3) global(x=3,y=3).

It also wasn’t my problem. I want mouse(x=0.05,y=0.05) global(x=3,y=3) but there is no way to distinguish the InputAction’s supplied processor from the InputBinding one. The only way I could find in my own solution was counting occurrences of the type in the InputAction.processors string, I don’t understand why there is no place that this information is stored, especially since it would be so easy to do with references: InputAction.inputProcessors and you’re there. Things just… don’t seem connected the way they should.

Ok, I'm just finding this, and it looks like really bad news for me, since I am trying to do exactly the thing you appear to be trying to do, @CaseyHofland

Namely: I'd like to give my user a menu button that can be clicked which would invert the Y axis on the Look action.

It's the reason I put in the question here a little while ago. I was thinking it'd be only a line or two of code more than what I have there, but looking at your solution (behind the spoiler, above) I'm thinking I'm probably not going to be able to do this at all in Unity's New Input System.

I assume your solution works for you, but it will take me a long time to go through that and try to apply it to my situation. I'm still very grateful that you posted it! Also, I think your notes that nobody asked for are pretty helpful to me, if dispiriting, so thanks also for those!

Unity, please make this much simpler! I wouldn't think this is a big ask, to be able to invert the look action from inside the game. I've seen tons of games do this.

1 Like

Hey @JeffreyBennett_1 , I came up with a much better solution using extension methods. This I found is the way too go forward. Going through the whole reflection path is 1: a slippery slope because who knows how the API will change and 2: any small change I wanted to make takes days. Extension methods give me the control I need to implement input changes how I see fit.

InputActionExtensions.InputProcessors

[spoiler]

This here will latch onto InputActions to allow for that list I was talking about in my rant. Instead of calling action.processors you can simply call action.Reprocessors() and manipulate the returned list, than process the value in an InputAction.CallbackContext doing context.ReadRevalue<float>();
There's no check in place to see if the processors are of the right type so be mindful of that.

#nullable enable
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;

namespace UnityExtras.InputSystem
{
    public static partial class InputActionExtensions
    {
        [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
        private static void ClearReprocessors()
        {
            actionReprocessors.Clear();
            bindingReprocessors.Clear();
        }

        private static Dictionary<InputAction, List<InputProcessor>> actionReprocessors = new();
        private static Dictionary<InputBinding, List<InputProcessor>> bindingReprocessors = new();

        public static List<InputProcessor> Reprocessors(this InputAction inputAction)
        {
            actionReprocessors.TryAdd(inputAction, new());
            return actionReprocessors[inputAction];
        }

        public static List<InputProcessor> Reprocessors(this InputBinding inputBinding)
        {
            bindingReprocessors.TryAdd(inputBinding, new());
            return bindingReprocessors[inputBinding];
        }

        public static TValue ReadRevalue<TValue>(this InputAction.CallbackContext context)
            where TValue : struct
        {
            var value = context.ReadValue<TValue>();

            // Process the actions reprocessors.
            if (actionReprocessors.TryGetValue(context.action, out var inputProcessors))
            {
                for (int i = 0; i < inputProcessors.Count; i++)
                {
                    var inputProcessor = (InputProcessor<TValue>)inputProcessors[i];
                    value = inputProcessor.Process(value, context.control);
                }
            }

            // Process the bindings reprocessors.
            var bindingIndex = context.action.controls.IndexOf(control => control == context.control); // If this ever causes issues, use reflection to get context.bindingIndex instead.
            var binding = context.action.bindings[bindingIndex];
            if (bindingReprocessors.TryGetValue(binding, out inputProcessors))
            {
                for (int i = 0; i < inputProcessors.Count; i++)
                {
                    var inputProcessor = (InputProcessor<TValue>)inputProcessors[i];
                    value = inputProcessor.Process(value, context.control);
                }
            }

            return value;
        }
    }
}

[/spoiler]

Serializable Processors

[spoiler]

As a little bonus, it's time I won't get back anyway, here is a serializable processor! It's pretty unoptimized but since this is only really useful in option menus it shouldn't matter as it's not called upon often, nor manipulated by the dozens.

#nullable enable
using System;
using UnityEngine;
using UnityEngine.InputSystem;

namespace UnityExtras.InputSystem
{
    [Serializable]
    public struct Processor
    {
        [SerializeField] private string? _inputProcessorAssemblyQualifiedName;
        [SerializeField] private string[]? _inputProcessorValueStrings;

        public InputProcessor? inputProcessor
        {
            get
            {
                Type? processorType;
                if (string.IsNullOrEmpty(_inputProcessorAssemblyQualifiedName) || (processorType = Type.GetType(_inputProcessorAssemblyQualifiedName)) == null)
                {
                    return null;
                }

                var inputProcessor = (InputProcessor)Activator.CreateInstance(processorType);

                var processorFields = processorType.GetFields();
                for (int i = 0; i < processorFields.Length && i < _inputProcessorValueStrings?.Length; i++)
                {
                    var processorField = processorFields[i];
                    var processorValueString = _inputProcessorValueStrings[i];

                    try
                    {
                        var processorValue = Convert.ChangeType(processorValueString, processorField.FieldType);
                        processorField.SetValue(inputProcessor, processorValue);
                    }
                    catch (Exception) { }
                }

                return inputProcessor;
            }
            set
            {
                var processorType = value?.GetType();
                _inputProcessorAssemblyQualifiedName = processorType?.AssemblyQualifiedName;

                var processorFields = processorType?.GetFields();
                _inputProcessorValueStrings = new string[processorFields?.Length ?? 0];
                for (int i = 0; i < processorFields?.Length; i++)
                {
                    var processorField = processorFields[i];
                    _inputProcessorValueStrings[i] = processorField.GetValue(value).ToString();
                }
            }
        }

        public static implicit operator InputProcessor?(Processor processor) => processor.inputProcessor;
        public static implicit operator Processor(InputProcessor? inputProcessor) => new() { inputProcessor = inputProcessor };
    }
}

Drawer (put in Editor folder yadayada you know the drill)

#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;

namespace UnityExtras.InputSystem.Editor
{
#pragma warning disable IDE0065 // Misplaced using directive
    using InputSystem = UnityEngine.InputSystem.InputSystem;
#pragma warning restore IDE0065 // Misplaced using directive

    [CustomPropertyDrawer(typeof(Processor))]
    public class ProcessorDrawer : PropertyDrawer
    {
        public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
        {
            EditorGUI.BeginProperty(position, label, property);

            SerializedProperty _inputProcessorAssemblyQualifiedName = property.FindPropertyRelative(nameof(_inputProcessorAssemblyQualifiedName));

            position.height = EditorGUIUtility.singleLineHeight;
            using var stringCheck = new EditorGUI.ChangeCheckScope();
            var processorStrings = InputSystem.ListProcessors().ToArray();
            var processorTypes = Array.ConvertAll(processorStrings, processorString => InputSystem.TryGetProcessor(processorString));
            var index = Array.IndexOf(processorTypes, Type.GetType(_inputProcessorAssemblyQualifiedName.stringValue));
            index = EditorGUI.Popup(position, index == -1 ? " " : processorStrings[index], index, processorStrings);
            if (stringCheck.changed)
            {
                var processorType = processorTypes[index];
                _inputProcessorAssemblyQualifiedName.stringValue = processorType?.AssemblyQualifiedName;
            }

            property.isExpanded = EditorGUI.Foldout(position, property.isExpanded, label, true);
            if (property.isExpanded)
            {
                EditorGUI.indentLevel++;

                var target = property.serializedObject.targetObject;
                var value = fieldInfo.GetValue(target);

                if (value != null)
                {
                    if (!(value is Processor processor)
                        && value is IList<Processor> processors)
                    {
                        var path = property.propertyPath;
                        index = int.Parse(path.Substring(index = path.LastIndexOf('[') + 1, path.LastIndexOf(']') - index));
                        processor = processors[index];
                    }

                    SerializedProperty _inputProcessorValueStrings = property.FindPropertyRelative(nameof(_inputProcessorValueStrings));

                    if (stringCheck.changed)
                    {
                        for (int i = 0; i < _inputProcessorValueStrings.arraySize; i++)
                        {
                            _inputProcessorValueStrings.GetArrayElementAtIndex(i).stringValue = null;
                        }

                        property.serializedObject.ApplyModifiedProperties();
                    }

                    var processorFields = processor.inputProcessor?.GetType().GetFields();
                    _inputProcessorValueStrings.ClearArray();
                    _inputProcessorValueStrings.arraySize = processorFields?.Length ?? 0;

                    for (int i = 0; i < processorFields?.Length; i++)
                    {
                        var processorField = processorFields[i];
                        _inputProcessorValueStrings.InsertArrayElementAtIndex(i);

                        value = processorField.GetValue(processor.inputProcessor);
                        position.y += EditorGUIUtility.singleLineHeight + 2f;

                        switch (value)
                        {
                            case bool:
                                value = EditorGUI.Toggle(position, processorField.Name, (bool)value);
                                break;
                            case float:
                            case double:
                            case decimal:
                                value = EditorGUI.FloatField(position, processorField.Name, (float)value);
                                break;
                            case byte:
                            case sbyte:
                            case short:
                            case ushort:
                            case int:
                            case uint:
                                value = EditorGUI.IntField(position, processorField.Name, (int)value);
                                break;
                            case long:
                            case ulong:
                                value = EditorGUI.LongField(position, processorField.Name, (long)value);
                                break;
                            case char:
                            case string:
                                value = EditorGUI.TextField(position, processorField.Name, (string)value);
                                break;
                        }

                        _inputProcessorValueStrings.GetArrayElementAtIndex(i).stringValue = value.ToString();
                    }
                }

                EditorGUI.indentLevel--;
            }

            EditorGUI.EndProperty();
        }

        public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
        {
            var height = base.GetPropertyHeight(property, label);

            if (property.isExpanded)
            {
                var target = property.serializedObject.targetObject;
                var value = fieldInfo.GetValue(target);

                if (value != null)
                {
                    if (!(value is Processor processor)
                        && value is IList<Processor> processors)
                    {
                        var path = property.propertyPath;
                        int index = int.Parse(path.Substring(index = path.LastIndexOf('[') + 1, path.LastIndexOf(']') - index));
                        processor = processors[index];
                    }

                    height += (EditorGUIUtility.singleLineHeight + 2f) * processor.inputProcessor?.GetType().GetFields().Length ?? 0;
                }
            }

            return height;
        }
    }
}

[/spoiler]

Shoddy example Implementation

[spoiler]

public class Test : MonoBehaviour
{
    public InputInteractionReference input;
    [SerializeField] private List<Processor> processors = new();

    private void OnEnable()
    {
        input.action.Reprocessors().AddRange(processors);
        input.action.performed += Performed;
    }


    private void Update()
    {
        input.action.Reprocessors().Clear();
        input.action.Reprocessors().AddRange(processors);
    }

    private void Performed(InputAction.CallbackContext context)
    {
        Debug.Log(context.ReadRevalue<float>());
    }
}

[/spoiler]

Oh, how very cool!

Thank you for sharing! I need to go through this and check it out.

My crappy solution was to duplicate my player control action map and change just the one setting for the processor on the Look action. Now, when my player selects the inverted look controls I change over to the "Inverted" action map (that is identical to the main Player action map in all ways except for its name and the inverted processor on the Look action).

Ugh.

:hushed: My condolences

Does anybody have the code (Just the code and a simple and in-depth explanation of what it does) to change an inputAction to an inputActionReference?

Just put your input action in an input action asset (via copy and paste), change InputAction to InputActionReference, select your reference. Ez

All it does is reference an InputAction instead of embedding it. It just allows you to stay more organized: just call InputActionReference.action to work with InputAction in code

Damn, this really hurts. Who designed this? Like seriously, I'm trying to override my processor for my Vector2 at runtime and I can't even do that! Jesus I've made a whole multiplayer game, and I cannot edit a processor for my player input! WTF!