Skip to content

Commit

Permalink
Improve WaitUntil timer precision
Browse files Browse the repository at this point in the history
  • Loading branch information
Osspial committed Aug 27, 2018
1 parent d2da953 commit c6627af
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 88 deletions.
213 changes: 126 additions & 87 deletions src/platform_impl/windows/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
//! The closure passed to the `execute_in_thread` method takes an `Inserter` that you can use to
//! add a `WindowState` entry to a list of window to be used by the callback.

use winapi::shared::basetsd::DWORD_PTR;
use winapi::shared::basetsd::{DWORD_PTR, LONG_PTR};
use winapi::shared::basetsd::UINT_PTR;
use std::{mem, ptr};
use std::ffi::OsString;
Expand Down Expand Up @@ -101,7 +101,6 @@ pub struct WindowState {
pub maximized: bool,
pub resizable: bool,
pub mouse_buttons_down: u32,
pub modal_timer_handle: UINT_PTR
}

impl WindowState {
Expand Down Expand Up @@ -191,7 +190,8 @@ impl<T> EventLoop<T> {
event_loop: self,
control_flow: ControlFlow::default(),
runner_state: RunnerState::New,
modal_loop_data: None,
in_modal_loop: false,
modal_redraw_window: self.thread_msg_target,
event_handler: unsafe {
// Transmute used to erase lifetimes.
mem::transmute::<
Expand All @@ -214,8 +214,6 @@ impl<T> EventLoop<T> {
}

unsafe {
let timer_handle = winuser::SetTimer(ptr::null_mut(), 0, 0x7FFFFFFF, None);

let mut msg = mem::uninitialized();
let mut msg_unprocessed = false;

Expand All @@ -242,16 +240,7 @@ impl<T> EventLoop<T> {
msg_unprocessed = true;
}
ControlFlow::WaitUntil(resume_time) => {
let now = Instant::now();
if now <= resume_time {
let duration = resume_time - now;
winuser::SetTimer(ptr::null_mut(), timer_handle, dur2timeout(duration), None);
if 0 == winuser::GetMessageW(&mut msg, ptr::null_mut(), 0, 0) {
break 'main
}
winuser::SetTimer(ptr::null_mut(), timer_handle, 0x7FFFFFFF, None);
msg_unprocessed = true;
}
wait_until_time_or_msg(resume_time);
},
ControlFlow::Poll => ()
}
Expand Down Expand Up @@ -287,15 +276,11 @@ pub(crate) struct EventLoopRunner<T> {
event_loop: *const EventLoop<T>,
control_flow: ControlFlow,
runner_state: RunnerState,
modal_loop_data: Option<ModalLoopData>,
modal_redraw_window: HWND,
in_modal_loop: bool,
event_handler: *mut FnMut(Event<T>, &RootEventLoop<T>, &mut ControlFlow)
}

struct ModalLoopData {
hwnd: HWND,
timer_handle: UINT_PTR
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum RunnerState {
/// The event loop has just been created, and an `Init` event must be sent.
Expand Down Expand Up @@ -364,12 +349,18 @@ impl<T> EventLoopRunner<T> {
}

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

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

// Returns true if the wait time was reached, and false if a message must be processed.
unsafe fn wait_until_time_or_msg(wait_until: Instant) -> bool {
let mut msg = mem::uninitialized();
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.
winuser::MsgWaitForMultipleObjects(
0,
ptr::null(),
1,
dur2timeout(wait_until - now).saturating_sub(1),
winuser::QS_ALLINPUT
);
while Instant::now() < wait_until {
if 0 != winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 0) {
return false;
}
}
}

return true;
}
// Implementation taken from https://github.com/rust-lang/rust/blob/db5476571d9b27c862b95c1e64764b0ac8980e23/src/libstd/sys/windows/mod.rs
fn dur2timeout(dur: Duration) -> DWORD {
// Note that a duration is a (u64, u32) (seconds, nanoseconds) pair, and the
Expand Down Expand Up @@ -649,7 +663,7 @@ lazy_static! {
fn thread_event_target_window<T>(event_loop_runner: EventLoopRunnerShared<T>) -> (HWND, Sender<T>) {
unsafe {
let window = winuser::CreateWindowExW(
0,
winuser::WS_EX_NOACTIVATE | winuser::WS_EX_TRANSPARENT | winuser::WS_EX_LAYERED,
THREAD_EVENT_TARGET_WINDOW_CLASS.as_ptr(),
ptr::null_mut(),
0,
Expand All @@ -660,6 +674,14 @@ fn thread_event_target_window<T>(event_loop_runner: EventLoopRunnerShared<T>) ->
libloaderapi::GetModuleHandleW(ptr::null()),
ptr::null_mut()
);
winuser::SetWindowLongPtrW(
window,
winuser::GWL_STYLE,
// The window technically has to be visible to receive WM_PAINT messages (which are used
// for delivering events during resizes), but it isn't displayed to the user because of
// the LAYERED style.
(winuser::WS_VISIBLE | winuser::WS_POPUP) as LONG_PTR
);

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

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

match msg {
winuser::WM_SYSCOMMAND => {
{
let mut window_state = subclass_input.window_state.lock();
if window_state.modal_timer_handle == 0 {
window_state.modal_timer_handle = winuser::SetTimer(window, 0, 0x7FFFFFFF, None);
}
}
commctrl::DefSubclassProc(window, msg, wparam, lparam)
}
winuser::WM_ENTERSIZEMOVE => {
let modal_timer_handle = subclass_input.window_state.lock().modal_timer_handle;
if let ELRSharedOption::Runner(runner) = *subclass_input.event_loop_runner.borrow_mut() {
(*runner).modal_loop_data = Some(ModalLoopData {
hwnd: window,
timer_handle: modal_timer_handle
});
let runner = subclass_input.event_loop_runner.borrow_mut();
if let ELRSharedOption::Runner(runner) = *runner {
(*runner).in_modal_loop = true;
}
winuser::SetTimer(window, modal_timer_handle, 0, None);
0
},
winuser::WM_EXITSIZEMOVE => {
let modal_timer_handle = subclass_input.window_state.lock().modal_timer_handle;
if let ELRSharedOption::Runner(runner) = *subclass_input.event_loop_runner.borrow_mut() {
(*runner).modal_loop_data = None;
let runner = subclass_input.event_loop_runner.borrow_mut();
if let ELRSharedOption::Runner(runner) = *runner {
(*runner).in_modal_loop = false;
}
winuser::SetTimer(window, modal_timer_handle, 0x7FFFFFFF, None);
0
},
winuser::WM_TIMER => {
let modal_timer_handle = subclass_input.window_state.lock().modal_timer_handle;
if wparam == modal_timer_handle {
let runner = subclass_input.event_loop_runner.borrow_mut();
if let ELRSharedOption::Runner(runner) = *runner {
let runner = &mut *runner;
if runner.modal_loop_data.is_some() {
runner.events_cleared();
match runner.control_flow {
ControlFlow::Exit => (),
ControlFlow::Wait => {
winuser::SetTimer(window, modal_timer_handle, 0x7FFFFFFF, None);
},
ControlFlow::WaitUntil(resume_time) => {
let now = Instant::now();
let duration = match now <= resume_time {
true => dur2timeout(resume_time - now),
false => 0
};
winuser::SetTimer(window, modal_timer_handle, duration, None);
},
ControlFlow::Poll => {
winuser::SetTimer(window, modal_timer_handle, 0, None);
}
}

runner.new_events();
}
}
}
0
}

winuser::WM_NCCREATE => {
enable_non_client_dpi_scaling(window);
commctrl::DefSubclassProc(window, msg, wparam, lparam)
Expand All @@ -804,12 +779,6 @@ unsafe extern "system" fn public_window_callback<T>(

winuser::WM_DESTROY => {
use event::WindowEvent::Destroyed;
{
let window_state = subclass_input.window_state.lock();
if window_state.modal_timer_handle != 0 {
winuser::KillTimer(window, window_state.modal_timer_handle);
}
}
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: Destroyed
Expand Down Expand Up @@ -1537,6 +1506,76 @@ unsafe extern "system" fn thread_event_target_callback<T>(
drop(subclass_input);
0
},
// Because WM_PAINT comes after all other messages, we use it during modal loops to detect
// when the event queue has been emptied. See `process_event` for more details.
winuser::WM_PAINT => {
winuser::ValidateRect(window, ptr::null());
let queue_call_again = || {
winuser::RedrawWindow(
window,
ptr::null(),
ptr::null_mut(),
winuser::RDW_INTERNALPAINT
);
};
let in_modal_loop = {
let runner = subclass_input.event_loop_runner.borrow_mut();
if let ELRSharedOption::Runner(runner) = *runner {
(*runner).in_modal_loop
} else {
false
}
};
if in_modal_loop {
let mut msg = mem::uninitialized();
loop {
if 0 == winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 0) {
break;
}
// Clear all paint/timer messages from the queue before sending the events cleared message.
match msg.message {
// Flush the event queue of WM_PAINT messages.
winuser::WM_PAINT |
winuser::WM_TIMER => {
// Remove the message from the message queue.
winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 1);

if msg.hwnd != window {
winuser::TranslateMessage(&mut msg);
winuser::DispatchMessageW(&mut msg);
}
},
// If the message isn't one of those three, it may be handled by the modal
// loop so we should return control flow to it.
_ => {
queue_call_again();
return 0;
}
}
}

let runner = subclass_input.event_loop_runner.borrow_mut();
if let ELRSharedOption::Runner(runner) = *runner {
let runner = &mut *runner;
runner.events_cleared();
match runner.control_flow {
// Waiting is handled by the modal loop.
ControlFlow::Exit |
ControlFlow::Wait => runner.new_events(),
ControlFlow::WaitUntil(resume_time) => {
wait_until_time_or_msg(resume_time);
runner.new_events();
queue_call_again();
},
ControlFlow::Poll => {
runner.new_events();
queue_call_again();
}
}
}
}
0
}
_ if msg == *USER_EVENT_MSG_ID => {
if let Ok(event) = subclass_input.user_event_receiver.recv() {
subclass_input.send_event(Event::UserEvent(event));
Expand Down
1 change: 0 additions & 1 deletion src/platform_impl/windows/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1023,7 +1023,6 @@ unsafe fn init<T>(
mouse_in_window: false,
saved_window_info: None,
mouse_buttons_down: 0,
modal_timer_handle: 0
};
// Creating a mutex to track the current window state
Arc::new(Mutex::new(window_state))
Expand Down

0 comments on commit c6627af

Please sign in to comment.