Windows: Increase wait timer resolution (#2007)

Windows: Increase wait timer resolution for more accurate timing when using `WaitUntil`.
This commit is contained in:
Markus Siglreithmaier 2021-11-02 21:51:39 +01:00 committed by GitHub
parent 5f4df54895
commit cfbe8462cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 34 additions and 5 deletions

View File

@ -19,6 +19,7 @@
- On X11, if RANDR based scale factor is higher than 20 reset it to 1
- On Wayland, add an enabled-by-default feature called `wayland-dlopen` so users can opt out of using `dlopen` to load system libraries.
- **Breaking:** On Android, bump `ndk` and `ndk-glue` to 0.4.
- On Windows, increase wait timer resolution for more accurate timing when using `WaitUntil`.
# 0.25.0 (2021-05-15)

View File

@ -81,6 +81,8 @@ features = [
"wingdi",
"winnt",
"winuser",
"mmsystem",
"timeapi"
]
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies]

View File

@ -25,7 +25,7 @@ use winapi::{
windowsx, winerror,
},
um::{
libloaderapi, ole2, processthreadsapi, winbase,
libloaderapi, mmsystem, ole2, processthreadsapi, timeapi, winbase,
winnt::{HANDLE, LONG, LPCSTR, SHORT},
winuser,
},
@ -324,6 +324,22 @@ fn get_wait_thread_id() -> DWORD {
}
}
lazy_static! {
static ref WAIT_PERIOD_MIN: Option<UINT> = unsafe {
let mut caps = mmsystem::TIMECAPS {
wPeriodMin: 0,
wPeriodMax: 0,
};
if timeapi::timeGetDevCaps(&mut caps, mem::size_of::<mmsystem::TIMECAPS>() as _)
== mmsystem::TIMERR_NOERROR
{
Some(caps.wPeriodMin)
} else {
None
}
};
}
fn wait_thread(parent_thread_id: DWORD, msg_window_id: HWND) {
unsafe {
let mut msg: winuser::MSG;
@ -366,16 +382,26 @@ fn wait_thread(parent_thread_id: DWORD, msg_window_id: HWND) {
if let Some(wait_until) = wait_until_opt {
let now = Instant::now();
if now < wait_until {
// MsgWaitForMultipleObjects tends to overshoot just a little bit. We subtract
// 1 millisecond from the requested time and spinlock for the remainder to
// compensate for that.
// Windows' scheduler has a default accuracy of several ms. This isn't good enough for
// `WaitUntil`, so we request the Windows scheduler to use a higher accuracy if possible.
// If we couldn't query the timer capabilities, then we use the default resolution.
if let Some(period) = *WAIT_PERIOD_MIN {
timeapi::timeBeginPeriod(period);
}
// `MsgWaitForMultipleObjects` is bound by the granularity of the scheduler period.
// Because of this, we try to reduce the requested time just enough to undershoot `wait_until`
// by the smallest amount possible, and then we busy loop for the remaining time inside the
// NewEvents message handler.
let resume_reason = winuser::MsgWaitForMultipleObjectsEx(
0,
ptr::null(),
dur2timeout(wait_until - now).saturating_sub(1),
dur2timeout(wait_until - now).saturating_sub(WAIT_PERIOD_MIN.unwrap_or(1)),
winuser::QS_ALLEVENTS,
winuser::MWMO_INPUTAVAILABLE,
);
if let Some(period) = *WAIT_PERIOD_MIN {
timeapi::timeEndPeriod(period);
}
if resume_reason == winerror::WAIT_TIMEOUT {
winuser::PostMessageW(msg_window_id, *PROCESS_NEW_EVENTS_MSG_ID, 0, 0);
wait_until_opt = None;