A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using System.Reflection;
4using Unity.Collections;
5using UnityEngine.InputSystem.Utilities;
6
7////TODO: reuse interaction, processor, and composite instances from prior resolves
8
9namespace UnityEngine.InputSystem
10{
11 /// <summary>
12 /// Heart of the binding resolution machinery. Consumes lists of bindings
13 /// and spits out out a list of resolved bindings together with their needed
14 /// execution state.
15 /// </summary>
16 /// <remarks>
17 /// One or more <see cref="InputActionMap">action maps</see> can be added to the same
18 /// resolver. The result is a combination of the binding state of all maps.
19 ///
20 /// The data set up by a resolver is for consumption by <see cref="InputActionState"/>.
21 /// Essentially, InputBindingResolver does all the wiring and <see cref="InputActionState"/>
22 /// does all the actual execution based on the resulting data.
23 /// </remarks>
24 /// <seealso cref="InputActionState.Initialize"/>
25 internal struct InputBindingResolver : IDisposable
26 {
27 public int totalProcessorCount;
28 public int totalCompositeCount;
29 public int totalInteractionCount;
30 public int totalMapCount => memory.mapCount;
31 public int totalActionCount => memory.actionCount;
32 public int totalBindingCount => memory.bindingCount;
33 public int totalControlCount => memory.controlCount;
34
35 public InputActionMap[] maps;
36 public InputControl[] controls;
37 public InputActionState.UnmanagedMemory memory;
38 public IInputInteraction[] interactions;
39 public InputProcessor[] processors;
40 public InputBindingComposite[] composites;
41
42 /// <summary>
43 /// Binding mask used to globally mask out bindings.
44 /// </summary>
45 /// <remarks>
46 /// This is empty by default.
47 ///
48 /// The bindings of each map will be <see cref="InputBinding.Matches">matched</see> against this
49 /// binding. Any bindings that don't match will get skipped and not resolved to controls.
50 ///
51 /// Note that regardless of whether a binding will be resolved to controls or not, it will get
52 /// an entry in <see cref="memory"/>. Otherwise we would have to have a more complicated
53 /// mapping from <see cref="InputActionMap.bindings"/> to a binding state in <see cref="memory"/>.
54 /// </remarks>
55 public InputBinding? bindingMask;
56
57 private bool m_IsControlOnlyResolve;
58
59 /// <summary>
60 /// Release native memory held by the resolver.
61 /// </summary>
62 public void Dispose()
63 {
64 memory.Dispose();
65 }
66
67 /// <summary>
68 /// Steal the already allocated arrays from the given state.
69 /// </summary>
70 /// <param name="state">Action map state that was previously created.</param>
71 /// <param name="isFullResolve">If false, the only thing that is allowed to change in the re-resolution
72 /// is the list of controls. In other words, devices may have been added or removed but otherwise the configuration
73 /// is exactly the same as in the last resolve. If true, anything may have changed and the resolver will only reuse
74 /// allocations but not contents.</param>
75 public void StartWithPreviousResolve(InputActionState state, bool isFullResolve)
76 {
77 Debug.Assert(state != null, "Received null state");
78 Debug.Assert(!state.isProcessingControlStateChange,
79 "Cannot re-resolve bindings for an InputActionState that is currently executing an action callback; binding resolution must be deferred to until after the callback has completed");
80
81 m_IsControlOnlyResolve = !isFullResolve;
82
83 maps = state.maps;
84 interactions = state.interactions;
85 processors = state.processors;
86 composites = state.composites;
87 controls = state.controls;
88
89 // Clear the arrays so that we don't leave references around.
90 if (isFullResolve)
91 {
92 if (maps != null)
93 Array.Clear(maps, 0, state.totalMapCount);
94 if (interactions != null)
95 Array.Clear(interactions, 0, state.totalInteractionCount);
96 if (processors != null)
97 Array.Clear(processors, 0, state.totalProcessorCount);
98 if (composites != null)
99 Array.Clear(composites, 0, state.totalCompositeCount);
100 }
101 if (controls != null) // Always clear this one as every resolve will change it.
102 Array.Clear(controls, 0, state.totalControlCount);
103
104 // Null out the arrays on the state so that there is no strange bugs with
105 // the state reading from arrays that no longer belong to it.
106 state.maps = null;
107 state.interactions = null;
108 state.processors = null;
109 state.composites = null;
110 state.controls = null;
111 }
112
113 /// <summary>
114 /// Resolve and add all bindings and actions from the given map.
115 /// </summary>
116 /// <param name="actionMap"></param>
117 /// <remarks>
118 /// This is where all binding resolution happens for actions. The method walks through the binding array
119 /// in <paramref name="actionMap"/> and adds any controls, interactions, processors, and composites as it goes.
120 /// </remarks>
121 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1809:AvoidExcessiveLocals", Justification = "TODO: Refactor later.")]
122 public unsafe void AddActionMap(InputActionMap actionMap)
123 {
124 Debug.Assert(actionMap != null, "Received null map");
125
126 InputSystem.EnsureInitialized();
127
128 var actionsInThisMap = actionMap.m_Actions;
129 var bindingsInThisMap = actionMap.m_Bindings;
130 var bindingCountInThisMap = bindingsInThisMap?.Length ?? 0;
131 var actionCountInThisMap = actionsInThisMap?.Length ?? 0;
132 var mapIndex = totalMapCount;
133
134 // Keep track of indices for this map.
135 var actionStartIndex = totalActionCount;
136 var bindingStartIndex = totalBindingCount;
137 var controlStartIndex = totalControlCount;
138 var interactionStartIndex = totalInteractionCount;
139 var processorStartIndex = totalProcessorCount;
140 var compositeStartIndex = totalCompositeCount;
141
142 // Allocate an initial block of memory. We probably will have to re-allocate once
143 // at the end to accommodate interactions and controls added from the map.
144 var newMemory = new InputActionState.UnmanagedMemory();
145 newMemory.Allocate(
146 mapCount: totalMapCount + 1,
147 actionCount: totalActionCount + actionCountInThisMap,
148 bindingCount: totalBindingCount + bindingCountInThisMap,
149 // We reallocate for the following once we know the final count.
150 interactionCount: totalInteractionCount,
151 compositeCount: totalCompositeCount,
152 controlCount: totalControlCount);
153 if (memory.isAllocated)
154 newMemory.CopyDataFrom(memory);
155
156 ////TODO: make sure composite objects get all the bindings they need
157 ////TODO: handle case where we have bindings resolving to the same control
158 //// (not so clear cut what to do there; each binding may have a different interaction setup, for example)
159 var currentCompositeBindingIndex = InputActionState.kInvalidIndex;
160 var currentCompositeIndex = InputActionState.kInvalidIndex;
161 var currentCompositePartCount = 0;
162 var currentCompositeActionIndexInMap = InputActionState.kInvalidIndex;
163 InputAction currentCompositeAction = null;
164 var bindingMaskOnThisMap = actionMap.m_BindingMask;
165 var devicesForThisMap = actionMap.devices;
166 var isSingletonAction = actionMap.m_SingletonAction != null;
167
168 // Can't use `using` as we need to use it with `ref`.
169 var resolvedControls = new InputControlList<InputControl>(Allocator.Temp);
170
171 // We gather all controls in temporary memory and then move them over into newMemory once
172 // we're done resolving.
173 try
174 {
175 for (var n = 0; n < bindingCountInThisMap; ++n)
176 {
177 var bindingStatesPtr = newMemory.bindingStates;
178 ref var unresolvedBinding = ref bindingsInThisMap[n];
179 var bindingIndex = bindingStartIndex + n;
180 var isComposite = unresolvedBinding.isComposite;
181 var isPartOfComposite = !isComposite && unresolvedBinding.isPartOfComposite;
182 var bindingState = &bindingStatesPtr[bindingIndex];
183
184 try
185 {
186 ////TODO: if it's a composite, check if any of the children matches our binding masks (if any) and skip composite if none do
187
188 var firstControlIndex = 0; // numControls dictates whether this is a valid index or not.
189 var firstInteractionIndex = InputActionState.kInvalidIndex;
190 var firstProcessorIndex = InputActionState.kInvalidIndex;
191 var actionIndexForBinding = InputActionState.kInvalidIndex;
192 var partIndex = InputActionState.kInvalidIndex;
193
194 var numControls = 0;
195 var numInteractions = 0;
196 var numProcessors = 0;
197
198 // Make sure that if it's part of a composite, we are actually part of a composite.
199 if (isPartOfComposite && currentCompositeBindingIndex == InputActionState.kInvalidIndex)
200 throw new InvalidOperationException(
201 $"Binding '{unresolvedBinding}' is marked as being part of a composite but the preceding binding is not a composite");
202
203 // Try to find action.
204 //
205 // NOTE: We ignore actions on bindings that are part of composites. We only allow
206 // actions to be triggered from the composite itself.
207 var actionIndexInMap = InputActionState.kInvalidIndex;
208 var actionName = unresolvedBinding.action;
209 InputAction action = null;
210 if (!isPartOfComposite)
211 {
212 if (isSingletonAction)
213 {
214 // Singleton actions always ignore names.
215 actionIndexInMap = 0;
216 }
217 else if (!string.IsNullOrEmpty(actionName))
218 {
219 ////REVIEW: should we fail here if we don't manage to find the action
220 actionIndexInMap = actionMap.FindActionIndex(actionName);
221 }
222
223 if (actionIndexInMap != InputActionState.kInvalidIndex)
224 action = actionsInThisMap[actionIndexInMap];
225 }
226 else
227 {
228 actionIndexInMap = currentCompositeActionIndexInMap;
229 action = currentCompositeAction;
230 }
231
232 // If it's a composite, start a chain.
233 if (isComposite)
234 {
235 currentCompositeBindingIndex = bindingIndex;
236 currentCompositeAction = action;
237 currentCompositeActionIndexInMap = actionIndexInMap;
238 }
239
240 // Determine if the binding is disabled.
241 // Disabled if path is empty.
242 var path = unresolvedBinding.effectivePath;
243 var bindingIsDisabled = string.IsNullOrEmpty(path)
244
245 // Also, if we can't find the action to trigger for the binding, we just go and disable
246 // the binding.
247 || action == null
248
249 // Also, disabled if binding doesn't match with our binding mask (might be empty).
250 || (!isComposite && bindingMask != null &&
251 !bindingMask.Value.Matches(ref unresolvedBinding,
252 InputBinding.MatchOptions.EmptyGroupMatchesAny))
253
254 // Also, disabled if binding doesn't match the binding mask on the map (might be empty).
255 || (!isComposite && bindingMaskOnThisMap != null &&
256 !bindingMaskOnThisMap.Value.Matches(ref unresolvedBinding,
257 InputBinding.MatchOptions.EmptyGroupMatchesAny))
258
259 // Finally, also disabled if binding doesn't match the binding mask on the action (might be empty).
260 || (!isComposite && action?.m_BindingMask != null &&
261 !action.m_BindingMask.Value.Matches(ref unresolvedBinding,
262 InputBinding.MatchOptions.EmptyGroupMatchesAny));
263
264 // If the binding isn't disabled, look up controls now. We do this first as we may still disable the
265 // binding if it doesn't resolve to any controls or resolves only to controls already bound to by
266 // other bindings.
267 //
268 // NOTE: We continuously add controls here to `resolvedControls`. Once we've completed our
269 // pass over the bindings in the map, `resolvedControls` will have all the controls for
270 // the current map.
271 if (!bindingIsDisabled && !isComposite)
272 {
273 firstControlIndex = memory.controlCount + resolvedControls.Count;
274 if (devicesForThisMap != null)
275 {
276 // Search in devices for only this map.
277 var list = devicesForThisMap.Value;
278 for (var i = 0; i < list.Count; ++i)
279 {
280 var device = list[i];
281 if (!device.added)
282 continue; // Skip devices that have been removed.
283 numControls += InputControlPath.TryFindControls(device, path, 0, ref resolvedControls);
284 }
285 }
286 else
287 {
288 // Search globally.
289 numControls = InputSystem.FindControls(path, ref resolvedControls);
290 }
291 }
292
293 // If the binding isn't disabled, resolve its controls, processors, and interactions.
294 if (!bindingIsDisabled)
295 {
296 // NOTE: When isFullResolve==false, it is *imperative* that we do count processor and interaction
297 // counts here come out exactly the same as in the previous full resolve.
298
299 // Instantiate processors.
300 var processorString = unresolvedBinding.effectiveProcessors;
301 if (!string.IsNullOrEmpty(processorString))
302 {
303 // Add processors from binding.
304 firstProcessorIndex = InstantiateWithParameters(InputProcessor.s_Processors, processorString,
305 ref processors, ref totalProcessorCount, actionMap, ref unresolvedBinding);
306 if (firstProcessorIndex != InputActionState.kInvalidIndex)
307 numProcessors = totalProcessorCount - firstProcessorIndex;
308 }
309 if (!string.IsNullOrEmpty(action.m_Processors))
310 {
311 // Add processors from action.
312 var index = InstantiateWithParameters(InputProcessor.s_Processors, action.m_Processors, ref processors,
313 ref totalProcessorCount, actionMap, ref unresolvedBinding);
314 if (index != InputActionState.kInvalidIndex)
315 {
316 if (firstProcessorIndex == InputActionState.kInvalidIndex)
317 firstProcessorIndex = index;
318 numProcessors += totalProcessorCount - index;
319 }
320 }
321
322 // Instantiate interactions.
323 if (isPartOfComposite)
324 {
325 // Composite's part use composite interactions
326 if (currentCompositeBindingIndex != InputActionState.kInvalidIndex)
327 {
328 firstInteractionIndex = bindingStatesPtr[currentCompositeBindingIndex].interactionStartIndex;
329 numInteractions = bindingStatesPtr[currentCompositeBindingIndex].interactionCount;
330 }
331 }
332 else
333 {
334 var interactionString = unresolvedBinding.effectiveInteractions;
335 if (!string.IsNullOrEmpty(interactionString))
336 {
337 // Add interactions from binding.
338 firstInteractionIndex = InstantiateWithParameters(InputInteraction.s_Interactions, interactionString,
339 ref interactions, ref totalInteractionCount, actionMap, ref unresolvedBinding);
340 if (firstInteractionIndex != InputActionState.kInvalidIndex)
341 numInteractions = totalInteractionCount - firstInteractionIndex;
342 }
343 if (!string.IsNullOrEmpty(action.m_Interactions))
344 {
345 // Add interactions from action.
346 var index = InstantiateWithParameters(InputInteraction.s_Interactions, action.m_Interactions,
347 ref interactions, ref totalInteractionCount, actionMap, ref unresolvedBinding);
348 if (index != InputActionState.kInvalidIndex)
349 {
350 if (firstInteractionIndex == InputActionState.kInvalidIndex)
351 firstInteractionIndex = index;
352 numInteractions += totalInteractionCount - index;
353 }
354 }
355 }
356
357 // If it's the start of a composite chain, create the composite.
358 if (isComposite)
359 {
360 // The composite binding entry itself does not resolve to any controls.
361 // It creates a composite binding object which is then populated from
362 // subsequent bindings.
363
364 // Instantiate. For composites, the path is the name of the composite.
365 var composite = InstantiateBindingComposite(ref unresolvedBinding, actionMap);
366 currentCompositeIndex =
367 ArrayHelpers.AppendWithCapacity(ref composites, ref totalCompositeCount, composite);
368
369 // Record where the controls for parts of the composite start.
370 firstControlIndex = memory.controlCount + resolvedControls.Count;
371 }
372 else
373 {
374 // If we've reached the end of a composite chain, finish
375 // off the current composite.
376 if (!isPartOfComposite && currentCompositeBindingIndex != InputActionState.kInvalidIndex)
377 {
378 currentCompositePartCount = 0;
379 currentCompositeBindingIndex = InputActionState.kInvalidIndex;
380 currentCompositeIndex = InputActionState.kInvalidIndex;
381 currentCompositeAction = null;
382 currentCompositeActionIndexInMap = InputActionState.kInvalidIndex;
383 }
384 }
385 }
386
387 // If the binding is part of a composite, pass the resolved controls
388 // on to the composite.
389 if (isPartOfComposite && currentCompositeBindingIndex != InputActionState.kInvalidIndex && numControls > 0)
390 {
391 // Make sure the binding is named. The name determines what in the composite
392 // to bind to.
393 if (string.IsNullOrEmpty(unresolvedBinding.name))
394 throw new InvalidOperationException(
395 $"Binding '{unresolvedBinding}' that is part of composite '{composites[currentCompositeIndex]}' is missing a name");
396
397 // Assign an index to the current part of the composite which
398 // can be used by the composite to read input from this part.
399 partIndex = AssignCompositePartIndex(composites[currentCompositeIndex], unresolvedBinding.name,
400 ref currentCompositePartCount);
401
402 // Keep track of total number of controls bound in the composite.
403 bindingStatesPtr[currentCompositeBindingIndex].controlCount += numControls;
404
405 // Force action index on part binding to be same as that of composite.
406 actionIndexForBinding = bindingStatesPtr[currentCompositeBindingIndex].actionIndex;
407 }
408 else if (actionIndexInMap != InputActionState.kInvalidIndex)
409 {
410 actionIndexForBinding = actionStartIndex + actionIndexInMap;
411 }
412
413 // Store resolved binding.
414 *bindingState = new InputActionState.BindingState
415 {
416 controlStartIndex = firstControlIndex,
417 // For composites, this will be adjusted as we add each part.
418 controlCount = numControls,
419 interactionStartIndex = firstInteractionIndex,
420 interactionCount = numInteractions,
421 processorStartIndex = firstProcessorIndex,
422 processorCount = numProcessors,
423 isComposite = isComposite,
424 isPartOfComposite = unresolvedBinding.isPartOfComposite,
425 partIndex = partIndex,
426 actionIndex = actionIndexForBinding,
427 compositeOrCompositeBindingIndex = isComposite ? currentCompositeIndex : currentCompositeBindingIndex,
428 mapIndex = totalMapCount,
429 wantsInitialStateCheck = action?.wantsInitialStateCheck ?? false
430 };
431 }
432 catch (Exception exception)
433 {
434 Debug.LogError(
435 $"{exception.GetType().Name} while resolving binding '{unresolvedBinding}' in action map '{actionMap}'");
436 Debug.LogException(exception);
437
438 // Don't swallow exceptions that indicate something is wrong in the code rather than
439 // in the data.
440 if (exception.IsExceptionIndicatingBugInCode())
441 throw;
442 }
443 }
444
445 // Re-allocate memory to accommodate controls and interaction states. The count for those
446 // we only know once we've completed all resolution.
447 var controlCountInThisMap = resolvedControls.Count;
448 var newTotalControlCount = memory.controlCount + controlCountInThisMap;
449 if (newMemory.interactionCount != totalInteractionCount ||
450 newMemory.compositeCount != totalCompositeCount ||
451 newMemory.controlCount != newTotalControlCount)
452 {
453 var finalMemory = new InputActionState.UnmanagedMemory();
454
455 finalMemory.Allocate(
456 mapCount: newMemory.mapCount,
457 actionCount: newMemory.actionCount,
458 bindingCount: newMemory.bindingCount,
459 controlCount: newTotalControlCount,
460 interactionCount: totalInteractionCount,
461 compositeCount: totalCompositeCount);
462
463 finalMemory.CopyDataFrom(newMemory);
464
465 newMemory.Dispose();
466 newMemory = finalMemory;
467 }
468
469 // Add controls to array.
470 var controlCountInArray = memory.controlCount;
471 ArrayHelpers.AppendListWithCapacity(ref controls, ref controlCountInArray, resolvedControls);
472 Debug.Assert(controlCountInArray == newTotalControlCount,
473 "Control array should have combined count of old and new controls");
474
475 // Set up control to binding index mapping.
476 for (var i = 0; i < bindingCountInThisMap; ++i)
477 {
478 var bindingStatesPtr = newMemory.bindingStates;
479 var bindingState = &bindingStatesPtr[bindingStartIndex + i];
480 var numControls = bindingState->controlCount;
481 var startIndex = bindingState->controlStartIndex;
482 for (var n = 0; n < numControls; ++n)
483 newMemory.controlIndexToBindingIndex[startIndex + n] = bindingStartIndex + i;
484 }
485
486 // Initialize initial interaction states.
487 for (var i = memory.interactionCount; i < newMemory.interactionCount; ++i)
488 {
489 ref var interactionState = ref newMemory.interactionStates[i];
490 interactionState.phase = InputActionPhase.Waiting;
491 interactionState.triggerControlIndex = InputActionState.kInvalidIndex;
492 }
493
494 // Initialize action data.
495 var runningIndexInBindingIndices = memory.bindingCount;
496 for (var i = 0; i < actionCountInThisMap; ++i)
497 {
498 var action = actionsInThisMap[i];
499 var actionIndex = actionStartIndex + i;
500
501 // Correlate action with its trigger state.
502 action.m_ActionIndexInState = actionIndex;
503
504 Debug.Assert(runningIndexInBindingIndices < ushort.MaxValue, "Binding start index on action exceeds limit");
505 newMemory.actionBindingIndicesAndCounts[actionIndex * 2] = (ushort)runningIndexInBindingIndices;
506
507 // Collect bindings for action.
508 var firstBindingIndexForAction = -1;
509 var bindingCountForAction = 0;
510 var numPossibleConcurrentActuations = 0;
511
512 for (var n = 0; n < bindingCountInThisMap; ++n)
513 {
514 var bindingIndex = bindingStartIndex + n;
515 var bindingState = &newMemory.bindingStates[bindingIndex];
516 if (bindingState->actionIndex != actionIndex)
517 continue;
518 if (bindingState->isPartOfComposite)
519 continue;
520
521 Debug.Assert(bindingIndex <= ushort.MaxValue, "Binding index exceeds limit");
522 newMemory.actionBindingIndices[runningIndexInBindingIndices] = (ushort)bindingIndex;
523 ++runningIndexInBindingIndices;
524 ++bindingCountForAction;
525
526 if (firstBindingIndexForAction == -1)
527 firstBindingIndexForAction = bindingIndex;
528
529 // Keep track of how many concurrent actuations we may be seeing on the action so that
530 // we know whether we need to enable conflict resolution or not.
531 if (bindingState->isComposite)
532 {
533 // Composite binding. Actuates as a whole. Check if the composite has successfully
534 // resolved any controls. If so, it adds one possible actuation.
535 if (bindingState->controlCount > 0)
536 ++numPossibleConcurrentActuations;
537 }
538 else
539 {
540 // Normal binding. Every successfully resolved control results in one possible actuation.
541 numPossibleConcurrentActuations += bindingState->controlCount;
542 }
543 }
544
545 if (firstBindingIndexForAction == -1)
546 firstBindingIndexForAction = 0;
547
548 Debug.Assert(bindingCountForAction < ushort.MaxValue, "Binding count on action exceeds limit");
549 newMemory.actionBindingIndicesAndCounts[actionIndex * 2 + 1] = (ushort)bindingCountForAction;
550
551 // See if we may need conflict resolution on this action. Never needed for pass-through actions.
552 // Otherwise, if we have more than one bound control or have several bindings and one of them
553 // is a composite, we enable it.
554 var isPassThroughAction = action.type == InputActionType.PassThrough;
555 var isButtonAction = action.type == InputActionType.Button;
556 var mayNeedConflictResolution = !isPassThroughAction && numPossibleConcurrentActuations > 1;
557
558 // Initialize initial trigger state.
559 newMemory.actionStates[actionIndex] =
560 new InputActionState.TriggerState
561 {
562 phase = InputActionPhase.Disabled,
563 mapIndex = mapIndex,
564 controlIndex = InputActionState.kInvalidIndex,
565 interactionIndex = InputActionState.kInvalidIndex,
566 isPassThrough = isPassThroughAction,
567 isButton = isButtonAction,
568 mayNeedConflictResolution = mayNeedConflictResolution,
569 bindingIndex = firstBindingIndexForAction
570 };
571 }
572
573 // Store indices for map.
574 newMemory.mapIndices[mapIndex] =
575 new InputActionState.ActionMapIndices
576 {
577 actionStartIndex = actionStartIndex,
578 actionCount = actionCountInThisMap,
579 controlStartIndex = controlStartIndex,
580 controlCount = controlCountInThisMap,
581 bindingStartIndex = bindingStartIndex,
582 bindingCount = bindingCountInThisMap,
583 interactionStartIndex = interactionStartIndex,
584 interactionCount = totalInteractionCount - interactionStartIndex,
585 processorStartIndex = processorStartIndex,
586 processorCount = totalProcessorCount - processorStartIndex,
587 compositeStartIndex = compositeStartIndex,
588 compositeCount = totalCompositeCount - compositeStartIndex,
589 };
590 actionMap.m_MapIndexInState = mapIndex;
591 var finalActionMapCount = memory.mapCount;
592 ArrayHelpers.AppendWithCapacity(ref maps, ref finalActionMapCount, actionMap, capacityIncrement: 4);
593 Debug.Assert(finalActionMapCount == newMemory.mapCount,
594 "Final action map count should match old action map count plus one");
595
596 // As a final act, swap the new memory in.
597 memory.Dispose();
598 memory = newMemory;
599 }
600 catch (Exception)
601 {
602 // Don't leak our native memory when we throw an exception.
603 newMemory.Dispose();
604 throw;
605 }
606 finally
607 {
608 resolvedControls.Dispose();
609 }
610 }
611
612 private List<NameAndParameters> m_Parameters; // We retain this to reuse the allocation.
613 private int InstantiateWithParameters<TType>(TypeTable registrations, string namesAndParameters, ref TType[] array, ref int count, InputActionMap actionMap, ref InputBinding binding)
614 {
615 if (!NameAndParameters.ParseMultiple(namesAndParameters, ref m_Parameters))
616 return InputActionState.kInvalidIndex;
617
618 var firstIndex = count;
619 for (var i = 0; i < m_Parameters.Count; ++i)
620 {
621 // Look up type.
622 var objectRegistrationName = m_Parameters[i].name;
623 var type = registrations.LookupTypeRegistration(objectRegistrationName);
624 if (type == null)
625 {
626 Debug.LogError(
627 $"No {typeof(TType).Name} with name '{objectRegistrationName}' (mentioned in '{namesAndParameters}') has been registered");
628 continue;
629 }
630
631 if (!m_IsControlOnlyResolve)
632 {
633 // Instantiate it.
634 if (!(Activator.CreateInstance(type) is TType instance))
635 {
636 Debug.LogError(
637 $"Type '{type.Name}' registered as '{objectRegistrationName}' (mentioned in '{namesAndParameters}') is not an {typeof(TType).Name}");
638 continue;
639 }
640
641 // Pass parameters to it.
642 ApplyParameters(m_Parameters[i].parameters, instance, actionMap, ref binding, objectRegistrationName,
643 namesAndParameters);
644
645 // Add to list.
646 ArrayHelpers.AppendWithCapacity(ref array, ref count, instance);
647 }
648 else
649 {
650 Debug.Assert(type.IsInstanceOfType(array[count]), "Type of instance in array does not match expected type");
651 ++count;
652 }
653 }
654
655 return firstIndex;
656 }
657
658 private static InputBindingComposite InstantiateBindingComposite(ref InputBinding binding, InputActionMap actionMap)
659 {
660 var nameAndParametersParsed = NameAndParameters.Parse(binding.effectivePath);
661
662 // Look up.
663 var type = InputBindingComposite.s_Composites.LookupTypeRegistration(nameAndParametersParsed.name);
664 if (type == null)
665 throw new InvalidOperationException(
666 $"No binding composite with name '{nameAndParametersParsed.name}' has been registered");
667
668 // Instantiate.
669 if (!(Activator.CreateInstance(type) is InputBindingComposite instance))
670 throw new InvalidOperationException(
671 $"Registered type '{type.Name}' used for '{nameAndParametersParsed.name}' is not an InputBindingComposite");
672
673 // Set parameters.
674 ApplyParameters(nameAndParametersParsed.parameters, instance, actionMap, ref binding, nameAndParametersParsed.name,
675 binding.effectivePath);
676
677 return instance;
678 }
679
680 private static void ApplyParameters(ReadOnlyArray<NamedValue> parameters, object instance, InputActionMap actionMap, ref InputBinding binding, string objectRegistrationName, string namesAndParameters)
681 {
682 foreach (var parameter in parameters)
683 {
684 // Find field.
685 var field = instance.GetType().GetField(parameter.name,
686 BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
687 if (field == null)
688 {
689 Debug.LogError(
690 $"Type '{instance.GetType().Name}' registered as '{objectRegistrationName}' (mentioned in '{namesAndParameters}') has no public field called '{parameter.name}'");
691 continue;
692 }
693 var fieldTypeCode = Type.GetTypeCode(field.FieldType);
694
695 // See if we have a parameter override.
696 var parameterOverride =
697 InputActionRebindingExtensions.ParameterOverride.Find(actionMap, ref binding, parameter.name, objectRegistrationName);
698 var value = parameterOverride != null
699 ? parameterOverride.Value.value
700 : parameter.value;
701
702 field.SetValue(instance, value.ConvertTo(fieldTypeCode).ToObject());
703 }
704 }
705
706 private static int AssignCompositePartIndex(object composite, string name, ref int currentCompositePartCount)
707 {
708 var type = composite.GetType();
709
710 ////REVIEW: check for [InputControl] attribute?
711
712 ////TODO: allow this to be a property instead
713 // Look up field.
714 var field = type.GetField(name,
715 BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
716 if (field == null)
717 throw new InvalidOperationException(
718 $"Cannot find public field '{name}' used as parameter of binding composite '{composite}' of type '{type}'");
719
720 ////REVIEW: should we wrap part numbers in a struct instead of using int?
721
722 // Type-check.
723 var fieldType = field.FieldType;
724 if (fieldType != typeof(int))
725 throw new InvalidOperationException(
726 $"Field '{name}' used as a parameter of binding composite '{composite}' must be of type 'int' but is of type '{type.Name}' instead");
727
728 ////REVIEW: this creates garbage; need a better solution to get to zero garbage during re-resolving
729 // See if we've already assigned a part index. This can happen if there are multiple bindings
730 // for the same named slot on the composite (e.g. multiple "Negative" bindings on an axis composite).
731 var partIndex = (int)field.GetValue(composite);
732 if (partIndex == 0)
733 {
734 // No, not assigned yet. Create new part index.
735 partIndex = ++currentCompositePartCount;
736 field.SetValue(composite, partIndex);
737 }
738
739 return partIndex;
740 }
741 }
742}