Example #1
0
        /// <summary>
        /// Add a control to the list.
        /// </summary>
        /// <param name="item">Control to add. Allowed to be <c>null</c>.</param>
        /// <remarks>
        /// If necessary, <see cref="Capacity"/> will be increased.
        ///
        /// It is allowed to add nulls to the list. This can be useful, for example, when
        /// specific indices in the list correlate with specific matches and a given match
        /// needs to be marked as "matches nothing".
        /// </remarks>
        /// <seealso cref="Remove"/>
        public void Add(TControl item)
        {
            var index     = ToIndex(item);
            var allocator = m_Allocator != Allocator.Invalid ? m_Allocator : Allocator.Persistent;

            ArrayHelpers.AppendWithCapacity(ref m_Indices, ref m_Count, index, allocator: allocator);
        }
Example #2
0
 internal void AppendTo(ref TControl[] array, ref int count)
 {
     for (var i = 0; i < m_Count; ++i)
     {
         ArrayHelpers.AppendWithCapacity(ref array, ref count, this[i]);
     }
 }
Example #3
0
        private int ResolveProcessors(string processorString)
        {
            var firstProcessorIndex = totalProcessorCount;

            if (!InputControlLayout.ParseNameAndParameterList(processorString, ref m_Parameters))
            {
                return(firstProcessorIndex);
            }

            for (var i = 0; i < m_Parameters.Count; ++i)
            {
                // Look up processor.
                var type = InputControlProcessor.s_Processors.LookupTypeRegistration(m_Parameters[i].name);
                if (type == null)
                {
                    throw new Exception(string.Format(
                                            "No processor with name '{0}' (mentioned in '{1}') has been registered", m_Parameters[i].name,
                                            processorString));
                }

                // Instantiate it.
                var processor = Activator.CreateInstance(type);

                // Pass parameters to it.
                InputDeviceBuilder.SetParameters(processor, m_Parameters[i].parameters);

                // Add to list.
                ArrayHelpers.AppendWithCapacity(ref processors, ref totalProcessorCount, processor);
            }

            return(firstProcessorIndex);
        }
 public RebindingOperation WithControlsExcluding(string path)
 {
     if (string.IsNullOrEmpty(path))
     {
         throw new ArgumentNullException(nameof(path));
     }
     for (var i = 0; i < m_ExcludePathCount; ++i)
     {
         if (string.Compare(m_ExcludePaths[i], path, StringComparison.InvariantCultureIgnoreCase) == 0)
         {
             return(this);
         }
     }
     ArrayHelpers.AppendWithCapacity(ref m_ExcludePaths, ref m_ExcludePathCount, path);
     return(this);
 }
Example #5
0
        private int ResolveInteractions(string interactionString)
        {
            ////REVIEW: We're piggybacking off the processor parsing here as the two syntaxes are identical. Might consider
            ////        moving the logic to a shared place.
            ////        Alternatively, may split the paths. May help in getting rid of unnecessary allocations.

            var firstInteractionIndex = totalInteractionCount;

            if (!InputControlLayout.ParseNameAndParameterList(interactionString, ref m_Parameters))
            {
                return(firstInteractionIndex);
            }

            for (var i = 0; i < m_Parameters.Count; ++i)
            {
                // Look up interaction.
                var type = InputInteraction.s_Interactions.LookupTypeRegistration(m_Parameters[i].name);
                if (type == null)
                {
                    throw new Exception(string.Format(
                                            "No interaction with name '{0}' (mentioned in '{1}') has been registered", m_Parameters[i].name,
                                            interactionString));
                }

                // Instantiate it.
                var interaction = Activator.CreateInstance(type) as IInputInteraction;
                if (interaction == null)
                {
                    throw new Exception(string.Format("Interaction '{0}' is not an IInputInteraction", m_Parameters[i].name));
                }

                // Pass parameters to it.
                InputDeviceBuilder.SetParameters(interaction, m_Parameters[i].parameters);

                // Add to list.
                var interactionStateCount = totalInteractionCount;
                ArrayHelpers.AppendWithCapacity(ref interactionStates, ref interactionStateCount,
                                                new InputActionMapState.InteractionState
                {
                    phase = InputActionPhase.Waiting
                });
                ArrayHelpers.AppendWithCapacity(ref interactions, ref totalInteractionCount, interaction);
                Debug.Assert(interactionStateCount == totalInteractionCount);
            }

            return(firstInteractionIndex);
        }
Example #6
0
        private static bool AssignDeviceInternal(int userIndex, InputDevice device)
        {
            var deviceCount      = s_AllUserData[userIndex].deviceCount;
            var deviceStartIndex = s_AllUserData[userIndex].deviceStartIndex;

            // Ignore if already assigned to user.
            for (var i = 0; i < deviceCount; ++i)
            {
                if (s_AllDevices[deviceStartIndex + i] == device)
                {
                    return(false);
                }
            }

            // Move our devices to end of array.
            if (deviceCount > 0)
            {
                ArrayHelpers.MoveSlice(s_AllDevices, deviceStartIndex, s_AllDeviceCount - deviceCount,
                                       deviceCount);

                // Adjust users that have been impacted by the change.
                for (var i = 0; i < s_AllUserCount; ++i)
                {
                    if (i == userIndex)
                    {
                        continue;
                    }

                    if (s_AllUserData[i].deviceStartIndex <= deviceStartIndex)
                    {
                        continue;
                    }

                    s_AllUserData[i].deviceStartIndex -= deviceCount;
                }
            }

            // Append to array.
            deviceStartIndex = s_AllDeviceCount - deviceCount;
            s_AllUserData[userIndex].deviceStartIndex = deviceStartIndex;
            ArrayHelpers.AppendWithCapacity(ref s_AllDevices, ref s_AllDeviceCount, device);
            ++s_AllUserData[userIndex].deviceCount;

            return(true);
        }
Example #7
0
        protected void AddPointer(Pointer pointer)
        {
            if (pointer == null)
            {
                throw new ArgumentNullException(nameof(pointer));
            }

            // Ignore if already added.
            if (ArrayHelpers.ContainsReference(m_Sources, m_NumSources, pointer))
            {
                return;
            }

            var numPositions = m_NumSources;

            ArrayHelpers.AppendWithCapacity(ref m_CurrentPositions, ref numPositions, Vector2.zero);
            var index = ArrayHelpers.AppendWithCapacity(ref m_Sources, ref m_NumSources, pointer);

            InstallStateChangeMonitors(index);
        }
Example #8
0
        protected void AddPointer(Pointer pointer)
        {
            if (pointer == null)
            {
                throw new ArgumentNullException(nameof(pointer));
            }

            // Ignore if already added.
            if (m_Pointers.ContainsReference(m_NumPointers, pointer))
            {
                return;
            }

            // Add to list.
            var numPointers = m_NumPointers;

            ArrayHelpers.AppendWithCapacity(ref m_Pointers, ref m_NumPointers, pointer);
            ArrayHelpers.AppendWithCapacity(ref m_CurrentPositions, ref numPointers, default);

            InputSystem.DisableDevice(pointer, keepSendingEvents: true);
        }
Example #9
0
        /// <summary>
        /// Add a new user.
        /// </summary>
        /// <param name="userName">Optional <see cref="userName"/> to assign to the newly created user.</param>
        /// <returns>A newly created user.</returns>
        /// <remarks>
        /// Adding a user sends a notification with <see cref="InputUserChange.Added"/> through <see cref="onChange"/>.
        ///
        /// The user will start out with no devices and no actions assigned.
        ///
        /// The user is added to <see cref="all"/>.
        /// </remarks>
        public static void Add(IInputUser user, string userName = null)
        {
            if (user == null)
            {
                throw new ArgumentNullException("user");
            }

            var userData = new UserData
            {
                id       = ++s_LastUserId,
                userName = userName,
            };

            // Add to list.
            var userCount = s_AllUserCount;

            ArrayHelpers.AppendWithCapacity(ref s_AllUsers, ref userCount, user);
            ArrayHelpers.AppendWithCapacity(ref s_AllUserData, ref s_AllUserCount, userData);

            // Send notification.
            Notify(user, InputUserChange.Added);
        }
Example #10
0
        /// <summary>
        /// Push a single action onto the stack.
        /// </summary>
        /// <param name="action"></param>
        public void Push(InputAction action)
        {
            if (action == null)
            {
                throw new ArgumentNullException("action");
            }
            if (action.enabled)
            {
                throw new ArgumentException(
                          string.Format("Cannot add action '{0}' to stack as it is currently enabled", action), "action");
            }

            // Add.
            EnsureCapacity(1);
            ArrayHelpers.AppendWithCapacity(ref m_Actions, ref m_ActionCount, action);

            // Enable action, if necessary.
            if (enabled)
            {
                action.Enable();
            }
        }
        private void InitializeIfNeeded(SerializedProperty property)
        {
            if (m_Initialized)
            {
                return;
            }

            // Look for action events.
            foreach (var child in property.GetChildren())
            {
                // Skip properties that aren't action events.
                var type = child.GetFieldType();
                if (type == null || !typeof(UnityEvent <InputAction.CallbackContext>).IsAssignableFrom(type))
                {
                    continue;
                }

                // Add to list.
                ArrayHelpers.AppendWithCapacity(ref m_ActionEvents, ref m_ActionEventCount, child.Copy());
            }

            m_Initialized = true;
        }
            public void AddCandidate(InputControl control, float score)
            {
                if (control == null)
                {
                    throw new ArgumentNullException(nameof(control));
                }

                // If it's already added, update score.
                var index = m_Candidates.IndexOf(control);

                if (index != -1)
                {
                    m_Scores[index] = score;
                }
                else
                {
                    // Otherwise, add it.
                    var candidateCount = m_Candidates.Count;
                    m_Candidates.Add(control);
                    ArrayHelpers.AppendWithCapacity(ref m_Scores, ref candidateCount, score);
                }

                SortCandidatesByScore();
            }
Example #13
0
        public unsafe void AddActionMap(InputActionMap map)
        {
            Debug.Assert(map != null, "Received null map");

            var actionsInThisMap      = map.m_Actions;
            var bindingsInThisMap     = map.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             = map.m_BindingMask;
            var         devicesForThisMap = map.devices;

            // 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 (!string.IsNullOrEmpty(actionName))
                            {
                                ////REVIEW: should we fail here if we don't manage to find the action
                                actionIndexInMap = map.FindActionIndex(actionName);
                            }
                            else if (map.m_SingletonAction != null)
                            {
                                // Special-case for singleton actions that don't have names.
                                actionIndexInMap = 0;
                            }

                            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, resolve its controls, processors, and interactions.
                        if (!bindingIsDisabled)
                        {
                            // Instantiate processors.
                            var processorString = unresolvedBinding.effectiveProcessors;
                            if (!string.IsNullOrEmpty(processorString))
                            {
                                // Add processors from binding.
                                firstProcessorIndex = ResolveProcessors(processorString);
                                if (firstProcessorIndex != InputActionState.kInvalidIndex)
                                {
                                    numProcessors = totalProcessorCount - firstProcessorIndex;
                                }
                            }
                            if (!string.IsNullOrEmpty(action.m_Processors))
                            {
                                // Add processors from action.
                                var index = ResolveProcessors(action.m_Processors);
                                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 = ResolveInteractions(interactionString);
                                if (firstInteractionIndex != InputActionState.kInvalidIndex)
                                {
                                    numInteractions = totalInteractionCount - firstInteractionIndex;
                                }
                            }
                            if (!string.IsNullOrEmpty(action.m_Interactions))
                            {
                                // Add interactions from action.
                                var index = ResolveInteractions(action.m_Interactions);
                                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. Otherwise, go and
                            // resolve controls for the binding.
                            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(unresolvedBinding.path);
                                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;
                                }

                                // Look up controls.
                                //
                                // 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.
                                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 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");
                            }

                            // Give a part index for the
                            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 '{map}'");
                        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)
                {
                    newMemory.interactionStates[i].phase = InputActionPhase.Waiting;
                }

                // 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;

                    // Collect bindings for action.
                    var bindingStartIndexForAction      = runningIndexInBindingIndices;
                    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;

                        // 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;
                        }
                    }
                    Debug.Assert(bindingStartIndexForAction < ushort.MaxValue, "Binding start index on action exceeds limit");
                    Debug.Assert(bindingCountForAction < ushort.MaxValue, "Binding count on action exceeds limit");
                    newMemory.actionBindingIndicesAndCounts[actionIndex * 2]     = (ushort)bindingStartIndexForAction;
                    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,
                    };
                }

                // 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,
                };
                map.m_MapIndexInState = mapIndex;
                var finalActionMapCount = memory.mapCount;
                ArrayHelpers.AppendWithCapacity(ref maps, ref finalActionMapCount, map, 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;
            }
Example #14
0
 /// <summary>
 /// Called when the gamepad is added to the system.
 /// </summary>
 protected override void OnAdded()
 {
     ArrayHelpers.AppendWithCapacity(ref s_Gamepads, ref s_GamepadCount, this);
 }
        private static void UpdateControllers()
        {
            if (api == null)
            {
                return;
            }

            // Update controller state.
            api.RunFrame();

            // Check if we have any new controllers have appeared.
            if (s_ConnectedControllers == null)
            {
                s_ConnectedControllers = new SteamHandle <SteamController> [STEAM_CONTROLLER_MAX_COUNT];
            }
            var numConnectedControllers = api.GetConnectedControllers(s_ConnectedControllers);

            for (var i = 0; i < numConnectedControllers; ++i)
            {
                var handle = s_ConnectedControllers[i];

                // See if we already have a device for this one.
                if (s_InputDevices != null)
                {
                    SteamController existingDevice = null;
                    for (var n = 0; n < s_InputDeviceCount; ++n)
                    {
                        if (s_InputDevices[n].handle == handle)
                        {
                            existingDevice = s_InputDevices[n];
                            break;
                        }
                    }

                    // Yes, we do.
                    if (existingDevice != null)
                    {
                        continue;
                    }
                }

                // No, so create a new device.
                var controllerLayouts = InputSystem.ListLayoutsBasedOn("SteamController");
                foreach (var layout in controllerLayouts)
                {
                    // Rather than directly creating a device with the layout, let it go through
                    // the usual matching process.
                    var device = InputSystem.AddDevice(new InputDeviceDescription
                    {
                        interfaceName = SteamController.kSteamInterface,
                        product       = layout
                    });

                    // Make sure it's a SteamController we got.
                    var steamDevice = device as SteamController;
                    if (steamDevice == null)
                    {
                        Debug.LogError(string.Format(
                                           "InputDevice created from layout '{0}' based on the 'SteamController' layout is not a SteamController",
                                           device.layout));
                        continue;
                    }

                    // Resolve the controller's actions.
                    steamDevice.InvokeResolveActions();

                    // Assign it the Steam controller handle.
                    steamDevice.handle = handle;

                    ArrayHelpers.AppendWithCapacity(ref s_InputDevices, ref s_InputDeviceCount, steamDevice);
                }
            }

            // Update all controllers we have.
            for (var i = 0; i < s_InputDeviceCount; ++i)
            {
                var device = s_InputDevices[i];
                var handle = device.handle;

                // Check if the device still exists.
                var stillExists = false;
                for (var n = 0; n < numConnectedControllers; ++n)
                {
                    if (s_ConnectedControllers[n] == handle)
                    {
                        stillExists = true;
                        break;
                    }
                }

                // If not, remove it.
                if (!stillExists)
                {
                    ArrayHelpers.EraseAtByMovingTail(s_InputDevices, ref s_InputDeviceCount, i);
                    ////REVIEW: should this rather queue a device removal event?
                    InputSystem.RemoveDevice(device);
                    --i;
                    continue;
                }

                ////TODO: support polling Steam controllers on an async polling thread adhering to InputSystem.pollingFrequency
                // Otherwise, update it.
                device.InvokeUpdate();
            }
        }
Example #16
0
        /// <summary>
        /// Resolve and add all bindings and actions from the given map.
        /// </summary>
        /// <param name="map"></param>
        /// <exception cref="Exception"></exception>
        public void AddActionMap(InputActionMap map)
        {
            Debug.Assert(map != null);

            // Keep track of indices for this map.
            var bindingStartIndex     = totalBindingCount;
            var controlStartIndex     = totalControlCount;
            var interactionStartIndex = totalInteractionCount;
            var processorStartIndex   = totalProcessorCount;
            var compositeStartIndex   = totalCompositeCount;
            var actionStartIndex      = totalActionCount;

            // Allocate binding states.
            var bindingsInThisMap     = map.m_Bindings;
            var bindingCountInThisMap = bindingsInThisMap != null ? bindingsInThisMap.Length : 0;

            totalBindingCount += bindingCountInThisMap;
            ArrayHelpers.GrowBy(ref bindingStates, totalBindingCount);

            ////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 = InputActionMapState.kInvalidIndex;
            var currentCompositeIndex        = InputActionMapState.kInvalidIndex;
            var currentCompositePartIndex    = 0;
            var bindingMaskOnThisMap         = map.m_BindingMask;
            var actionsInThisMap             = map.m_Actions;
            var devicesForThisMap            = map.devices;
            var actionCountInThisMap         = actionsInThisMap != null ? actionsInThisMap.Length : 0;
            var resolvedControls             = new InputControlList <InputControl>(Allocator.Temp);

            try
            {
                for (var n = 0; n < bindingCountInThisMap; ++n)
                {
                    var unresolvedBinding = bindingsInThisMap[n];
                    var bindingIndex      = bindingStartIndex + n;

                    // Set binding state to defaults.
                    bindingStates[bindingIndex].mapIndex = totalMapCount;
                    bindingStates[bindingIndex].compositeOrCompositeBindingIndex = InputActionMapState.kInvalidIndex;
                    bindingStates[bindingIndex].actionIndex = InputActionMapState.kInvalidIndex;

                    // Skip binding if it is disabled (path is empty string).
                    var path = unresolvedBinding.effectivePath;
                    if (unresolvedBinding.path == "")
                    {
                        continue;
                    }

                    // Skip binding if it doesn't match with our binding mask (might be empty).
                    if (bindingMask != null && !bindingMask.Value.Matches(ref unresolvedBinding))
                    {
                        continue;
                    }

                    // Skip binding if it doesn't match the binding mask on the map (might be empty).
                    if (bindingMaskOnThisMap != null && !bindingMaskOnThisMap.Value.Matches(ref unresolvedBinding))
                    {
                        continue;
                    }

                    // Try to find action.
                    // NOTE: Technically, we allow individual bindings of composites to trigger actions independent
                    //       of the action triggered by the composite.
                    var actionIndexInMap = InputActionMapState.kInvalidIndex;
                    var actionName       = unresolvedBinding.action;
                    if (!string.IsNullOrEmpty(actionName))
                    {
                        actionIndexInMap = map.TryGetActionIndex(actionName);
                    }
                    else if (map.m_SingletonAction != null)
                    {
                        // Special-case for singleton actions that don't have names.
                        actionIndexInMap = 0;
                    }

                    // Skip binding if it doesn't match the binding mask on the action (might be empty).
                    if (actionIndexInMap != InputActionMapState.kInvalidIndex)
                    {
                        var action = actionsInThisMap[actionIndexInMap];
                        if (action.m_BindingMask != null && !action.m_BindingMask.Value.Matches(ref unresolvedBinding))
                        {
                            continue;
                        }
                    }

                    // Instantiate processors.
                    var firstProcessorIndex = 0;
                    var numProcessors       = 0;
                    var processors          = unresolvedBinding.effectiveProcessors;
                    if (!string.IsNullOrEmpty(processors))
                    {
                        firstProcessorIndex = ResolveProcessors(processors);
                        if (processors != null)
                        {
                            numProcessors = totalProcessorCount - firstProcessorIndex;
                        }
                    }

                    // Instantiate interactions.
                    var firstInteractionIndex = 0;
                    var numInteractions       = 0;
                    var interactions          = unresolvedBinding.effectiveInteractions;
                    if (!string.IsNullOrEmpty(interactions))
                    {
                        firstInteractionIndex = ResolveInteractions(interactions);
                        if (interactionStates != null)
                        {
                            numInteractions = totalInteractionCount - firstInteractionIndex;
                        }
                    }

                    ////TODO: allow specifying parameters for composite on its path (same way as parameters work for interactions)
                    ////      (Example: "Axis(min=-1,max=1)" creates an axis that goes from -1..1 instead of the default 0..1)
                    // If it's the start of a composite chain, create the composite.
                    if (unresolvedBinding.isComposite)
                    {
                        ////REVIEW: what to do about interactions on composites?

                        // Instantiate. For composites, the path is the name of the composite.
                        var composite = InstantiateBindingComposite(unresolvedBinding.path);
                        currentCompositeIndex =
                            ArrayHelpers.AppendWithCapacity(ref composites, ref totalCompositeCount, composite);
                        currentCompositeBindingIndex = bindingIndex;
                        bindingStates[bindingIndex]  = new InputActionMapState.BindingState
                        {
                            actionIndex = actionStartIndex + actionIndexInMap,
                            compositeOrCompositeBindingIndex = currentCompositeIndex,
                            processorStartIndex   = firstProcessorIndex,
                            processorCount        = numProcessors,
                            interactionCount      = numInteractions,
                            interactionStartIndex = firstInteractionIndex,
                            mapIndex    = totalMapCount,
                            isComposite = true,
                        };

                        // The composite binding entry itself does not resolve to any controls.
                        // It creates a composite binding object which is then populated from
                        // subsequent bindings.
                        continue;
                    }

                    // If we've reached the end of a composite chain, finish
                    // off the current composite.
                    if (!unresolvedBinding.isPartOfComposite &&
                        currentCompositeBindingIndex != InputActionMapState.kInvalidIndex)
                    {
                        currentCompositePartIndex    = 0;
                        currentCompositeBindingIndex = InputActionMapState.kInvalidIndex;
                        currentCompositeIndex        = InputActionMapState.kInvalidIndex;
                    }

                    // Look up controls.
                    var firstControlIndex = totalControlCount;
                    int numControls       = 0;
                    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 (numControls > 0)
                    {
                        resolvedControls.AppendTo(ref controls, ref totalControlCount);
                        resolvedControls.Clear();
                    }

                    // If the binding is part of a composite, pass the resolved controls
                    // on to the composite.
                    if (unresolvedBinding.isPartOfComposite &&
                        currentCompositeBindingIndex != InputActionMapState.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 Exception(string.Format(
                                                    "Binding with path '{0}' that is part of composite '{1}' is missing a name",
                                                    path, composites[currentCompositeIndex]));
                        }

                        // Install the controls on the binding.
                        BindControlInComposite(composites[currentCompositeIndex], unresolvedBinding.name,
                                               ref currentCompositePartIndex);
                    }

                    // Add entry for resolved binding.
                    bindingStates[bindingIndex] = new InputActionMapState.BindingState
                    {
                        controlStartIndex                = firstControlIndex,
                        controlCount                     = numControls,
                        interactionStartIndex            = firstInteractionIndex,
                        interactionCount                 = numInteractions,
                        processorStartIndex              = firstProcessorIndex,
                        processorCount                   = numProcessors,
                        isPartOfComposite                = unresolvedBinding.isPartOfComposite,
                        partIndex                        = currentCompositePartIndex,
                        actionIndex                      = actionIndexInMap,
                        compositeOrCompositeBindingIndex = currentCompositeBindingIndex,
                        mapIndex = totalMapCount,
                    };
                }
            }
            finally
            {
                resolvedControls.Dispose();
            }

            // Set up control to binding index mapping.
            var controlCountInThisMap = totalControlCount - controlStartIndex;

            ArrayHelpers.GrowBy(ref controlIndexToBindingIndex, controlCountInThisMap);
            for (var i = 0; i < bindingCountInThisMap; ++i)
            {
                var numControls = bindingStates[bindingStartIndex + i].controlCount;
                var startIndex  = bindingStates[bindingStartIndex + i].controlStartIndex;
                for (var n = 0; n < numControls; ++n)
                {
                    controlIndexToBindingIndex[startIndex + n] = i;
                }
            }

            // Store indices for map.
            var numMaps  = totalMapCount;
            var mapIndex = ArrayHelpers.AppendWithCapacity(ref maps, ref numMaps, map);

            ArrayHelpers.AppendWithCapacity(ref mapIndices, ref totalMapCount, new InputActionMapState.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,
            });
            map.m_MapIndexInState = mapIndex;

            // Allocate action states.
            if (actionCountInThisMap > 0)
            {
                // Assign action indices.
                var actions = map.m_Actions;
                for (var i = 0; i < actionCountInThisMap; ++i)
                {
                    actions[i].m_ActionIndex = totalActionCount + i;
                }

                ArrayHelpers.GrowBy(ref actionStates, actionCountInThisMap);
                totalActionCount += actionCountInThisMap;
                for (var i = 0; i < actionCountInThisMap; ++i)
                {
                    actionStates[i].mapIndex = mapIndex;
                }
            }
        }
Example #17
0
        /// <summary>
        /// Resolve and add all bindings and actions from the given map.
        /// </summary>
        /// <param name="map"></param>
        /// <exception cref="Exception"></exception>
        public void AddActionMap(InputActionMap map)
        {
            Debug.Assert(map != null);
            Debug.Assert(map.m_MapIndex == InputActionMapState.kInvalidIndex);

            // Keep track of indices for this map.
            var bindingStartIndex     = totalBindingCount;
            var controlStartIndex     = totalControlCount;
            var interactionStartIndex = totalInteractionCount;
            var processorStartIndex   = totalProcessorCount;
            var compositeStartIndex   = totalCompositeCount;
            var actionStartIndex      = totalActionCount;

            // Allocate binding states.
            var bindingsInThisMap     = map.m_Bindings;
            var bindingCountInThisMap = bindingsInThisMap != null ? bindingsInThisMap.Length : 0;

            totalBindingCount += bindingCountInThisMap;
            ArrayHelpers.GrowBy(ref bindingStates, totalBindingCount);

            ////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 currentCompositeIndex = InputActionMapState.kInvalidIndex;
            var actionsInThisMap      = map.m_Actions;
            var actionCountInThisMap  = actionsInThisMap != null ? actionsInThisMap.Length : 0;

            for (var n = 0; n < bindingCountInThisMap; ++n)
            {
                var unresolvedBinding = bindingsInThisMap[n];

                // Skip binding if it is disabled (path is empty string).
                var path = unresolvedBinding.effectivePath;
                if (unresolvedBinding.path == "")
                {
                    continue;
                }

                // Try to find action.
                var actionIndex = InputActionMapState.kInvalidIndex;
                var actionName  = unresolvedBinding.action;
                if (!string.IsNullOrEmpty(actionName))
                {
                    actionIndex = map.TryGetActionIndex(actionName);
                }
                else if (map.m_SingletonAction != null)
                {
                    // Special-case for singleton actions that don't have names.
                    actionIndex = 0;
                }

                // Instantiate processors.
                var firstProcessorIndex = 0;
                var numProcessors       = 0;
                var processors          = unresolvedBinding.effectiveProcessors;
                if (!string.IsNullOrEmpty(processors))
                {
                    firstProcessorIndex = ResolveProcessors(processors);
                    if (processors != null)
                    {
                        numProcessors = totalProcessorCount - firstProcessorIndex;
                    }
                }

                ////TODO: allow specifying parameters for composite on its path (same way as parameters work for interactions)
                // If it's the start of a composite chain, create the composite.
                if (unresolvedBinding.isComposite)
                {
                    ////REVIEW: what to do about interactions on composites?

                    // Instantiate. For composites, the path is the name of the composite.
                    var composite = InstantiateBindingComposite(unresolvedBinding.path);
                    currentCompositeIndex =
                        ArrayHelpers.AppendWithCapacity(ref composites, ref totalCompositeCount, composite);
                    bindingStates[bindingStartIndex + n] = new InputActionMapState.BindingState
                    {
                        actionIndex         = actionIndex,
                        compositeIndex      = currentCompositeIndex,
                        processorStartIndex = firstProcessorIndex,
                        processorCount      = numProcessors,
                        mapIndex            = totalMapCount,
                    };

                    // The composite binding entry itself does not resolve to any controls.
                    // It creates a composite binding object which is then populated from
                    // subsequent bindings.
                    continue;
                }

                // If we've reached the end of a composite chain, finish
                // off the current composite.
                if (!unresolvedBinding.isPartOfComposite && currentCompositeIndex != InputActionMapState.kInvalidIndex)
                {
                    currentCompositeIndex = InputActionMapState.kInvalidIndex;
                }

                // Look up controls.
                var firstControlIndex = totalControlCount;
                if (controls == null)
                {
                    controls = new InputControl[10];
                }
                var resolvedControls = new ArrayOrListWrapper <InputControl>(controls, totalControlCount);
                var numControls      = InputSystem.GetControls(path, ref resolvedControls);
                controls          = resolvedControls.array;
                totalControlCount = resolvedControls.count;

                // Instantiate interactions.
                var firstInteractionIndex = 0;
                var numInteractions       = 0;
                var interactions          = unresolvedBinding.effectiveInteractions;
                if (!string.IsNullOrEmpty(interactions))
                {
                    firstInteractionIndex = ResolveInteractions(interactions);
                    if (interactionStates != null)
                    {
                        numInteractions = totalInteractionCount - firstInteractionIndex;
                    }
                }

                // Add entry for resolved binding.
                bindingStates[bindingStartIndex + n] = new InputActionMapState.BindingState
                {
                    controlStartIndex     = firstControlIndex,
                    controlCount          = numControls,
                    interactionStartIndex = firstInteractionIndex,
                    interactionCount      = numInteractions,
                    processorStartIndex   = firstProcessorIndex,
                    processorCount        = numProcessors,
                    isPartOfComposite     = unresolvedBinding.isPartOfComposite,
                    actionIndex           = actionIndex,
                    compositeIndex        = currentCompositeIndex,
                    mapIndex = totalMapCount,
                };

                // If the binding is part of a composite, pass the resolve controls
                // on to the composite.
                if (unresolvedBinding.isPartOfComposite && currentCompositeIndex != InputActionMapState.kInvalidIndex && numControls != 0)
                {
                    ////REVIEW: what should we do when a single binding in a composite resolves to multiple controls?
                    ////        if the composite has more than one bindable control, it's not readily apparent how we would group them
                    if (numControls > 1)
                    {
                        throw new NotImplementedException("Handling case where single binding in composite resolves to multiple controls");
                    }

                    // Make sure the binding is named. The name determines what in the composite
                    // to bind to.
                    if (string.IsNullOrEmpty(unresolvedBinding.name))
                    {
                        throw new Exception(string.Format(
                                                "Binding that is part of composite '{0}' is missing a name",
                                                composites[currentCompositeIndex]));
                    }

                    // Install the control on the binding.
                    BindControlInComposite(composites[currentCompositeIndex], unresolvedBinding.name,
                                           controls[firstControlIndex]);
                }
            }

            // Set up control to binding index mapping.
            var controlCountInThisMap = totalControlCount - controlStartIndex;

            ArrayHelpers.GrowBy(ref controlIndexToBindingIndex, controlCountInThisMap);
            for (var i = 0; i < bindingCountInThisMap; ++i)
            {
                var numControls = bindingStates[bindingStartIndex + i].controlCount;
                var startIndex  = bindingStates[bindingStartIndex + i].controlStartIndex;
                for (var n = 0; n < numControls; ++n)
                {
                    controlIndexToBindingIndex[startIndex + n] = i;
                }
            }

            // Store indices for map.
            var numMaps  = totalMapCount;
            var mapIndex = ArrayHelpers.AppendWithCapacity(ref maps, ref numMaps, map);

            ArrayHelpers.AppendWithCapacity(ref mapIndices, ref totalMapCount, new InputActionMapState.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,
            });
            map.m_MapIndex = mapIndex;

            // Allocate action states.
            if (actionCountInThisMap > 0)
            {
                // Assign action indices.
                var actions = map.m_Actions;
                for (var i = 0; i < actionCountInThisMap; ++i)
                {
                    actions[i].m_ActionIndex = totalActionCount + i;
                }

                ArrayHelpers.GrowBy(ref actionStates, actionCountInThisMap);
                totalActionCount += actionCountInThisMap;
                for (var i = 0; i < actionCountInThisMap; ++i)
                {
                    actionStates[i].mapIndex = mapIndex;
                }
            }
        }
            private unsafe void OnEvent(InputEventPtr eventPtr, InputDevice device)
            {
                if (device == null)
                {
                    return;
                }

                // Ignore if not a state event.
                if (!eventPtr.IsA <StateEvent>() && !eventPtr.IsA <DeltaStateEvent>())
                {
                    return;
                }

                // Go through controls and see if there's anything interesting in the event.
                var controls              = device.allControls;
                var controlCount          = controls.Count;
                var haveChangedCandidates = false;

                for (var i = 0; i < controlCount; ++i)
                {
                    var control = controls[i];

                    // Skip controls that have no state in the event.
                    var statePtr = control.GetStatePtrFromStateEvent(eventPtr);
                    if (statePtr == null)
                    {
                        continue;
                    }

                    // If the control that cancels has been actuated, abort the operation now.
                    if (!string.IsNullOrEmpty(m_CancelBinding) && InputControlPath.Matches(m_CancelBinding, control) &&
                        !control.CheckStateIsAtDefault(statePtr) && control.HasValueChangeInState(statePtr))
                    {
                        OnCancel();
                        break;
                    }

                    // Skip noisy controls.
                    if (control.noisy && (m_Flags & Flags.DontIgnoreNoisyControls) == 0)
                    {
                        continue;
                    }

                    // If controls have to match a certain path, check if this one does.
                    if (m_IncludePathCount > 0 && !HavePathMatch(control, m_IncludePaths, m_IncludePathCount))
                    {
                        continue;
                    }

                    // If controls must not match certain path, make sure the control doesn't.
                    if (m_ExcludePathCount > 0 && HavePathMatch(control, m_ExcludePaths, m_ExcludePathCount))
                    {
                        continue;
                    }

                    // If we're expecting controls of a certain type, skip if control isn't of
                    // the right type.
                    if (m_ControlType != null && !m_ControlType.IsInstanceOfType(control))
                    {
                        continue;
                    }

                    // If we're expecting controls to be based on a specific layout, skip if control
                    // isn't based on that layout.
                    if (!m_ExpectedLayout.IsEmpty() &&
                        m_ExpectedLayout != control.m_Layout &&
                        !InputControlLayout.s_Layouts.IsBasedOn(m_ExpectedLayout, control.m_Layout))
                    {
                        continue;
                    }

                    // Skip controls that are in their default state.
                    // NOTE: This is the cheapest check with respect to looking at actual state. So
                    //       do this first before looking further at the state.
                    if (control.CheckStateIsAtDefault(statePtr))
                    {
                        continue;
                    }

                    // Skip controls that have no effective value change.
                    // NOTE: This will run the full processor stack and is more involved.
                    if (!control.HasValueChangeInState(statePtr))
                    {
                        continue;
                    }

                    // If we have a magnitude threshold, see if control passes it.
                    var magnitude = -1f;
                    if (m_MagnitudeThreshold >= 0f)
                    {
                        magnitude = control.EvaluateMagnitude(statePtr);
                        if (magnitude >= 0 && magnitude < m_MagnitudeThreshold)
                        {
                            continue; // No, so skip.
                        }
                    }

                    // Compute score.
                    float score;
                    if (m_OnComputeScore != null)
                    {
                        score = m_OnComputeScore(control, eventPtr);
                    }
                    else
                    {
                        score = magnitude;

                        // We don't want synthetic controls to not be bindable at all but they should
                        // generally cede priority to controls that aren't synthetic. So we bump all
                        // scores of controls that aren't synthetic.
                        if (!control.synthetic)
                        {
                            score += 1f;
                        }
                    }

                    // Control is a candidate.
                    // See if we already singled the control out as a potential candidate.
                    var candidateIndex = m_Candidates.IndexOf(control);
                    if (candidateIndex != -1)
                    {
                        // Yes, we did. So just check whether it became a better candidate than before.
                        if (m_Scores[candidateIndex] < score)
                        {
                            haveChangedCandidates    = true;
                            m_Scores[candidateIndex] = score;

                            if (m_WaitSecondsAfterMatch > 0)
                            {
                                m_LastMatchTime = InputRuntime.s_Instance.currentTime;
                            }
                        }
                    }
                    else
                    {
                        // No, so add it.
                        var candidateCount = m_Candidates.Count;
                        m_Candidates.Add(control);
                        ArrayHelpers.AppendWithCapacity(ref m_Scores, ref candidateCount, score);
                        haveChangedCandidates = true;

                        if (m_WaitSecondsAfterMatch > 0)
                        {
                            m_LastMatchTime = InputRuntime.s_Instance.currentTime;
                        }
                    }
                }

                if (haveChangedCandidates && !canceled)
                {
                    // If we have a callback that wants to control matching, leave it to the callback to decide
                    // whether the rebind is complete or not. Otherwise, just complete.
                    if (m_OnPotentialMatch != null)
                    {
                        SortCandidatesByScore();
                        m_OnPotentialMatch(this);
                    }
                    else if (m_WaitSecondsAfterMatch <= 0)
                    {
                        OnComplete();
                    }
                    else
                    {
                        SortCandidatesByScore();
                    }
                }
            }
        public unsafe void AddActionMap(InputActionMap map)
        {
            Debug.Assert(map != null, "Received null map");

            var actionsInThisMap      = map.m_Actions;
            var bindingsInThisMap     = map.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             = map.m_BindingMask;
            var         devicesForThisMap = map.devices;

            // 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 (!string.IsNullOrEmpty(actionName))
                            {
                                ////REVIEW: should we fail here if we don't manage to find the action
                                actionIndexInMap = map.FindActionIndex(actionName);
                            }
                            else if (map.m_SingletonAction != null)
                            {
                                // Special-case for singleton actions that don't have names.
                                actionIndexInMap = 0;
                            }

                            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);
                            }

                            // Check for controls that are already bound to the action through other
                            // bindings. The first binding that grabs a specific control gets to "own" it.
                            if (numControls > 0)
                            {
                                for (var i = 0; i < n; ++i)
                                {
                                    ref var otherBindingState = ref bindingStatesPtr[bindingStartIndex + i];

                                    // Skip if binding has no controls.
                                    if (otherBindingState.controlCount == 0)
                                    {
                                        continue;
                                    }

                                    // Skip if binding isn't from same action.
                                    if (otherBindingState.actionIndex != actionStartIndex + actionIndexInMap)
                                    {
                                        continue;
                                    }

                                    // Check for controls in the set that we just resolved that are also on the other
                                    // binding. Each such control we find, we kick out of the list.
                                    for (var k = 0; k < numControls; ++k)
                                    {
                                        var controlOnCurrentBinding    = resolvedControls[firstControlIndex + k - memory.controlCount];
                                        var controlIndexOnOtherBinding = resolvedControls.IndexOf(controlOnCurrentBinding,
                                                                                                  otherBindingState.controlStartIndex - memory.controlCount, otherBindingState.controlCount);
                                        if (controlIndexOnOtherBinding != -1)
                                        {
                                            // Control is bound to a previous binding. Remove it from the current binding.
                                            resolvedControls.RemoveAt(firstControlIndex + k - memory.controlCount);
                                            --numControls;
                                            --k;
                                        }
                                    }
                                }
                            }

                            // Disable binding if it doesn't resolve to any controls.
                            // NOTE: This also happens to bindings that got all their resolved controls removed because other bindings from the same
                            //       action already grabbed them.
                            if (numControls == 0)
                            {
                                bindingIsDisabled = true;
                            }
                        }

                        // If the binding isn't disabled, resolve its controls, processors, and interactions.
                        if (!bindingIsDisabled)
                        {
                            // Instantiate processors.
                            var processorString = unresolvedBinding.effectiveProcessors;
                            if (!string.IsNullOrEmpty(processorString))
                            {
                                // Add processors from binding.
                                firstProcessorIndex = ResolveProcessors(processorString);
                                if (firstProcessorIndex != InputActionState.kInvalidIndex)
                                {
                                    numProcessors = totalProcessorCount - firstProcessorIndex;
                                }
                            }
                            if (!string.IsNullOrEmpty(action.m_Processors))
                            {
                                // Add processors from action.
                                var index = ResolveProcessors(action.m_Processors);
                                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 = ResolveInteractions(interactionString);
                                if (firstInteractionIndex != InputActionState.kInvalidIndex)
                                {
                                    numInteractions = totalInteractionCount - firstInteractionIndex;
                                }
                            }
                            if (!string.IsNullOrEmpty(action.m_Interactions))
                            {
                                // Add interactions from action.
                                var index = ResolveInteractions(action.m_Interactions);
                                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. Otherwise, go and
                            // resolve controls for the binding.
                            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(unresolvedBinding.path);
                                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");
                            }

                            // Give a part index for the
                            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 '{map}'");
                        Debug.LogException(exception);

                        // Don't swallow exceptions that indicate something is wrong in the code rather than
                        // in the data.
                        if (exception.IsExceptionIndicatingBugInCode())
                        {
                            throw;
                        }
                    }
                }
Example #20
0
 /// <summary>
 /// Called when the joystick is added to the system.
 /// </summary>
 protected override void OnAdded()
 {
     ArrayHelpers.AppendWithCapacity(ref s_Joysticks, ref s_JoystickCount, this);
 }
Example #21
0
        private static void UpdateControllers()
        {
            if (api == null)
            {
                return;
            }

            // Check if we have any controllers have appeared or disappeared.
            if (s_ConnectedControllers == null)
            {
                s_ConnectedControllers = new ulong[STEAM_CONTROLLER_MAX_COUNT];
            }
            var numConnectedControllers = api.GetConnectedControllers(s_ConnectedControllers);

            for (var i = 0; i < numConnectedControllers; ++i)
            {
                var handle = s_ConnectedControllers[i];

                // See if we already have a device for this one.
                if (s_InputDevices != null)
                {
                    SteamController existingDevice = null;
                    for (var n = 0; n < s_InputDeviceCount; ++n)
                    {
                        if (s_InputDevices[n].steamControllerHandle == handle)
                        {
                            existingDevice = s_InputDevices[n];
                            break;
                        }
                    }

                    // Yes, we do.
                    if (existingDevice != null)
                    {
                        continue;
                    }
                }

                // No, so create a new device.
                var controllerLayouts = InputSystem.ListLayoutsBasedOn("SteamController");
                foreach (var layout in controllerLayouts)
                {
                    // Rather than directly creating a device with the layout, let it go through
                    // the usual matching process.
                    var device = InputSystem.AddDevice(new InputDeviceDescription
                    {
                        interfaceName = SteamController.kSteamInterface,
                        product       = layout
                    });

                    // Make sure it's a SteamController we got.
                    var steamDevice = device as SteamController;
                    if (steamDevice == null)
                    {
                        Debug.LogError(string.Format(
                                           "InputDevice created from layout '{0}' based on the 'SteamController' layout is not a SteamController",
                                           device.layout));
                        continue;
                    }

                    // Assign it the Steam controller handle.
                    steamDevice.steamControllerHandle = handle;

                    ArrayHelpers.AppendWithCapacity(ref s_InputDevices, ref s_InputDeviceCount, steamDevice);
                }
            }
            if (s_InputDevices != null)
            {
                // Remove anything no longer there.
                for (var i = 0; i < s_InputDeviceCount; ++i)
                {
                    var device      = s_InputDevices[i];
                    var handle      = device.steamControllerHandle;
                    var stillExists = false;
                    for (var n = 0; n < numConnectedControllers; ++n)
                    {
                        if (s_ConnectedControllers[n] == handle)
                        {
                            stillExists = true;
                            break;
                        }
                    }

                    if (!stillExists)
                    {
                        ArrayHelpers.EraseAtByMovingTail(s_InputDevices, ref s_InputDeviceCount, i);
                        InputSystem.RemoveDevice(device);
                    }
                }
            }
        }
        /// <summary>
        /// Called when an action is triggered in one of the action maps added to the manager. Records
        /// relevant trigger information to surface in event list later on.
        /// </summary>
        /// <param name="context"></param>
        unsafe void IInputActionCallbackReceiver.OnActionTriggered(ref InputAction.CallbackContext context)
        {
            var controlIndex = context.m_ControlIndex;
            var control      = context.control;
            var time         = context.m_Time;

            // See if already have a trigger record for the control.
            var triggerIndex = -1;

            for (var i = 0; i < m_TriggerDataCount; ++i)
            {
                var otherControlIndex = m_TriggerDataBuffer[i].controlIndex;
                if (otherControlIndex != controlIndex)
                {
                    ////REVIEW: shouldn't we make sure somehow that control indices are unique?
                    // NOTE: We're not just comparing control indices here but rather actual control references
                    //       as the same control may appear multiple times in the list.
                    var otherControl = m_State.controls[otherControlIndex];
                    if (!ReferenceEquals(otherControl, control))
                    {
                        continue;
                    }
                }

                if (!Mathf.Approximately((float)m_TriggerDataBuffer[i].time, (float)time))
                {
                    continue;
                }

                triggerIndex = i;
                break;
            }

            // If not, create one.
            if (triggerIndex == -1)
            {
                // Save the current state of the control. Copy full bytes only (means we may be grabbing some
                // state from other controls here but that doesn't matter).
                var stateSizeInBytes   = control.m_StateBlock.alignedSizeInBytes;
                var stateOffset        = control.m_StateBlock.byteOffset;
                var statePtr           = (byte *)control.currentStatePtr.ToPointer() + stateOffset;
                var offsetOfSavedState = ArrayHelpers.GrowWithCapacity(ref m_StateDataBuffer, ref m_StateDataSize, (int)stateSizeInBytes, 1024);
                UnsafeUtility.MemCpy((byte *)m_StateDataBuffer.GetUnsafePtr() + offsetOfSavedState, statePtr,
                                     stateSizeInBytes);

                // Append trigger data.
                var triggerData = new TriggerData
                {
                    controlIndex     = controlIndex,
                    time             = time,
                    stateOffset      = (uint)offsetOfSavedState,
                    stateSizeInBytes = stateSizeInBytes,
                };
                triggerIndex =
                    ArrayHelpers.AppendWithCapacity(ref m_TriggerDataBuffer, ref m_TriggerDataCount, triggerData);
            }

            // Add action record.
            var data = m_TriggerDataBuffer[triggerIndex];

            ++data.actionEventCount;
            var actionIndex = ArrayHelpers.AppendWithCapacity(ref m_ActionDataBuffer, ref m_ActionDataCount,
                                                              new ActionData
            {
                triggerIndex     = triggerIndex,
                bindingIndex     = context.m_BindingIndex,
                interactionIndex = context.m_InteractionIndex,
                phase            = context.phase,
            });

            if (data.actionEventCount == 1)
            {
                data.actionEventIndex = actionIndex;
            }
            m_TriggerDataBuffer[triggerIndex] = data;
        }