A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections;
3using System.Collections.Generic;
4using System.Linq.Expressions;
5using System.Reflection;
6using Unity.Collections.LowLevel.Unsafe;
7using UnityEngine.InputSystem.Utilities;
8
9namespace UnityEngine.InputSystem
10{
11 partial class InputActionRebindingExtensions
12 {
13 /// <summary>
14 /// Return the current value of the given parameter as found on the processors, interactions, or composites
15 /// of the action's current bindings.
16 /// </summary>
17 /// <param name="action">Action on whose bindings to look for the value of the given parameter.</param>
18 /// <param name="name">Name of the parameter to get the value of. Case-insensitive. This can either be just the name of the
19 /// parameter (like <c>"duration"</c> or expressed as <c>nameof(TapInteraction.duration)</c>) or can be prefixed with the
20 /// type of object to get the parameter value from. For example, <c>"tap:duration"</c> will specifically get the <c>"duration"</c>
21 /// parameter from the object registered as <c>"tap"</c> (which will usually be <see cref="Interactions.TapInteraction"/>).</param>
22 /// <param name="bindingMask">Optional mask that determines on which bindings to look for objects with parameters. If used, only
23 /// bindings that match (see <see cref="InputBinding.Matches"/>) the given mask will be taken into account.</param>
24 /// <returns>The current value of the given parameter or <c>null</c> if the parameter could not be found.</returns>
25 /// <remarks>
26 /// Parameters are found on interactions (<see cref="IInputInteraction"/>), processors (<see cref="InputProcessor"/>), and
27 /// composites (see <see cref="InputBindingComposite"/>) that are applied to bindings. For example, the following binding
28 /// adds a <c>Hold</c> interaction with a custom <c>duration</c> parameter on top of binding to the gamepad's A button:
29 ///
30 /// <example>
31 /// <code>
32 /// new InputBinding
33 /// {
34 /// path = "<Gamepad>/buttonSouth",
35 /// interactions = "hold(duration=0.6)"
36 /// };
37 /// </code>
38 /// </example>
39 ///
40 /// In the editor UI, parameters are set graphically from the properties sections in the right-most pane
41 /// in the action editor when an action or a binding is selected.
42 ///
43 /// When the binding above is applied to an action, the <c>duration</c> parameter from the <c>Hold</c> interaction can be
44 /// queried like so:
45 ///
46 /// <example>
47 /// <code>
48 /// action.GetParameterValue("duration") // Returns 0.6
49 /// </code>
50 /// </example>
51 ///
52 /// Note that if there are multiple objects on the action that use the same parameter name, the value of the <em>first</em> parameter
53 /// that is encountered is returned. Also note that this method will create GC heap garbage.
54 ///
55 /// The type of object to query the parameter from can be include in the <paramref name="name"/> parameter. For example, if
56 /// an action has both a <see cref="Interactions.TapInteraction"/> and a <see cref="Interactions.HoldInteraction"/> on it, the
57 /// <c>duration</c> parameter can be queried independently like so:
58 ///
59 /// <example>
60 /// <code>
61 /// // Query "duration" from "hold":
62 /// action.GetParameterValue("hold:duration");
63 ///
64 /// // Query "duration" from "tap":
65 /// action.GetParameterValue("tap:duration");
66 /// </code>
67 /// </example>
68 ///
69 /// The names used here to identify the object holding the parameter are the same used by <see cref="InputSystem.RegisterInteraction"/>,
70 /// <see cref="InputSystem.RegisterBindingComposite"/>, and <see cref="InputSystem.RegisterProcessor"/>.
71 /// </remarks>
72 /// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c> -or- <paramref name="name"/> is <c>null</c></exception>
73 /// <seealso cref="ApplyParameterOverride(InputActionMap,string,PrimitiveValue,InputBinding)"/>
74 /// <seealso cref="ApplyBindingOverride(InputAction,string,string,string)"/>
75 /// <seealso cref="Editor.InputParameterEditor"/>
76 public static PrimitiveValue? GetParameterValue(this InputAction action, string name, InputBinding bindingMask = default)
77 {
78 if (action == null)
79 throw new ArgumentNullException(nameof(action));
80 if (string.IsNullOrEmpty(name))
81 throw new ArgumentNullException(nameof(name));
82
83 return action.GetParameterValue(new ParameterOverride(name, bindingMask));
84 }
85
86 private static PrimitiveValue? GetParameterValue(this InputAction action, ParameterOverride parameterOverride)
87 {
88 parameterOverride.bindingMask.action = action.name;
89
90 var actionMap = action.GetOrCreateActionMap();
91 actionMap.ResolveBindingsIfNecessary();
92 foreach (var parameter in new ParameterEnumerable(actionMap.m_State, parameterOverride, actionMap.m_MapIndexInState))
93 {
94 var value = parameter.field.GetValue(parameter.instance);
95 return PrimitiveValue.FromObject(value);
96 }
97
98 return null;
99 }
100
101 /// <summary>
102 /// Return the current value of the given parameter as found on the processors, interactions, or composites
103 /// of the action's current bindings.
104 /// </summary>
105 /// <param name="action">Action on whose bindings to look for the value of the given parameter.</param>
106 /// <param name="name">Name of the parameter to get the value of. Case-insensitive. This can either be just the name of the
107 /// parameter (like <c>"duration"</c> or expressed as <c>nameof(TapInteraction.duration)</c>) or can be prefixed with the
108 /// type of object to get the parameter value from. For example, <c>"tap:duration"</c> will specifically get the <c>"duration"</c>
109 /// parameter from the object registered as <c>"tap"</c> (which will usually be <see cref="Interactions.TapInteraction"/>).</param>
110 /// <param name="bindingIndex">Index of the binding in <paramref name="action"/>'s <see cref="InputAction.bindings"/>
111 /// to look for processors, interactions, and composites on.</param>
112 /// <returns>The current value of the given parameter or <c>null</c> if the parameter not could be found.</returns>
113 /// <remarks>
114 /// This method is a variation of <see cref="ApplyParameterOverride(InputActionMap,string,PrimitiveValue,InputBinding)"/>
115 /// to specifically target a single binding by index. Otherwise, the method is identical in functionality.
116 /// </remarks>
117 /// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c> -or- <paramref name="name"/> is <c>null</c></exception>
118 public static PrimitiveValue? GetParameterValue(this InputAction action, string name, int bindingIndex)
119 {
120 if (action == null)
121 throw new ArgumentNullException(nameof(action));
122 if (string.IsNullOrEmpty(name))
123 throw new ArgumentNullException(nameof(name));
124 if (bindingIndex < 0)
125 throw new ArgumentOutOfRangeException(nameof(bindingIndex));
126
127 var indexOnMap = action.BindingIndexOnActionToBindingIndexOnMap(bindingIndex);
128 var bindingMask = new InputBinding { id = action.GetOrCreateActionMap().bindings[indexOnMap].id };
129
130 return action.GetParameterValue(name, bindingMask);
131 }
132
133 /// <summary>
134 /// Return the current value of the given parameter as found on the processors, interactions, or composites
135 /// of the action's current bindings.
136 /// </summary>
137 /// <param name="action">Action on whose bindings to look for the value of the given parameter.</param>
138 /// <param name="expr">An expression such as <c>(TapInteraction x) => x.duration</c> that determines the
139 /// name and type of the parameter being looked for.</param>
140 /// <param name="bindingMask">Optional mask that determines on which bindings to look for objects with parameters. If used, only
141 /// bindings that match (see <see cref="InputBinding.Matches"/>) the given mask will be taken into account.</param>
142 /// <returns>The current value of the given parameter or <c>null</c> if the parameter not could be found.</returns>
143 /// <remarks>
144 /// This method is a variation of <see cref="ApplyParameterOverride(InputActionMap,string,PrimitiveValue,InputBinding)"/>
145 /// that encapsulates a reference to the name of the parameter and the type of object it is found on in a way that is
146 /// type-safe and does not involve strings.
147 ///
148 /// <example>
149 /// <code>
150 /// // Get the "duration" parameter from a TapInteraction.
151 /// // This is equivalent to calling GetParameterValue("tap:duration")
152 /// // but will return a float? instead of a PrimitiveValue?.
153 /// action.GetParameterValue((TapInteraction x) => x.duration)
154 /// </code>
155 /// </example>
156 /// </remarks>
157 /// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c> -or- <paramref name="expr"/> is <c>null</c></exception>
158 /// <seealso cref="ApplyParameterOverride{TObject,TValue}(InputAction,Expression{Func{TObject,TValue}},TValue,InputBinding)"/>
159 public static unsafe TValue? GetParameterValue<TObject, TValue>(this InputAction action, Expression<Func<TObject, TValue>> expr, InputBinding bindingMask = default)
160 where TValue : struct
161 {
162 if (action == null)
163 throw new ArgumentNullException(nameof(action));
164 if (expr == null)
165 throw new ArgumentNullException(nameof(expr));
166
167 var parameterOverride = ExtractParameterOverride(expr, bindingMask);
168 var value = action.GetParameterValue(parameterOverride);
169
170 if (value == null)
171 return null;
172
173 // Type is guaranteed to match but just in case,
174 // make extra sure with a check here.
175 if (Type.GetTypeCode(typeof(TValue)) == value.Value.type)
176 {
177 // Can't just cast here so use UnsafeUtility to work around that.
178 var v = value.Value;
179 var result = default(TValue);
180 UnsafeUtility.MemCpy(UnsafeUtility.AddressOf(ref result),
181 v.valuePtr,
182 UnsafeUtility.SizeOf<TValue>());
183 return result;
184 }
185
186 // Shouldn't get here but just in case, do a conversion using C#'s Convert
187 // machinery as a fallback.
188 return (TValue)Convert.ChangeType(value.Value.ToObject(), typeof(TValue));
189 }
190
191 /// <summary>
192 /// Set the value of the given parameter on the <see cref="InputBindingComposite"/>, <see cref="IInputInteraction"/>,
193 /// and <see cref="InputProcessor"/> objects found on the <see cref="InputAction.bindings"/> of <paramref name="action"/>.
194 /// </summary>
195 /// <param name="action">An action on whose <see cref="InputAction.bindings"/> to look for objects to set
196 /// the parameter value on.</param>
197 /// <param name="expr">An expression such as <c>(TapInteraction x) => x.duration</c> that determines the
198 /// name and type of the parameter whose value to set.</param>
199 /// <param name="value">New value to assign to the parameter.</param>
200 /// <param name="bindingMask">Optional mask that determines on which bindings to look for objects with parameters. If used, only
201 /// bindings that match (see <see cref="InputBinding.Matches"/>) the given mask will have the override applied to them.</param>
202 /// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c> -or- <paramref name="expr"/> is <c>null</c>
203 /// or empty.</exception>
204 /// <remarks>
205 /// This method is a variation of <see cref="ApplyParameterOverride(InputAction,string,PrimitiveValue,InputBinding)"/>
206 /// that encapsulates a reference to the name of the parameter and the type of object it is found on in a way that is
207 /// type-safe and does not involve strings.
208 ///
209 /// <example>
210 /// <code>
211 /// // Override the "duration" parameter from a TapInteraction.
212 /// // This is equivalent to calling ApplyParameterOverride("tap:duration", 0.4f).
213 /// action.ApplyParameterOverride((TapInteraction x) => x.duration, 0.4f);
214 /// </code>
215 /// </example>
216 /// </remarks>
217 /// <seealso cref="GetParameterValue{TObject,TValue}(InputAction,Expression{Func{TObject,TValue}},InputBinding)"/>
218 public static void ApplyParameterOverride<TObject, TValue>(this InputAction action, Expression<Func<TObject, TValue>> expr, TValue value,
219 InputBinding bindingMask = default)
220 where TValue : struct
221 {
222 if (action == null)
223 throw new ArgumentNullException(nameof(action));
224 if (expr == null)
225 throw new ArgumentNullException(nameof(expr));
226
227 var actionMap = action.GetOrCreateActionMap();
228 actionMap.ResolveBindingsIfNecessary();
229 bindingMask.action = action.name;
230
231 var parameterOverride = ExtractParameterOverride(expr, bindingMask, PrimitiveValue.From(value));
232
233 ApplyParameterOverride(actionMap.m_State, actionMap.m_MapIndexInState,
234 ref actionMap.m_ParameterOverrides, ref actionMap.m_ParameterOverridesCount,
235 parameterOverride);
236 }
237
238 /// <summary>
239 /// Set the value of the given parameter on the <see cref="InputBindingComposite"/>, <see cref="IInputInteraction"/>,
240 /// and <see cref="InputProcessor"/> objects found on the <see cref="InputActionMap.bindings"/> of <paramref name="actionMap"/>.
241 /// </summary>
242 /// <param name="actionMap">An action on whose <see cref="InputActionMap.bindings"/> to look for objects to set
243 /// the parameter value on.</param>
244 /// <param name="expr">An expression such as <c>(TapInteraction x) => x.duration</c> that determines the
245 /// name and type of the parameter whose value to set.</param>
246 /// <param name="value">New value to assign to the parameter.</param>
247 /// <param name="bindingMask">Optional mask that determines on which bindings to look for objects with parameters. If used, only
248 /// bindings that match (see <see cref="InputBinding.Matches"/>) the given mask will have the override applied to them.</param>
249 /// <exception cref="ArgumentNullException"><paramref name="actionMap"/> is <c>null</c> -or- <paramref name="expr"/> is <c>null</c>
250 /// or empty.</exception>
251 /// <remarks>
252 /// This method is a variation of <see cref="ApplyParameterOverride(InputActionMap,string,PrimitiveValue,InputBinding)"/>
253 /// that encapsulates a reference to the name of the parameter and the type of object it is found on in a way that is
254 /// type-safe and does not involve strings.
255 ///
256 /// <example>
257 /// <code>
258 /// // Override the "duration" parameter from a TapInteraction.
259 /// // This is equivalent to calling mApplyParameterOverride("tap:duration", 0.4f).
260 /// actionMap.ApplyParameterOverride((TapInteraction x) => x.duration, 0.4f);
261 /// </code>
262 /// </example>
263 /// </remarks>
264 /// <seealso cref="GetParameterValue{TObject,TValue}(InputAction,Expression{Func{TObject,TValue}},InputBinding)"/>
265 public static void ApplyParameterOverride<TObject, TValue>(this InputActionMap actionMap, Expression<Func<TObject, TValue>> expr, TValue value,
266 InputBinding bindingMask = default)
267 where TValue : struct
268 {
269 if (actionMap == null)
270 throw new ArgumentNullException(nameof(actionMap));
271 if (expr == null)
272 throw new ArgumentNullException(nameof(expr));
273
274 actionMap.ResolveBindingsIfNecessary();
275
276 var parameterOverride = ExtractParameterOverride(expr, bindingMask, PrimitiveValue.From(value));
277
278 ApplyParameterOverride(actionMap.m_State, actionMap.m_MapIndexInState,
279 ref actionMap.m_ParameterOverrides, ref actionMap.m_ParameterOverridesCount,
280 parameterOverride);
281 }
282
283 /// <summary>
284 /// Set the value of the given parameter on the <see cref="InputBindingComposite"/>, <see cref="IInputInteraction"/>,
285 /// and <see cref="InputProcessor"/> objects found on the <see cref="InputActionMap.bindings"/> of the <see cref="InputActionAsset.actionMaps"/>
286 /// in <paramref name="asset"/>.
287 /// </summary>
288 /// <param name="asset">An asset on whose <see cref="InputActionMap.bindings"/> to look for objects to set
289 /// the parameter value on.</param>
290 /// <param name="expr">An expression such as <c>(TapInteraction x) => x.duration</c> that determines the
291 /// name and type of the parameter whose value to set.</param>
292 /// <param name="value">New value to assign to the parameter.</param>
293 /// <param name="bindingMask">Optional mask that determines on which bindings to look for objects with parameters. If used, only
294 /// bindings that match (see <see cref="InputBinding.Matches"/>) the given mask will have the override applied to them.</param>
295 /// <exception cref="ArgumentNullException"><paramref name="asset"/> is <c>null</c> -or- <paramref name="expr"/> is <c>null</c>
296 /// or empty.</exception>
297 /// <remarks>
298 /// This method is a variation of <see cref="ApplyParameterOverride(InputActionAsset,string,PrimitiveValue,InputBinding)"/>
299 /// that encapsulates a reference to the name of the parameter and the type of object it is found on in a way that is
300 /// type-safe and does not involve strings.
301 ///
302 /// <example>
303 /// <code>
304 /// // Override the "duration" parameter from a TapInteraction.
305 /// // This is equivalent to calling mApplyParameterOverride("tap:duration", 0.4f).
306 /// asset.ApplyParameterOverride((TapInteraction x) => x.duration, 0.4f);
307 /// </code>
308 /// </example>
309 /// </remarks>
310 /// <seealso cref="GetParameterValue{TObject,TValue}(InputAction,Expression{Func{TObject,TValue}},InputBinding)"/>
311 public static void ApplyParameterOverride<TObject, TValue>(this InputActionAsset asset, Expression<Func<TObject, TValue>> expr, TValue value,
312 InputBinding bindingMask = default)
313 where TValue : struct
314 {
315 if (asset == null)
316 throw new ArgumentNullException(nameof(asset));
317 if (expr == null)
318 throw new ArgumentNullException(nameof(expr));
319
320 asset.ResolveBindingsIfNecessary();
321
322 var parameterOverride = ExtractParameterOverride(expr, bindingMask, PrimitiveValue.From(value));
323
324 ApplyParameterOverride(asset.m_SharedStateForAllMaps, -1,
325 ref asset.m_ParameterOverrides, ref asset.m_ParameterOverridesCount,
326 parameterOverride);
327 }
328
329 private static ParameterOverride ExtractParameterOverride<TObject, TValue>(Expression<Func<TObject, TValue>> expr,
330 InputBinding bindingMask = default, PrimitiveValue value = default)
331 {
332 if (!(expr is LambdaExpression lambda))
333 throw new ArgumentException($"Expression must be a LambdaExpression but was a {expr.GetType().Name} instead", nameof(expr));
334
335 if (!(lambda.Body is MemberExpression body))
336 {
337 // If the field type in the lambda doesn't match the TValue type being used,
338 // but there is a coercion, the compiler will automatically insert a Convert(x.name, TValue)
339 // expression.
340 if (lambda.Body is UnaryExpression unary && unary.NodeType == ExpressionType.Convert && unary.Operand is MemberExpression b)
341 {
342 body = b;
343 }
344 else
345 {
346 throw new ArgumentException(
347 $"Body in LambdaExpression must be a MemberExpression (x.name) but was a {expr.GetType().Name} instead",
348 nameof(expr));
349 }
350 }
351
352 string objectRegistrationName;
353 if (typeof(InputProcessor).IsAssignableFrom(typeof(TObject)))
354 objectRegistrationName = InputProcessor.s_Processors.FindNameForType(typeof(TObject));
355 else if (typeof(IInputInteraction).IsAssignableFrom(typeof(TObject)))
356 objectRegistrationName = InputInteraction.s_Interactions.FindNameForType(typeof(TObject));
357 else if (typeof(InputBindingComposite).IsAssignableFrom(typeof(TObject)))
358 objectRegistrationName = InputBindingComposite.s_Composites.FindNameForType(typeof(TObject));
359 else
360 throw new ArgumentException(
361 $"Given type must be an InputProcessor, IInputInteraction, or InputBindingComposite (was {typeof(TObject).Name})",
362 nameof(TObject));
363
364 return new ParameterOverride(objectRegistrationName, body.Member.Name, bindingMask, value);
365 }
366
367 /// <summary>
368 /// Set the value of the given parameter on the <see cref="InputBindingComposite"/>, <see cref="IInputInteraction"/>,
369 /// and <see cref="InputProcessor"/> objects found on the <see cref="InputActionMap.bindings"/> of <paramref name="actionMap"/>.
370 /// </summary>
371 /// <param name="actionMap">An action map on whose <see cref="InputActionMap.bindings"/> to look for objects to set
372 /// the parameter value on.</param>
373 /// <param name="name">Name of the parameter to get the value of. Case-insensitive. This can either be just the name of the
374 /// parameter (like <c>"duration"</c> or expressed as <c>nameof(TapInteraction.duration)</c>) or can be prefixed with the
375 /// type of object to get the parameter value from. For example, <c>"tap:duration"</c> will specifically get the <c>"duration"</c>
376 /// parameter from the object registered as <c>"tap"</c> (which will usually be <see cref="Interactions.TapInteraction"/>).</param>
377 /// <param name="value">New value to assign to the parameter.</param>
378 /// <param name="bindingMask">A binding mask that determines which of <paramref name="actionMap"/>'s <see cref="InputActionMap.bindings"/>
379 /// to apply the override to. By default this is empty which leads to the override to be applied to all bindings in the map.</param>
380 /// <remarks>
381 /// This method both directly applies the new value and also stores the override internally.
382 ///
383 /// If an override for the same parameter <paramref name="name"/> and with the same <paramref name="bindingMask"/> already exists,
384 /// its value is simply updated. No new override will be created.
385 ///
386 /// You can use this method to set parameters (public fields) on composites, interactions, and processors that are created
387 /// from bindings.
388 ///
389 /// <example>
390 /// <code>
391 /// // Create an action map with two actions.
392 /// var map = new InputActionMap();
393 /// var action1 = map.AddAction("action1");
394 /// var action2 = map.AddAction("action2");
395 ///
396 /// // Add a binding to each action to which a "ClampProcessor" is applied.
397 /// // This processor has two parameters:
398 /// // - "min" (float)
399 /// // - "max" (float)
400 /// action1.AddBinding(">Gamepad>/rightTrigger", processors: "clamp(min=0.2,max=0.8)");
401 /// action2.AddBinding(">Gamepad>/leftTrigger", processors: "clamp(min=0.2,max=0.8)");
402 ///
403 /// // Apply parameter overrides to set the values differently.
404 /// // This will apply the setting to *both* the bindings on action1 *and* action2.
405 /// map.ApplyParameterOverride("min", 0.3f);
406 /// map.ApplyParameterOverride("max", 0.9f);
407 /// </code>
408 /// </example>
409 ///
410 /// An override can optionally be directed at a specific type of object.
411 ///
412 /// <example>
413 /// <code>
414 /// map.ApplyParameterOverride("clamp:min", 0.3f);
415 /// map.ApplyParameterOverride("clamp:max", 0.9f);
416 /// </code>
417 /// </example>
418 ///
419 /// By default, the parameter override will apply to all bindings in the map. To limit the override
420 /// to specific bindings, you can supply a <paramref name="bindingMask"/>.
421 ///
422 /// <example>
423 /// <code>
424 /// // Apply a parameter override only to action1.
425 /// map.ApplyBindingOverride("clamp:min", 0.25f, new InputBinding { action = action1.name });
426 ///
427 /// // Apply a parameter override only to a specific binding path.
428 /// map.ApplyBindingOverride("clamp:min", 0.4f, new InputBinding { path = "<Gamepad>/leftTrigger" });
429 /// </code>
430 /// </example>
431 ///
432 /// If multiple overrides exist for the same parameter, an attempt is made to choose the override that is most specific.
433 /// Say, that you apply an override for <c>"duration"</c> on an entire <see cref="InputActionAsset"/> using
434 /// <see cref="ApplyParameterOverride(InputActionAsset,String,PrimitiveValue,InputBinding)"/>. But then you also apply
435 /// an override to just an individual <see cref="InputAction"/> inside the asset. In this case, the <c>"duration"</c>
436 /// override for just that action will be applied to bindings of that action and the override inside the asset will
437 /// be applied to bindings of all other actions. Note that if multiple overrides exist that could all be considered
438 /// equally valid, the behavior is undecided.
439 ///
440 /// Note that parameter overrides stay in place on the map. Like binding overrides, however, they are not
441 /// automatically persisted and thus need to be reapplied when actions are loaded from assets. This will, however, be applied
442 /// automatically to bindings added to the action in the future as well as whenever bindings are resolved to controls.
443 /// </remarks>
444 /// <exception cref="ArgumentNullException"><paramref name="actionMap"/> is <c>null</c> -or- <paramref name="name"/> is <c>null</c>
445 /// or empty.</exception>
446 /// <seealso cref="GetParameterValue(InputAction,string,InputBinding)"/>
447 public static void ApplyParameterOverride(this InputActionMap actionMap, string name, PrimitiveValue value, InputBinding bindingMask = default)
448 {
449 if (actionMap == null)
450 throw new ArgumentNullException(nameof(actionMap));
451 if (string.IsNullOrEmpty(name))
452 throw new ArgumentNullException(nameof(name));
453
454 actionMap.ResolveBindingsIfNecessary();
455
456 ApplyParameterOverride(actionMap.m_State, actionMap.m_MapIndexInState,
457 ref actionMap.m_ParameterOverrides, ref actionMap.m_ParameterOverridesCount,
458 new ParameterOverride(name, bindingMask, value));
459 }
460
461 /// <summary>
462 /// Set the value of the given parameter on the <see cref="InputBindingComposite"/>, <see cref="IInputInteraction"/>,
463 /// and <see cref="InputProcessor"/> objects found on the <see cref="InputActionMap.bindings"/> of each of the <see cref="InputActionAsset.actionMaps"/>
464 /// in <paramref name="asset"/>.
465 /// </summary>
466 /// <param name="asset">An <c>.inputactions</c> asset on whose <see cref="InputActionMap.bindings"/> to look for objects to set
467 /// the parameter value on.</param>
468 /// <param name="name">Name of the parameter to get the value of. Case-insensitive. This can either be just the name of the
469 /// parameter (like <c>"duration"</c> or expressed as <c>nameof(TapInteraction.duration)</c>) or can be prefixed with the
470 /// type of object to get the parameter value from. For example, <c>"tap:duration"</c> will specifically get the <c>"duration"</c>
471 /// parameter from the object registered as <c>"tap"</c> (which will usually be <see cref="Interactions.TapInteraction"/>).</param>
472 /// <param name="value">New value to assign to the parameter.</param>
473 /// <param name="bindingMask">A binding mask that determines which of the <see cref="InputActionMap.bindings"/>
474 /// to apply the override to. By default this is empty which leads to the override to be applied to all bindings in the asset.</param>
475 /// <remarks>
476 /// This method both directly applies the new value and also stores the override internally.
477 ///
478 /// If an override for the same parameter <paramref name="name"/> and with the same <paramref name="bindingMask"/> already exists,
479 /// its value is simply updated. No new override will be created.
480 ///
481 /// You can use this method to set parameters (public fields) on composites, interactions, and processors that are created
482 /// from bindings.
483 ///
484 /// <example>
485 /// <code>
486 /// // Create an asset with one action map and two actions.
487 /// var asset = ScriptableObject.CreateInstance<InputActionAsset>();
488 /// var map = asset.AddActionMap("map");
489 /// var action1 = map.AddAction("action1");
490 /// var action2 = map.AddAction("action2");
491 ///
492 /// // Add a binding to each action to which a "ClampProcessor" is applied.
493 /// // This processor has two parameters:
494 /// // - "min" (float)
495 /// // - "max" (float)
496 /// action1.AddBinding(">Gamepad>/rightTrigger", processors: "clamp(min=0.2,max=0.8)");
497 /// action2.AddBinding(">Gamepad>/leftTrigger", processors: "clamp(min=0.2,max=0.8)");
498 ///
499 /// // Apply parameter overrides to set the values differently.
500 /// // This will apply the setting to *both* the bindings on action1 *and* action2.
501 /// asset.ApplyParameterOverride("min", 0.3f);
502 /// asset.ApplyParameterOverride("max", 0.9f);
503 /// </code>
504 /// </example>
505 ///
506 /// An override can optionally be directed at a specific type of object.
507 ///
508 /// <example>
509 /// <code>
510 /// asset.ApplyParameterOverride("clamp:min", 0.3f);
511 /// asset.ApplyParameterOverride("clamp:max", 0.9f);
512 /// </code>
513 /// </example>
514 ///
515 /// By default, the parameter override will apply to all bindings in the asset. To limit the override
516 /// to specific bindings, you can supply a <paramref name="bindingMask"/>.
517 ///
518 /// <example>
519 /// <code>
520 /// // Apply a parameter override only to action1.
521 /// asset.ApplyBindingOverride("clamp:min", 0.25f, new InputBinding { action = action1.name });
522 ///
523 /// // Apply a parameter override only to a specific binding path.
524 /// asset.ApplyBindingOverride("clamp:min", 0.4f, new InputBinding { path = "<Gamepad>/leftTrigger" });
525 /// </code>
526 /// </example>
527 ///
528 /// If multiple overrides exist for the same parameter, an attempt is made to choose the override that is most specific.
529 /// Say, that you apply an override for <c>"duration"</c> on an entire <see cref="InputActionAsset"/> using
530 /// <see cref="ApplyParameterOverride(InputActionAsset,String,PrimitiveValue,InputBinding)"/>. But then you also apply
531 /// an override to just an individual <see cref="InputAction"/> inside the asset. In this case, the <c>"duration"</c>
532 /// override for just that action will be applied to bindings of that action and the override inside the asset will
533 /// be applied to bindings of all other actions. Note that if multiple overrides exist that could all be considered
534 /// equally valid, the behavior is undecided.
535 ///
536 /// Note that parameter overrides stay in place on the map. Like binding overrides, however, they are not
537 /// automatically persisted and thus need to be reapplied when actions are loaded from assets. This will, however, be applied
538 /// automatically to bindings added to the action in the future as well as whenever bindings are resolved to controls.
539 /// </remarks>
540 /// <exception cref="ArgumentNullException"><paramref name="asset"/> is <c>null</c> -or- <paramref name="name"/> is <c>null</c>
541 /// or empty.</exception>
542 /// <seealso cref="GetParameterValue(InputAction,string,InputBinding)"/>
543 public static void ApplyParameterOverride(this InputActionAsset asset, string name, PrimitiveValue value, InputBinding bindingMask = default)
544 {
545 if (asset == null)
546 throw new ArgumentNullException(nameof(asset));
547 if (string.IsNullOrEmpty(name))
548 throw new ArgumentNullException(nameof(name));
549
550 asset.ResolveBindingsIfNecessary();
551
552 ApplyParameterOverride(asset.m_SharedStateForAllMaps, -1,
553 ref asset.m_ParameterOverrides, ref asset.m_ParameterOverridesCount,
554 new ParameterOverride(name, bindingMask, value));
555 }
556
557 /// <summary>
558 /// Set the value of the given parameter on the <see cref="InputBindingComposite"/>, <see cref="IInputInteraction"/>,
559 /// and <see cref="InputProcessor"/> objects found on the <see cref="InputAction.bindings"/> of <paramref name="action"/>.
560 /// </summary>
561 /// <param name="action">An action on whose <see cref="InputAction.bindings"/> to look for objects to set
562 /// the parameter value on.</param>
563 /// <param name="name">Name of the parameter to get the value of. Case-insensitive. This can either be just the name of the
564 /// parameter (like <c>"duration"</c> or expressed as <c>nameof(TapInteraction.duration)</c>) or can be prefixed with the
565 /// type of object to get the parameter value from. For example, <c>"tap:duration"</c> will specifically get the <c>"duration"</c>
566 /// parameter from the object registered as <c>"tap"</c> (which will usually be <see cref="Interactions.TapInteraction"/>).</param>
567 /// <param name="value">New value to assign to the parameter.</param>
568 /// <param name="bindingMask">A binding mask that determines which of <paramref name="action"/>'s <see cref="InputAction.bindings"/>
569 /// to apply the override to. By default this is empty which leads to the override to be applied to all bindings of the action.</param>
570 /// <remarks>
571 /// This method both directly applies the new value and also stores the override internally.
572 ///
573 /// If an override for the same parameter <paramref name="name"/> on the same <paramref name="action"/> and with the same
574 /// <paramref name="bindingMask"/> already exists, its value is simply updated. No new override will be created.
575 ///
576 /// You can use this method to set parameters (public fields) on composites, interactions, and processors that are created
577 /// from bindings.
578 ///
579 /// <example>
580 /// <code>
581 /// // Create an action with a binding that has a "ClampProcessor" applied to it.
582 /// // This processor has two parameters:
583 /// // - "min" (float)
584 /// // - "max" (float)
585 /// var action = new InputAction(binding: ">Gamepad>/rightTrigger", processors: "clamp(min=0.2,max=0.8)");
586 ///
587 /// // Apply parameter overrides to set the values differently.
588 /// action.ApplyParameterOverride("min", 0.3f);
589 /// action.ApplyParameterOverride("max", 0.9f);
590 /// </code>
591 /// </example>
592 ///
593 /// An override can optionally be directed at a specific type of object.
594 ///
595 /// <example>
596 /// <code>
597 /// // Create an action with both a "tap" and a "hold" interaction. Both have a
598 /// // "duration" parameter.
599 /// var action = new InputAction(binding: "<Gamepad>/buttonSouth", interactions: "tap;hold");
600 ///
601 /// // Apply parameter overrides individually to the two.
602 /// action.ApplyParameterOverride("tap:duration", 0.6f);
603 /// action.ApplyParameterOverride("hold:duration", 4f);
604 /// </code>
605 /// </example>
606 ///
607 /// By default, the parameter override will apply to all bindings on the action. To limit the override
608 /// to specific bindings, you can supply a <paramref name="bindingMask"/>.
609 ///
610 /// <example>
611 /// <code>
612 /// // Create a "look" style action with a mouse and a gamepad binding.
613 /// var lookAction = new InputAction();
614 /// lookAction.AddBinding("<Mouse>/delta", processors: "scaleVector2", groups: "Mouse");
615 /// lookAction.AddBinding("<Gamepad>/rightStick", processors: "scaleVector2", groups: "Gamepad");
616 ///
617 /// // Override scaling of the mouse delta individually.
618 /// lookAction.ApplyBindingOverride("scaleVector2:x", 0.25f, InputBinding.MaskByGroup("Mouse"));
619 /// lookAction.ApplyBindingOverride("scaleVector2:y", 0.25f, InputBinding.MaskByGroup("Mouse"));
620 ///
621 /// // Can also do that by path.
622 /// lookAction.ApplyBindingOverride("scaleVector2:x", 0.25f, new InputBinding("<Mouse>/delta"));
623 /// lookAction.ApplyBindingOverride("scaleVector2:y", 0.25f, new InputBinding("<Mouse>/delta"));
624 /// </code>
625 /// </example>
626 ///
627 /// Note that parameter overrides stay in place on the action. Like binding overrides, however, they are not
628 /// automatically persisted and thus need to be reapplied when actions are loaded from assets. This will, however, be applied
629 /// automatically to bindings added to the action in the future as well as whenever bindings for the action are resolved.
630 /// </remarks>
631 /// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c> -or- <paramref name="name"/> is <c>null</c>
632 /// or empty.</exception>
633 /// <seealso cref="GetParameterValue(InputAction,string,InputBinding)"/>
634 public static void ApplyParameterOverride(this InputAction action, string name, PrimitiveValue value, InputBinding bindingMask = default)
635 {
636 if (action == null)
637 throw new ArgumentNullException(nameof(action));
638 if (name == null)
639 throw new ArgumentNullException(nameof(name));
640
641 var actionMap = action.GetOrCreateActionMap();
642 actionMap.ResolveBindingsIfNecessary();
643 bindingMask.action = action.name;
644
645 ApplyParameterOverride(actionMap.m_State, actionMap.m_MapIndexInState,
646 ref actionMap.m_ParameterOverrides, ref actionMap.m_ParameterOverridesCount,
647 new ParameterOverride(name, bindingMask, value));
648 }
649
650 /// <summary>
651 /// Set the value of the given parameter on the <see cref="InputBindingComposite"/>, <see cref="IInputInteraction"/>,
652 /// and <see cref="InputProcessor"/> objects found on the <see cref="InputAction.bindings"/> of <paramref name="action"/>.
653 /// </summary>
654 /// <param name="action">An action on whose <see cref="InputAction.bindings"/> to look for objects to set
655 /// the parameter value on.</param>
656 /// <param name="name">Name of the parameter to get the value of. Case-insensitive. This can either be just the name of the
657 /// parameter (like <c>"duration"</c> or expressed as <c>nameof(TapInteraction.duration)</c>) or can be prefixed with the
658 /// type of object to get the parameter value from. For example, <c>"tap:duration"</c> will specifically get the <c>"duration"</c>
659 /// parameter from the object registered as <c>"tap"</c> (which will usually be <see cref="Interactions.TapInteraction"/>).</param>
660 /// <param name="value">New value to assign to the parameter.</param>
661 /// <param name="bindingIndex">Index of the binding in <see cref="InputAction.bindings"/> of <paramref name="action"/> to which
662 /// to restrict the parameter override to.</param>
663 /// <exception cref="ArgumentOutOfRangeException"><paramref name="bindingIndex"/> is negative or equal or greater than the number of
664 /// <see cref="InputAction.bindings"/> of <paramref name="action"/>.</exception>
665 /// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c> -or- <paramref name="name"/> is <c>null</c>
666 /// or empty.</exception>
667 /// <remarks>
668 /// This method is a variation of <see cref="ApplyParameterOverride(InputActionMap,string,PrimitiveValue,InputBinding)"/> which
669 /// allows specifying a binding by index. It otherwise behaves identically to that method.
670 /// </remarks>
671 public static void ApplyParameterOverride(this InputAction action, string name, PrimitiveValue value, int bindingIndex)
672 {
673 if (action == null)
674 throw new ArgumentNullException(nameof(action));
675 if (string.IsNullOrEmpty(name))
676 throw new ArgumentNullException(nameof(name));
677 if (bindingIndex < 0)
678 throw new ArgumentOutOfRangeException(nameof(bindingIndex));
679
680 var indexOnMap = action.BindingIndexOnActionToBindingIndexOnMap(bindingIndex);
681 var bindingMask = new InputBinding { id = action.GetOrCreateActionMap().bindings[indexOnMap].id };
682
683 action.ApplyParameterOverride(name, value, bindingMask);
684 }
685
686 private static void ApplyParameterOverride(InputActionState state, int mapIndex,
687 ref ParameterOverride[] parameterOverrides, ref int parameterOverridesCount, ParameterOverride parameterOverride)
688 {
689 // Update the parameter overrides on the map or asset.
690 var haveExistingOverride = false;
691 if (parameterOverrides != null)
692 {
693 // Try to find existing override.
694 for (var i = 0; i < parameterOverridesCount; ++i)
695 {
696 ref var p = ref parameterOverrides[i];
697 if (string.Equals(p.objectRegistrationName, parameterOverride.objectRegistrationName, StringComparison.OrdinalIgnoreCase) &&
698 string.Equals(p.parameter, parameterOverride.parameter, StringComparison.OrdinalIgnoreCase) &&
699 p.bindingMask == parameterOverride.bindingMask)
700 {
701 haveExistingOverride = true;
702 // Update value on existing override.
703 p = parameterOverride;
704 break;
705 }
706 }
707 }
708 if (!haveExistingOverride)
709 {
710 // Add new override.
711 ArrayHelpers.AppendWithCapacity(ref parameterOverrides, ref parameterOverridesCount, parameterOverride);
712 }
713
714 // Set value on all current processor and/or interaction instances that use the parameter.
715 foreach (var parameter in new ParameterEnumerable(state, parameterOverride, mapIndex))
716 {
717 // We cannot just blindly apply the parameter here as the override we have set may be less
718 // specific than an override we already have applied. So instead, we look up the most specific
719 // override and set that.
720 var actionMap = state.GetActionMap(parameter.bindingIndex);
721 ref var binding = ref state.GetBinding(parameter.bindingIndex);
722 var overrideToApply = ParameterOverride.Find(actionMap, ref binding, parameterOverride.parameter,
723 parameterOverride.objectRegistrationName);
724 if (overrideToApply.HasValue)
725 {
726 var fieldTypeCode = Type.GetTypeCode(parameter.field.FieldType);
727 parameter.field.SetValue(parameter.instance, overrideToApply.Value.value.ConvertTo(fieldTypeCode).ToObject());
728 }
729 }
730 }
731
732 internal struct Parameter
733 {
734 public object instance;
735 public FieldInfo field;
736 public int bindingIndex;
737 }
738
739 // Finds all instances of a parameter in one or more actions.
740 private struct ParameterEnumerable : IEnumerable<Parameter>
741 {
742 private InputActionState m_State;
743 private ParameterOverride m_Parameter;
744 private int m_MapIndex;
745
746 public ParameterEnumerable(InputActionState state, ParameterOverride parameter, int mapIndex = -1)
747 {
748 m_State = state;
749 m_Parameter = parameter;
750 m_MapIndex = mapIndex;
751 }
752
753 public ParameterEnumerator GetEnumerator()
754 {
755 return new ParameterEnumerator(m_State, m_Parameter, m_MapIndex);
756 }
757
758 IEnumerator<Parameter> IEnumerable<Parameter>.GetEnumerator()
759 {
760 return GetEnumerator();
761 }
762
763 IEnumerator IEnumerable.GetEnumerator()
764 {
765 return GetEnumerator();
766 }
767 }
768
769 private struct ParameterEnumerator : IEnumerator<Parameter>
770 {
771 private InputActionState m_State;
772 private int m_MapIndex;
773 private int m_BindingCurrentIndex;
774 private int m_BindingEndIndex;
775 private int m_InteractionCurrentIndex;
776 private int m_InteractionEndIndex;
777 private int m_ProcessorCurrentIndex;
778 private int m_ProcessorEndIndex;
779
780 private InputBinding m_BindingMask;
781 private Type m_ObjectType;
782 private string m_ParameterName;
783 private bool m_MayBeInteraction;
784 private bool m_MayBeProcessor;
785 private bool m_MayBeComposite;
786 private bool m_CurrentBindingIsComposite;
787 private object m_CurrentObject;
788 private FieldInfo m_CurrentParameter;
789
790 public ParameterEnumerator(InputActionState state, ParameterOverride parameter, int mapIndex = -1)
791 : this()
792 {
793 m_State = state;
794 m_ParameterName = parameter.parameter;
795 m_MapIndex = mapIndex;
796 m_ObjectType = parameter.objectType;
797 m_MayBeComposite = m_ObjectType == null || typeof(InputBindingComposite).IsAssignableFrom(m_ObjectType);
798 m_MayBeProcessor = m_ObjectType == null || typeof(InputProcessor).IsAssignableFrom(m_ObjectType);
799 m_MayBeInteraction = m_ObjectType == null || typeof(IInputInteraction).IsAssignableFrom(m_ObjectType);
800 m_BindingMask = parameter.bindingMask;
801 Reset();
802 }
803
804 private bool MoveToNextBinding()
805 {
806 // Find a binding that matches our mask.
807 while (true)
808 {
809 ++m_BindingCurrentIndex;
810 if (m_BindingCurrentIndex >= m_BindingEndIndex)
811 return false; // Reached the end.
812
813 ref var binding = ref m_State.GetBinding(m_BindingCurrentIndex);
814 ref var bindingState = ref m_State.GetBindingState(m_BindingCurrentIndex);
815
816 // Skip any binding that has no associated objects with parameters.
817 if (bindingState.processorCount == 0 && bindingState.interactionCount == 0 && !binding.isComposite)
818 continue;
819
820 // If we're only looking for composites, skip any binding that isn't one.
821 if (m_MayBeComposite && !m_MayBeProcessor && !m_MayBeInteraction && !binding.isComposite)
822 continue;
823
824 // If we're only looking for processors, skip any that hasn't got any.
825 if (m_MayBeProcessor && !m_MayBeComposite && !m_MayBeInteraction && bindingState.processorCount == 0)
826 continue;
827
828 // If we're only looking for interactions, skip any that hasn't got any.
829 if (m_MayBeInteraction && !m_MayBeComposite && !m_MayBeProcessor && bindingState.interactionCount == 0)
830 continue;
831
832 if (m_BindingMask.Matches(ref binding))
833 {
834 if (m_MayBeComposite)
835 m_CurrentBindingIsComposite = binding.isComposite;
836
837 // Reset interaction and processor count.
838 m_ProcessorCurrentIndex = bindingState.processorStartIndex - 1; // Minus one to account for first MoveNext().
839 m_ProcessorEndIndex = bindingState.processorStartIndex + bindingState.processorCount;
840 m_InteractionCurrentIndex = bindingState.interactionStartIndex - 1; // Minus one to account for first MoveNext().
841 m_InteractionEndIndex = bindingState.interactionStartIndex + bindingState.interactionCount;
842
843 return true;
844 }
845 }
846 }
847
848 private bool MoveToNextInteraction()
849 {
850 while (m_InteractionCurrentIndex < m_InteractionEndIndex)
851 {
852 ++m_InteractionCurrentIndex;
853 if (m_InteractionCurrentIndex == m_InteractionEndIndex)
854 break;
855 var interaction = m_State.interactions[m_InteractionCurrentIndex];
856 if (FindParameter(interaction))
857 return true;
858 }
859 return false;
860 }
861
862 private bool MoveToNextProcessor()
863 {
864 while (m_ProcessorCurrentIndex < m_ProcessorEndIndex)
865 {
866 ++m_ProcessorCurrentIndex;
867 if (m_ProcessorCurrentIndex == m_ProcessorEndIndex)
868 break;
869 var processor = m_State.processors[m_ProcessorCurrentIndex];
870 if (FindParameter(processor))
871 return true;
872 }
873 return false;
874 }
875
876 private bool FindParameter(object instance)
877 {
878 if (m_ObjectType != null && !m_ObjectType.IsInstanceOfType(instance))
879 return false;
880
881 var field = instance.GetType().GetField(m_ParameterName,
882 BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase);
883 if (field == null)
884 return false;
885
886 m_CurrentParameter = field;
887 m_CurrentObject = instance;
888
889 return true;
890 }
891
892 public bool MoveNext()
893 {
894 while (true)
895 {
896 if (m_MayBeInteraction && MoveToNextInteraction())
897 return true;
898
899 if (m_MayBeProcessor && MoveToNextProcessor())
900 return true;
901
902 if (!MoveToNextBinding())
903 return false;
904
905 if (m_MayBeComposite && m_CurrentBindingIsComposite)
906 {
907 var compositeIndex = m_State.GetBindingState(m_BindingCurrentIndex).compositeOrCompositeBindingIndex;
908 var composite = m_State.composites[compositeIndex];
909 if (FindParameter(composite))
910 return true;
911 }
912 }
913 }
914
915 public unsafe void Reset()
916 {
917 m_CurrentObject = default;
918 m_CurrentParameter = default;
919 m_InteractionCurrentIndex = default;
920 m_InteractionEndIndex = default;
921 m_ProcessorCurrentIndex = default;
922 m_ProcessorEndIndex = default;
923 m_CurrentBindingIsComposite = default;
924 if (m_MapIndex < 0)
925 {
926 m_BindingCurrentIndex = -1; // Account for first MoveNext().
927 m_BindingEndIndex = m_State.totalBindingCount;
928 }
929 else
930 {
931 m_BindingCurrentIndex = m_State.mapIndices[m_MapIndex].bindingStartIndex - 1; // Account for first MoveNext().
932 m_BindingEndIndex = m_State.mapIndices[m_MapIndex].bindingStartIndex + m_State.mapIndices[m_MapIndex].bindingCount;
933 }
934 }
935
936 public Parameter Current => new Parameter
937 {
938 instance = m_CurrentObject,
939 field = m_CurrentParameter,
940 bindingIndex = m_BindingCurrentIndex,
941 };
942
943 object IEnumerator.Current => Current;
944
945 public void Dispose()
946 {
947 }
948 }
949
950 internal struct ParameterOverride
951 {
952 public string objectRegistrationName; // Optional. Such as "hold" or "scale".
953 public string parameter;
954 public InputBinding bindingMask;
955 public PrimitiveValue value;
956
957 public Type objectType =>
958 InputProcessor.s_Processors.LookupTypeRegistration(objectRegistrationName)
959 ?? InputInteraction.s_Interactions.LookupTypeRegistration(objectRegistrationName)
960 ?? InputBindingComposite.s_Composites.LookupTypeRegistration(objectRegistrationName);
961
962 public ParameterOverride(string parameterName, InputBinding bindingMask, PrimitiveValue value = default)
963 {
964 var colonIndex = parameterName.IndexOf(':');
965 if (colonIndex < 0)
966 {
967 objectRegistrationName = null;
968 parameter = parameterName;
969 }
970 else
971 {
972 objectRegistrationName = parameterName.Substring(0, colonIndex);
973 parameter = parameterName.Substring(colonIndex + 1);
974 }
975 this.bindingMask = bindingMask;
976 this.value = value;
977 }
978
979 public ParameterOverride(string objectRegistrationName, string parameterName, InputBinding bindingMask, PrimitiveValue value = default)
980 {
981 this.objectRegistrationName = objectRegistrationName;
982 this.parameter = parameterName;
983 this.bindingMask = bindingMask;
984 this.value = value;
985 }
986
987 // Find the *most specific* override to apply to the given parameter.
988 public static ParameterOverride? Find(InputActionMap actionMap, ref InputBinding binding, string parameterName, string objectRegistrationName)
989 {
990 // Look at level of map.
991 var overrideOnMap = Find(actionMap.m_ParameterOverrides, actionMap.m_ParameterOverridesCount, ref binding, parameterName,
992 objectRegistrationName);
993
994 // Look at level of asset (if present).
995 var asset = actionMap.asset;
996 var overrideOnAsset = asset != null
997 ? Find(asset.m_ParameterOverrides, asset.m_ParameterOverridesCount, ref binding, parameterName,
998 objectRegistrationName)
999 : null;
1000
1001 return PickMoreSpecificOne(overrideOnMap, overrideOnAsset);
1002 }
1003
1004 private static ParameterOverride? Find(ParameterOverride[] overrides, int overrideCount,
1005 ref InputBinding binding, string parameterName, string objectRegistrationName)
1006 {
1007 ParameterOverride? result = null;
1008 for (var i = 0; i < overrideCount; ++i)
1009 {
1010 ref var current = ref overrides[i];
1011
1012 if (!string.Equals(parameterName, current.parameter, StringComparison.OrdinalIgnoreCase))
1013 continue; // Different parameter name.
1014
1015 if (!current.bindingMask.Matches(binding))
1016 continue;
1017
1018 if (current.objectRegistrationName != null && !string.Equals(current.objectRegistrationName, objectRegistrationName,
1019 StringComparison.OrdinalIgnoreCase))
1020 continue;
1021
1022 if (result == null)
1023 {
1024 // First match.
1025 result = current;
1026 }
1027 else
1028 {
1029 // Already have a match. See which one is more specific.
1030 result = PickMoreSpecificOne(result, current);
1031 }
1032 }
1033 return result;
1034 }
1035
1036 private static ParameterOverride? PickMoreSpecificOne(ParameterOverride? first, ParameterOverride? second)
1037 {
1038 if (first == null)
1039 return second;
1040 if (second == null)
1041 return first;
1042
1043 // Having an objectRegistrationName always wins vs not having one.
1044 if (first.Value.objectRegistrationName != null && second.Value.objectRegistrationName == null)
1045 return first;
1046 if (second.Value.objectRegistrationName != null && first.Value.objectRegistrationName == null)
1047 return second;
1048
1049 // Targeting a specific path always wins vs not doing so.
1050 if (first.Value.bindingMask.effectivePath != null && second.Value.bindingMask.effectivePath == null)
1051 return first;
1052 if (second.Value.bindingMask.effectivePath != null && first.Value.bindingMask.effectivePath == null)
1053 return second;
1054
1055 // Targeting a specific actions always wins vs not doing so.
1056 if (first.Value.bindingMask.action != null && second.Value.bindingMask.action == null)
1057 return first;
1058 if (second.Value.bindingMask.action != null && first.Value.bindingMask.action == null)
1059 return second;
1060
1061 // Undecided. First wins by default.
1062 return first;
1063 }
1064 }
1065 }
1066}