From 420840278b5d708e34b10194aba02ce37e9ac7bf Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Sun, 18 Jun 2023 11:42:57 +0100 Subject: [PATCH] Windows: Implement EventLoopExtPumpEvents and EventLoopExtRunOnDemand A surprising amount of work was required to enable these extensions on Windows. I had originally assumed that pump_events was going to be very similar to run except would use PeekMessageW instead of GetMessageW to avoid blocking the external loop but I found the Windows backend broke several assumptions I had. Overall I think these changes can hopefully be considered a quite a significant simplification (I think it's a net deletion of a fair amount of code) and I think it also helps bring it into slightly closer alignment with other backends too Key changes: - I have removed the `wait_thread` that was a fairly fiddly way of handling `ControlFlow::WaitUntil` timeouts in favor of using `SetTimer` which works with the same messages picked up by `GetMessage` and `PeekMessage`. - I have removed the ordering guarantees between `MainEventsCleared`, `RedrawRequested` and `RedrawEventsCleared` events due to the complexity in maintaining this artificial ordering, which is already not supported consistently across backends anyway (in particular this ordering already isn't compatible with how MacOS / iOS work). - `RedrawRequested` events are now directly dispatched via `WM_PAINT` messages - comparable to how `RedrawRequested` is dispatched via `drawRect` in the MacOS backend. - I have re-worked how `NewEvents`, `MainEventsCleared`, and `RedrawEventsCleared` get dispatched to be more in line with the MacOS backend and also more in line with how we have recently discussed defining them for all platforms. `NewEvents` is conceptually delivered when the event loop "wakes up" and `MainEventsCleared` gets dispatched when the event loop is about to ask the OS to wait for new events. This is a more portable model, and is already how these events work in the MacOS backend. `RedrawEventsCleared` are just delivered after `MainEventsCleared` but this event no longer has a useful meaning. Probably the most controversial thing here is that this "breaks" the ordering rules for redraw event handling, but since my changes interacted with how the order is maintained I was very reluctant to figure out how to continue maintaining something that we have recently been discussing changing: https://github.com/rust-windowing/winit/issues/2640. Additionally, since the MacOS backend already doesn't strictly maintain this order it's somewhat academic to see this as a breakage if Winit applications can't really rely on it already. This updates the documentation for `request_redraw()` to reflect that we no longer guarantee that `RedrawRequested` events must be dispatched after `MainEventsCleared`. --- src/platform/mod.rs | 8 +- src/platform_impl/windows/event_loop.rs | 575 ++++++++---------- .../windows/event_loop/runner.rs | 172 ++---- src/platform_impl/windows/window.rs | 2 - src/window.rs | 18 +- 5 files changed, 341 insertions(+), 434 deletions(-) diff --git a/src/platform/mod.rs b/src/platform/mod.rs index e6c2546d..319d3390 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -11,8 +11,8 @@ //! //! And the following platform-specific modules: //! -//! - `run_ondemand` (available on `android`) -//! - `pump_events` (available on `android`) +//! - `run_ondemand` (available on `windows`, `android`) +//! - `pump_events` (available on `windows`, `android`) //! - `run_return` (available on `windows`, `unix`, `macos`, and `android`) //! //! However only the module corresponding to the platform you're compiling to will be available. @@ -36,10 +36,10 @@ pub mod windows; #[cfg(x11_platform)] pub mod x11; -#[cfg(any(android_platform))] +#[cfg(any(windows_platform, android_platform))] pub mod run_ondemand; -#[cfg(any(android_platform,))] +#[cfg(any(windows_platform, android_platform,))] pub mod pump_events; #[cfg(any( diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index d3b4bf6a..5e8c9ca2 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -14,7 +14,6 @@ use std::{ mpsc::{self, Receiver, Sender}, Arc, Mutex, MutexGuard, }, - thread, time::{Duration, Instant}, }; @@ -23,13 +22,11 @@ use raw_window_handle::{RawDisplayHandle, WindowsDisplayHandle}; use windows_sys::Win32::{ Devices::HumanInterfaceDevice::MOUSE_MOVE_RELATIVE, - Foundation::{BOOL, HANDLE, HWND, LPARAM, LRESULT, POINT, RECT, WAIT_TIMEOUT, WPARAM}, + Foundation::{BOOL, HANDLE, HWND, LPARAM, LRESULT, POINT, RECT, WPARAM}, Graphics::Gdi::{ - GetMonitorInfoW, GetUpdateRect, MonitorFromRect, MonitorFromWindow, RedrawWindow, - ScreenToClient, ValidateRect, MONITORINFO, MONITOR_DEFAULTTONULL, RDW_INTERNALPAINT, - SC_SCREENSAVE, + GetMonitorInfoW, MonitorFromRect, MonitorFromWindow, RedrawWindow, ScreenToClient, + ValidateRect, MONITORINFO, MONITOR_DEFAULTTONULL, RDW_INTERNALPAINT, SC_SCREENSAVE, }, - Media::{timeBeginPeriod, timeEndPeriod, timeGetDevCaps, TIMECAPS, TIMERR_NOERROR}, System::{ Ole::RevokeDragDrop, Threading::{GetCurrentThreadId, INFINITE}, @@ -54,34 +51,35 @@ use windows_sys::Win32::{ }, WindowsAndMessaging::{ CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW, GetCursorPos, - GetMenu, GetMessageW, LoadCursorW, MsgWaitForMultipleObjectsEx, PeekMessageW, - PostMessageW, PostThreadMessageW, RegisterClassExW, RegisterWindowMessageA, SetCursor, - SetWindowPos, TranslateMessage, CREATESTRUCTW, GIDC_ARRIVAL, GIDC_REMOVAL, GWL_STYLE, - GWL_USERDATA, HTCAPTION, HTCLIENT, MINMAXINFO, MNC_CLOSE, MSG, MWMO_INPUTAVAILABLE, - NCCALCSIZE_PARAMS, PM_NOREMOVE, PM_QS_PAINT, PM_REMOVE, PT_PEN, PT_TOUCH, QS_ALLEVENTS, - RI_KEY_E0, RI_KEY_E1, RI_MOUSE_WHEEL, SC_MINIMIZE, SC_RESTORE, SIZE_MAXIMIZED, - SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE, SWP_NOZORDER, WHEEL_DELTA, WINDOWPOS, - WM_CAPTURECHANGED, WM_CLOSE, WM_CREATE, WM_DESTROY, WM_DPICHANGED, WM_ENTERSIZEMOVE, - WM_EXITSIZEMOVE, WM_GETMINMAXINFO, WM_IME_COMPOSITION, WM_IME_ENDCOMPOSITION, - WM_IME_SETCONTEXT, WM_IME_STARTCOMPOSITION, WM_INPUT, WM_INPUT_DEVICE_CHANGE, - WM_KEYDOWN, WM_KEYUP, WM_KILLFOCUS, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, - WM_MBUTTONUP, WM_MENUCHAR, WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCACTIVATE, - WM_NCCALCSIZE, WM_NCCREATE, WM_NCDESTROY, WM_NCLBUTTONDOWN, WM_PAINT, WM_POINTERDOWN, - WM_POINTERUP, WM_POINTERUPDATE, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR, - WM_SETFOCUS, WM_SETTINGCHANGE, WM_SIZE, WM_SYSCOMMAND, WM_SYSKEYDOWN, WM_SYSKEYUP, - WM_TOUCH, WM_WINDOWPOSCHANGED, WM_WINDOWPOSCHANGING, WM_XBUTTONDOWN, WM_XBUTTONUP, - WNDCLASSEXW, WS_EX_LAYERED, WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW, WS_EX_TRANSPARENT, - WS_OVERLAPPED, WS_POPUP, WS_VISIBLE, + GetMenu, GetMessageW, KillTimer, LoadCursorW, PeekMessageW, PostMessageW, + RegisterClassExW, RegisterWindowMessageA, SetCursor, SetTimer, SetWindowPos, + TranslateMessage, CREATESTRUCTW, GIDC_ARRIVAL, GIDC_REMOVAL, GWL_STYLE, GWL_USERDATA, + HTCAPTION, HTCLIENT, MINMAXINFO, MNC_CLOSE, MSG, NCCALCSIZE_PARAMS, PM_REMOVE, PT_PEN, + PT_TOUCH, RI_KEY_E0, RI_KEY_E1, RI_MOUSE_WHEEL, SC_MINIMIZE, SC_RESTORE, + SIZE_MAXIMIZED, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE, SWP_NOZORDER, WHEEL_DELTA, + WINDOWPOS, WM_CAPTURECHANGED, WM_CLOSE, WM_CREATE, WM_DESTROY, WM_DPICHANGED, + WM_ENTERSIZEMOVE, WM_EXITSIZEMOVE, WM_GETMINMAXINFO, WM_IME_COMPOSITION, + WM_IME_ENDCOMPOSITION, WM_IME_SETCONTEXT, WM_IME_STARTCOMPOSITION, WM_INPUT, + WM_INPUT_DEVICE_CHANGE, WM_KEYDOWN, WM_KEYUP, WM_KILLFOCUS, WM_LBUTTONDOWN, + WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MENUCHAR, WM_MOUSEHWHEEL, WM_MOUSEMOVE, + WM_MOUSEWHEEL, WM_NCACTIVATE, WM_NCCALCSIZE, WM_NCCREATE, WM_NCDESTROY, + WM_NCLBUTTONDOWN, WM_PAINT, WM_POINTERDOWN, WM_POINTERUP, WM_POINTERUPDATE, + WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR, WM_SETFOCUS, WM_SETTINGCHANGE, WM_SIZE, + WM_SYSCOMMAND, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_TOUCH, WM_WINDOWPOSCHANGED, + WM_WINDOWPOSCHANGING, WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSEXW, WS_EX_LAYERED, + WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW, WS_EX_TRANSPARENT, WS_OVERLAPPED, WS_POPUP, + WS_VISIBLE, }, }, }; use crate::{ dpi::{PhysicalPosition, PhysicalSize}, + error::RunLoopError, event::{DeviceEvent, Event, Force, Ime, RawKeyEvent, Touch, TouchPhase, WindowEvent}, event_loop::{ControlFlow, DeviceEvents, EventLoopClosed, EventLoopWindowTarget as RootELW}, keyboard::{KeyCode, ModifiersState}, - platform::scancode::KeyCodeExtScancode, + platform::{pump_events::PumpStatus, scancode::KeyCodeExtScancode}, platform_impl::platform::{ dark_mode::try_theme, dpi::{become_dpi_aware, dpi_to_scale_factor}, @@ -99,6 +97,8 @@ use crate::{ }; use runner::{EventLoopRunner, EventLoopRunnerShared}; +use self::runner::RunnerState; + use super::window::set_skip_taskbar; type GetPointerFrameInfoHistory = unsafe extern "system" fn( @@ -216,13 +216,7 @@ impl EventLoop { let thread_msg_target = create_event_target_window::(); - thread::Builder::new() - .name("winit wait thread".to_string()) - .spawn(move || wait_thread(thread_id, thread_msg_target)) - .expect("Failed to spawn winit wait thread"); - let wait_thread_id = get_wait_thread_id(); - - let runner_shared = Rc::new(EventLoopRunner::new(thread_msg_target, wait_thread_id)); + let runner_shared = Rc::new(EventLoopRunner::new(thread_msg_target)); let thread_msg_sender = insert_event_target_window_data::(thread_msg_target, runner_shared.clone()); @@ -253,34 +247,246 @@ impl EventLoop { where F: 'static + FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), { - let exit_code = self.run_return(event_handler); + let exit_code = match self.run_ondemand(event_handler) { + Err(RunLoopError::ExitFailure(code)) => code, + Err(_err) => 1, + Ok(_) => 0, + }; ::std::process::exit(exit_code); } - pub fn run_return(&mut self, mut event_handler: F) -> i32 + pub fn run_return(&mut self, event_handler: F) -> i32 where F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), { - let event_loop_windows_ref = &self.window_target; + match self.run_ondemand(event_handler) { + Err(RunLoopError::ExitFailure(code)) => code, + Err(_err) => 1, + Ok(_) => 0, + } + } - unsafe { - self.window_target - .p - .runner_shared - .set_event_handler(move |event, control_flow| { + pub fn run_ondemand(&mut self, mut event_handler: F) -> Result<(), RunLoopError> + where + F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), + { + { + let runner = &self.window_target.p.runner_shared; + if runner.state() != RunnerState::Uninitialized { + return Err(RunLoopError::AlreadyRunning); + } + + let event_loop_windows_ref = &self.window_target; + // # Safety + // We make sure to call runner.clear_event_handler() before + // returning + unsafe { + runner.set_event_handler(move |event, control_flow| { event_handler(event, event_loop_windows_ref, control_flow) }); + } } + let exit_code = loop { + if let ControlFlow::ExitWithCode(code) = self.wait_and_dispatch_message() { + break code; + } + + if let ControlFlow::ExitWithCode(code) = self.dispatch_peeked_messages() { + break code; + } + }; + + let runner = &self.window_target.p.runner_shared; + runner.loop_destroyed(); + + // # Safety + // We assume that this will effectively call `runner.clear_event_handler()` + // to meet the safety requirements for calling `runner.set_event_handler()` above. + runner.reset_runner(); + + if exit_code == 0 { + Ok(()) + } else { + Err(RunLoopError::ExitFailure(exit_code)) + } + } + + pub fn pump_events(&mut self, mut event_handler: F) -> PumpStatus + where + F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), + { + { + let runner = &self.window_target.p.runner_shared; + let event_loop_windows_ref = &self.window_target; + + // # Safety + // We make sure to call runner.clear_event_handler() before + // returning + // + // Note: we're currently assuming nothing can panic and unwind + // to leave the runner in an unsound state with an associated + // event handler. + unsafe { + runner.set_event_handler(move |event, control_flow| { + event_handler(event, event_loop_windows_ref, control_flow) + }); + runner.wakeup(); + } + } + + self.dispatch_peeked_messages(); + let runner = &self.window_target.p.runner_shared; - let exit_code = unsafe { - let mut msg = mem::zeroed(); + let status = if let ControlFlow::ExitWithCode(code) = runner.control_flow() { + runner.loop_destroyed(); - runner.poll(); - 'main: loop { - if GetMessageW(&mut msg, 0, 0, 0) == false.into() { - break 'main 0; + // Immediately reset the internal state for the loop to allow + // the loop to be run more than once. + runner.reset_runner(); + PumpStatus::Exit(code) + } else { + runner.prepare_wait(); + PumpStatus::Continue + }; + + // We wait until we've checked for an exit status before clearing the + // application callback, in case we need to dispatch a LoopDestroyed event + // + // # Safety + // This pairs up with our call to `runner.set_event_handler` and ensures + // the application's callback can't be held beyond its lifetime. + runner.clear_event_handler(); + + status + } + + /// Wait for one message and dispatch it, optionally with a timeout if control_flow is `WaitUntil` + fn wait_and_dispatch_message(&mut self) -> ControlFlow { + let start = Instant::now(); + + let runner = &self.window_target.p.runner_shared; + + let timeout = match runner.control_flow() { + ControlFlow::Wait => None, + ControlFlow::Poll => Some(Duration::ZERO), + ControlFlow::WaitUntil(wait_deadline) => { + Some(wait_deadline.saturating_duration_since(start)) + } + ControlFlow::ExitWithCode(_code) => unreachable!(), + }; + + fn get_msg_with_timeout(msg: &mut MSG, timeout: Option) -> PumpStatus { + unsafe { + // A timeout of None means wait indefinitely (so we don't need to call SetTimer) + let timer_id = timeout.map(|timeout| SetTimer(0, 0, dur2timeout(timeout), None)); + let get_status = GetMessageW(msg, 0, 0, 0); + if let Some(timer_id) = timer_id { + KillTimer(0, timer_id); + } + // A return value of 0 implies `WM_QUIT` + if get_status == 0 { + PumpStatus::Exit(0) + } else { + PumpStatus::Continue + } + } + } + + /// Fetch the next MSG either via PeekMessage or GetMessage depending on whether the + /// requested timeout is `ZERO` (and so we don't want to block) + /// + /// Returns `None` if if no MSG was read, else a `Continue` or `Exit` status + fn wait_for_msg(msg: &mut MSG, timeout: Option) -> Option { + if timeout == Some(Duration::ZERO) { + unsafe { + if PeekMessageW(msg, 0, 0, 0, PM_REMOVE) != 0 { + Some(PumpStatus::Continue) + } else { + None + } + } + } else { + Some(get_msg_with_timeout(msg, timeout)) + } + } + + // We aim to be consistent with the MacOS backend which has a RunLoop + // observer that will dispatch MainEventsCleared when about to wait for + // events, and NewEvents after the RunLoop wakes up. + // + // We emulate similar behaviour by treating `GetMessage` as our wait + // point and wake up point (when it returns) and we drain all other + // pending messages via `PeekMessage` until we come back to "wait" via + // `GetMessage` + // + runner.prepare_wait(); + + // # Safety + // The Windows API has no documented requirement for bitwise + // initializing a `MSG` struct (it can be uninitialized memory for the C + // API) and there's no API to construct or initialize a `MSG`. This + // is the simplest way avoid unitialized memory in Rust + let mut msg = unsafe { mem::zeroed() }; + let msg_status = wait_for_msg(&mut msg, timeout); + + // Before we potentially exit, make sure to consistently emit an event for the wake up + runner.wakeup(); + + match msg_status { + None => {} // No MSG to dispatch + Some(PumpStatus::Exit(code)) => { + runner.set_exit_control_flow(code); + return runner.control_flow(); + } + Some(PumpStatus::Continue) => { + unsafe { + let handled = if let Some(callback) = self.msg_hook.as_deref_mut() { + callback(&mut msg as *mut _ as *mut _) + } else { + false + }; + if !handled { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + } + + if let Err(payload) = runner.take_panic_error() { + runner.reset_runner(); + panic::resume_unwind(payload); + } + } + } + + runner.control_flow() + } + + /// Dispatch all queued messages via `PeekMessageW` + fn dispatch_peeked_messages(&mut self) -> ControlFlow { + let runner = &self.window_target.p.runner_shared; + + // We generally want to continue dispatching all pending messages + // but we also allow dispatching to be interrupted as a means to + // ensure the `pump_events` won't indefinitely block an external + // event loop if there are too many pending events. This interrupt + // flag will be set after dispatching `RedrawRequested` events. + runner.interrupt_msg_dispatch.set(false); + + // # Safety + // The Windows API has no documented requirement for bitwise + // initializing a `MSG` struct (it can be uninitialized memory for the C + // API) and there's no API to construct or initialize a `MSG`. This + // is the simplest way avoid unitialized memory in Rust + let mut msg = unsafe { mem::zeroed() }; + + let mut control_flow = runner.control_flow(); + + loop { + unsafe { + if PeekMessageW(&mut msg, 0, 0, 0, PM_REMOVE) == false.into() { + break; } let handled = if let Some(callback) = self.msg_hook.as_deref_mut() { @@ -292,26 +498,24 @@ impl EventLoop { TranslateMessage(&msg); DispatchMessageW(&msg); } - - if let Err(payload) = runner.take_panic_error() { - runner.reset_runner(); - panic::resume_unwind(payload); - } - - if let ControlFlow::ExitWithCode(code) = runner.control_flow() { - if !runner.handling_events() { - break 'main code; - } - } } - }; - unsafe { - runner.loop_destroyed(); + if let Err(payload) = runner.take_panic_error() { + runner.reset_runner(); + panic::resume_unwind(payload); + } + + control_flow = runner.control_flow(); + if let ControlFlow::ExitWithCode(_code) = control_flow { + break; + } + + if runner.interrupt_msg_dispatch.get() { + break; + } } - runner.reset_runner(); - exit_code + control_flow } pub fn create_proxy(&self) -> EventLoopProxy { @@ -391,109 +595,6 @@ fn main_thread_id() -> u32 { unsafe { MAIN_THREAD_ID } } -fn get_wait_thread_id() -> u32 { - unsafe { - let mut msg = mem::zeroed(); - let result = GetMessageW( - &mut msg, - -1, - SEND_WAIT_THREAD_ID_MSG_ID.get(), - SEND_WAIT_THREAD_ID_MSG_ID.get(), - ); - assert_eq!( - msg.message, - SEND_WAIT_THREAD_ID_MSG_ID.get(), - "this shouldn't be possible. please open an issue with Winit. error code: {result}" - ); - msg.lParam as u32 - } -} - -static WAIT_PERIOD_MIN: Lazy> = Lazy::new(|| unsafe { - let mut caps = TIMECAPS { - wPeriodMin: 0, - wPeriodMax: 0, - }; - if timeGetDevCaps(&mut caps, mem::size_of::() as u32) == TIMERR_NOERROR { - Some(caps.wPeriodMin) - } else { - None - } -}); - -fn wait_thread(parent_thread_id: u32, msg_window_id: HWND) { - unsafe { - let mut msg: MSG; - - let cur_thread_id = GetCurrentThreadId(); - PostThreadMessageW( - parent_thread_id, - SEND_WAIT_THREAD_ID_MSG_ID.get(), - 0, - cur_thread_id as LPARAM, - ); - - let mut wait_until_opt = None; - 'main: loop { - // Zeroing out the message ensures that the `WaitUntilInstantBox` doesn't get - // double-freed if `MsgWaitForMultipleObjectsEx` returns early and there aren't - // additional messages to process. - msg = mem::zeroed(); - - if wait_until_opt.is_some() { - if PeekMessageW(&mut msg, 0, 0, 0, PM_REMOVE) != false.into() { - TranslateMessage(&msg); - DispatchMessageW(&msg); - } - } else if GetMessageW(&mut msg, 0, 0, 0) == false.into() { - break 'main; - } else { - TranslateMessage(&msg); - DispatchMessageW(&msg); - } - - if msg.message == WAIT_UNTIL_MSG_ID.get() { - wait_until_opt = Some(*WaitUntilInstantBox::from_raw(msg.lParam as *mut _)); - } else if msg.message == CANCEL_WAIT_UNTIL_MSG_ID.get() { - wait_until_opt = None; - } - - if let Some(wait_until) = wait_until_opt { - let now = Instant::now(); - if now < wait_until { - // 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 { - 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 = MsgWaitForMultipleObjectsEx( - 0, - ptr::null(), - dur2timeout(wait_until - now).saturating_sub(WAIT_PERIOD_MIN.unwrap_or(1)), - QS_ALLEVENTS, - MWMO_INPUTAVAILABLE, - ); - if let Some(period) = *WAIT_PERIOD_MIN { - timeEndPeriod(period); - } - if resume_reason == WAIT_TIMEOUT { - PostMessageW(msg_window_id, PROCESS_NEW_EVENTS_MSG_ID.get(), 0, 0); - wait_until_opt = None; - } - } else { - PostMessageW(msg_window_id, PROCESS_NEW_EVENTS_MSG_ID.get(), 0, 0); - wait_until_opt = None; - } - } - } - } -} - // Implementation taken from https://github.com/rust-lang/rust/blob/db5476571d9b27c862b95c1e64764b0ac8980e23/src/libstd/sys/windows/mod.rs fn dur2timeout(dur: Duration) -> u32 { // Note that a duration is a (u64, u32) (seconds, nanoseconds) pair, and the @@ -612,8 +713,6 @@ impl EventLoopProxy { } } -type WaitUntilInstantBox = Box; - /// A lazily-initialized window message ID. pub struct LazyMessageId { /// The ID. @@ -673,13 +772,6 @@ static USER_EVENT_MSG_ID: LazyMessageId = LazyMessageId::new("Winit::WakeupMsg\0 // WPARAM contains a Box> that must be retrieved with `Box::from_raw`, // and LPARAM is unused. static EXEC_MSG_ID: LazyMessageId = LazyMessageId::new("Winit::ExecMsg\0"); -static PROCESS_NEW_EVENTS_MSG_ID: LazyMessageId = LazyMessageId::new("Winit::ProcessNewEvents\0"); -/// lparam is the wait thread's message id. -static SEND_WAIT_THREAD_ID_MSG_ID: LazyMessageId = LazyMessageId::new("Winit::SendWaitThreadId\0"); -/// lparam points to a `Box` signifying the time `PROCESS_NEW_EVENTS_MSG_ID` should -/// be sent. -static WAIT_UNTIL_MSG_ID: LazyMessageId = LazyMessageId::new("Winit::WaitUntil\0"); -static CANCEL_WAIT_UNTIL_MSG_ID: LazyMessageId = LazyMessageId::new("Winit::CancelWaitUntil\0"); // Message sent by a `Window` when it wants to be destroyed by the main thread. // WPARAM and LPARAM are unused. pub static DESTROY_MSG_ID: LazyMessageId = LazyMessageId::new("Winit::DestroyMsg\0"); @@ -795,74 +887,6 @@ fn normalize_pointer_pressure(pressure: u32) -> Option { } } -/// Flush redraw events for Winit's windows. -/// -/// Winit's API guarantees that all redraw events will be clustered together and dispatched all at -/// once, but the standard Windows message loop doesn't always exhibit that behavior. If multiple -/// windows have had redraws scheduled, but an input event is pushed to the message queue between -/// the `WM_PAINT` call for the first window and the `WM_PAINT` call for the second window, Windows -/// will dispatch the input event immediately instead of flushing all the redraw events. This -/// function explicitly pulls all of Winit's redraw events out of the event queue so that they -/// always all get processed in one fell swoop. -/// -/// Returns `true` if this invocation flushed all the redraw events. If this function is re-entrant, -/// it won't flush the redraw events and will return `false`. -#[must_use] -unsafe fn flush_paint_messages( - except: Option, - runner: &EventLoopRunner, -) -> bool { - if !runner.redrawing() { - runner.main_events_cleared(); - let mut msg = mem::zeroed(); - runner.owned_windows(|redraw_window| { - if Some(redraw_window) == except { - return; - } - - if PeekMessageW( - &mut msg, - redraw_window, - WM_PAINT, - WM_PAINT, - PM_REMOVE | PM_QS_PAINT, - ) == false.into() - { - return; - } - - TranslateMessage(&msg); - DispatchMessageW(&msg); - }); - true - } else { - false - } -} - -unsafe fn process_control_flow(runner: &EventLoopRunner) { - match runner.control_flow() { - ControlFlow::Poll => { - PostMessageW( - runner.thread_msg_target(), - PROCESS_NEW_EVENTS_MSG_ID.get(), - 0, - 0, - ); - } - ControlFlow::Wait => (), - ControlFlow::WaitUntil(until) => { - PostThreadMessageW( - runner.wait_thread_id(), - WAIT_UNTIL_MSG_ID.get(), - 0, - Box::into_raw(WaitUntilInstantBox::new(until)) as isize, - ); - } - ControlFlow::ExitWithCode(_) => (), - } -} - /// Emit a `ModifiersChanged` event whenever modifiers have changed. /// Returns the current modifier state fn update_modifiers(window: HWND, userdata: &WindowData) { @@ -988,13 +1012,6 @@ unsafe fn public_window_callback_inner( lparam: LPARAM, userdata: &WindowData, ) -> LRESULT { - RedrawWindow( - userdata.event_loop_runner.thread_msg_target(), - ptr::null(), - 0, - RDW_INTERNALPAINT, - ); - let mut result = ProcResult::DefWindowProc(wparam); // Send new modifiers before sending key events. @@ -1116,7 +1133,6 @@ unsafe fn public_window_callback_inner( window_id: RootWindowId(WindowId(window)), event: Destroyed, }); - userdata.event_loop_runner.remove_window(window); result = ProcResult::Value(0); } @@ -1132,13 +1148,7 @@ unsafe fn public_window_callback_inner( // redraw the window outside the normal flow of the event loop. RedrawWindow(window, ptr::null(), 0, RDW_INTERNALPAINT); } else { - let managing_redraw = - flush_paint_messages(Some(window), &userdata.event_loop_runner); userdata.send_event(Event::RedrawRequested(RootWindowId(WindowId(window)))); - if managing_redraw { - userdata.event_loop_runner.redraw_events_cleared(); - process_control_flow(&userdata.event_loop_runner); - } } result = ProcResult::DefWindowProc(wparam); } @@ -2270,27 +2280,8 @@ unsafe extern "system" fn thread_event_target_callback( userdata_removed = true; 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. WM_PAINT => { ValidateRect(window, ptr::null()); - // If the WM_PAINT handler in `public_window_callback` has already flushed the redraw - // events, `handling_events` will return false and we won't emit a second - // `RedrawEventsCleared` event. - if userdata.event_loop_runner.handling_events() { - if userdata.event_loop_runner.should_buffer() { - // This branch can be triggered when a nested win32 event loop is triggered - // inside of the `event_handler` callback. - RedrawWindow(window, ptr::null(), 0, RDW_INTERNALPAINT); - } else { - // This WM_PAINT handler will never be re-entrant because `flush_paint_messages` - // doesn't call WM_PAINT for the thread event target (i.e. this window). - assert!(flush_paint_messages(None, &userdata.event_loop_runner)); - userdata.event_loop_runner.redraw_events_cleared(); - process_control_flow(&userdata.event_loop_runner); - } - } - // Default WM_PAINT behaviour. This makes sure modals and popups are shown immediatly when opening them. DefWindowProcW(window, msg, wparam, lparam) } @@ -2329,40 +2320,6 @@ unsafe extern "system" fn thread_event_target_callback( function(); 0 } - _ if msg == PROCESS_NEW_EVENTS_MSG_ID.get() => { - PostThreadMessageW( - userdata.event_loop_runner.wait_thread_id(), - CANCEL_WAIT_UNTIL_MSG_ID.get(), - 0, - 0, - ); - - // if the control_flow is WaitUntil, make sure the given moment has actually passed - // before emitting NewEvents - if let ControlFlow::WaitUntil(wait_until) = userdata.event_loop_runner.control_flow() { - let mut msg = mem::zeroed(); - while Instant::now() < wait_until { - if PeekMessageW(&mut msg, 0, 0, 0, PM_NOREMOVE) != false.into() { - // This works around a "feature" in PeekMessageW. If the message PeekMessageW - // gets is a WM_PAINT message that had RDW_INTERNALPAINT set (i.e. doesn't - // have an update region), PeekMessageW will remove that window from the - // redraw queue even though we told it not to remove messages from the - // queue. We fix it by re-dispatching an internal paint message to that - // window. - if msg.message == WM_PAINT { - let mut rect = mem::zeroed(); - if GetUpdateRect(msg.hwnd, &mut rect, false.into()) == false.into() { - RedrawWindow(msg.hwnd, ptr::null(), 0, RDW_INTERNALPAINT); - } - } - - break; - } - } - } - userdata.event_loop_runner.poll(); - 0 - } _ => DefWindowProcW(window, msg, wparam, lparam), }; diff --git a/src/platform_impl/windows/event_loop/runner.rs b/src/platform_impl/windows/event_loop/runner.rs index c4ac1eb3..c39b3f70 100644 --- a/src/platform_impl/windows/event_loop/runner.rs +++ b/src/platform_impl/windows/event_loop/runner.rs @@ -1,16 +1,13 @@ use std::{ any::Any, cell::{Cell, RefCell}, - collections::{HashSet, VecDeque}, - mem, panic, ptr, + collections::VecDeque, + mem, panic, rc::Rc, time::Instant, }; -use windows_sys::Win32::{ - Foundation::HWND, - Graphics::Gdi::{RedrawWindow, RDW_INTERNALPAINT}, -}; +use windows_sys::Win32::Foundation::HWND; use crate::{ dpi::PhysicalSize, @@ -30,7 +27,11 @@ type EventHandler = Cell, &mut ControlFlow) pub(crate) struct EventLoopRunner { // The event loop's win32 handles pub(super) thread_msg_target: HWND, - wait_thread_id: u32, + + // Setting this will ensure pump_events will return to the external + // loop asap. E.g. set after each RedrawRequested to ensure pump_events + // can't stall an external loop beyond a frame + pub(super) interrupt_msg_dispatch: Cell, control_flow: Cell, runner_state: Cell, @@ -38,8 +39,6 @@ pub(crate) struct EventLoopRunner { event_handler: EventHandler, event_buffer: RefCell>>, - owned_windows: Cell>, - panic_error: Cell>, } @@ -47,7 +46,7 @@ pub type PanicError = Box; /// See `move_state_to` function for details on how the state loop works. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -enum RunnerState { +pub(crate) enum RunnerState { /// The event loop has just been created, and an `Init` event must be sent. Uninitialized, /// The event loop is idling. @@ -55,9 +54,6 @@ enum RunnerState { /// The event loop is handling the OS's events and sending them to the user's callback. /// `NewEvents` has been sent, and `MainEventsCleared` hasn't. HandlingMainEvents, - /// The event loop is handling the redraw events and sending them to the user's callback. - /// `MainEventsCleared` has been sent, and `RedrawEventsCleared` hasn't. - HandlingRedrawEvents, /// The event loop has been destroyed. No other events will be emitted. Destroyed, } @@ -68,20 +64,30 @@ enum BufferedEvent { } impl EventLoopRunner { - pub(crate) fn new(thread_msg_target: HWND, wait_thread_id: u32) -> EventLoopRunner { + pub(crate) fn new(thread_msg_target: HWND) -> EventLoopRunner { EventLoopRunner { thread_msg_target, - wait_thread_id, + interrupt_msg_dispatch: Cell::new(false), runner_state: Cell::new(RunnerState::Uninitialized), control_flow: Cell::new(ControlFlow::Poll), panic_error: Cell::new(None), last_events_cleared: Cell::new(Instant::now()), event_handler: Cell::new(None), event_buffer: RefCell::new(VecDeque::new()), - owned_windows: Cell::new(HashSet::new()), } } + /// Associate the application's event handler with the runner + /// + /// # Safety + /// This is ignoring the lifetime of the application handler (which may not + /// outlive the EventLoopRunner) and can lead to undefined behaviour if + /// the handler is not cleared before the end of real lifetime. + /// + /// All public APIs that take an event handler (`run`, `run_ondemand`, + /// `pump_events`) _must_ pair a call to `set_event_handler` with + /// a call to `clear_event_handler` before returning to avoid + /// undefined behaviour. pub(crate) unsafe fn set_event_handler(&self, f: F) where F: FnMut(Event<'_, T>, &mut ControlFlow), @@ -93,18 +99,22 @@ impl EventLoopRunner { assert!(old_event_handler.is_none()); } + pub(crate) fn clear_event_handler(&self) { + self.event_handler.set(None); + } + pub(crate) fn reset_runner(&self) { let EventLoopRunner { thread_msg_target: _, - wait_thread_id: _, + interrupt_msg_dispatch, runner_state, panic_error, control_flow, last_events_cleared: _, event_handler, event_buffer: _, - owned_windows: _, } = self; + interrupt_msg_dispatch.set(false); runner_state.set(RunnerState::Uninitialized); panic_error.set(None); control_flow.set(ControlFlow::Poll); @@ -114,18 +124,11 @@ impl EventLoopRunner { /// State retrieval functions. impl EventLoopRunner { + #[allow(unused)] pub fn thread_msg_target(&self) -> HWND { self.thread_msg_target } - pub fn wait_thread_id(&self) -> u32 { - self.wait_thread_id - } - - pub fn redrawing(&self) -> bool { - self.runner_state.get() == RunnerState::HandlingRedrawEvents - } - pub fn take_panic_error(&self) -> Result<(), PanicError> { match self.panic_error.take() { Some(err) => Err(err), @@ -133,12 +136,16 @@ impl EventLoopRunner { } } - pub fn control_flow(&self) -> ControlFlow { - self.control_flow.get() + pub fn state(&self) -> RunnerState { + self.runner_state.get() } - pub fn handling_events(&self) -> bool { - self.runner_state.get() != RunnerState::Idle + pub fn set_exit_control_flow(&self, code: i32) { + self.control_flow.set(ControlFlow::ExitWithCode(code)) + } + + pub fn control_flow(&self) -> ControlFlow { + self.control_flow.get() } pub fn should_buffer(&self) -> bool { @@ -177,42 +184,25 @@ impl EventLoopRunner { None } } - pub fn register_window(&self, window: HWND) { - let mut owned_windows = self.owned_windows.take(); - owned_windows.insert(window); - self.owned_windows.set(owned_windows); - } - - pub fn remove_window(&self, window: HWND) { - let mut owned_windows = self.owned_windows.take(); - owned_windows.remove(&window); - self.owned_windows.set(owned_windows); - } - - pub fn owned_windows(&self, mut f: impl FnMut(HWND)) { - let mut owned_windows = self.owned_windows.take(); - for hwnd in &owned_windows { - f(*hwnd); - } - let new_owned_windows = self.owned_windows.take(); - owned_windows.extend(&new_owned_windows); - self.owned_windows.set(owned_windows); - } } /// Event dispatch functions. impl EventLoopRunner { - pub(crate) unsafe fn poll(&self) { + pub(crate) fn prepare_wait(&self) { + self.move_state_to(RunnerState::Idle); + } + + pub(crate) fn wakeup(&self) { self.move_state_to(RunnerState::HandlingMainEvents); } - pub(crate) unsafe fn send_event(&self, event: Event<'_, T>) { + pub(crate) fn send_event(&self, event: Event<'_, T>) { if let Event::RedrawRequested(_) = event { - if self.runner_state.get() != RunnerState::HandlingRedrawEvents { - warn!("RedrawRequested dispatched without explicit MainEventsCleared"); - self.move_state_to(RunnerState::HandlingRedrawEvents); - } self.call_event_handler(event); + // As a rule, to ensure that `pump_events` can't block an external event loop + // for too long, we always guarantee that `pump_events` will return control to + // the external loop asap after a `RedrawRequested` event is dispatched. + self.interrupt_msg_dispatch.set(true); } else if self.should_buffer() { // If the runner is already borrowed, we're in the middle of an event loop invocation. Add // the event to a buffer to be processed later. @@ -220,25 +210,16 @@ impl EventLoopRunner { .borrow_mut() .push_back(BufferedEvent::from_event(event)) } else { - self.move_state_to(RunnerState::HandlingMainEvents); self.call_event_handler(event); self.dispatch_buffered_events(); } } - pub(crate) unsafe fn main_events_cleared(&self) { - self.move_state_to(RunnerState::HandlingRedrawEvents); - } - - pub(crate) unsafe fn redraw_events_cleared(&self) { - self.move_state_to(RunnerState::Idle); - } - - pub(crate) unsafe fn loop_destroyed(&self) { + pub(crate) fn loop_destroyed(&self) { self.move_state_to(RunnerState::Destroyed); } - unsafe fn call_event_handler(&self, event: Event<'_, T>) { + fn call_event_handler(&self, event: Event<'_, T>) { self.catch_unwind(|| { let mut control_flow = self.control_flow.take(); let mut event_handler = self.event_handler.take() @@ -255,7 +236,7 @@ impl EventLoopRunner { }); } - unsafe fn dispatch_buffered_events(&self) { + fn dispatch_buffered_events(&self) { loop { // We do this instead of using a `while let` loop because if we use a `while let` // loop the reference returned `borrow_mut()` doesn't get dropped until the end @@ -278,24 +259,22 @@ impl EventLoopRunner { /// Uninitialized /// | /// V - /// HandlingMainEvents - /// ^ | - /// | V - /// Idle <--- HandlingRedrawEvents - /// | - /// V - /// Destroyed + /// Idle + /// ^ | + /// | V + /// HandlingMainEvents + /// | + /// V + /// Destroyed /// ``` /// /// Attempting to transition back to `Uninitialized` will result in a panic. Attempting to - /// transition *from* `Destroyed` will also reuslt in a panic. Transitioning to the current + /// transition *from* `Destroyed` will also result in a panic. Transitioning to the current /// state is a no-op. Even if the `new_runner_state` isn't the immediate next state in the /// runner state machine (e.g. `self.runner_state == HandlingMainEvents` and /// `new_runner_state == Idle`), the intermediate state transitions will still be executed. - unsafe fn move_state_to(&self, new_runner_state: RunnerState) { - use RunnerState::{ - Destroyed, HandlingMainEvents, HandlingRedrawEvents, Idle, Uninitialized, - }; + fn move_state_to(&self, new_runner_state: RunnerState) { + use RunnerState::{Destroyed, HandlingMainEvents, Idle, Uninitialized}; match ( self.runner_state.replace(new_runner_state), @@ -304,17 +283,12 @@ impl EventLoopRunner { (Uninitialized, Uninitialized) | (Idle, Idle) | (HandlingMainEvents, HandlingMainEvents) - | (HandlingRedrawEvents, HandlingRedrawEvents) | (Destroyed, Destroyed) => (), // State transitions that initialize the event loop. (Uninitialized, HandlingMainEvents) => { self.call_new_events(true); } - (Uninitialized, HandlingRedrawEvents) => { - self.call_new_events(true); - self.call_event_handler(Event::MainEventsCleared); - } (Uninitialized, Idle) => { self.call_new_events(true); self.call_event_handler(Event::MainEventsCleared); @@ -332,19 +306,11 @@ impl EventLoopRunner { (Idle, HandlingMainEvents) => { self.call_new_events(false); } - (Idle, HandlingRedrawEvents) => { - self.call_new_events(false); - self.call_event_handler(Event::MainEventsCleared); - } (Idle, Destroyed) => { self.call_event_handler(Event::LoopDestroyed); } - (HandlingMainEvents, HandlingRedrawEvents) => { - self.call_event_handler(Event::MainEventsCleared); - } (HandlingMainEvents, Idle) => { - warn!("RedrawEventsCleared emitted without explicit MainEventsCleared"); self.call_event_handler(Event::MainEventsCleared); self.call_redraw_events_cleared(); } @@ -354,24 +320,11 @@ impl EventLoopRunner { self.call_event_handler(Event::LoopDestroyed); } - (HandlingRedrawEvents, Idle) => { - self.call_redraw_events_cleared(); - } - (HandlingRedrawEvents, HandlingMainEvents) => { - warn!("NewEvents emitted without explicit RedrawEventsCleared"); - self.call_redraw_events_cleared(); - self.call_new_events(false); - } - (HandlingRedrawEvents, Destroyed) => { - self.call_redraw_events_cleared(); - self.call_event_handler(Event::LoopDestroyed); - } - (Destroyed, _) => panic!("cannot move state from Destroyed"), } } - unsafe fn call_new_events(&self, init: bool) { + fn call_new_events(&self, init: bool) { let start_cause = match (init, self.control_flow()) { (true, _) => StartCause::Init, (false, ControlFlow::Poll) => StartCause::Poll, @@ -402,10 +355,9 @@ impl EventLoopRunner { self.call_event_handler(Event::Resumed); } self.dispatch_buffered_events(); - RedrawWindow(self.thread_msg_target, ptr::null(), 0, RDW_INTERNALPAINT); } - unsafe fn call_redraw_events_cleared(&self) { + fn call_redraw_events_cleared(&self) { self.call_event_handler(Event::RedrawEventsCleared); self.last_events_cleared.set(Instant::now()); } diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index ff07b8eb..04bd78d3 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -987,8 +987,6 @@ impl<'a, T: 'static> InitData<'a, T> { None }; - self.event_loop.runner_shared.register_window(win.window.0); - event_loop::WindowData { window_state: win.window_state.clone(), event_loop_runner: self.event_loop.runner_shared.clone(), diff --git a/src/window.rs b/src/window.rs index 40d43c58..017501f6 100644 --- a/src/window.rs +++ b/src/window.rs @@ -524,25 +524,25 @@ impl Window { self.window.scale_factor() } - /// Emits a [`Event::RedrawRequested`] event in the associated event loop after all OS - /// events have been processed by the event loop. + /// Requests a future [`Event::RedrawRequested`] event to be emitted in a way that is + /// synchronized and / or throttled by the windowing system. /// /// This is the **strongly encouraged** method of redrawing windows, as it can integrate with /// OS-requested redraws (e.g. when a window gets resized). /// - /// This function can cause `RedrawRequested` events to be emitted after [`Event::MainEventsCleared`] - /// but before `Event::NewEvents` if called in the following circumstances: - /// * While processing `MainEventsCleared`. - /// * While processing a `RedrawRequested` event that was sent during `MainEventsCleared` or any - /// directly subsequent `RedrawRequested` event. + /// Applications should always aim to redraw whenever they receive a `RedrawRequested` event. + /// + /// There are no strong guarantees about when exactly a `RedrawRequest` event will be emitted + /// with respect to other events, since the requirements can vary significantly between + /// windowing systems. /// /// ## Platform-specific /// + /// - **Windows** This API uses `RedrawWindow` to request a `WM_PAINT` message and `RedrawRequested` + /// is emitted in sync with any `WM_PAINT` messages /// - **iOS:** Can only be called on the main thread. - /// - **Android:** Subsequent calls after `MainEventsCleared` are not handled. /// /// [`Event::RedrawRequested`]: crate::event::Event::RedrawRequested - /// [`Event::MainEventsCleared`]: crate::event::Event::MainEventsCleared #[inline] pub fn request_redraw(&self) { self.window.request_redraw()