Пример #1
0
 static void RegisterLayouts()
 {
     Inputs.RegisterLayout <HandheldARInputDevice>(
         matches: new InputDeviceMatcher()
         .WithInterface(XRUtilities.kXRInterfaceMatchAnyVersion)
         .WithProduct("(ARCore)")
         );
 }
Пример #2
0
        static void RegisterLayouts()
        {
            if (!Api.AtLeast11_0())
            {
                return;
            }

            Inputs.RegisterLayout <HandheldARInputDevice>(
                matches: new InputDeviceMatcher()
                .WithInterface(XRUtilities.InterfaceMatchAnyVersion)
                .WithProduct("(ARKit)")
                );
        }
Пример #3
0
        static void RegisterLayouts()
        {
            if (!Api.platformAndroid || !Api.loaderPresent)
            {
                return;
            }

            Inputs.RegisterLayout <HandheldARInputDevice>(
                matches: new InputDeviceMatcher()
                .WithInterface(XRUtilities.InterfaceMatchAnyVersion)
                .WithProduct("(ARCore)")
                );
        }
Пример #4
0
        public virtual void Setup()
        {
            try
            {
                // Apparently, NUnit is reusing instances :(
                m_KeyInfos = default;

                // Disable input debugger so we don't waste time responding to all the
                // input system activity from the tests.
                #if UNITY_EDITOR
                InputDebuggerWindow.Disable();
                #endif

                runtime = new InputTestRuntime();

                // Push current input system state on stack.
                InputSystem.SaveAndReset(enableRemoting: false, runtime: runtime);

                // Override the editor messing with logic like canRunInBackground and focus and
                // make it behave like in the player.
                #if UNITY_EDITOR
                InputSystem.settings.editorInputBehaviorInPlayMode = InputSettings.EditorInputBehaviorInPlayMode.AllDeviceInputAlwaysGoesToGameView;
                #endif

                // For a [UnityTest] play mode test, we don't want editor updates interfering with the test,
                // so turn them off.
                #if UNITY_EDITOR
                if (Application.isPlaying && IsUnityTest())
                {
                    InputSystem.s_Manager.m_UpdateMask &= ~InputUpdateType.Editor;
                }
                #endif

                // We use native collections in a couple places. We when leak them, we want to know where exactly
                // the allocation came from so enable full leak detection in tests.
                NativeLeakDetection.Mode = NativeLeakDetectionMode.EnabledWithStackTrace;

                // For [UnityTest]s, we need to process input in sync with the player loop. However, InputTestRuntime
                // is divorced from the player loop by virtue of not being tied into NativeInputSystem. Listen
                // for NativeInputSystem.Update here and trigger input processing in our isolated InputSystem.
                // This is irrelevant for normal [Test]s but for [UnityTest]s that run over several frames, it's crucial.
                // NOTE: We're severing the tie the previous InputManager had to NativeInputRuntime here. This means that
                //       device removal events that happen to occur while tests are running will get lost.
                NativeInputRuntime.instance.onUpdate =
                    (InputUpdateType updateType, ref InputEventBuffer buffer) =>
                {
                    if (InputSystem.s_Manager.ShouldRunUpdate(updateType))
                    {
                        InputSystem.Update(updateType);
                    }
                    // We ignore any input coming from native.
                    buffer.Reset();
                };
                NativeInputRuntime.instance.onShouldRunUpdate =
                    updateType => true;

                #if UNITY_EDITOR
                m_OnPlayModeStateChange = OnPlayModeStateChange;
                EditorApplication.playModeStateChanged += m_OnPlayModeStateChange;
                #endif

                // Always want to merge by default
                InputSystem.settings.disableRedundantEventsMerging = false;
            }
            catch (Exception exception)
            {
                Debug.LogError("Failed to set up input system for test " + TestContext.CurrentContext.Test.Name);
                Debug.LogException(exception);
                throw;
            }

            m_Initialized = true;

            if (InputSystem.devices.Count > 0)
            {
                Assert.Fail("Input system should not have devices after reset");
            }
        }
Пример #5
0
        public unsafe void AddActionMap(InputActionMap map)
        {
            Debug.Assert(map != null, "Received null map");

            var actionsInThisMap      = map.m_Actions;
            var bindingsInThisMap     = map.m_Bindings;
            var bindingCountInThisMap = bindingsInThisMap?.Length ?? 0;
            var actionCountInThisMap  = actionsInThisMap?.Length ?? 0;
            var mapIndex = totalMapCount;

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

            // Allocate an initial block of memory. We probably will have to re-allocate once
            // at the end to accommodate interactions and controls added from the map.
            var newMemory = new InputActionState.UnmanagedMemory();

            newMemory.Allocate(
                mapCount: totalMapCount + 1,
                actionCount: totalActionCount + actionCountInThisMap,
                bindingCount: totalBindingCount + bindingCountInThisMap,
                // We reallocate for the following once we know the final count.
                interactionCount: totalInteractionCount,
                compositeCount: totalCompositeCount,
                controlCount: totalControlCount);
            if (memory.isAllocated)
            {
                newMemory.CopyDataFrom(memory);
            }

            ////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     = InputActionState.kInvalidIndex;
            var         currentCompositeIndex            = InputActionState.kInvalidIndex;
            var         currentCompositePartCount        = 0;
            var         currentCompositeActionIndexInMap = InputActionState.kInvalidIndex;
            InputAction currentCompositeAction           = null;
            var         bindingMaskOnThisMap             = map.m_BindingMask;
            var         devicesForThisMap = map.devices;

            // Can't use `using` as we need to use it with `ref`.
            var resolvedControls = new InputControlList <InputControl>(Allocator.Temp);

            // We gather all controls in temporary memory and then move them over into newMemory once
            // we're done resolving.
            try
            {
                for (var n = 0; n < bindingCountInThisMap; ++n)
                {
                    var     bindingStatesPtr  = newMemory.bindingStates;
                    ref var unresolvedBinding = ref bindingsInThisMap[n];
                    var     bindingIndex      = bindingStartIndex + n;
                    var     isComposite       = unresolvedBinding.isComposite;
                    var     isPartOfComposite = !isComposite && unresolvedBinding.isPartOfComposite;
                    var     bindingState      = &bindingStatesPtr[bindingIndex];

                    try
                    {
                        ////TODO: if it's a composite, check if any of the children matches our binding masks (if any) and skip composite if none do

                        var firstControlIndex     = 0; // numControls dictates whether this is a valid index or not.
                        var firstInteractionIndex = InputActionState.kInvalidIndex;
                        var firstProcessorIndex   = InputActionState.kInvalidIndex;
                        var actionIndexForBinding = InputActionState.kInvalidIndex;
                        var partIndex             = InputActionState.kInvalidIndex;

                        var numControls     = 0;
                        var numInteractions = 0;
                        var numProcessors   = 0;

                        // Make sure that if it's part of a composite, we are actually part of a composite.
                        if (isPartOfComposite && currentCompositeBindingIndex == InputActionState.kInvalidIndex)
                        {
                            throw new InvalidOperationException(
                                      $"Binding '{unresolvedBinding}' is marked as being part of a composite but the preceding binding is not a composite");
                        }

                        // Try to find action.
                        //
                        // NOTE: We ignore actions on bindings that are part of composites. We only allow
                        //       actions to be triggered from the composite itself.
                        var         actionIndexInMap = InputActionState.kInvalidIndex;
                        var         actionName       = unresolvedBinding.action;
                        InputAction action           = null;
                        if (!isPartOfComposite)
                        {
                            if (!string.IsNullOrEmpty(actionName))
                            {
                                ////REVIEW: should we fail here if we don't manage to find the action
                                actionIndexInMap = map.FindActionIndex(actionName);
                            }
                            else if (map.m_SingletonAction != null)
                            {
                                // Special-case for singleton actions that don't have names.
                                actionIndexInMap = 0;
                            }

                            if (actionIndexInMap != InputActionState.kInvalidIndex)
                            {
                                action = actionsInThisMap[actionIndexInMap];
                            }
                        }
                        else
                        {
                            actionIndexInMap = currentCompositeActionIndexInMap;
                            action           = currentCompositeAction;
                        }

                        // Determine if the binding is disabled.
                        // Disabled if path is empty.
                        var path = unresolvedBinding.effectivePath;
                        var bindingIsDisabled = string.IsNullOrEmpty(path);

                        // Also, disabled if binding doesn't match with our binding mask (might be empty).
                        bindingIsDisabled |= !isComposite && bindingMask != null &&
                                             !bindingMask.Value.Matches(ref unresolvedBinding,
                                                                        InputBinding.MatchOptions.EmptyGroupMatchesAny);

                        // Also, disabled if binding doesn't match the binding mask on the map (might be empty).
                        bindingIsDisabled |= !isComposite && bindingMaskOnThisMap != null &&
                                             !bindingMaskOnThisMap.Value.Matches(ref unresolvedBinding, InputBinding.MatchOptions.EmptyGroupMatchesAny);

                        // Finally, also disabled if binding doesn't match the binding mask on the action (might be empty).
                        bindingIsDisabled |= !isComposite && action?.m_BindingMask != null &&
                                             !action.m_BindingMask.Value.Matches(ref unresolvedBinding, InputBinding.MatchOptions.EmptyGroupMatchesAny);

                        // If the binding isn't disabled, resolve its controls, processors, and interactions.
                        if (!bindingIsDisabled)
                        {
                            // Instantiate processors.
                            var processorString = unresolvedBinding.effectiveProcessors;
                            if (!string.IsNullOrEmpty(processorString))
                            {
                                // Add processors from binding.
                                firstProcessorIndex = ResolveProcessors(processorString);
                                if (firstProcessorIndex != InputActionState.kInvalidIndex)
                                {
                                    numProcessors = totalProcessorCount - firstProcessorIndex;
                                }
                            }
                            if (action != null && !string.IsNullOrEmpty(action.m_Processors))
                            {
                                // Add processors from action.
                                var index = ResolveProcessors(action.m_Processors);
                                if (index != InputActionState.kInvalidIndex)
                                {
                                    if (firstProcessorIndex == InputActionState.kInvalidIndex)
                                    {
                                        firstProcessorIndex = index;
                                    }
                                    numProcessors += totalProcessorCount - index;
                                }
                            }

                            // Instantiate interactions.
                            var interactionString = unresolvedBinding.effectiveInteractions;
                            if (!string.IsNullOrEmpty(interactionString))
                            {
                                // Add interactions from binding.
                                firstInteractionIndex = ResolveInteractions(interactionString);
                                if (firstInteractionIndex != InputActionState.kInvalidIndex)
                                {
                                    numInteractions = totalInteractionCount - firstInteractionIndex;
                                }
                            }
                            if (action != null && !string.IsNullOrEmpty(action.m_Interactions))
                            {
                                // Add interactions from action.
                                var index = ResolveInteractions(action.m_Interactions);
                                if (index != InputActionState.kInvalidIndex)
                                {
                                    if (firstInteractionIndex == InputActionState.kInvalidIndex)
                                    {
                                        firstInteractionIndex = index;
                                    }
                                    numInteractions += totalInteractionCount - index;
                                }
                            }

                            // If it's the start of a composite chain, create the composite.
                            if (isComposite)
                            {
                                var actionIndexForComposite = actionIndexInMap != InputActionState.kInvalidIndex
                                    ? actionStartIndex + actionIndexInMap
                                    : InputActionState.kInvalidIndex;

                                // 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;
                                currentCompositeAction           = action;
                                currentCompositeActionIndexInMap = actionIndexInMap;

                                *bindingState = new InputActionState.BindingState
                                {
                                    actionIndex = actionIndexForComposite,
                                    compositeOrCompositeBindingIndex = currentCompositeIndex,
                                    processorStartIndex   = firstProcessorIndex,
                                    processorCount        = numProcessors,
                                    interactionCount      = numInteractions,
                                    interactionStartIndex = firstInteractionIndex,
                                    mapIndex    = totalMapCount,
                                    isComposite = true,
                                    // Record where the controls for parts of the composite start.
                                    controlStartIndex = memory.controlCount + resolvedControls.Count,
                                };

                                // 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 (!isPartOfComposite && currentCompositeBindingIndex != InputActionState.kInvalidIndex)
                            {
                                currentCompositePartCount        = 0;
                                currentCompositeBindingIndex     = InputActionState.kInvalidIndex;
                                currentCompositeIndex            = InputActionState.kInvalidIndex;
                                currentCompositeAction           = null;
                                currentCompositeActionIndexInMap = InputActionState.kInvalidIndex;
                            }

                            // Look up controls.
                            //
                            // NOTE: We continuously add controls here to `resolvedControls`. Once we've completed our
                            //       pass over the bindings in the map, `resolvedControls` will have all the controls for
                            //       the current map.
                            firstControlIndex = memory.controlCount + resolvedControls.Count;
                            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 the binding is part of a composite, pass the resolved controls
                        // on to the composite.
                        if (isPartOfComposite && currentCompositeBindingIndex != InputActionState.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 InvalidOperationException(
                                          $"Binding '{unresolvedBinding}' that is part of composite '{composites[currentCompositeIndex]}' is missing a name");
                            }

                            // Give a part index for the
                            partIndex = AssignCompositePartIndex(composites[currentCompositeIndex], unresolvedBinding.name,
                                                                 ref currentCompositePartCount);

                            // Keep track of total number of controls bound in the composite.
                            bindingStatesPtr[currentCompositeBindingIndex].controlCount += numControls;

                            // Force action index on part binding to be same as that of composite.
                            actionIndexForBinding = bindingStatesPtr[currentCompositeBindingIndex].actionIndex;
                        }
                        else if (actionIndexInMap != InputActionState.kInvalidIndex)
                        {
                            actionIndexForBinding = actionStartIndex + actionIndexInMap;
                        }

                        // Store resolved binding.
                        *bindingState = new InputActionState.BindingState
                        {
                            controlStartIndex                = firstControlIndex,
                            controlCount                     = numControls,
                            interactionStartIndex            = firstInteractionIndex,
                            interactionCount                 = numInteractions,
                            processorStartIndex              = firstProcessorIndex,
                            processorCount                   = numProcessors,
                            isPartOfComposite                = unresolvedBinding.isPartOfComposite,
                            partIndex                        = partIndex,
                            actionIndex                      = actionIndexForBinding,
                            compositeOrCompositeBindingIndex = currentCompositeBindingIndex,
                            mapIndex = totalMapCount,
                            wantsInitialStateCheck = action?.wantsInitialStateCheck ?? false
                        };
                    }
                    catch (Exception exception)
                    {
                        Debug.LogError(
                            $"{exception.GetType().Name} while resolving binding '{unresolvedBinding}' in action map '{map}'");
                        Debug.LogException(exception);

                        // Don't swallow exceptions that indicate something is wrong in the code rather than
                        // in the data.
                        if (exception.IsExceptionIndicatingBugInCode())
                        {
                            throw;
                        }
                    }
                }

                // Re-allocate memory to accommodate controls and interaction states. The count for those
                // we only know once we've completed all resolution.
                var controlCountInThisMap = resolvedControls.Count;
                var newTotalControlCount  = memory.controlCount + controlCountInThisMap;
                if (newMemory.interactionCount != totalInteractionCount ||
                    newMemory.compositeCount != totalCompositeCount ||
                    newMemory.controlCount != newTotalControlCount)
                {
                    var finalMemory = new InputActionState.UnmanagedMemory();

                    finalMemory.Allocate(
                        mapCount: newMemory.mapCount,
                        actionCount: newMemory.actionCount,
                        bindingCount: newMemory.bindingCount,
                        controlCount: newTotalControlCount,
                        interactionCount: totalInteractionCount,
                        compositeCount: totalCompositeCount);

                    finalMemory.CopyDataFrom(newMemory);

                    newMemory.Dispose();
                    newMemory = finalMemory;
                }

                // Add controls to array.
                var controlCountInArray = memory.controlCount;
                ArrayHelpers.AppendListWithCapacity(ref controls, ref controlCountInArray, resolvedControls);
                Debug.Assert(controlCountInArray == newTotalControlCount,
                             "Control array should have combined count of old and new controls");

                // Set up control to binding index mapping.
                for (var i = 0; i < bindingCountInThisMap; ++i)
                {
                    var bindingStatesPtr = newMemory.bindingStates;
                    var bindingState     = &bindingStatesPtr[bindingStartIndex + i];
                    var numControls      = bindingState->controlCount;
                    var startIndex       = bindingState->controlStartIndex;
                    for (var n = 0; n < numControls; ++n)
                    {
                        newMemory.controlIndexToBindingIndex[startIndex + n] = bindingStartIndex + i;
                    }
                }

                // Initialize initial interaction states.
                for (var i = memory.interactionCount; i < newMemory.interactionCount; ++i)
                {
                    newMemory.interactionStates[i].phase = InputActionPhase.Waiting;
                }

                // Initialize action data.
                var runningIndexInBindingIndices = memory.bindingCount;
                for (var i = 0; i < actionCountInThisMap; ++i)
                {
                    var action      = actionsInThisMap[i];
                    var actionIndex = actionStartIndex + i;

                    // Correlate action with its trigger state.
                    action.m_ActionIndexInState = actionIndex;

                    // Collect bindings for action.
                    var bindingStartIndexForAction      = runningIndexInBindingIndices;
                    var bindingCountForAction           = 0;
                    var numPossibleConcurrentActuations = 0;

                    for (var n = 0; n < bindingCountInThisMap; ++n)
                    {
                        var bindingIndex = bindingStartIndex + n;
                        var bindingState = &newMemory.bindingStates[bindingIndex];
                        if (bindingState->actionIndex != actionIndex)
                        {
                            continue;
                        }
                        if (bindingState->isPartOfComposite)
                        {
                            continue;
                        }

                        Debug.Assert(bindingIndex <= ushort.MaxValue, "Binding index exceeds limit");
                        newMemory.actionBindingIndices[runningIndexInBindingIndices] = (ushort)bindingIndex;
                        ++runningIndexInBindingIndices;
                        ++bindingCountForAction;

                        // Keep track of how many concurrent actuations we may be seeing on the action so that
                        // we know whether we need to enable conflict resolution or not.
                        if (bindingState->isComposite)
                        {
                            // Composite binding. Actuates as a whole. Check if the composite has successfully
                            // resolved any controls. If so, it adds one possible actuation.
                            if (bindingState->controlCount > 0)
                            {
                                ++numPossibleConcurrentActuations;
                            }
                        }
                        else
                        {
                            // Normal binding. Every successfully resolved control results in one possible actuation.
                            numPossibleConcurrentActuations += bindingState->controlCount;
                        }
                    }
                    Debug.Assert(bindingStartIndexForAction < ushort.MaxValue, "Binding start index on action exceeds limit");
                    Debug.Assert(bindingCountForAction < ushort.MaxValue, "Binding count on action exceeds limit");
                    newMemory.actionBindingIndicesAndCounts[actionIndex * 2]     = (ushort)bindingStartIndexForAction;
                    newMemory.actionBindingIndicesAndCounts[actionIndex * 2 + 1] = (ushort)bindingCountForAction;

                    // See if we may need conflict resolution on this action. Never needed for pass-through actions.
                    // Otherwise, if we have more than one bound control or have several bindings and one of them
                    // is a composite, we enable it.
                    var isPassThroughAction       = action.type == InputActionType.PassThrough;
                    var mayNeedConflictResolution = !isPassThroughAction && numPossibleConcurrentActuations > 1;

                    // Initialize initial trigger state.
                    newMemory.actionStates[actionIndex] =
                        new InputActionState.TriggerState
                    {
                        phase                     = InputActionPhase.Disabled,
                        mapIndex                  = mapIndex,
                        controlIndex              = InputActionState.kInvalidIndex,
                        interactionIndex          = InputActionState.kInvalidIndex,
                        passThrough               = isPassThroughAction,
                        mayNeedConflictResolution = mayNeedConflictResolution,
                    };
                }

                // Store indices for map.
                newMemory.mapIndices[mapIndex] =
                    new InputActionState.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;
                var finalActionMapCount = memory.mapCount;
                ArrayHelpers.AppendWithCapacity(ref maps, ref finalActionMapCount, map, capacityIncrement: 4);
                Debug.Assert(finalActionMapCount == newMemory.mapCount,
                             "Final action map count should match old action map count plus one");

                // As a final act, swap the new memory in.
                memory.Dispose();
                memory = newMemory;
            }