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`.
This commit is contained in:
Robert Bragg 2023-06-18 11:42:57 +01:00 committed by Kirill Chibisov
parent f5e73b0af4
commit 420840278b
5 changed files with 341 additions and 434 deletions

View file

@ -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(

View file

@ -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<T: 'static> EventLoop<T> {
let thread_msg_target = create_event_target_window::<T>();
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::<T>(thread_msg_target, runner_shared.clone());
@ -253,34 +247,246 @@ impl<T: 'static> EventLoop<T> {
where
F: 'static + FnMut(Event<'_, T>, &RootELW<T>, &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<F>(&mut self, mut event_handler: F) -> i32
pub fn run_return<F>(&mut self, event_handler: F) -> i32
where
F: FnMut(Event<'_, T>, &RootELW<T>, &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<F>(&mut self, mut event_handler: F) -> Result<(), RunLoopError>
where
F: FnMut(Event<'_, T>, &RootELW<T>, &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<F>(&mut self, mut event_handler: F) -> PumpStatus
where
F: FnMut(Event<'_, T>, &RootELW<T>, &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<Duration>) -> 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<Duration>) -> Option<PumpStatus> {
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<T: 'static> EventLoop<T> {
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<T> {
@ -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<Option<u32>> = Lazy::new(|| unsafe {
let mut caps = TIMECAPS {
wPeriodMin: 0,
wPeriodMax: 0,
};
if timeGetDevCaps(&mut caps, mem::size_of::<TIMECAPS>() 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<T: 'static> EventLoopProxy<T> {
}
}
type WaitUntilInstantBox = Box<Instant>;
/// 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<Box<dyn FnMut()>> 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<Instant>` 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<Force> {
}
}
/// 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<T: 'static>(
except: Option<HWND>,
runner: &EventLoopRunner<T>,
) -> 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<T: 'static>(runner: &EventLoopRunner<T>) {
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<T>(window: HWND, userdata: &WindowData<T>) {
@ -988,13 +1012,6 @@ unsafe fn public_window_callback_inner<T: 'static>(
lparam: LPARAM,
userdata: &WindowData<T>,
) -> 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<T: 'static>(
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<T: 'static>(
// 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<T: 'static>(
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<T: 'static>(
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),
};

View file

@ -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<T> = Cell<Option<Box<dyn FnMut(Event<'_, T>, &mut ControlFlow)
pub(crate) struct EventLoopRunner<T: 'static> {
// 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<bool>,
control_flow: Cell<ControlFlow>,
runner_state: Cell<RunnerState>,
@ -38,8 +39,6 @@ pub(crate) struct EventLoopRunner<T: 'static> {
event_handler: EventHandler<T>,
event_buffer: RefCell<VecDeque<BufferedEvent<T>>>,
owned_windows: Cell<HashSet<HWND>>,
panic_error: Cell<Option<PanicError>>,
}
@ -47,7 +46,7 @@ pub type PanicError = Box<dyn Any + Send + 'static>;
/// 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<T: 'static> {
}
impl<T> EventLoopRunner<T> {
pub(crate) fn new(thread_msg_target: HWND, wait_thread_id: u32) -> EventLoopRunner<T> {
pub(crate) fn new(thread_msg_target: HWND) -> EventLoopRunner<T> {
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<F>(&self, f: F)
where
F: FnMut(Event<'_, T>, &mut ControlFlow),
@ -93,18 +99,22 @@ impl<T> EventLoopRunner<T> {
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<T> EventLoopRunner<T> {
/// State retrieval functions.
impl<T> EventLoopRunner<T> {
#[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<T> EventLoopRunner<T> {
}
}
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<T> EventLoopRunner<T> {
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<T> EventLoopRunner<T> {
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<T> EventLoopRunner<T> {
.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<T> EventLoopRunner<T> {
});
}
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<T> EventLoopRunner<T> {
/// 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<T> EventLoopRunner<T> {
(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<T> EventLoopRunner<T> {
(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<T> EventLoopRunner<T> {
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<T> EventLoopRunner<T> {
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());
}

View file

@ -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(),

View file

@ -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()