Beispiel #1
0
 public InputControlListDebugView(InputControlList <TControl> list)
 {
     m_Controls = list.ToArray();
 }
        private static TControl MatchByUsageAtDeviceRootRecursive <TControl>(InputDevice device, string path, int indexInPath,
                                                                             ref InputControlList <TControl> matches, bool matchMultiple)
            where TControl : InputControl
        {
            var usages = device.m_UsagesForEachControl;

            if (usages == null)
            {
                return(null);
            }

            var usageCount           = usages.Length;
            var startIndex           = indexInPath + 1;
            var pathCanMatchMultiple = PathComponentCanYieldMultipleMatches(path, indexInPath);
            var pathLength           = path.Length;

            Debug.Assert(path[indexInPath] == '{');
            ++indexInPath;
            if (indexInPath == pathLength)
            {
                throw new Exception(string.Format("Invalid path spec '{0}'; trailing '{{'", path));
            }

            TControl lastMatch = null;

            for (var i = 0; i < usageCount; ++i)
            {
                var usage = usages[i];

                // Match usage agaist path.
                var usageIsMatch = MatchPathComponent(usage, path, ref indexInPath, PathComponentType.Usage);

                // If it isn't a match, go to next usage.
                if (!usageIsMatch)
                {
                    indexInPath = startIndex;
                    continue;
                }

                var controlMatchedByUsage = device.m_UsageToControl[i];

                // If there's more to go in the path, dive into the children of the control.
                if (indexInPath < pathLength && path[indexInPath] == '/')
                {
                    lastMatch = MatchChildrenRecursive(controlMatchedByUsage, path, indexInPath + 1,
                                                       ref matches, matchMultiple);

                    // We can stop going through usages if we matched something and the
                    // path component covering usage does not contain wildcards.
                    if (lastMatch != null && !pathCanMatchMultiple)
                    {
                        break;
                    }

                    // We can stop going through usages if we have a match and are only
                    // looking for a single one.
                    if (lastMatch != null && !matchMultiple)
                    {
                        break;
                    }
                }
                else
                {
                    lastMatch = controlMatchedByUsage as TControl;
                    if (lastMatch != null)
                    {
                        if (matchMultiple)
                        {
                            matches.Add(lastMatch);
                        }
                        else
                        {
                            // Only looking for single match and we have one.
                            break;
                        }
                    }
                }
            }

            return(lastMatch);
        }
Beispiel #3
0
 public Enumerator(InputControlList <TControl> list)
 {
     m_Count   = list.m_Count;
     m_Current = -1;
     m_Indices = m_Count > 0 ? (ulong *)list.m_Indices.GetUnsafeReadOnlyPtr() : null;
 }
        ////TODO: refactor this to use the new PathParser

        /// <summary>
        /// Recursively match path elements in <paramref name="path"/>.
        /// </summary>
        /// <param name="control">Current control we're at.</param>
        /// <param name="path">Control path we are matching against.</param>
        /// <param name="indexInPath">Index of current component in <paramref name="path"/>.</param>
        /// <param name="matches"></param>
        /// <param name="matchMultiple"></param>
        /// <typeparam name="TControl"></typeparam>
        /// <returns></returns>
        private static TControl MatchControlsRecursive <TControl>(InputControl control, string path, int indexInPath,
                                                                  ref InputControlList <TControl> matches, bool matchMultiple)
            where TControl : InputControl
        {
            var pathLength = path.Length;

            // Try to get a match. A path spec has three components:
            //    "<layout>{usage}name"
            // All are optional but at least one component must be present.
            // Names can be aliases, too.
            // We don't tap InputControl.path strings of controls so as to not create a
            // bunch of string objects while feeling our way down the hierarchy.

            var controlIsMatch = true;

            // Match by layout.
            if (path[indexInPath] == '<')
            {
                ++indexInPath;
                controlIsMatch =
                    MatchPathComponent(control.layout, path, ref indexInPath, PathComponentType.Layout);

                // If the layout isn't a match, walk up the base layout
                // chain and match each base layout.
                if (!controlIsMatch)
                {
                    var baseLayout = control.m_Layout;
                    while (InputControlLayout.s_Layouts.baseLayoutTable.TryGetValue(baseLayout, out baseLayout))
                    {
                        controlIsMatch = MatchPathComponent(baseLayout, path, ref indexInPath,
                                                            PathComponentType.Layout);
                        if (controlIsMatch)
                        {
                            break;
                        }
                    }
                }
            }

            // Match by usage.
            if (indexInPath < pathLength && path[indexInPath] == '{' && controlIsMatch)
            {
                ++indexInPath;

                for (var i = 0; i < control.usages.Count; ++i)
                {
                    controlIsMatch = MatchPathComponent(control.usages[i], path, ref indexInPath, PathComponentType.Usage);
                    if (controlIsMatch)
                    {
                        break;
                    }
                }
            }

            // Match by name.
            if (indexInPath < pathLength && controlIsMatch && path[indexInPath] != '/')
            {
                // Normal name match.
                controlIsMatch = MatchPathComponent(control.name, path, ref indexInPath, PathComponentType.Name);

                // Alternative match by alias.
                if (!controlIsMatch)
                {
                    for (var i = 0; i < control.aliases.Count && !controlIsMatch; ++i)
                    {
                        controlIsMatch = MatchPathComponent(control.aliases[i], path, ref indexInPath,
                                                            PathComponentType.Name);
                    }
                }
            }

            // If we have a match, return it or, if there's children, recurse into them.
            if (controlIsMatch)
            {
                // If we ended up on a wildcard, we've successfully matched it.
                if (indexInPath < pathLength && path[indexInPath] == '*')
                {
                    ++indexInPath;
                }

                // If we've reached the end of the path, we have a match.
                if (indexInPath == pathLength)
                {
                    // Check type.
                    var match = control as TControl;
                    if (match == null)
                    {
                        return(null);
                    }

                    if (matchMultiple)
                    {
                        matches.Add(match);
                    }
                    return(match);
                }

                // If we've reached a separator, dive into our children.
                if (path[indexInPath] == '/')
                {
                    ++indexInPath;

                    // Silently accept trailing slashes.
                    if (indexInPath == pathLength)
                    {
                        // Check type.
                        var match = control as TControl;
                        if (match == null)
                        {
                            return(null);
                        }

                        if (matchMultiple)
                        {
                            matches.Add(match);
                        }
                        return(match);
                    }

                    // See if we want to match children by usage or by name.
                    TControl lastMatch;
                    if (path[indexInPath] == '{')
                    {
                        ////TODO: support scavenging a subhierarchy for usages
                        if (!ReferenceEquals(control.device, control))
                        {
                            throw new NotImplementedException(
                                      "Matching usages inside subcontrols instead of at device root");
                        }

                        // Usages are kind of like entry points that can route to anywhere else
                        // on a device's control hierarchy and then we keep going from that re-routed
                        // point.
                        lastMatch = MatchByUsageAtDeviceRootRecursive(control.device, path, indexInPath, ref matches, matchMultiple);
                    }
                    else
                    {
                        // Go through children and see what we can match.
                        lastMatch = MatchChildrenRecursive(control, path, indexInPath, ref matches, matchMultiple);
                    }

                    return(lastMatch);
                }
            }

            return(null);
        }
Beispiel #5
0
        /// <summary>
        /// Resolve and add all bindings and actions from the given map.
        /// </summary>
        /// <param name="map"></param>
        /// <remarks>
        /// This is where all binding resolution happens for actions. The method walks through the binding array
        /// in <paramref name="map"/> and adds any controls, interactions, processors, and composites as it goes.
        /// </remarks>
        public unsafe void AddActionMap(InputActionMap map)
        {
            Debug.Assert(map != null);

            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
            {
                var bindingStatesPtr = newMemory.bindingStates;
                for (var n = 0; n < bindingCountInThisMap; ++n)
                {
                    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

                        // Set binding state to defaults.
                        bindingState->mapIndex = totalMapCount;
                        bindingState->compositeOrCompositeBindingIndex = InputActionState.kInvalidIndex;
                        bindingState->actionIndex = InputActionState.kInvalidIndex;

                        // Make sure that if it's part of a composite, we are actually part of a composite.
                        if (isPartOfComposite && currentCompositeBindingIndex == InputActionState.kInvalidIndex)
                        {
                            throw new Exception(
                                      $"Binding '{unresolvedBinding}' is marked as being part of a composite but the preceding binding is not a composite");
                        }

                        // 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: 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.TryGetActionIndex(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;
                        }

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

                        // Instantiate processors.
                        var firstProcessorIndex = InputActionState.kInvalidIndex;
                        var numProcessors       = 0;
                        var processorString     = unresolvedBinding.effectiveProcessors;
                        if (!string.IsNullOrEmpty(processorString))
                        {
                            // Add processors from binding.
                            firstProcessorIndex = ResolveProcessors(processorString);
                            if (firstProcessorIndex != InputActionState.kInvalidIndex)
                            {
                                numProcessors = totalProcessorCount - firstProcessorIndex;
                            }
                        }
                        if (action != null && !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 firstInteractionIndex = InputActionState.kInvalidIndex;
                        var numInteractions       = 0;
                        var interactionString     = unresolvedBinding.effectiveInteractions;
                        if (!string.IsNullOrEmpty(interactionString))
                        {
                            // Add interactions from binding.
                            firstInteractionIndex = ResolveInteractions(interactionString);
                            if (firstInteractionIndex != InputActionState.kInvalidIndex)
                            {
                                numInteractions = totalInteractionCount - firstInteractionIndex;
                            }
                        }
                        if (action != null && !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.
                        if (isComposite)
                        {
                            var actionIndexForComposite = actionIndexInMap != InputActionState.kInvalidIndex
                                ? actionStartIndex + actionIndexInMap
                                : InputActionState.kInvalidIndex;

                            // 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;
                            currentCompositeAction           = action;
                            currentCompositeActionIndexInMap = actionIndexInMap;

                            *bindingState = new InputActionState.BindingState
                            {
                                actionIndex = actionIndexForComposite,
                                compositeOrCompositeBindingIndex = currentCompositeIndex,
                                processorStartIndex   = firstProcessorIndex,
                                processorCount        = numProcessors,
                                interactionCount      = numInteractions,
                                interactionStartIndex = firstInteractionIndex,
                                mapIndex    = totalMapCount,
                                isComposite = true,
                                // Record where the controls for parts of the composite start.
                                controlStartIndex = memory.controlCount + resolvedControls.Count,
                            };

                            // 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 (!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.
                        var firstControlIndex = memory.controlCount + resolvedControls.Count;
                        var 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 the binding is part of a composite, pass the resolved controls
                        // on to the composite.
                        var partIndex             = InputActionState.kInvalidIndex;
                        var actionIndexForBinding = InputActionState.kInvalidIndex;
                        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 Exception(
                                          $"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;
                        }

                        // Add entry for resolved binding.
                        *bindingState = new InputActionState.BindingState
                        {
                            controlStartIndex                = firstControlIndex,
                            controlCount                     = numControls,
                            interactionStartIndex            = firstInteractionIndex,
                            interactionCount                 = numInteractions,
                            processorStartIndex              = firstProcessorIndex,
                            processorCount                   = numProcessors,
                            isPartOfComposite                = unresolvedBinding.isPartOfComposite,
                            partIndex                        = partIndex,
                            actionIndex                      = actionIndexForBinding,
                            compositeOrCompositeBindingIndex = currentCompositeBindingIndex,
                            mapIndex = totalMapCount,
                        };
                    }
                    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 exception;
                        }
                    }
                }

                // 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 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_ActionIndex = 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[i * 2]     = (ushort)bindingStartIndexForAction;
                    newMemory.actionBindingIndicesAndCounts[i * 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.passThrough;
                    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,
                        continuous                = action.continuous,
                        passThrough               = isPassThroughAction,
                        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;
            }
 public static int TryFindControls(InputControl control, string path, ref InputControlList <InputControl> matches, int indexInPath = 0)
 {
     return(TryFindControls(control, path, indexInPath, ref matches));
 }
        /// <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];
                            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;
                }
            }
        }
Beispiel #8
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(InputControlList <InputDevice> devices)
        {
            // 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,
            });
        }