123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730 |
- using System;
- using System.Collections.Generic;
- using System.Reflection;
- using Unity.Collections;
- using UnityEngine.InputSystem.Utilities;
-
- ////TODO: reuse interaction, processor, and composite instances from prior resolves
-
- namespace UnityEngine.InputSystem
- {
- /// <summary>
- /// Heart of the binding resolution machinery. Consumes lists of bindings
- /// and spits out out a list of resolved bindings together with their needed
- /// execution state.
- /// </summary>
- /// <remarks>
- /// One or more <see cref="InputActionMap">action maps</see> can be added to the same
- /// resolver. The result is a combination of the binding state of all maps.
- ///
- /// The data set up by a resolver is for consumption by <see cref="InputActionState"/>.
- /// Essentially, InputBindingResolver does all the wiring and <see cref="InputActionState"/>
- /// does all the actual execution based on the resulting data.
- /// </remarks>
- /// <seealso cref="InputActionState.Initialize"/>
- internal struct InputBindingResolver : IDisposable
- {
- public int totalProcessorCount;
- public int totalCompositeCount;
- public int totalInteractionCount;
- public int totalMapCount => memory.mapCount;
- public int totalActionCount => memory.actionCount;
- public int totalBindingCount => memory.bindingCount;
- public int totalControlCount => memory.controlCount;
-
- public InputActionMap[] maps;
- public InputControl[] controls;
- public InputActionState.UnmanagedMemory memory;
- public IInputInteraction[] interactions;
- public InputProcessor[] processors;
- public InputBindingComposite[] composites;
-
- /// <summary>
- /// Binding mask used to globally mask out bindings.
- /// </summary>
- /// <remarks>
- /// This is empty by default.
- ///
- /// The bindings of each map will be <see cref="InputBinding.Matches">matched</see> against this
- /// binding. Any bindings that don't match will get skipped and not resolved to controls.
- ///
- /// Note that regardless of whether a binding will be resolved to controls or not, it will get
- /// an entry in <see cref="memory"/>. Otherwise we would have to have a more complicated
- /// mapping from <see cref="InputActionMap.bindings"/> to a binding state in <see cref="memory"/>.
- /// </remarks>
- public InputBinding? bindingMask;
-
- private bool m_IsControlOnlyResolve;
-
- /// <summary>
- /// Release native memory held by the resolver.
- /// </summary>
- public void Dispose()
- {
- memory.Dispose();
- }
-
- /// <summary>
- /// Steal the already allocated arrays from the given state.
- /// </summary>
- /// <param name="state">Action map state that was previously created.</param>
- /// <param name="isFullResolve">If false, the only thing that is allowed to change in the re-resolution
- /// is the list of controls. In other words, devices may have been added or removed but otherwise the configuration
- /// is exactly the same as in the last resolve. If true, anything may have changed and the resolver will only reuse
- /// allocations but not contents.</param>
- public void StartWithPreviousResolve(InputActionState state, bool isFullResolve)
- {
- Debug.Assert(state != null, "Received null state");
- Debug.Assert(!state.isProcessingControlStateChange,
- "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");
-
- m_IsControlOnlyResolve = !isFullResolve;
-
- maps = state.maps;
- interactions = state.interactions;
- processors = state.processors;
- composites = state.composites;
- controls = state.controls;
-
- // Clear the arrays so that we don't leave references around.
- if (isFullResolve)
- {
- if (maps != null)
- Array.Clear(maps, 0, state.totalMapCount);
- if (interactions != null)
- Array.Clear(interactions, 0, state.totalInteractionCount);
- if (processors != null)
- Array.Clear(processors, 0, state.totalProcessorCount);
- if (composites != null)
- Array.Clear(composites, 0, state.totalCompositeCount);
- }
- if (controls != null) // Always clear this one as every resolve will change it.
- Array.Clear(controls, 0, state.totalControlCount);
-
- // Null out the arrays on the state so that there is no strange bugs with
- // the state reading from arrays that no longer belong to it.
- state.maps = null;
- state.interactions = null;
- state.processors = null;
- state.composites = null;
- state.controls = null;
- }
-
- /// <summary>
- /// Resolve and add all bindings and actions from the given map.
- /// </summary>
- /// <param name="actionMap"></param>
- /// <remarks>
- /// This is where all binding resolution happens for actions. The method walks through the binding array
- /// in <paramref name="actionMap"/> and adds any controls, interactions, processors, and composites as it goes.
- /// </remarks>
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1809:AvoidExcessiveLocals", Justification = "TODO: Refactor later.")]
- public unsafe void AddActionMap(InputActionMap actionMap)
- {
- Debug.Assert(actionMap != null, "Received null map");
-
- InputSystem.EnsureInitialized();
-
- var actionsInThisMap = actionMap.m_Actions;
- var bindingsInThisMap = actionMap.m_Bindings;
- var bindingCountInThisMap = bindingsInThisMap?.Length ?? 0;
- var actionCountInThisMap = actionsInThisMap?.Length ?? 0;
- var mapIndex = totalMapCount;
-
- // Keep track of indices for this map.
- var actionStartIndex = totalActionCount;
- var bindingStartIndex = totalBindingCount;
- var controlStartIndex = totalControlCount;
- var interactionStartIndex = totalInteractionCount;
- var processorStartIndex = totalProcessorCount;
- var compositeStartIndex = totalCompositeCount;
-
- // Allocate an initial block of memory. We probably will have to re-allocate once
- // at the end to accommodate interactions and controls added from the map.
- var newMemory = new InputActionState.UnmanagedMemory();
- newMemory.Allocate(
- mapCount: totalMapCount + 1,
- actionCount: totalActionCount + actionCountInThisMap,
- bindingCount: totalBindingCount + bindingCountInThisMap,
- // We reallocate for the following once we know the final count.
- interactionCount: totalInteractionCount,
- compositeCount: totalCompositeCount,
- controlCount: totalControlCount);
- if (memory.isAllocated)
- newMemory.CopyDataFrom(memory);
-
- ////TODO: make sure composite objects get all the bindings they need
- ////TODO: handle case where we have bindings resolving to the same control
- //// (not so clear cut what to do there; each binding may have a different interaction setup, for example)
- var currentCompositeBindingIndex = InputActionState.kInvalidIndex;
- var currentCompositeIndex = InputActionState.kInvalidIndex;
- var currentCompositePartCount = 0;
- var currentCompositeActionIndexInMap = InputActionState.kInvalidIndex;
- InputAction currentCompositeAction = null;
- var bindingMaskOnThisMap = actionMap.m_BindingMask;
- var devicesForThisMap = actionMap.devices;
- var isSingletonAction = actionMap.m_SingletonAction != null;
-
- // Can't use `using` as we need to use it with `ref`.
- var resolvedControls = new InputControlList<InputControl>(Allocator.Temp);
-
- // We gather all controls in temporary memory and then move them over into newMemory once
- // we're done resolving.
- try
- {
- for (var n = 0; n < bindingCountInThisMap; ++n)
- {
- var bindingStatesPtr = newMemory.bindingStates;
- ref var unresolvedBinding = ref bindingsInThisMap[n];
- var bindingIndex = bindingStartIndex + n;
- var isComposite = unresolvedBinding.isComposite;
- var isPartOfComposite = !isComposite && unresolvedBinding.isPartOfComposite;
- var bindingState = &bindingStatesPtr[bindingIndex];
-
- try
- {
- ////TODO: if it's a composite, check if any of the children matches our binding masks (if any) and skip composite if none do
-
- var firstControlIndex = 0; // numControls dictates whether this is a valid index or not.
- var firstInteractionIndex = InputActionState.kInvalidIndex;
- var firstProcessorIndex = InputActionState.kInvalidIndex;
- var actionIndexForBinding = InputActionState.kInvalidIndex;
- var partIndex = InputActionState.kInvalidIndex;
-
- var numControls = 0;
- var numInteractions = 0;
- var numProcessors = 0;
-
- // Make sure that if it's part of a composite, we are actually part of a composite.
- if (isPartOfComposite && currentCompositeBindingIndex == InputActionState.kInvalidIndex)
- throw new InvalidOperationException(
- $"Binding '{unresolvedBinding}' is marked as being part of a composite but the preceding binding is not a composite");
-
- // Try to find action.
- //
- // NOTE: We ignore actions on bindings that are part of composites. We only allow
- // actions to be triggered from the composite itself.
- var actionIndexInMap = InputActionState.kInvalidIndex;
- var actionName = unresolvedBinding.action;
- InputAction action = null;
- if (!isPartOfComposite)
- {
- if (isSingletonAction)
- {
- // Singleton actions always ignore names.
- actionIndexInMap = 0;
- }
- else if (!string.IsNullOrEmpty(actionName))
- {
- ////REVIEW: should we fail here if we don't manage to find the action
- actionIndexInMap = actionMap.FindActionIndex(actionName);
- }
-
- if (actionIndexInMap != InputActionState.kInvalidIndex)
- action = actionsInThisMap[actionIndexInMap];
- }
- else
- {
- actionIndexInMap = currentCompositeActionIndexInMap;
- action = currentCompositeAction;
- }
-
- // If it's a composite, start a chain.
- if (isComposite)
- {
- currentCompositeBindingIndex = bindingIndex;
- currentCompositeAction = action;
- currentCompositeActionIndexInMap = actionIndexInMap;
- }
-
- // Determine if the binding is disabled.
- // Disabled if path is empty.
- var path = unresolvedBinding.effectivePath;
- var bindingIsDisabled = string.IsNullOrEmpty(path)
-
- // Also, if we can't find the action to trigger for the binding, we just go and disable
- // the binding.
- || action == null
-
- // Also, disabled if binding doesn't match with our binding mask (might be empty).
- || (!isComposite && bindingMask != null &&
- !bindingMask.Value.Matches(ref unresolvedBinding,
- InputBinding.MatchOptions.EmptyGroupMatchesAny))
-
- // Also, disabled if binding doesn't match the binding mask on the map (might be empty).
- || (!isComposite && bindingMaskOnThisMap != null &&
- !bindingMaskOnThisMap.Value.Matches(ref unresolvedBinding,
- InputBinding.MatchOptions.EmptyGroupMatchesAny))
-
- // Finally, also disabled if binding doesn't match the binding mask on the action (might be empty).
- || (!isComposite && action?.m_BindingMask != null &&
- !action.m_BindingMask.Value.Matches(ref unresolvedBinding,
- InputBinding.MatchOptions.EmptyGroupMatchesAny));
-
- // If the binding isn't disabled, look up controls now. We do this first as we may still disable the
- // binding if it doesn't resolve to any controls or resolves only to controls already bound to by
- // other bindings.
- //
- // NOTE: We continuously add controls here to `resolvedControls`. Once we've completed our
- // pass over the bindings in the map, `resolvedControls` will have all the controls for
- // the current map.
- if (!bindingIsDisabled && !isComposite)
- {
- firstControlIndex = memory.controlCount + resolvedControls.Count;
- if (devicesForThisMap != null)
- {
- // Search in devices for only this map.
- var list = devicesForThisMap.Value;
- for (var i = 0; i < list.Count; ++i)
- {
- var device = list[i];
- if (!device.added)
- continue; // Skip devices that have been removed.
- numControls += InputControlPath.TryFindControls(device, path, 0, ref resolvedControls);
- }
- }
- else
- {
- // Search globally.
- numControls = InputSystem.FindControls(path, ref resolvedControls);
- }
- }
-
- // If the binding isn't disabled, resolve its controls, processors, and interactions.
- if (!bindingIsDisabled)
- {
- // NOTE: When isFullResolve==false, it is *imperative* that we do count processor and interaction
- // counts here come out exactly the same as in the previous full resolve.
-
- // Instantiate processors.
- var processorString = unresolvedBinding.effectiveProcessors;
- if (!string.IsNullOrEmpty(processorString))
- {
- // Add processors from binding.
- firstProcessorIndex = InstantiateWithParameters(InputProcessor.s_Processors, processorString,
- ref processors, ref totalProcessorCount, actionMap, ref unresolvedBinding);
- if (firstProcessorIndex != InputActionState.kInvalidIndex)
- numProcessors = totalProcessorCount - firstProcessorIndex;
- }
- if (!string.IsNullOrEmpty(action.m_Processors))
- {
- // Add processors from action.
- var index = InstantiateWithParameters(InputProcessor.s_Processors, action.m_Processors, ref processors,
- ref totalProcessorCount, actionMap, ref unresolvedBinding);
- if (index != InputActionState.kInvalidIndex)
- {
- if (firstProcessorIndex == InputActionState.kInvalidIndex)
- firstProcessorIndex = index;
- numProcessors += totalProcessorCount - index;
- }
- }
-
- // Instantiate interactions.
- var interactionString = unresolvedBinding.effectiveInteractions;
- if (!string.IsNullOrEmpty(interactionString))
- {
- // Add interactions from binding.
- firstInteractionIndex = InstantiateWithParameters(InputInteraction.s_Interactions, interactionString,
- ref interactions, ref totalInteractionCount, actionMap, ref unresolvedBinding);
- if (firstInteractionIndex != InputActionState.kInvalidIndex)
- numInteractions = totalInteractionCount - firstInteractionIndex;
- }
- if (!string.IsNullOrEmpty(action.m_Interactions))
- {
- // Add interactions from action.
- var index = InstantiateWithParameters(InputInteraction.s_Interactions, action.m_Interactions,
- ref interactions, ref totalInteractionCount, actionMap, ref unresolvedBinding);
- if (index != InputActionState.kInvalidIndex)
- {
- if (firstInteractionIndex == InputActionState.kInvalidIndex)
- firstInteractionIndex = index;
- numInteractions += totalInteractionCount - index;
- }
- }
-
- // If it's the start of a composite chain, create the composite.
- if (isComposite)
- {
- // The composite binding entry itself does not resolve to any controls.
- // It creates a composite binding object which is then populated from
- // subsequent bindings.
-
- // Instantiate. For composites, the path is the name of the composite.
- var composite = InstantiateBindingComposite(ref unresolvedBinding, actionMap);
- currentCompositeIndex =
- ArrayHelpers.AppendWithCapacity(ref composites, ref totalCompositeCount, composite);
-
- // Record where the controls for parts of the composite start.
- firstControlIndex = memory.controlCount + resolvedControls.Count;
- }
- else
- {
- // If we've reached the end of a composite chain, finish
- // off the current composite.
- if (!isPartOfComposite && currentCompositeBindingIndex != InputActionState.kInvalidIndex)
- {
- currentCompositePartCount = 0;
- currentCompositeBindingIndex = InputActionState.kInvalidIndex;
- currentCompositeIndex = InputActionState.kInvalidIndex;
- currentCompositeAction = null;
- currentCompositeActionIndexInMap = InputActionState.kInvalidIndex;
- }
- }
- }
-
- // If the binding is part of a composite, pass the resolved controls
- // on to the composite.
- if (isPartOfComposite && currentCompositeBindingIndex != InputActionState.kInvalidIndex && numControls > 0)
- {
- // Make sure the binding is named. The name determines what in the composite
- // to bind to.
- if (string.IsNullOrEmpty(unresolvedBinding.name))
- throw new InvalidOperationException(
- $"Binding '{unresolvedBinding}' that is part of composite '{composites[currentCompositeIndex]}' is missing a name");
-
- // Assign an index to the current part of the composite which
- // can be used by the composite to read input from this part.
- partIndex = AssignCompositePartIndex(composites[currentCompositeIndex], unresolvedBinding.name,
- ref currentCompositePartCount);
-
- // Keep track of total number of controls bound in the composite.
- bindingStatesPtr[currentCompositeBindingIndex].controlCount += numControls;
-
- // Force action index on part binding to be same as that of composite.
- actionIndexForBinding = bindingStatesPtr[currentCompositeBindingIndex].actionIndex;
- }
- else if (actionIndexInMap != InputActionState.kInvalidIndex)
- {
- actionIndexForBinding = actionStartIndex + actionIndexInMap;
- }
-
- // Store resolved binding.
- *bindingState = new InputActionState.BindingState
- {
- controlStartIndex = firstControlIndex,
- // For composites, this will be adjusted as we add each part.
- controlCount = numControls,
- interactionStartIndex = firstInteractionIndex,
- interactionCount = numInteractions,
- processorStartIndex = firstProcessorIndex,
- processorCount = numProcessors,
- isComposite = isComposite,
- isPartOfComposite = unresolvedBinding.isPartOfComposite,
- partIndex = partIndex,
- actionIndex = actionIndexForBinding,
- compositeOrCompositeBindingIndex = isComposite ? currentCompositeIndex : currentCompositeBindingIndex,
- mapIndex = totalMapCount,
- wantsInitialStateCheck = action?.wantsInitialStateCheck ?? false
- };
- }
- catch (Exception exception)
- {
- Debug.LogError(
- $"{exception.GetType().Name} while resolving binding '{unresolvedBinding}' in action map '{actionMap}'");
- Debug.LogException(exception);
-
- // Don't swallow exceptions that indicate something is wrong in the code rather than
- // in the data.
- if (exception.IsExceptionIndicatingBugInCode())
- throw;
- }
- }
-
- // Re-allocate memory to accommodate controls and interaction states. The count for those
- // we only know once we've completed all resolution.
- var controlCountInThisMap = resolvedControls.Count;
- var newTotalControlCount = memory.controlCount + controlCountInThisMap;
- if (newMemory.interactionCount != totalInteractionCount ||
- newMemory.compositeCount != totalCompositeCount ||
- newMemory.controlCount != newTotalControlCount)
- {
- var finalMemory = new InputActionState.UnmanagedMemory();
-
- finalMemory.Allocate(
- mapCount: newMemory.mapCount,
- actionCount: newMemory.actionCount,
- bindingCount: newMemory.bindingCount,
- controlCount: newTotalControlCount,
- interactionCount: totalInteractionCount,
- compositeCount: totalCompositeCount);
-
- finalMemory.CopyDataFrom(newMemory);
-
- newMemory.Dispose();
- newMemory = finalMemory;
- }
-
- // Add controls to array.
- var controlCountInArray = memory.controlCount;
- ArrayHelpers.AppendListWithCapacity(ref controls, ref controlCountInArray, resolvedControls);
- Debug.Assert(controlCountInArray == newTotalControlCount,
- "Control array should have combined count of old and new controls");
-
- // Set up control to binding index mapping.
- for (var i = 0; i < bindingCountInThisMap; ++i)
- {
- var bindingStatesPtr = newMemory.bindingStates;
- var bindingState = &bindingStatesPtr[bindingStartIndex + i];
- var numControls = bindingState->controlCount;
- var startIndex = bindingState->controlStartIndex;
- for (var n = 0; n < numControls; ++n)
- newMemory.controlIndexToBindingIndex[startIndex + n] = bindingStartIndex + i;
- }
-
- // Initialize initial interaction states.
- for (var i = memory.interactionCount; i < newMemory.interactionCount; ++i)
- {
- ref var interactionState = ref newMemory.interactionStates[i];
- interactionState.phase = InputActionPhase.Waiting;
- interactionState.triggerControlIndex = InputActionState.kInvalidIndex;
- }
-
- // Initialize action data.
- var runningIndexInBindingIndices = memory.bindingCount;
- for (var i = 0; i < actionCountInThisMap; ++i)
- {
- var action = actionsInThisMap[i];
- var actionIndex = actionStartIndex + i;
-
- // Correlate action with its trigger state.
- action.m_ActionIndexInState = actionIndex;
-
- Debug.Assert(runningIndexInBindingIndices < ushort.MaxValue, "Binding start index on action exceeds limit");
- newMemory.actionBindingIndicesAndCounts[actionIndex * 2] = (ushort)runningIndexInBindingIndices;
-
- // Collect bindings for action.
- var firstBindingIndexForAction = -1;
- var bindingCountForAction = 0;
- var numPossibleConcurrentActuations = 0;
-
- for (var n = 0; n < bindingCountInThisMap; ++n)
- {
- var bindingIndex = bindingStartIndex + n;
- var bindingState = &newMemory.bindingStates[bindingIndex];
- if (bindingState->actionIndex != actionIndex)
- continue;
- if (bindingState->isPartOfComposite)
- continue;
-
- Debug.Assert(bindingIndex <= ushort.MaxValue, "Binding index exceeds limit");
- newMemory.actionBindingIndices[runningIndexInBindingIndices] = (ushort)bindingIndex;
- ++runningIndexInBindingIndices;
- ++bindingCountForAction;
-
- if (firstBindingIndexForAction == -1)
- firstBindingIndexForAction = bindingIndex;
-
- // Keep track of how many concurrent actuations we may be seeing on the action so that
- // we know whether we need to enable conflict resolution or not.
- if (bindingState->isComposite)
- {
- // Composite binding. Actuates as a whole. Check if the composite has successfully
- // resolved any controls. If so, it adds one possible actuation.
- if (bindingState->controlCount > 0)
- ++numPossibleConcurrentActuations;
- }
- else
- {
- // Normal binding. Every successfully resolved control results in one possible actuation.
- numPossibleConcurrentActuations += bindingState->controlCount;
- }
- }
-
- if (firstBindingIndexForAction == -1)
- firstBindingIndexForAction = 0;
-
- Debug.Assert(bindingCountForAction < ushort.MaxValue, "Binding count on action exceeds limit");
- newMemory.actionBindingIndicesAndCounts[actionIndex * 2 + 1] = (ushort)bindingCountForAction;
-
- // See if we may need conflict resolution on this action. Never needed for pass-through actions.
- // Otherwise, if we have more than one bound control or have several bindings and one of them
- // is a composite, we enable it.
- var isPassThroughAction = action.type == InputActionType.PassThrough;
- var isButtonAction = action.type == InputActionType.Button;
- var mayNeedConflictResolution = !isPassThroughAction && numPossibleConcurrentActuations > 1;
-
- // Initialize initial trigger state.
- newMemory.actionStates[actionIndex] =
- new InputActionState.TriggerState
- {
- phase = InputActionPhase.Disabled,
- mapIndex = mapIndex,
- controlIndex = InputActionState.kInvalidIndex,
- interactionIndex = InputActionState.kInvalidIndex,
- isPassThrough = isPassThroughAction,
- isButton = isButtonAction,
- mayNeedConflictResolution = mayNeedConflictResolution,
- bindingIndex = firstBindingIndexForAction
- };
- }
-
- // Store indices for map.
- newMemory.mapIndices[mapIndex] =
- new InputActionState.ActionMapIndices
- {
- actionStartIndex = actionStartIndex,
- actionCount = actionCountInThisMap,
- controlStartIndex = controlStartIndex,
- controlCount = controlCountInThisMap,
- bindingStartIndex = bindingStartIndex,
- bindingCount = bindingCountInThisMap,
- interactionStartIndex = interactionStartIndex,
- interactionCount = totalInteractionCount - interactionStartIndex,
- processorStartIndex = processorStartIndex,
- processorCount = totalProcessorCount - processorStartIndex,
- compositeStartIndex = compositeStartIndex,
- compositeCount = totalCompositeCount - compositeStartIndex,
- };
- actionMap.m_MapIndexInState = mapIndex;
- var finalActionMapCount = memory.mapCount;
- ArrayHelpers.AppendWithCapacity(ref maps, ref finalActionMapCount, actionMap, capacityIncrement: 4);
- Debug.Assert(finalActionMapCount == newMemory.mapCount,
- "Final action map count should match old action map count plus one");
-
- // As a final act, swap the new memory in.
- memory.Dispose();
- memory = newMemory;
- }
- catch (Exception)
- {
- // Don't leak our native memory when we throw an exception.
- newMemory.Dispose();
- throw;
- }
- finally
- {
- resolvedControls.Dispose();
- }
- }
-
- private List<NameAndParameters> m_Parameters; // We retain this to reuse the allocation.
- private int InstantiateWithParameters<TType>(TypeTable registrations, string namesAndParameters, ref TType[] array, ref int count, InputActionMap actionMap, ref InputBinding binding)
- {
- if (!NameAndParameters.ParseMultiple(namesAndParameters, ref m_Parameters))
- return InputActionState.kInvalidIndex;
-
- var firstIndex = count;
- for (var i = 0; i < m_Parameters.Count; ++i)
- {
- // Look up type.
- var objectRegistrationName = m_Parameters[i].name;
- var type = registrations.LookupTypeRegistration(objectRegistrationName);
- if (type == null)
- {
- Debug.LogError(
- $"No {typeof(TType).Name} with name '{objectRegistrationName}' (mentioned in '{namesAndParameters}') has been registered");
- continue;
- }
-
- if (!m_IsControlOnlyResolve)
- {
- // Instantiate it.
- if (!(Activator.CreateInstance(type) is TType instance))
- {
- Debug.LogError(
- $"Type '{type.Name}' registered as '{objectRegistrationName}' (mentioned in '{namesAndParameters}') is not an {typeof(TType).Name}");
- continue;
- }
-
- // Pass parameters to it.
- ApplyParameters(m_Parameters[i].parameters, instance, actionMap, ref binding, objectRegistrationName,
- namesAndParameters);
-
- // Add to list.
- ArrayHelpers.AppendWithCapacity(ref array, ref count, instance);
- }
- else
- {
- Debug.Assert(type.IsInstanceOfType(array[count]), "Type of instance in array does not match expected type");
- ++count;
- }
- }
-
- return firstIndex;
- }
-
- private static InputBindingComposite InstantiateBindingComposite(ref InputBinding binding, InputActionMap actionMap)
- {
- var nameAndParametersParsed = NameAndParameters.Parse(binding.effectivePath);
-
- // Look up.
- var type = InputBindingComposite.s_Composites.LookupTypeRegistration(nameAndParametersParsed.name);
- if (type == null)
- throw new InvalidOperationException(
- $"No binding composite with name '{nameAndParametersParsed.name}' has been registered");
-
- // Instantiate.
- if (!(Activator.CreateInstance(type) is InputBindingComposite instance))
- throw new InvalidOperationException(
- $"Registered type '{type.Name}' used for '{nameAndParametersParsed.name}' is not an InputBindingComposite");
-
- // Set parameters.
- ApplyParameters(nameAndParametersParsed.parameters, instance, actionMap, ref binding, nameAndParametersParsed.name,
- binding.effectivePath);
-
- return instance;
- }
-
- private static void ApplyParameters(ReadOnlyArray<NamedValue> parameters, object instance, InputActionMap actionMap, ref InputBinding binding, string objectRegistrationName, string namesAndParameters)
- {
- foreach (var parameter in parameters)
- {
- // Find field.
- var field = instance.GetType().GetField(parameter.name,
- BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
- if (field == null)
- {
- Debug.LogError(
- $"Type '{instance.GetType().Name}' registered as '{objectRegistrationName}' (mentioned in '{namesAndParameters}') has no public field called '{parameter.name}'");
- continue;
- }
- var fieldTypeCode = Type.GetTypeCode(field.FieldType);
-
- // See if we have a parameter override.
- var parameterOverride =
- InputActionRebindingExtensions.ParameterOverride.Find(actionMap, ref binding, parameter.name, objectRegistrationName);
- var value = parameterOverride != null
- ? parameterOverride.Value.value
- : parameter.value;
-
- field.SetValue(instance, value.ConvertTo(fieldTypeCode).ToObject());
- }
- }
-
- private static int AssignCompositePartIndex(object composite, string name, ref int currentCompositePartCount)
- {
- var type = composite.GetType();
-
- ////REVIEW: check for [InputControl] attribute?
-
- ////TODO: allow this to be a property instead
- // Look up field.
- var field = type.GetField(name,
- BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
- if (field == null)
- throw new InvalidOperationException(
- $"Cannot find public field '{name}' used as parameter of binding composite '{composite}' of type '{type}'");
-
- ////REVIEW: should we wrap part numbers in a struct instead of using int?
-
- // Type-check.
- var fieldType = field.FieldType;
- if (fieldType != typeof(int))
- throw new InvalidOperationException(
- $"Field '{name}' used as a parameter of binding composite '{composite}' must be of type 'int' but is of type '{type.Name}' instead");
-
- ////REVIEW: this creates garbage; need a better solution to get to zero garbage during re-resolving
- // See if we've already assigned a part index. This can happen if there are multiple bindings
- // for the same named slot on the composite (e.g. multiple "Negative" bindings on an axis composite).
- var partIndex = (int)field.GetValue(composite);
- if (partIndex == 0)
- {
- // No, not assigned yet. Create new part index.
- partIndex = ++currentCompositePartCount;
- field.SetValue(composite, partIndex);
- }
-
- return partIndex;
- }
- }
- }
|