public static InputControl[] TryFindControls(InputControl control, string path, int indexInPath = 0) { var matches = new InputControlList <InputControl>(Allocator.Temp); try { TryFindControls(control, path, indexInPath, ref matches); return(matches.ToArray()); } finally { matches.Dispose(); } }
////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, }); }
/// <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 bindingMaskOnThisMap = map.m_BindingMask; var actionsInThisMap = map.m_Actions; 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.Matches(ref unresolvedBinding)) { continue; } // Skip binding if it doesn't match the binding mask on the map (might be empty). if (!bindingMaskOnThisMap.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 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; } // Skip binding if it doesn't match the binding mask on the action (might be empty). if (actionIndex != InputActionMapState.kInvalidIndex && !map.m_Actions[actionIndex].m_BindingMask.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) // 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 = actionIndex, compositeOrCompositeBindingIndex = currentCompositeIndex, processorStartIndex = firstProcessorIndex, processorCount = numProcessors, interactionCount = numInteractions, interactionStartIndex = firstInteractionIndex, 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 && currentCompositeBindingIndex != InputActionMapState.kInvalidIndex) { currentCompositeBindingIndex = InputActionMapState.kInvalidIndex; currentCompositeIndex = InputActionMapState.kInvalidIndex; } // Look up controls. var firstControlIndex = totalControlCount; var numControls = InputSystem.FindControls(path, ref resolvedControls); if (numControls > 0) { resolvedControls.AppendTo(ref controls, ref totalControlCount); resolvedControls.Clear(); } // 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, actionIndex = actionIndex, compositeOrCompositeBindingIndex = currentCompositeBindingIndex, mapIndex = totalMapCount, }; // If the binding is part of a composite, pass the resolve controls // on to the composite. if (unresolvedBinding.isPartOfComposite && currentCompositeBindingIndex != 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]); } } } 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; } } }
/// <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; ArrayHelpers.GrowWithCapacity(ref bindingStates, ref totalBindingCount, bindingCountInThisMap); ////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; var isComposite = unresolvedBinding.isComposite; ////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. 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 (!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: 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 (!isComposite && 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; } } // If it's the start of a composite chain, create the composite. if (unresolvedBinding.isComposite) { // 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]; 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 (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; var controlIndexToBindingIndexCount = controlStartIndex; ArrayHelpers.GrowWithCapacity(ref controlIndexToBindingIndex, ref controlIndexToBindingIndexCount, 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.GrowWithCapacity(ref actionStates, ref totalActionCount, actionCountInThisMap); for (var i = 0; i < actionCountInThisMap; ++i) { actionStates[actionStartIndex + i].mapIndex = mapIndex; actionStates[actionStartIndex + i].controlIndex = InputActionMapState.kInvalidIndex; } } }