// Perform a search for controls starting with the given control as root and matching
        // the given path from the given position. Puts all matching controls on the list and
        // returns the number of controls that have been matched.
        //
        // Does not tap 'path' strings of controls so we don't create a bunch of
        // string objects while feeling our way down the hierarchy.
        //
        // Matching is case-insensitive.
        //
        // NOTE: Does not allocate!
        public static int TryFindControls(InputControl control, string path, int indexInPath,
                                          List <InputControl> matches)
        {
            var wrapper = new ArrayOrListWrapper <InputControl>(matches);

            return(TryFindControls(control, path, indexInPath, ref wrapper));
        }
        // Return the first control that matches the given path.
        //
        // NOTE: Does not allocate!
        public static InputControl TryFindControl(InputControl control, string path, int indexInPath = 0)
        {
            if (control == null)
            {
                throw new ArgumentNullException("control");
            }
            if (path == null)
            {
                throw new ArgumentNullException("path");
            }

            if (indexInPath == 0 && path[0] == '/')
            {
                ++indexInPath;
            }

            var none = new ArrayOrListWrapper <InputControl>();

            return(MatchControlsRecursive(control, path, indexInPath, ref none));
        }
        internal static int TryFindControls(InputControl control, string path, int indexInPath,
                                            ref ArrayOrListWrapper <InputControl> matches)
        {
            if (control == null)
            {
                throw new ArgumentNullException("control");
            }
            if (path == null)
            {
                throw new ArgumentNullException("path");
            }

            if (indexInPath == 0 && path[0] == '/')
            {
                ++indexInPath;
            }

            var countBefore = matches.count;

            MatchControlsRecursive(control, path, indexInPath, ref matches);
            return(matches.count - countBefore);
        }
        private static InputControl MatchChildrenRecursive(InputControl control, string path, int indexInPath,
                                                           ref ArrayOrListWrapper <InputControl> matches)
        {
            var          childCount           = control.m_ChildrenReadOnly.Count;
            InputControl lastMatch            = null;
            var          pathCanMatchMultiple = PathComponentCanYieldMultipleMatches(path, indexInPath);

            for (var i = 0; i < childCount; ++i)
            {
                var child      = control.m_ChildrenReadOnly[i];
                var childMatch = MatchControlsRecursive(child, path, indexInPath, ref matches);

                if (childMatch == null)
                {
                    continue;
                }

                // If the child matched something an there's no wildcards in the child
                // portion of the path, we can stop searching.
                if (!pathCanMatchMultiple)
                {
                    return(childMatch);
                }

                // If we are only looking for the first match and a child matched,
                // we can also stop.
                if (matches.isNull)
                {
                    return(childMatch);
                }

                // Otherwise we have to go hunting through the hierarchy in case there are
                // more matches.
                lastMatch = childMatch;
            }

            return(lastMatch);
        }
        private static InputControl MatchByUsageAtDeviceRootRecursive(InputDevice device, string path, int indexInPath,
                                                                      ref ArrayOrListWrapper <InputControl> matches)
        {
            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));
            }

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

                    // 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 && matches.isNull)
                    {
                        break;
                    }
                }
                else
                {
                    lastMatch = controlMatchedByUsage;
                    if (!matches.isNull)
                    {
                        matches.Add(controlMatchedByUsage);
                    }
                    else
                    {
                        // Only looking for single match and we have one.
                        break;
                    }
                }
            }

            return(lastMatch);
        }
        ////TODO: refactor this to use the new PathParser

        private static InputControl MatchControlsRecursive(InputControl control, string path, int indexInPath, ref ArrayOrListWrapper <InputControl> matches)
        {
            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.

            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)
                {
                    if (!matches.isNull)
                    {
                        matches.Add(control);
                    }
                    return(control);
                }

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

                    // Silently accept trailing slashes.
                    if (indexInPath == pathLength)
                    {
                        if (!matches.isNull)
                        {
                            matches.Add(control);
                        }
                        return(control);
                    }

                    // See if we want to match children by usage or by name.
                    InputControl lastMatch = null;
                    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);
                    }
                    else
                    {
                        // Go through children and see what we can match.
                        lastMatch = MatchChildrenRecursive(control, path, indexInPath, ref matches);
                    }

                    return(lastMatch);
                }
            }

            return(null);
        }
Beispiel #7
0
        /// <summary>
        /// Resolve and add all bindings and actions from the given map.
        /// </summary>
        /// <param name="map"></param>
        /// <exception cref="Exception"></exception>
        public void AddActionMap(InputActionMap map)
        {
            Debug.Assert(map != null);
            Debug.Assert(map.m_MapIndex == InputActionMapState.kInvalidIndex);

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

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

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

            ////TODO: make sure composite objects get all the bindings they need
            ////TODO: handle case where we have bindings resolving to the same control
            ////      (not so clear cut what to do there; each binding may have a different interaction setup, for example)
            var currentCompositeIndex = InputActionMapState.kInvalidIndex;
            var actionsInThisMap      = map.m_Actions;
            var actionCountInThisMap  = actionsInThisMap != null ? actionsInThisMap.Length : 0;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            ArrayHelpers.AppendWithCapacity(ref mapIndices, ref totalMapCount, new InputActionMapState.ActionMapIndices
            {
                actionStartIndex      = actionStartIndex,
                actionCount           = actionCountInThisMap,
                controlStartIndex     = controlStartIndex,
                controlCount          = controlCountInThisMap,
                bindingStartIndex     = bindingStartIndex,
                bindingCount          = bindingCountInThisMap,
                interactionStartIndex = interactionStartIndex,
                interactionCount      = totalInteractionCount - interactionStartIndex,
                processorStartIndex   = processorStartIndex,
                processorCount        = totalProcessorCount - processorStartIndex,
                compositeStartIndex   = compositeStartIndex,
                compositeCount        = totalCompositeCount - compositeStartIndex,
            });
            map.m_MapIndex = mapIndex;

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

                ArrayHelpers.GrowBy(ref actionStates, actionCountInThisMap);
                totalActionCount += actionCountInThisMap;
                for (var i = 0; i < actionCountInThisMap; ++i)
                {
                    actionStates[i].mapIndex = mapIndex;
                }
            }
        }