// 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); }
/// <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; } } }