예제 #1
0
        public static InputControl[] TryFindControls(InputControl control, string path, int indexInPath = 0)
        {
            var matches = new InputControlList <InputControl>(Allocator.Temp);

            try
            {
                TryFindControls(control, path, indexInPath, ref matches);
                return(matches.ToArray());
            }
            finally
            {
                matches.Dispose();
            }
        }
예제 #2
0
        ////REVIEW: have mode where instead of matching only the first device that matches a requirement, we match as many
        ////        as we can get? (could be useful for single-player)
        /// <summary>
        /// Based on a list of devices, make a selection that matches the <see cref="deviceRequirements">requirements</see>
        /// imposed by the control scheme.
        /// </summary>
        /// <param name="devices">A list of devices to choose from.</param>
        /// <returns>A <see cref="MatchResult"/> structure containing the result of the pick. Note that this structure
        /// must be manually <see cref="MatchResult.Dispose">disposed</see> or unmanaged memory will be leaked.</returns>
        /// <remarks>
        /// Does not allocate managed memory.
        /// </remarks>
        public MatchResult PickDevicesFrom <TDevices>(TDevices devices)
            where TDevices : IReadOnlyList <InputDevice>
        {
            // Empty device requirements match anything while not really picking anything.
            if (m_DeviceRequirements == null || m_DeviceRequirements.Length == 0)
            {
                return(new MatchResult
                {
                    m_Result = MatchResult.Result.AllSatisfied,
                });
            }

            // Go through each requirement and match it.
            // NOTE: Even if `devices` is empty, we don't know yet whether we have a NoMatch.
            //       All our devices may be optional.
            var haveAllRequired  = true;
            var haveAllOptional  = true;
            var requirementCount = m_DeviceRequirements.Length;
            var controls         = new InputControlList <InputControl>(Allocator.Persistent, requirementCount);

            try
            {
                var orChainIsSatisfied        = false;
                var orChainHasRequiredDevices = false;
                for (var i = 0; i < requirementCount; ++i)
                {
                    var isOR       = m_DeviceRequirements[i].isOR;
                    var isOptional = m_DeviceRequirements[i].isOptional;

                    // If this is an OR requirement and we already have a match in this OR chain,
                    // skip this requirement.
                    if (isOR && orChainIsSatisfied)
                    {
                        // Skill need to add an entry for this requirement.
                        controls.Add(null);
                        continue;
                    }

                    // Null and empty paths shouldn't make it into the list but make double
                    // sure here. Simply ignore entries that don't have a path.
                    var path = m_DeviceRequirements[i].controlPath;
                    if (string.IsNullOrEmpty(path))
                    {
                        controls.Add(null);
                        continue;
                    }

                    // Find the first matching control among the devices we have.
                    InputControl match = null;
                    for (var n = 0; n < devices.Count; ++n)
                    {
                        var device = devices[n];

                        // See if we have a match.
                        var matchedControl = InputControlPath.TryFindControl(device, path);
                        if (matchedControl == null)
                        {
                            continue; // No.
                        }
                        // We have a match but if we've already match the same control through another requirement,
                        // we can't use the match.
                        if (controls.Contains(matchedControl))
                        {
                            continue;
                        }

                        match = matchedControl;
                        break;
                    }

                    // Check requirements in AND and OR chains. We look ahead here to find out whether
                    // the next requirement is starting an OR chain. As the OR combines with the previous
                    // requirement in the list, this affects our current requirement.
                    var nextIsOR = i + 1 < requirementCount && m_DeviceRequirements[i + 1].isOR;
                    if (nextIsOR)
                    {
                        // Shouldn't get here if the chain is already satisfied. Should be handled
                        // at beginning of loop and we shouldn't even be looking at finding controls
                        // in that case.
                        Debug.Assert(!orChainIsSatisfied);

                        // It's an OR with the next requirement. Depends on the outcome of other matches whether
                        // we're good or not.

                        if (match != null)
                        {
                            // First match in this chain.
                            orChainIsSatisfied = true;
                        }
                        else
                        {
                            // Chain not satisfied yet.

                            if (!isOptional)
                            {
                                orChainHasRequiredDevices = true;
                            }
                        }
                    }
                    else if (isOR && i == requirementCount - 1)
                    {
                        // It's an OR at the very end of the requirements list. Terminate
                        // the OR chain.

                        if (match == null)
                        {
                            if (orChainHasRequiredDevices)
                            {
                                haveAllRequired = false;
                            }
                            else
                            {
                                haveAllOptional = false;
                            }
                        }
                    }
                    else
                    {
                        // It's an AND.

                        if (match == null)
                        {
                            if (isOptional)
                            {
                                haveAllOptional = false;
                            }
                            else
                            {
                                haveAllRequired = false;
                            }
                        }

                        // Terminate ongoing OR chain.
                        if (i > 0 && m_DeviceRequirements[i - 1].isOR)
                        {
                            if (!orChainIsSatisfied)
                            {
                                if (orChainHasRequiredDevices)
                                {
                                    haveAllRequired = false;
                                }
                                else
                                {
                                    haveAllOptional = false;
                                }
                            }
                            orChainIsSatisfied = false;
                        }
                    }

                    // Add match to list. Maybe null.
                    controls.Add(match);
                }

                // We should have matched each of our requirements.
                Debug.Assert(controls.Count == requirementCount);
            }
            catch (Exception)
            {
                controls.Dispose();
                throw;
            }

            return(new MatchResult
            {
                m_Result = !haveAllRequired
                    ? MatchResult.Result.MissingRequired
                    : !haveAllOptional
                    ? MatchResult.Result.MissingOptional
                    : MatchResult.Result.AllSatisfied,
                m_Controls = controls,
                m_Requirements = m_DeviceRequirements,
            });
        }
예제 #3
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 bindingMaskOnThisMap         = map.m_BindingMask;
            var actionsInThisMap             = map.m_Actions;
            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.Matches(ref unresolvedBinding))
                    {
                        continue;
                    }

                    // Skip binding if it doesn't match the binding mask on the map (might be empty).
                    if (!bindingMaskOnThisMap.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 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;
                    }

                    // Skip binding if it doesn't match the binding mask on the action (might be empty).
                    if (actionIndex != InputActionMapState.kInvalidIndex &&
                        !map.m_Actions[actionIndex].m_BindingMask.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)
                    // 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 = actionIndex,
                            compositeOrCompositeBindingIndex = currentCompositeIndex,
                            processorStartIndex   = firstProcessorIndex,
                            processorCount        = numProcessors,
                            interactionCount      = numInteractions,
                            interactionStartIndex = firstInteractionIndex,
                            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 &&
                        currentCompositeBindingIndex != InputActionMapState.kInvalidIndex)
                    {
                        currentCompositeBindingIndex = InputActionMapState.kInvalidIndex;
                        currentCompositeIndex        = InputActionMapState.kInvalidIndex;
                    }

                    // Look up controls.
                    var firstControlIndex = totalControlCount;
                    var numControls       = InputSystem.FindControls(path, ref resolvedControls);
                    if (numControls > 0)
                    {
                        resolvedControls.AppendTo(ref controls, ref totalControlCount);
                        resolvedControls.Clear();
                    }

                    // 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,
                        actionIndex                      = actionIndex,
                        compositeOrCompositeBindingIndex = currentCompositeBindingIndex,
                        mapIndex = totalMapCount,
                    };

                    // If the binding is part of a composite, pass the resolve controls
                    // on to the composite.
                    if (unresolvedBinding.isPartOfComposite &&
                        currentCompositeBindingIndex != 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]);
                    }
                }
            }
            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;
                }
            }
        }
예제 #4
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;

            ArrayHelpers.GrowWithCapacity(ref bindingStates, ref totalBindingCount, bindingCountInThisMap);

            ////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;
                    var isComposite       = unresolvedBinding.isComposite;

                    ////TODO: if it's a composite, check if any of the children matches our binding masks (if any) and skip composite if none do

                    // 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 (!isComposite && 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 (!isComposite && 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 (!isComposite && 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;
                        }
                    }

                    // If it's the start of a composite chain, create the composite.
                    if (unresolvedBinding.isComposite)
                    {
                        // 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;
            var controlIndexToBindingIndexCount = controlStartIndex;

            ArrayHelpers.GrowWithCapacity(ref controlIndexToBindingIndex, ref controlIndexToBindingIndexCount,
                                          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.GrowWithCapacity(ref actionStates, ref totalActionCount, actionCountInThisMap);
                for (var i = 0; i < actionCountInThisMap; ++i)
                {
                    actionStates[actionStartIndex + i].mapIndex     = mapIndex;
                    actionStates[actionStartIndex + i].controlIndex = InputActionMapState.kInvalidIndex;
                }
            }
        }