Maze / Library /PackageCache /com.unity.inputsystem@1.6.1 /InputSystem /Actions /InputBindingResolver.cs
| 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> | |
| [] | |
| 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; | |
| } | |
| } | |
| } | |