using System; using System.Collections; using System.Collections.Generic; using System.Linq.Expressions; using System.Reflection; using Unity.Collections.LowLevel.Unsafe; using UnityEngine.InputSystem.Utilities; namespace UnityEngine.InputSystem { partial class InputActionRebindingExtensions { /// /// Return the current value of the given parameter as found on the processors, interactions, or composites /// of the action's current bindings. /// /// Action on whose bindings to look for the value of the given parameter. /// Name of the parameter to get the value of. Case-insensitive. This can either be just the name of the /// parameter (like "duration" or expressed as nameof(TapInteraction.duration)) or can be prefixed with the /// type of object to get the parameter value from. For example, "tap:duration" will specifically get the "duration" /// parameter from the object registered as "tap" (which will usually be ). /// Optional mask that determines on which bindings to look for objects with parameters. If used, only /// bindings that match (see ) the given mask will be taken into account. /// The current value of the given parameter or null if the parameter could not be found. /// /// Parameters are found on interactions (), processors (), and /// composites (see ) that are applied to bindings. For example, the following binding /// adds a Hold interaction with a custom duration parameter on top of binding to the gamepad's A button: /// /// /// /// new InputBinding /// { /// path = "<Gamepad>/buttonSouth", /// interactions = "hold(duration=0.6)" /// }; /// /// /// /// In the editor UI, parameters are set graphically from the properties sections in the right-most pane /// in the action editor when an action or a binding is selected. /// /// When the binding above is applied to an action, the duration parameter from the Hold interaction can be /// queried like so: /// /// /// /// action.GetParameterValue("duration") // Returns 0.6 /// /// /// /// Note that if there are multiple objects on the action that use the same parameter name, the value of the first parameter /// that is encountered is returned. Also note that this method will create GC heap garbage. /// /// The type of object to query the parameter from can be include in the parameter. For example, if /// an action has both a and a on it, the /// duration parameter can be queried independently like so: /// /// /// /// // Query "duration" from "hold": /// action.GetParameterValue("hold:duration"); /// /// // Query "duration" from "tap": /// action.GetParameterValue("tap:duration"); /// /// /// /// The names used here to identify the object holding the parameter are the same used by , /// , and . /// /// is null -or- is null /// /// /// public static PrimitiveValue? GetParameterValue(this InputAction action, string name, InputBinding bindingMask = default) { if (action == null) throw new ArgumentNullException(nameof(action)); if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); return action.GetParameterValue(new ParameterOverride(name, bindingMask)); } private static PrimitiveValue? GetParameterValue(this InputAction action, ParameterOverride parameterOverride) { parameterOverride.bindingMask.action = action.name; var actionMap = action.GetOrCreateActionMap(); actionMap.ResolveBindingsIfNecessary(); foreach (var parameter in new ParameterEnumerable(actionMap.m_State, parameterOverride, actionMap.m_MapIndexInState)) { var value = parameter.field.GetValue(parameter.instance); return PrimitiveValue.FromObject(value); } return null; } /// /// Return the current value of the given parameter as found on the processors, interactions, or composites /// of the action's current bindings. /// /// Action on whose bindings to look for the value of the given parameter. /// Name of the parameter to get the value of. Case-insensitive. This can either be just the name of the /// parameter (like "duration" or expressed as nameof(TapInteraction.duration)) or can be prefixed with the /// type of object to get the parameter value from. For example, "tap:duration" will specifically get the "duration" /// parameter from the object registered as "tap" (which will usually be ). /// Index of the binding in 's /// to look for processors, interactions, and composites on. /// The current value of the given parameter or null if the parameter not could be found. /// /// This method is a variation of /// to specifically target a single binding by index. Otherwise, the method is identical in functionality. /// /// is null -or- is null public static PrimitiveValue? GetParameterValue(this InputAction action, string name, int bindingIndex) { if (action == null) throw new ArgumentNullException(nameof(action)); if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); if (bindingIndex < 0) throw new ArgumentOutOfRangeException(nameof(bindingIndex)); var indexOnMap = action.BindingIndexOnActionToBindingIndexOnMap(bindingIndex); var bindingMask = new InputBinding { id = action.GetOrCreateActionMap().bindings[indexOnMap].id }; return action.GetParameterValue(name, bindingMask); } /// /// Return the current value of the given parameter as found on the processors, interactions, or composites /// of the action's current bindings. /// /// Action on whose bindings to look for the value of the given parameter. /// An expression such as (TapInteraction x) => x.duration that determines the /// name and type of the parameter being looked for. /// Optional mask that determines on which bindings to look for objects with parameters. If used, only /// bindings that match (see ) the given mask will be taken into account. /// The current value of the given parameter or null if the parameter not could be found. /// /// This method is a variation of /// that encapsulates a reference to the name of the parameter and the type of object it is found on in a way that is /// type-safe and does not involve strings. /// /// /// /// // Get the "duration" parameter from a TapInteraction. /// // This is equivalent to calling GetParameterValue("tap:duration") /// // but will return a float? instead of a PrimitiveValue?. /// action.GetParameterValue((TapInteraction x) => x.duration) /// /// /// /// is null -or- is null /// public static unsafe TValue? GetParameterValue(this InputAction action, Expression> expr, InputBinding bindingMask = default) where TValue : struct { if (action == null) throw new ArgumentNullException(nameof(action)); if (expr == null) throw new ArgumentNullException(nameof(expr)); var parameterOverride = ExtractParameterOverride(expr, bindingMask); var value = action.GetParameterValue(parameterOverride); if (value == null) return null; // Type is guaranteed to match but just in case, // make extra sure with a check here. if (Type.GetTypeCode(typeof(TValue)) == value.Value.type) { // Can't just cast here so use UnsafeUtility to work around that. var v = value.Value; var result = default(TValue); UnsafeUtility.MemCpy(UnsafeUtility.AddressOf(ref result), v.valuePtr, UnsafeUtility.SizeOf()); return result; } // Shouldn't get here but just in case, do a conversion using C#'s Convert // machinery as a fallback. return (TValue)Convert.ChangeType(value.Value.ToObject(), typeof(TValue)); } /// /// Set the value of the given parameter on the , , /// and objects found on the of . /// /// An action on whose to look for objects to set /// the parameter value on. /// An expression such as (TapInteraction x) => x.duration that determines the /// name and type of the parameter whose value to set. /// New value to assign to the parameter. /// Optional mask that determines on which bindings to look for objects with parameters. If used, only /// bindings that match (see ) the given mask will have the override applied to them. /// is null -or- is null /// or empty. /// /// This method is a variation of /// that encapsulates a reference to the name of the parameter and the type of object it is found on in a way that is /// type-safe and does not involve strings. /// /// /// /// // Override the "duration" parameter from a TapInteraction. /// // This is equivalent to calling ApplyParameterOverride("tap:duration", 0.4f). /// action.ApplyParameterOverride((TapInteraction x) => x.duration, 0.4f); /// /// /// /// public static void ApplyParameterOverride(this InputAction action, Expression> expr, TValue value, InputBinding bindingMask = default) where TValue : struct { if (action == null) throw new ArgumentNullException(nameof(action)); if (expr == null) throw new ArgumentNullException(nameof(expr)); var actionMap = action.GetOrCreateActionMap(); actionMap.ResolveBindingsIfNecessary(); bindingMask.action = action.name; var parameterOverride = ExtractParameterOverride(expr, bindingMask, PrimitiveValue.From(value)); ApplyParameterOverride(actionMap.m_State, actionMap.m_MapIndexInState, ref actionMap.m_ParameterOverrides, ref actionMap.m_ParameterOverridesCount, parameterOverride); } /// /// Set the value of the given parameter on the , , /// and objects found on the of . /// /// An action on whose to look for objects to set /// the parameter value on. /// An expression such as (TapInteraction x) => x.duration that determines the /// name and type of the parameter whose value to set. /// New value to assign to the parameter. /// Optional mask that determines on which bindings to look for objects with parameters. If used, only /// bindings that match (see ) the given mask will have the override applied to them. /// is null -or- is null /// or empty. /// /// This method is a variation of /// that encapsulates a reference to the name of the parameter and the type of object it is found on in a way that is /// type-safe and does not involve strings. /// /// /// /// // Override the "duration" parameter from a TapInteraction. /// // This is equivalent to calling mApplyParameterOverride("tap:duration", 0.4f). /// actionMap.ApplyParameterOverride((TapInteraction x) => x.duration, 0.4f); /// /// /// /// public static void ApplyParameterOverride(this InputActionMap actionMap, Expression> expr, TValue value, InputBinding bindingMask = default) where TValue : struct { if (actionMap == null) throw new ArgumentNullException(nameof(actionMap)); if (expr == null) throw new ArgumentNullException(nameof(expr)); actionMap.ResolveBindingsIfNecessary(); var parameterOverride = ExtractParameterOverride(expr, bindingMask, PrimitiveValue.From(value)); ApplyParameterOverride(actionMap.m_State, actionMap.m_MapIndexInState, ref actionMap.m_ParameterOverrides, ref actionMap.m_ParameterOverridesCount, parameterOverride); } /// /// Set the value of the given parameter on the , , /// and objects found on the of the /// in . /// /// An asset on whose to look for objects to set /// the parameter value on. /// An expression such as (TapInteraction x) => x.duration that determines the /// name and type of the parameter whose value to set. /// New value to assign to the parameter. /// Optional mask that determines on which bindings to look for objects with parameters. If used, only /// bindings that match (see ) the given mask will have the override applied to them. /// is null -or- is null /// or empty. /// /// This method is a variation of /// that encapsulates a reference to the name of the parameter and the type of object it is found on in a way that is /// type-safe and does not involve strings. /// /// /// /// // Override the "duration" parameter from a TapInteraction. /// // This is equivalent to calling mApplyParameterOverride("tap:duration", 0.4f). /// asset.ApplyParameterOverride((TapInteraction x) => x.duration, 0.4f); /// /// /// /// public static void ApplyParameterOverride(this InputActionAsset asset, Expression> expr, TValue value, InputBinding bindingMask = default) where TValue : struct { if (asset == null) throw new ArgumentNullException(nameof(asset)); if (expr == null) throw new ArgumentNullException(nameof(expr)); asset.ResolveBindingsIfNecessary(); var parameterOverride = ExtractParameterOverride(expr, bindingMask, PrimitiveValue.From(value)); ApplyParameterOverride(asset.m_SharedStateForAllMaps, -1, ref asset.m_ParameterOverrides, ref asset.m_ParameterOverridesCount, parameterOverride); } private static ParameterOverride ExtractParameterOverride(Expression> expr, InputBinding bindingMask = default, PrimitiveValue value = default) { if (!(expr is LambdaExpression lambda)) throw new ArgumentException($"Expression must be a LambdaExpression but was a {expr.GetType().Name} instead", nameof(expr)); if (!(lambda.Body is MemberExpression body)) { // If the field type in the lambda doesn't match the TValue type being used, // but there is a coercion, the compiler will automatically insert a Convert(x.name, TValue) // expression. if (lambda.Body is UnaryExpression unary && unary.NodeType == ExpressionType.Convert && unary.Operand is MemberExpression b) { body = b; } else { throw new ArgumentException( $"Body in LambdaExpression must be a MemberExpression (x.name) but was a {expr.GetType().Name} instead", nameof(expr)); } } string objectRegistrationName; if (typeof(InputProcessor).IsAssignableFrom(typeof(TObject))) objectRegistrationName = InputProcessor.s_Processors.FindNameForType(typeof(TObject)); else if (typeof(IInputInteraction).IsAssignableFrom(typeof(TObject))) objectRegistrationName = InputInteraction.s_Interactions.FindNameForType(typeof(TObject)); else if (typeof(InputBindingComposite).IsAssignableFrom(typeof(TObject))) objectRegistrationName = InputBindingComposite.s_Composites.FindNameForType(typeof(TObject)); else throw new ArgumentException( $"Given type must be an InputProcessor, IInputInteraction, or InputBindingComposite (was {typeof(TObject).Name})", nameof(TObject)); return new ParameterOverride(objectRegistrationName, body.Member.Name, bindingMask, value); } /// /// Set the value of the given parameter on the , , /// and objects found on the of . /// /// An action map on whose to look for objects to set /// the parameter value on. /// Name of the parameter to get the value of. Case-insensitive. This can either be just the name of the /// parameter (like "duration" or expressed as nameof(TapInteraction.duration)) or can be prefixed with the /// type of object to get the parameter value from. For example, "tap:duration" will specifically get the "duration" /// parameter from the object registered as "tap" (which will usually be ). /// New value to assign to the parameter. /// A binding mask that determines which of 's /// to apply the override to. By default this is empty which leads to the override to be applied to all bindings in the map. /// /// This method both directly applies the new value and also stores the override internally. /// /// If an override for the same parameter and with the same already exists, /// its value is simply updated. No new override will be created. /// /// You can use this method to set parameters (public fields) on composites, interactions, and processors that are created /// from bindings. /// /// /// /// // Create an action map with two actions. /// var map = new InputActionMap(); /// var action1 = map.AddAction("action1"); /// var action2 = map.AddAction("action2"); /// /// // Add a binding to each action to which a "ClampProcessor" is applied. /// // This processor has two parameters: /// // - "min" (float) /// // - "max" (float) /// action1.AddBinding(">Gamepad>/rightTrigger", processors: "clamp(min=0.2,max=0.8)"); /// action2.AddBinding(">Gamepad>/leftTrigger", processors: "clamp(min=0.2,max=0.8)"); /// /// // Apply parameter overrides to set the values differently. /// // This will apply the setting to *both* the bindings on action1 *and* action2. /// map.ApplyParameterOverride("min", 0.3f); /// map.ApplyParameterOverride("max", 0.9f); /// /// /// /// An override can optionally be directed at a specific type of object. /// /// /// /// map.ApplyParameterOverride("clamp:min", 0.3f); /// map.ApplyParameterOverride("clamp:max", 0.9f); /// /// /// /// By default, the parameter override will apply to all bindings in the map. To limit the override /// to specific bindings, you can supply a . /// /// /// /// // Apply a parameter override only to action1. /// map.ApplyBindingOverride("clamp:min", 0.25f, new InputBinding { action = action1.name }); /// /// // Apply a parameter override only to a specific binding path. /// map.ApplyBindingOverride("clamp:min", 0.4f, new InputBinding { path = "<Gamepad>/leftTrigger" }); /// /// /// /// If multiple overrides exist for the same parameter, an attempt is made to choose the override that is most specific. /// Say, that you apply an override for "duration" on an entire using /// . But then you also apply /// an override to just an individual inside the asset. In this case, the "duration" /// override for just that action will be applied to bindings of that action and the override inside the asset will /// be applied to bindings of all other actions. Note that if multiple overrides exist that could all be considered /// equally valid, the behavior is undecided. /// /// Note that parameter overrides stay in place on the map. Like binding overrides, however, they are not /// automatically persisted and thus need to be reapplied when actions are loaded from assets. This will, however, be applied /// automatically to bindings added to the action in the future as well as whenever bindings are resolved to controls. /// /// is null -or- is null /// or empty. /// public static void ApplyParameterOverride(this InputActionMap actionMap, string name, PrimitiveValue value, InputBinding bindingMask = default) { if (actionMap == null) throw new ArgumentNullException(nameof(actionMap)); if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); actionMap.ResolveBindingsIfNecessary(); ApplyParameterOverride(actionMap.m_State, actionMap.m_MapIndexInState, ref actionMap.m_ParameterOverrides, ref actionMap.m_ParameterOverridesCount, new ParameterOverride(name, bindingMask, value)); } /// /// Set the value of the given parameter on the , , /// and objects found on the of each of the /// in . /// /// An .inputactions asset on whose to look for objects to set /// the parameter value on. /// Name of the parameter to get the value of. Case-insensitive. This can either be just the name of the /// parameter (like "duration" or expressed as nameof(TapInteraction.duration)) or can be prefixed with the /// type of object to get the parameter value from. For example, "tap:duration" will specifically get the "duration" /// parameter from the object registered as "tap" (which will usually be ). /// New value to assign to the parameter. /// A binding mask that determines which of the /// to apply the override to. By default this is empty which leads to the override to be applied to all bindings in the asset. /// /// This method both directly applies the new value and also stores the override internally. /// /// If an override for the same parameter and with the same already exists, /// its value is simply updated. No new override will be created. /// /// You can use this method to set parameters (public fields) on composites, interactions, and processors that are created /// from bindings. /// /// /// /// // Create an asset with one action map and two actions. /// var asset = ScriptableObject.CreateInstance<InputActionAsset>(); /// var map = asset.AddActionMap("map"); /// var action1 = map.AddAction("action1"); /// var action2 = map.AddAction("action2"); /// /// // Add a binding to each action to which a "ClampProcessor" is applied. /// // This processor has two parameters: /// // - "min" (float) /// // - "max" (float) /// action1.AddBinding(">Gamepad>/rightTrigger", processors: "clamp(min=0.2,max=0.8)"); /// action2.AddBinding(">Gamepad>/leftTrigger", processors: "clamp(min=0.2,max=0.8)"); /// /// // Apply parameter overrides to set the values differently. /// // This will apply the setting to *both* the bindings on action1 *and* action2. /// asset.ApplyParameterOverride("min", 0.3f); /// asset.ApplyParameterOverride("max", 0.9f); /// /// /// /// An override can optionally be directed at a specific type of object. /// /// /// /// asset.ApplyParameterOverride("clamp:min", 0.3f); /// asset.ApplyParameterOverride("clamp:max", 0.9f); /// /// /// /// By default, the parameter override will apply to all bindings in the asset. To limit the override /// to specific bindings, you can supply a . /// /// /// /// // Apply a parameter override only to action1. /// asset.ApplyBindingOverride("clamp:min", 0.25f, new InputBinding { action = action1.name }); /// /// // Apply a parameter override only to a specific binding path. /// asset.ApplyBindingOverride("clamp:min", 0.4f, new InputBinding { path = "<Gamepad>/leftTrigger" }); /// /// /// /// If multiple overrides exist for the same parameter, an attempt is made to choose the override that is most specific. /// Say, that you apply an override for "duration" on an entire using /// . But then you also apply /// an override to just an individual inside the asset. In this case, the "duration" /// override for just that action will be applied to bindings of that action and the override inside the asset will /// be applied to bindings of all other actions. Note that if multiple overrides exist that could all be considered /// equally valid, the behavior is undecided. /// /// Note that parameter overrides stay in place on the map. Like binding overrides, however, they are not /// automatically persisted and thus need to be reapplied when actions are loaded from assets. This will, however, be applied /// automatically to bindings added to the action in the future as well as whenever bindings are resolved to controls. /// /// is null -or- is null /// or empty. /// public static void ApplyParameterOverride(this InputActionAsset asset, string name, PrimitiveValue value, InputBinding bindingMask = default) { if (asset == null) throw new ArgumentNullException(nameof(asset)); if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); asset.ResolveBindingsIfNecessary(); ApplyParameterOverride(asset.m_SharedStateForAllMaps, -1, ref asset.m_ParameterOverrides, ref asset.m_ParameterOverridesCount, new ParameterOverride(name, bindingMask, value)); } /// /// Set the value of the given parameter on the , , /// and objects found on the of . /// /// An action on whose to look for objects to set /// the parameter value on. /// Name of the parameter to get the value of. Case-insensitive. This can either be just the name of the /// parameter (like "duration" or expressed as nameof(TapInteraction.duration)) or can be prefixed with the /// type of object to get the parameter value from. For example, "tap:duration" will specifically get the "duration" /// parameter from the object registered as "tap" (which will usually be ). /// New value to assign to the parameter. /// A binding mask that determines which of 's /// to apply the override to. By default this is empty which leads to the override to be applied to all bindings of the action. /// /// This method both directly applies the new value and also stores the override internally. /// /// If an override for the same parameter on the same and with the same /// already exists, its value is simply updated. No new override will be created. /// /// You can use this method to set parameters (public fields) on composites, interactions, and processors that are created /// from bindings. /// /// /// /// // Create an action with a binding that has a "ClampProcessor" applied to it. /// // This processor has two parameters: /// // - "min" (float) /// // - "max" (float) /// var action = new InputAction(binding: ">Gamepad>/rightTrigger", processors: "clamp(min=0.2,max=0.8)"); /// /// // Apply parameter overrides to set the values differently. /// action.ApplyParameterOverride("min", 0.3f); /// action.ApplyParameterOverride("max", 0.9f); /// /// /// /// An override can optionally be directed at a specific type of object. /// /// /// /// // Create an action with both a "tap" and a "hold" interaction. Both have a /// // "duration" parameter. /// var action = new InputAction(binding: "<Gamepad>/buttonSouth", interactions: "tap;hold"); /// /// // Apply parameter overrides individually to the two. /// action.ApplyParameterOverride("tap:duration", 0.6f); /// action.ApplyParameterOverride("hold:duration", 4f); /// /// /// /// By default, the parameter override will apply to all bindings on the action. To limit the override /// to specific bindings, you can supply a . /// /// /// /// // Create a "look" style action with a mouse and a gamepad binding. /// var lookAction = new InputAction(); /// lookAction.AddBinding("<Mouse>/delta", processors: "scaleVector2", groups: "Mouse"); /// lookAction.AddBinding("<Gamepad>/rightStick", processors: "scaleVector2", groups: "Gamepad"); /// /// // Override scaling of the mouse delta individually. /// lookAction.ApplyBindingOverride("scaleVector2:x", 0.25f, InputBinding.MaskByGroup("Mouse")); /// lookAction.ApplyBindingOverride("scaleVector2:y", 0.25f, InputBinding.MaskByGroup("Mouse")); /// /// // Can also do that by path. /// lookAction.ApplyBindingOverride("scaleVector2:x", 0.25f, new InputBinding("<Mouse>/delta")); /// lookAction.ApplyBindingOverride("scaleVector2:y", 0.25f, new InputBinding("<Mouse>/delta")); /// /// /// /// Note that parameter overrides stay in place on the action. Like binding overrides, however, they are not /// automatically persisted and thus need to be reapplied when actions are loaded from assets. This will, however, be applied /// automatically to bindings added to the action in the future as well as whenever bindings for the action are resolved. /// /// is null -or- is null /// or empty. /// public static void ApplyParameterOverride(this InputAction action, string name, PrimitiveValue value, InputBinding bindingMask = default) { if (action == null) throw new ArgumentNullException(nameof(action)); if (name == null) throw new ArgumentNullException(nameof(name)); var actionMap = action.GetOrCreateActionMap(); actionMap.ResolveBindingsIfNecessary(); bindingMask.action = action.name; ApplyParameterOverride(actionMap.m_State, actionMap.m_MapIndexInState, ref actionMap.m_ParameterOverrides, ref actionMap.m_ParameterOverridesCount, new ParameterOverride(name, bindingMask, value)); } /// /// Set the value of the given parameter on the , , /// and objects found on the of . /// /// An action on whose to look for objects to set /// the parameter value on. /// Name of the parameter to get the value of. Case-insensitive. This can either be just the name of the /// parameter (like "duration" or expressed as nameof(TapInteraction.duration)) or can be prefixed with the /// type of object to get the parameter value from. For example, "tap:duration" will specifically get the "duration" /// parameter from the object registered as "tap" (which will usually be ). /// New value to assign to the parameter. /// Index of the binding in of to which /// to restrict the parameter override to. /// is negative or equal or greater than the number of /// of . /// is null -or- is null /// or empty. /// /// This method is a variation of which /// allows specifying a binding by index. It otherwise behaves identically to that method. /// public static void ApplyParameterOverride(this InputAction action, string name, PrimitiveValue value, int bindingIndex) { if (action == null) throw new ArgumentNullException(nameof(action)); if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); if (bindingIndex < 0) throw new ArgumentOutOfRangeException(nameof(bindingIndex)); var indexOnMap = action.BindingIndexOnActionToBindingIndexOnMap(bindingIndex); var bindingMask = new InputBinding { id = action.GetOrCreateActionMap().bindings[indexOnMap].id }; action.ApplyParameterOverride(name, value, bindingMask); } private static void ApplyParameterOverride(InputActionState state, int mapIndex, ref ParameterOverride[] parameterOverrides, ref int parameterOverridesCount, ParameterOverride parameterOverride) { // Update the parameter overrides on the map or asset. var haveExistingOverride = false; if (parameterOverrides != null) { // Try to find existing override. for (var i = 0; i < parameterOverridesCount; ++i) { ref var p = ref parameterOverrides[i]; if (string.Equals(p.objectRegistrationName, parameterOverride.objectRegistrationName, StringComparison.OrdinalIgnoreCase) && string.Equals(p.parameter, parameterOverride.parameter, StringComparison.OrdinalIgnoreCase) && p.bindingMask == parameterOverride.bindingMask) { haveExistingOverride = true; // Update value on existing override. p = parameterOverride; break; } } } if (!haveExistingOverride) { // Add new override. ArrayHelpers.AppendWithCapacity(ref parameterOverrides, ref parameterOverridesCount, parameterOverride); } // Set value on all current processor and/or interaction instances that use the parameter. foreach (var parameter in new ParameterEnumerable(state, parameterOverride, mapIndex)) { // We cannot just blindly apply the parameter here as the override we have set may be less // specific than an override we already have applied. So instead, we look up the most specific // override and set that. var actionMap = state.GetActionMap(parameter.bindingIndex); ref var binding = ref state.GetBinding(parameter.bindingIndex); var overrideToApply = ParameterOverride.Find(actionMap, ref binding, parameterOverride.parameter, parameterOverride.objectRegistrationName); if (overrideToApply.HasValue) { var fieldTypeCode = Type.GetTypeCode(parameter.field.FieldType); parameter.field.SetValue(parameter.instance, overrideToApply.Value.value.ConvertTo(fieldTypeCode).ToObject()); } } } internal struct Parameter { public object instance; public FieldInfo field; public int bindingIndex; } // Finds all instances of a parameter in one or more actions. private struct ParameterEnumerable : IEnumerable { private InputActionState m_State; private ParameterOverride m_Parameter; private int m_MapIndex; public ParameterEnumerable(InputActionState state, ParameterOverride parameter, int mapIndex = -1) { m_State = state; m_Parameter = parameter; m_MapIndex = mapIndex; } public ParameterEnumerator GetEnumerator() { return new ParameterEnumerator(m_State, m_Parameter, m_MapIndex); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } private struct ParameterEnumerator : IEnumerator { private InputActionState m_State; private int m_MapIndex; private int m_BindingCurrentIndex; private int m_BindingEndIndex; private int m_InteractionCurrentIndex; private int m_InteractionEndIndex; private int m_ProcessorCurrentIndex; private int m_ProcessorEndIndex; private InputBinding m_BindingMask; private Type m_ObjectType; private string m_ParameterName; private bool m_MayBeInteraction; private bool m_MayBeProcessor; private bool m_MayBeComposite; private bool m_CurrentBindingIsComposite; private object m_CurrentObject; private FieldInfo m_CurrentParameter; public ParameterEnumerator(InputActionState state, ParameterOverride parameter, int mapIndex = -1) : this() { m_State = state; m_ParameterName = parameter.parameter; m_MapIndex = mapIndex; m_ObjectType = parameter.objectType; m_MayBeComposite = m_ObjectType == null || typeof(InputBindingComposite).IsAssignableFrom(m_ObjectType); m_MayBeProcessor = m_ObjectType == null || typeof(InputProcessor).IsAssignableFrom(m_ObjectType); m_MayBeInteraction = m_ObjectType == null || typeof(IInputInteraction).IsAssignableFrom(m_ObjectType); m_BindingMask = parameter.bindingMask; Reset(); } private bool MoveToNextBinding() { // Find a binding that matches our mask. while (true) { ++m_BindingCurrentIndex; if (m_BindingCurrentIndex >= m_BindingEndIndex) return false; // Reached the end. ref var binding = ref m_State.GetBinding(m_BindingCurrentIndex); ref var bindingState = ref m_State.GetBindingState(m_BindingCurrentIndex); // Skip any binding that has no associated objects with parameters. if (bindingState.processorCount == 0 && bindingState.interactionCount == 0 && !binding.isComposite) continue; // If we're only looking for composites, skip any binding that isn't one. if (m_MayBeComposite && !m_MayBeProcessor && !m_MayBeInteraction && !binding.isComposite) continue; // If we're only looking for processors, skip any that hasn't got any. if (m_MayBeProcessor && !m_MayBeComposite && !m_MayBeInteraction && bindingState.processorCount == 0) continue; // If we're only looking for interactions, skip any that hasn't got any. if (m_MayBeInteraction && !m_MayBeComposite && !m_MayBeProcessor && bindingState.interactionCount == 0) continue; if (m_BindingMask.Matches(ref binding)) { if (m_MayBeComposite) m_CurrentBindingIsComposite = binding.isComposite; // Reset interaction and processor count. m_ProcessorCurrentIndex = bindingState.processorStartIndex - 1; // Minus one to account for first MoveNext(). m_ProcessorEndIndex = bindingState.processorStartIndex + bindingState.processorCount; m_InteractionCurrentIndex = bindingState.interactionStartIndex - 1; // Minus one to account for first MoveNext(). m_InteractionEndIndex = bindingState.interactionStartIndex + bindingState.interactionCount; return true; } } } private bool MoveToNextInteraction() { while (m_InteractionCurrentIndex < m_InteractionEndIndex) { ++m_InteractionCurrentIndex; if (m_InteractionCurrentIndex == m_InteractionEndIndex) break; var interaction = m_State.interactions[m_InteractionCurrentIndex]; if (FindParameter(interaction)) return true; } return false; } private bool MoveToNextProcessor() { while (m_ProcessorCurrentIndex < m_ProcessorEndIndex) { ++m_ProcessorCurrentIndex; if (m_ProcessorCurrentIndex == m_ProcessorEndIndex) break; var processor = m_State.processors[m_ProcessorCurrentIndex]; if (FindParameter(processor)) return true; } return false; } private bool FindParameter(object instance) { if (m_ObjectType != null && !m_ObjectType.IsInstanceOfType(instance)) return false; var field = instance.GetType().GetField(m_ParameterName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase); if (field == null) return false; m_CurrentParameter = field; m_CurrentObject = instance; return true; } public bool MoveNext() { while (true) { if (m_MayBeInteraction && MoveToNextInteraction()) return true; if (m_MayBeProcessor && MoveToNextProcessor()) return true; if (!MoveToNextBinding()) return false; if (m_MayBeComposite && m_CurrentBindingIsComposite) { var compositeIndex = m_State.GetBindingState(m_BindingCurrentIndex).compositeOrCompositeBindingIndex; var composite = m_State.composites[compositeIndex]; if (FindParameter(composite)) return true; } } } public unsafe void Reset() { m_CurrentObject = default; m_CurrentParameter = default; m_InteractionCurrentIndex = default; m_InteractionEndIndex = default; m_ProcessorCurrentIndex = default; m_ProcessorEndIndex = default; m_CurrentBindingIsComposite = default; if (m_MapIndex < 0) { m_BindingCurrentIndex = -1; // Account for first MoveNext(). m_BindingEndIndex = m_State.totalBindingCount; } else { m_BindingCurrentIndex = m_State.mapIndices[m_MapIndex].bindingStartIndex - 1; // Account for first MoveNext(). m_BindingEndIndex = m_State.mapIndices[m_MapIndex].bindingStartIndex + m_State.mapIndices[m_MapIndex].bindingCount; } } public Parameter Current => new Parameter { instance = m_CurrentObject, field = m_CurrentParameter, bindingIndex = m_BindingCurrentIndex, }; object IEnumerator.Current => Current; public void Dispose() { } } internal struct ParameterOverride { public string objectRegistrationName; // Optional. Such as "hold" or "scale". public string parameter; public InputBinding bindingMask; public PrimitiveValue value; public Type objectType => InputProcessor.s_Processors.LookupTypeRegistration(objectRegistrationName) ?? InputInteraction.s_Interactions.LookupTypeRegistration(objectRegistrationName) ?? InputBindingComposite.s_Composites.LookupTypeRegistration(objectRegistrationName); public ParameterOverride(string parameterName, InputBinding bindingMask, PrimitiveValue value = default) { var colonIndex = parameterName.IndexOf(':'); if (colonIndex < 0) { objectRegistrationName = null; parameter = parameterName; } else { objectRegistrationName = parameterName.Substring(0, colonIndex); parameter = parameterName.Substring(colonIndex + 1); } this.bindingMask = bindingMask; this.value = value; } public ParameterOverride(string objectRegistrationName, string parameterName, InputBinding bindingMask, PrimitiveValue value = default) { this.objectRegistrationName = objectRegistrationName; this.parameter = parameterName; this.bindingMask = bindingMask; this.value = value; } // Find the *most specific* override to apply to the given parameter. public static ParameterOverride? Find(InputActionMap actionMap, ref InputBinding binding, string parameterName, string objectRegistrationName) { // Look at level of map. var overrideOnMap = Find(actionMap.m_ParameterOverrides, actionMap.m_ParameterOverridesCount, ref binding, parameterName, objectRegistrationName); // Look at level of asset (if present). var asset = actionMap.asset; var overrideOnAsset = asset != null ? Find(asset.m_ParameterOverrides, asset.m_ParameterOverridesCount, ref binding, parameterName, objectRegistrationName) : null; return PickMoreSpecificOne(overrideOnMap, overrideOnAsset); } private static ParameterOverride? Find(ParameterOverride[] overrides, int overrideCount, ref InputBinding binding, string parameterName, string objectRegistrationName) { ParameterOverride? result = null; for (var i = 0; i < overrideCount; ++i) { ref var current = ref overrides[i]; if (!string.Equals(parameterName, current.parameter, StringComparison.OrdinalIgnoreCase)) continue; // Different parameter name. if (!current.bindingMask.Matches(binding)) continue; if (current.objectRegistrationName != null && !string.Equals(current.objectRegistrationName, objectRegistrationName, StringComparison.OrdinalIgnoreCase)) continue; if (result == null) { // First match. result = current; } else { // Already have a match. See which one is more specific. result = PickMoreSpecificOne(result, current); } } return result; } private static ParameterOverride? PickMoreSpecificOne(ParameterOverride? first, ParameterOverride? second) { if (first == null) return second; if (second == null) return first; // Having an objectRegistrationName always wins vs not having one. if (first.Value.objectRegistrationName != null && second.Value.objectRegistrationName == null) return first; if (second.Value.objectRegistrationName != null && first.Value.objectRegistrationName == null) return second; // Targeting a specific path always wins vs not doing so. if (first.Value.bindingMask.effectivePath != null && second.Value.bindingMask.effectivePath == null) return first; if (second.Value.bindingMask.effectivePath != null && first.Value.bindingMask.effectivePath == null) return second; // Targeting a specific actions always wins vs not doing so. if (first.Value.bindingMask.action != null && second.Value.bindingMask.action == null) return first; if (second.Value.bindingMask.action != null && first.Value.bindingMask.action == null) return second; // Undecided. First wins by default. return first; } } } }