diff --git a/Packages/com.unity.inputsystem/InputSystem/Controls/InputControlPath.cs b/Packages/com.unity.inputsystem/InputSystem/Controls/InputControlPath.cs
index de993f9c65..e545efeab3 100644
--- a/Packages/com.unity.inputsystem/InputSystem/Controls/InputControlPath.cs
+++ b/Packages/com.unity.inputsystem/InputSystem/Controls/InputControlPath.cs
@@ -775,9 +775,7 @@ private static bool MatchesRecursive(ref PathParser parser, InputControl current
// Match current path component against current control.
return parser.current.Matches(currentControl);
}
-
- ////TODO: refactor this to use the new PathParser
-
+
///
/// Recursively match path elements in .
///
@@ -792,134 +790,80 @@ private static TControl MatchControlsRecursive(InputControl control, s
ref InputControlList matches, bool matchMultiple)
where TControl : InputControl
{
- var pathLength = path.Length;
+ if (control == null)
+ throw new ArgumentNullException(nameof(control));
+ if (path == null)
+ throw new ArgumentNullException(nameof(path));
- // Try to get a match. A path spec has three components:
- // "{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 pathLength = path.Length;
+ if (indexInPath < 0 || indexInPath > pathLength)
+ throw new ArgumentOutOfRangeException(nameof(indexInPath));
- var controlIsMatch = true;
+ // Create substring from current index so we can use PathParser for the next component.
+ var subPath = path.Substring(indexInPath);
+ if (subPath.Length == 0)
+ return null;
- // Match by layout.
- if (path[indexInPath] == '<')
+ // Find end of first component in subPath (stop at unescaped '/').
+ var compEnd = 0;
+ while (compEnd < subPath.Length)
{
- ++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)
+ if (subPath[compEnd] == '\\' && compEnd + 1 < subPath.Length)
{
- 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;
- }
+ // Skip escaped char.
+ compEnd += 2;
+ continue;
}
+ if (subPath[compEnd] == '/')
+ break;
+ compEnd++;
}
- // Match by usage.
- while (indexInPath < pathLength && path[indexInPath] == '{' && controlIsMatch)
- {
- ++indexInPath;
+ var componentStr = subPath.Substring(0, compEnd);
- for (var i = 0; i < control.usages.Count; ++i)
- {
- controlIsMatch = MatchPathComponent(control.usages[i], path, ref indexInPath, PathComponentType.Usage);
- if (controlIsMatch)
- break;
- }
- }
+ // Parse the single component.
+ var componentParser = new PathParser(componentStr);
+ if (!componentParser.MoveToNextComponent())
+ return null; // malformed/empty component
+
+ var component = componentParser.current;
- // Match by display name.
- if (indexInPath < pathLength - 1 && controlIsMatch && path[indexInPath] == '#' &&
- path[indexInPath + 1] == '(')
+ // Use ParsedPathComponent.Matches to test if current control satisfies component.
+ if (!component.Matches(control))
+ return null;
+
+ // If there's no more path after this component, we matched.
+ var restStart = compEnd;
+ // If there's a separating '/', skip it for the remainder.
+ if (restStart < subPath.Length && subPath[restStart] == '/')
+ restStart++;
+
+ var remainder = restStart >= subPath.Length ? string.Empty : subPath.Substring(restStart);
+
+ // If remainder is empty, we've reached the end -> success.
+ if (string.IsNullOrEmpty(remainder))
{
- indexInPath += 2;
- controlIsMatch = MatchPathComponent(control.displayName, path, ref indexInPath,
- PathComponentType.DisplayName);
+ if (!(control is TControl match))
+ return null;
+ if (matchMultiple)
+ matches.Add(match);
+ return match;
}
- // Match by name.
- if (indexInPath < pathLength && controlIsMatch && path[indexInPath] != '/')
+ // Otherwise, dive into children or route by usage depending on next char.
+ TControl lastMatch;
+ if (remainder[0] == '{')
{
- // 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);
- }
- }
+ // Usage-based routing from the device root. Pass remainder as a new path starting at 0.
+ lastMatch = MatchByUsageAtDeviceRootRecursive(control.device, remainder, 0, ref matches, matchMultiple);
}
-
- // If we have a match, return it or, if there's children, recurse into them.
- if (controlIsMatch)
+ else
{
- // 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] == '{')
- {
- // 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;
- }
+ // Recurse into children. Pass remainder as a new path starting at 0.
+ lastMatch = MatchChildrenRecursive(control, remainder, 0, ref matches, matchMultiple);
}
- return null;
+ return lastMatch;
}
private static TControl MatchByUsageAtDeviceRootRecursive(InputDevice device, string path, int indexInPath,