Skip to content

Fix low polling rate precision issues in WinInput.cs #5

@ModsTag

Description

@ModsTag

thank gemini help to translate 👍 , but more more more error 👎 , and add more is not my think content 👎👎👎
// in 1 hour, deepseek 👍👍👍
I've put all the descriptions in. If you don't understand, maybe you can take a look inside.


all description in ai.zip

Hi there,

I discovered an issue in WinInput.cs at line 51 where the Thread.Sleep() call causes polling rate inaccuracies when the PollingRate is set below 500.

Problem

Thread.Sleep() on Windows has a default resolution of approximately 15.625ms (64Hz). When PollingRate is low, the actual execution frequency is limited to around 64 times per second, preventing the intended polling intervals from being achieved.

Proposed Solutions

I've identified two potential approaches to resolve this issue:

Option 1: System-Wide Timer Resolution (Using winmm.dll)

This approach increases system timer resolution temporarily for more precise sleep intervals.

public static class WinInput {
    [DllImport("winmm.dll")]
    private static extern uint timeBeginPeriod(int period);
    
    [DllImport("winmm.dll")]
    private static extern uint timeEndPeriod(int period);
    
    public static void StartPolling(PollingRate rate) {
        // At line 51:
        timeBeginPeriod(2);  // Set precision to 2ms
        Thread.Sleep(1);
        timeEndPeriod(2);    // Restore default precision
    }
}

Pros:

  • Simple implementation
  • Works on all Windows versions

Cons:

  • Affects entire system timer resolution
  • May increase power consumption

Option 2: High-Resolution Waitable Timer (Using kernel32.dll)

This approach uses Windows' high-resolution waitable timers introduced in Windows 10 (1803/RS4).

public static class HighPrecisionTimer
{
    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern IntPtr CreateWaitableTimerEx(
        IntPtr lpTimerAttributes,
        string lpTimerName,
        uint dwFlags,
        uint dwDesiredAccess);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool SetWaitableTimerEx(
        IntPtr hTimer,
        [In] ref long lpDueTime,
        int lPeriod,
        IntPtr pfnCompletionRoutine,
        IntPtr lpArgToCompletionRoutine,
        IntPtr WakeContext,
        uint lTolerableDelay);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);

    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool CloseHandle(IntPtr hObject);

    private const uint SYNCHRONIZE = 0x00100000;
    private const uint TIMER_MODIFY_STATE = 0x0002;
    private const uint CREATE_WAITABLE_TIMER_HIGH_RESOLUTION = 0x00000002;
    private const uint INFINITE = 0xFFFFFFFF;

    public static void WaitSleep(long tick)
    {
        IntPtr hTimer = CreateWaitableTimerEx(
            IntPtr.Zero, 
            null, 
            CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, 
            SYNCHRONIZE | TIMER_MODIFY_STATE);

        if (hTimer != IntPtr.Zero)
        {
            long dueTime = -tick;

            if (SetWaitableTimerEx(hTimer, ref dueTime, 0, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, 0))
            {
                WaitForSingleObject(hTimer, INFINITE);
            }

            CloseHandle(hTimer);
        }
    }
}
public static class WinInput {
    [DllImport("winmm.dll")]
    private static extern uint timeBeginPeriod(int period);
    
    [DllImport("winmm.dll")]
    private static extern uint timeEndPeriod(int period);
    
    public static void StartPolling(PollingRate rate) {
        // At line 51:
        HighPrecisionTimer.WaitSleep(1 * 10000000);  // 10,000,000 = 1 second in file time units
    }
}

Pros:

  • Affects only the current handle/process
  • More precise and efficient

Cons:

  • Requires Windows 10 version 1803 (RS4) or later
  • Behavior may vary depending on system power management settings

Additional Suggestion

I also noticed that at line 53, there's a busy-wait loop without Thread.Yield(). Adding Thread.Yield() could help reduce CPU usage:

Thread.Yield();  // Add this in the busy-wait loop

This may help lower CPU consumption during polling (though I'm not 100% certain of the impact).

Would appreciate any feedback or suggestions on which approach aligns best with the project's goals.

Thank you for your time and for maintaining this great project!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions