public void State_StateChangeMonitorTimeout_CanBeAddedFromMonitorCallback()
    {
        var gamepad = InputSystem.AddDevice <Gamepad>();

        var monitorFired = false;
        var timeoutFired = false;

        IInputStateChangeMonitor monitor = null;

        monitor = InputSystem.AddStateChangeMonitor(gamepad.leftStick,
                                                    (control, time, eventPtr, monitorIndex) =>
        {
            Assert.That(!monitorFired);
            monitorFired = true;
            InputSystem.AddStateChangeMonitorTimeout(gamepad.leftStick, monitor,
                                                     runtime.currentTime + 1);
        }, timerExpiredCallback:
                                                    (control, time, monitorIndex, timerIndex) =>
        {
            Assert.That(!timeoutFired);
            timeoutFired = true;
        });

        // Trigger monitor callback.
        InputSystem.QueueStateEvent(gamepad, new GamepadState {
            leftStick = Vector2.one
        });
        InputSystem.Update();

        Assert.That(monitorFired);

        // Expire timer.
        runtime.currentTime += 2;
        InputSystem.Update();

        Assert.That(timeoutFired);
    }
    public void State_CanWaitForStateChangeWithinGivenAmountOfTime()
    {
        var gamepad = InputSystem.AddDevice <Gamepad>();

        var          monitorFired       = false;
        var          timeoutFired       = false;
        double?      receivedTime       = null;
        int?         receivedTimerIndex = null;
        InputControl receivedControl    = null;

        var monitor = InputSystem.AddStateChangeMonitor(gamepad.leftStick,
                                                        (control, time, eventPtr, monitorIndex) =>
        {
            Assert.That(!monitorFired);
            monitorFired = true;
        }, timerExpiredCallback:
                                                        (control, time, monitorIndex, timerIndex) =>
        {
            Assert.That(!timeoutFired);
            timeoutFired       = true;
            receivedTime       = time;
            receivedTimerIndex = timerIndex;
            receivedControl    = control;
        });

        // Add and immediately expire timeout.
        InputSystem.AddStateChangeMonitorTimeout(gamepad.leftStick, monitor, runtime.currentTime + 1,
                                                 timerIndex: 1234);
        runtime.currentTime += 2;
        InputSystem.Update();

        Assert.That(timeoutFired);
        Assert.That(!monitorFired);
        Assert.That(receivedTimerIndex.Value, Is.EqualTo(1234));
        Assert.That(receivedTime.Value, Is.EqualTo(runtime.currentTime).Within(0.00001));
        Assert.That(receivedControl, Is.SameAs(gamepad.leftStick));

        timeoutFired       = false;
        receivedTimerIndex = null;
        receivedTime       = null;

        // Add timeout and obsolete it by state change. Then advance past timeout time
        // and make sure we *don't* get a notification.
        InputSystem.AddStateChangeMonitorTimeout(gamepad.leftStick, monitor, runtime.currentTime + 1,
                                                 timerIndex: 4321);
        InputSystem.QueueStateEvent(gamepad, new GamepadState {
            leftStick = Vector2.one
        });
        InputSystem.Update();

        Assert.That(monitorFired);
        Assert.That(!timeoutFired);

        runtime.currentTime += 2;
        InputSystem.Update();

        Assert.That(!timeoutFired);

        // Add and remove timeout. Then advance past timeout time and make sure we *don't*
        // get a notification.
        InputSystem.AddStateChangeMonitorTimeout(gamepad.leftStick, monitor, runtime.currentTime + 1,
                                                 timerIndex: 1423);
        InputSystem.RemoveStateChangeMonitorTimeout(monitor, timerIndex: 1423);
        InputSystem.QueueStateEvent(gamepad, new GamepadState {
            leftStick = Vector2.one
        });
        InputSystem.Update();

        Assert.That(!timeoutFired);
    }