Skip to content

Commit

Permalink
Windows: Increase wait timer resolution (#2007)
Browse files Browse the repository at this point in the history
Windows: Increase wait timer resolution for more accurate timing when using `WaitUntil`.
  • Loading branch information
msiglreith committed Nov 2, 2021
1 parent 5f4df54 commit cfbe846
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -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)

Expand Down
2 changes: 2 additions & 0 deletions Cargo.toml
Expand Up @@ -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]
Expand Down
36 changes: 31 additions & 5 deletions src/platform_impl/windows/event_loop.rs
Expand Up @@ -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,
},
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit cfbe846

Please sign in to comment.