mirror of
https://github.com/rust-windowing/winit.git
synced 2026-09-03 07:10:05 -04:00
Improve WaitUntil timer precision
This commit is contained in:
@@ -99,7 +99,6 @@ pub struct WindowState {
|
|||||||
pub maximized: bool,
|
pub maximized: bool,
|
||||||
pub resizable: bool,
|
pub resizable: bool,
|
||||||
pub mouse_buttons_down: u32,
|
pub mouse_buttons_down: u32,
|
||||||
pub modal_timer_handle: UINT_PTR
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowState {
|
impl WindowState {
|
||||||
@@ -190,7 +189,8 @@ impl<T> EventLoop<T> {
|
|||||||
event_loop: self,
|
event_loop: self,
|
||||||
control_flow: ControlFlow::default(),
|
control_flow: ControlFlow::default(),
|
||||||
runner_state: RunnerState::New,
|
runner_state: RunnerState::New,
|
||||||
modal_loop_data: None,
|
in_modal_loop: false,
|
||||||
|
modal_redraw_window: self.thread_msg_target,
|
||||||
event_handler: unsafe {
|
event_handler: unsafe {
|
||||||
// Transmute used to erase lifetimes.
|
// Transmute used to erase lifetimes.
|
||||||
mem::transmute::<
|
mem::transmute::<
|
||||||
@@ -213,8 +213,6 @@ impl<T> EventLoop<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let timer_handle = winuser::SetTimer(ptr::null_mut(), 0, 0x7FFFFFFF, None);
|
|
||||||
|
|
||||||
let mut msg = mem::uninitialized();
|
let mut msg = mem::uninitialized();
|
||||||
let mut msg_unprocessed = false;
|
let mut msg_unprocessed = false;
|
||||||
|
|
||||||
@@ -241,16 +239,7 @@ impl<T> EventLoop<T> {
|
|||||||
msg_unprocessed = true;
|
msg_unprocessed = true;
|
||||||
}
|
}
|
||||||
ControlFlow::WaitUntil(resume_time) => {
|
ControlFlow::WaitUntil(resume_time) => {
|
||||||
let now = Instant::now();
|
wait_until_time_or_msg(resume_time);
|
||||||
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;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
ControlFlow::Poll => ()
|
ControlFlow::Poll => ()
|
||||||
}
|
}
|
||||||
@@ -286,15 +275,11 @@ pub(crate) struct EventLoopRunner<T> {
|
|||||||
event_loop: *const EventLoop<T>,
|
event_loop: *const EventLoop<T>,
|
||||||
control_flow: ControlFlow,
|
control_flow: ControlFlow,
|
||||||
runner_state: RunnerState,
|
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)
|
event_handler: *mut FnMut(Event<T>, &RootEventLoop<T>, &mut ControlFlow)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ModalLoopData {
|
|
||||||
hwnd: HWND,
|
|
||||||
timer_handle: UINT_PTR
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
enum RunnerState {
|
enum RunnerState {
|
||||||
/// The event loop has just been created, and an `Init` event must be sent.
|
/// The event loop has just been created, and an `Init` event must be sent.
|
||||||
@@ -363,12 +348,18 @@ impl<T> EventLoopRunner<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn process_event(&mut self, event: Event<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
|
// If we're in the modal loop, we need to have some mechanism for finding when the event
|
||||||
// reset in a prior call to `process_event`.
|
// queue has been cleared so we can call `events_cleared`. Windows doesn't give any utilities
|
||||||
if let Some(ModalLoopData{hwnd, timer_handle}) = self.modal_loop_data {
|
// for doing this, but it DOES guarantee that WM_PAINT will only occur after input events have
|
||||||
if self.runner_state != RunnerState::HandlingEvents {
|
// been processed. So, we send WM_PAINT to a dummy window which calls `events_cleared` when
|
||||||
winuser::SetTimer(hwnd, timer_handle, 0, None);
|
// 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
|
// If new event processing has to be done (i.e. call NewEvents or defer), do it. If we're
|
||||||
@@ -475,6 +466,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
|
// Implementation taken from https://github.com/rust-lang/rust/blob/db5476571d9b27c862b95c1e64764b0ac8980e23/src/libstd/sys/windows/mod.rs
|
||||||
fn dur2timeout(dur: Duration) -> DWORD {
|
fn dur2timeout(dur: Duration) -> DWORD {
|
||||||
// Note that a duration is a (u64, u32) (seconds, nanoseconds) pair, and the
|
// Note that a duration is a (u64, u32) (seconds, nanoseconds) pair, and the
|
||||||
@@ -648,7 +662,7 @@ lazy_static! {
|
|||||||
fn thread_event_target_window<T>(event_loop_runner: EventLoopRunnerShared<T>) -> (HWND, Sender<T>) {
|
fn thread_event_target_window<T>(event_loop_runner: EventLoopRunnerShared<T>) -> (HWND, Sender<T>) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let window = winuser::CreateWindowExW(
|
let window = winuser::CreateWindowExW(
|
||||||
0,
|
winuser::WS_EX_NOACTIVATE | winuser::WS_EX_TRANSPARENT | winuser::WS_EX_LAYERED,
|
||||||
THREAD_EVENT_TARGET_WINDOW_CLASS.as_ptr(),
|
THREAD_EVENT_TARGET_WINDOW_CLASS.as_ptr(),
|
||||||
ptr::null_mut(),
|
ptr::null_mut(),
|
||||||
0,
|
0,
|
||||||
@@ -659,6 +673,14 @@ fn thread_event_target_window<T>(event_loop_runner: EventLoopRunnerShared<T>) ->
|
|||||||
libloaderapi::GetModuleHandleW(ptr::null()),
|
libloaderapi::GetModuleHandleW(ptr::null()),
|
||||||
ptr::null_mut()
|
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 _
|
||||||
|
);
|
||||||
|
|
||||||
let (tx, rx) = crossbeam_channel::unbounded();
|
let (tx, rx) = crossbeam_channel::unbounded();
|
||||||
|
|
||||||
@@ -726,67 +748,20 @@ unsafe extern "system" fn public_window_callback<T>(
|
|||||||
let subclass_input = &mut*(subclass_input_ptr as *mut SubclassInput<T>);
|
let subclass_input = &mut*(subclass_input_ptr as *mut SubclassInput<T>);
|
||||||
|
|
||||||
match msg {
|
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 => {
|
winuser::WM_ENTERSIZEMOVE => {
|
||||||
let modal_timer_handle = subclass_input.window_state.lock().modal_timer_handle;
|
let runner = subclass_input.event_loop_runner.borrow_mut();
|
||||||
if let ELRSharedOption::Runner(runner) = *subclass_input.event_loop_runner.borrow_mut() {
|
if let ELRSharedOption::Runner(runner) = *runner {
|
||||||
(*runner).modal_loop_data = Some(ModalLoopData {
|
(*runner).in_modal_loop = true;
|
||||||
hwnd: window,
|
|
||||||
timer_handle: modal_timer_handle
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
winuser::SetTimer(window, modal_timer_handle, 0, None);
|
|
||||||
0
|
0
|
||||||
},
|
},
|
||||||
winuser::WM_EXITSIZEMOVE => {
|
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;
|
|
||||||
}
|
|
||||||
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();
|
let runner = subclass_input.event_loop_runner.borrow_mut();
|
||||||
if let ELRSharedOption::Runner(runner) = *runner {
|
if let ELRSharedOption::Runner(runner) = *runner {
|
||||||
let runner = &mut *runner;
|
(*runner).in_modal_loop = false;
|
||||||
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
|
0
|
||||||
}
|
},
|
||||||
|
|
||||||
winuser::WM_NCCREATE => {
|
winuser::WM_NCCREATE => {
|
||||||
enable_non_client_dpi_scaling(window);
|
enable_non_client_dpi_scaling(window);
|
||||||
commctrl::DefSubclassProc(window, msg, wparam, lparam)
|
commctrl::DefSubclassProc(window, msg, wparam, lparam)
|
||||||
@@ -804,12 +779,6 @@ unsafe extern "system" fn public_window_callback<T>(
|
|||||||
winuser::WM_DESTROY => {
|
winuser::WM_DESTROY => {
|
||||||
use event::WindowEvent::Destroyed;
|
use event::WindowEvent::Destroyed;
|
||||||
ole2::RevokeDragDrop(window);
|
ole2::RevokeDragDrop(window);
|
||||||
{
|
|
||||||
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 {
|
subclass_input.send_event(Event::WindowEvent {
|
||||||
window_id: RootWindowId(WindowId(window)),
|
window_id: RootWindowId(WindowId(window)),
|
||||||
event: Destroyed
|
event: Destroyed
|
||||||
@@ -1520,6 +1489,76 @@ unsafe extern "system" fn thread_event_target_callback<T>(
|
|||||||
drop(subclass_input);
|
drop(subclass_input);
|
||||||
0
|
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 msg == *USER_EVENT_MSG_ID => {
|
||||||
if let Ok(event) = subclass_input.user_event_receiver.recv() {
|
if let Ok(event) = subclass_input.user_event_receiver.recv() {
|
||||||
subclass_input.send_event(Event::UserEvent(event));
|
subclass_input.send_event(Event::UserEvent(event));
|
||||||
|
|||||||
@@ -1061,7 +1061,6 @@ unsafe fn init<T>(
|
|||||||
mouse_in_window: false,
|
mouse_in_window: false,
|
||||||
saved_window_info: None,
|
saved_window_info: None,
|
||||||
mouse_buttons_down: 0,
|
mouse_buttons_down: 0,
|
||||||
modal_timer_handle: 0
|
|
||||||
};
|
};
|
||||||
// Creating a mutex to track the current window state
|
// Creating a mutex to track the current window state
|
||||||
Arc::new(Mutex::new(window_state))
|
Arc::new(Mutex::new(window_state))
|
||||||
|
|||||||
Reference in New Issue
Block a user