Skip to content

Commit c6627af

Browse files
committed
Improve WaitUntil timer precision
1 parent d2da953 commit c6627af

File tree

2 files changed

+126
-88
lines changed

2 files changed

+126
-88
lines changed

src/platform_impl/windows/event_loop.rs

+126-87
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
//! The closure passed to the `execute_in_thread` method takes an `Inserter` that you can use to
1313
//! add a `WindowState` entry to a list of window to be used by the callback.
1414
15-
use winapi::shared::basetsd::DWORD_PTR;
15+
use winapi::shared::basetsd::{DWORD_PTR, LONG_PTR};
1616
use winapi::shared::basetsd::UINT_PTR;
1717
use std::{mem, ptr};
1818
use std::ffi::OsString;
@@ -101,7 +101,6 @@ pub struct WindowState {
101101
pub maximized: bool,
102102
pub resizable: bool,
103103
pub mouse_buttons_down: u32,
104-
pub modal_timer_handle: UINT_PTR
105104
}
106105

107106
impl WindowState {
@@ -191,7 +190,8 @@ impl<T> EventLoop<T> {
191190
event_loop: self,
192191
control_flow: ControlFlow::default(),
193192
runner_state: RunnerState::New,
194-
modal_loop_data: None,
193+
in_modal_loop: false,
194+
modal_redraw_window: self.thread_msg_target,
195195
event_handler: unsafe {
196196
// Transmute used to erase lifetimes.
197197
mem::transmute::<
@@ -214,8 +214,6 @@ impl<T> EventLoop<T> {
214214
}
215215

216216
unsafe {
217-
let timer_handle = winuser::SetTimer(ptr::null_mut(), 0, 0x7FFFFFFF, None);
218-
219217
let mut msg = mem::uninitialized();
220218
let mut msg_unprocessed = false;
221219

@@ -242,16 +240,7 @@ impl<T> EventLoop<T> {
242240
msg_unprocessed = true;
243241
}
244242
ControlFlow::WaitUntil(resume_time) => {
245-
let now = Instant::now();
246-
if now <= resume_time {
247-
let duration = resume_time - now;
248-
winuser::SetTimer(ptr::null_mut(), timer_handle, dur2timeout(duration), None);
249-
if 0 == winuser::GetMessageW(&mut msg, ptr::null_mut(), 0, 0) {
250-
break 'main
251-
}
252-
winuser::SetTimer(ptr::null_mut(), timer_handle, 0x7FFFFFFF, None);
253-
msg_unprocessed = true;
254-
}
243+
wait_until_time_or_msg(resume_time);
255244
},
256245
ControlFlow::Poll => ()
257246
}
@@ -287,15 +276,11 @@ pub(crate) struct EventLoopRunner<T> {
287276
event_loop: *const EventLoop<T>,
288277
control_flow: ControlFlow,
289278
runner_state: RunnerState,
290-
modal_loop_data: Option<ModalLoopData>,
279+
modal_redraw_window: HWND,
280+
in_modal_loop: bool,
291281
event_handler: *mut FnMut(Event<T>, &RootEventLoop<T>, &mut ControlFlow)
292282
}
293283

294-
struct ModalLoopData {
295-
hwnd: HWND,
296-
timer_handle: UINT_PTR
297-
}
298-
299284
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
300285
enum RunnerState {
301286
/// The event loop has just been created, and an `Init` event must be sent.
@@ -364,12 +349,18 @@ impl<T> EventLoopRunner<T> {
364349
}
365350

366351
unsafe fn process_event(&mut self, event: Event<T>) {
367-
// If we're in the middle of a modal loop, only set the timer for zero if it hasn't been
368-
// reset in a prior call to `process_event`.
369-
if let Some(ModalLoopData{hwnd, timer_handle}) = self.modal_loop_data {
370-
if self.runner_state != RunnerState::HandlingEvents {
371-
winuser::SetTimer(hwnd, timer_handle, 0, None);
372-
}
352+
// If we're in the modal loop, we need to have some mechanism for finding when the event
353+
// queue has been cleared so we can call `events_cleared`. Windows doesn't give any utilities
354+
// for doing this, but it DOES guarantee that WM_PAINT will only occur after input events have
355+
// been processed. So, we send WM_PAINT to a dummy window which calls `events_cleared` when
356+
// the events queue has been emptied.
357+
if self.in_modal_loop {
358+
winuser::RedrawWindow(
359+
self.modal_redraw_window,
360+
ptr::null(),
361+
ptr::null_mut(),
362+
winuser::RDW_INTERNALPAINT
363+
);
373364
}
374365

375366
// If new event processing has to be done (i.e. call NewEvents or defer), do it. If we're
@@ -476,6 +467,29 @@ impl<T> EventLoopRunner<T> {
476467
}
477468
}
478469

470+
// Returns true if the wait time was reached, and false if a message must be processed.
471+
unsafe fn wait_until_time_or_msg(wait_until: Instant) -> bool {
472+
let mut msg = mem::uninitialized();
473+
let now = Instant::now();
474+
if now <= wait_until {
475+
// MsgWaitForMultipleObjects tends to overshoot just a little bit. We subtract 1 millisecond
476+
// from the requested time and spinlock for the remainder to compensate for that.
477+
winuser::MsgWaitForMultipleObjects(
478+
0,
479+
ptr::null(),
480+
1,
481+
dur2timeout(wait_until - now).saturating_sub(1),
482+
winuser::QS_ALLINPUT
483+
);
484+
while Instant::now() < wait_until {
485+
if 0 != winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 0) {
486+
return false;
487+
}
488+
}
489+
}
490+
491+
return true;
492+
}
479493
// Implementation taken from https://github.com/rust-lang/rust/blob/db5476571d9b27c862b95c1e64764b0ac8980e23/src/libstd/sys/windows/mod.rs
480494
fn dur2timeout(dur: Duration) -> DWORD {
481495
// Note that a duration is a (u64, u32) (seconds, nanoseconds) pair, and the
@@ -649,7 +663,7 @@ lazy_static! {
649663
fn thread_event_target_window<T>(event_loop_runner: EventLoopRunnerShared<T>) -> (HWND, Sender<T>) {
650664
unsafe {
651665
let window = winuser::CreateWindowExW(
652-
0,
666+
winuser::WS_EX_NOACTIVATE | winuser::WS_EX_TRANSPARENT | winuser::WS_EX_LAYERED,
653667
THREAD_EVENT_TARGET_WINDOW_CLASS.as_ptr(),
654668
ptr::null_mut(),
655669
0,
@@ -660,6 +674,14 @@ fn thread_event_target_window<T>(event_loop_runner: EventLoopRunnerShared<T>) ->
660674
libloaderapi::GetModuleHandleW(ptr::null()),
661675
ptr::null_mut()
662676
);
677+
winuser::SetWindowLongPtrW(
678+
window,
679+
winuser::GWL_STYLE,
680+
// The window technically has to be visible to receive WM_PAINT messages (which are used
681+
// for delivering events during resizes), but it isn't displayed to the user because of
682+
// the LAYERED style.
683+
(winuser::WS_VISIBLE | winuser::WS_POPUP) as LONG_PTR
684+
);
663685

664686
let (tx, rx) = mpsc::channel();
665687

@@ -727,67 +749,20 @@ unsafe extern "system" fn public_window_callback<T>(
727749
let subclass_input = &mut*(subclass_input_ptr as *mut SubclassInput<T>);
728750

729751
match msg {
730-
winuser::WM_SYSCOMMAND => {
731-
{
732-
let mut window_state = subclass_input.window_state.lock();
733-
if window_state.modal_timer_handle == 0 {
734-
window_state.modal_timer_handle = winuser::SetTimer(window, 0, 0x7FFFFFFF, None);
735-
}
736-
}
737-
commctrl::DefSubclassProc(window, msg, wparam, lparam)
738-
}
739752
winuser::WM_ENTERSIZEMOVE => {
740-
let modal_timer_handle = subclass_input.window_state.lock().modal_timer_handle;
741-
if let ELRSharedOption::Runner(runner) = *subclass_input.event_loop_runner.borrow_mut() {
742-
(*runner).modal_loop_data = Some(ModalLoopData {
743-
hwnd: window,
744-
timer_handle: modal_timer_handle
745-
});
753+
let runner = subclass_input.event_loop_runner.borrow_mut();
754+
if let ELRSharedOption::Runner(runner) = *runner {
755+
(*runner).in_modal_loop = true;
746756
}
747-
winuser::SetTimer(window, modal_timer_handle, 0, None);
748757
0
749758
},
750759
winuser::WM_EXITSIZEMOVE => {
751-
let modal_timer_handle = subclass_input.window_state.lock().modal_timer_handle;
752-
if let ELRSharedOption::Runner(runner) = *subclass_input.event_loop_runner.borrow_mut() {
753-
(*runner).modal_loop_data = None;
760+
let runner = subclass_input.event_loop_runner.borrow_mut();
761+
if let ELRSharedOption::Runner(runner) = *runner {
762+
(*runner).in_modal_loop = false;
754763
}
755-
winuser::SetTimer(window, modal_timer_handle, 0x7FFFFFFF, None);
756764
0
757765
},
758-
winuser::WM_TIMER => {
759-
let modal_timer_handle = subclass_input.window_state.lock().modal_timer_handle;
760-
if wparam == modal_timer_handle {
761-
let runner = subclass_input.event_loop_runner.borrow_mut();
762-
if let ELRSharedOption::Runner(runner) = *runner {
763-
let runner = &mut *runner;
764-
if runner.modal_loop_data.is_some() {
765-
runner.events_cleared();
766-
match runner.control_flow {
767-
ControlFlow::Exit => (),
768-
ControlFlow::Wait => {
769-
winuser::SetTimer(window, modal_timer_handle, 0x7FFFFFFF, None);
770-
},
771-
ControlFlow::WaitUntil(resume_time) => {
772-
let now = Instant::now();
773-
let duration = match now <= resume_time {
774-
true => dur2timeout(resume_time - now),
775-
false => 0
776-
};
777-
winuser::SetTimer(window, modal_timer_handle, duration, None);
778-
},
779-
ControlFlow::Poll => {
780-
winuser::SetTimer(window, modal_timer_handle, 0, None);
781-
}
782-
}
783-
784-
runner.new_events();
785-
}
786-
}
787-
}
788-
0
789-
}
790-
791766
winuser::WM_NCCREATE => {
792767
enable_non_client_dpi_scaling(window);
793768
commctrl::DefSubclassProc(window, msg, wparam, lparam)
@@ -804,12 +779,6 @@ unsafe extern "system" fn public_window_callback<T>(
804779

805780
winuser::WM_DESTROY => {
806781
use event::WindowEvent::Destroyed;
807-
{
808-
let window_state = subclass_input.window_state.lock();
809-
if window_state.modal_timer_handle != 0 {
810-
winuser::KillTimer(window, window_state.modal_timer_handle);
811-
}
812-
}
813782
subclass_input.send_event(Event::WindowEvent {
814783
window_id: RootWindowId(WindowId(window)),
815784
event: Destroyed
@@ -1537,6 +1506,76 @@ unsafe extern "system" fn thread_event_target_callback<T>(
15371506
drop(subclass_input);
15381507
0
15391508
},
1509+
// Because WM_PAINT comes after all other messages, we use it during modal loops to detect
1510+
// when the event queue has been emptied. See `process_event` for more details.
1511+
winuser::WM_PAINT => {
1512+
winuser::ValidateRect(window, ptr::null());
1513+
let queue_call_again = || {
1514+
winuser::RedrawWindow(
1515+
window,
1516+
ptr::null(),
1517+
ptr::null_mut(),
1518+
winuser::RDW_INTERNALPAINT
1519+
);
1520+
};
1521+
let in_modal_loop = {
1522+
let runner = subclass_input.event_loop_runner.borrow_mut();
1523+
if let ELRSharedOption::Runner(runner) = *runner {
1524+
(*runner).in_modal_loop
1525+
} else {
1526+
false
1527+
}
1528+
};
1529+
if in_modal_loop {
1530+
let mut msg = mem::uninitialized();
1531+
loop {
1532+
if 0 == winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 0) {
1533+
break;
1534+
}
1535+
// Clear all paint/timer messages from the queue before sending the events cleared message.
1536+
match msg.message {
1537+
// Flush the event queue of WM_PAINT messages.
1538+
winuser::WM_PAINT |
1539+
winuser::WM_TIMER => {
1540+
// Remove the message from the message queue.
1541+
winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 1);
1542+
1543+
if msg.hwnd != window {
1544+
winuser::TranslateMessage(&mut msg);
1545+
winuser::DispatchMessageW(&mut msg);
1546+
}
1547+
},
1548+
// If the message isn't one of those three, it may be handled by the modal
1549+
// loop so we should return control flow to it.
1550+
_ => {
1551+
queue_call_again();
1552+
return 0;
1553+
}
1554+
}
1555+
}
1556+
1557+
let runner = subclass_input.event_loop_runner.borrow_mut();
1558+
if let ELRSharedOption::Runner(runner) = *runner {
1559+
let runner = &mut *runner;
1560+
runner.events_cleared();
1561+
match runner.control_flow {
1562+
// Waiting is handled by the modal loop.
1563+
ControlFlow::Exit |
1564+
ControlFlow::Wait => runner.new_events(),
1565+
ControlFlow::WaitUntil(resume_time) => {
1566+
wait_until_time_or_msg(resume_time);
1567+
runner.new_events();
1568+
queue_call_again();
1569+
},
1570+
ControlFlow::Poll => {
1571+
runner.new_events();
1572+
queue_call_again();
1573+
}
1574+
}
1575+
}
1576+
}
1577+
0
1578+
}
15401579
_ if msg == *USER_EVENT_MSG_ID => {
15411580
if let Ok(event) = subclass_input.user_event_receiver.recv() {
15421581
subclass_input.send_event(Event::UserEvent(event));

src/platform_impl/windows/window.rs

-1
Original file line numberDiff line numberDiff line change
@@ -1023,7 +1023,6 @@ unsafe fn init<T>(
10231023
mouse_in_window: false,
10241024
saved_window_info: None,
10251025
mouse_buttons_down: 0,
1026-
modal_timer_handle: 0
10271026
};
10281027
// Creating a mutex to track the current window state
10291028
Arc::new(Mutex::new(window_state))

0 commit comments

Comments
 (0)