예제 #1
0
        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($"Invalid path spec '{path}'; trailing '{{'");
            }

            TControl lastMatch = null;

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

                // Match usage against 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);
        }
예제 #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
        ////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.
                    if (!(control is TControl match))
                    {
                        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.
                        if (!(control is TControl match))
                        {
                            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);
        }