diff --git a/CHANGELOG.md b/CHANGELOG.md index 4aa0b6de4e..38f8156e81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,10 @@ And please only add new entries to the top of this list, right below the `# Unre - On Web, remove unnecessary `Window::is_dark_mode()`, which was replaced with `Window::theme()`. - On Web, add `WindowBuilderExtWebSys::with_append()` to append the canvas element to the web page on creation. - On Windows, add `drag_resize_window` method support. +- **Breaking** `run() ->!` has been replaced by `run() -> Result<(), RunLoopError>` for returning errors without calling `std::process::exit()` ([#2767](https://github.com/rust-windowing/winit/pull/2767)) +- **Breaking** Removed `EventLoopExtRunReturn` / `run_return` in favor of `EventLoopExtPumpEvents` / `pump_events` and `EventLoopExtRunOnDemand` / `run_ondemand` ([#2767](https://github.com/rust-windowing/winit/pull/2767)) +- `RedrawRequested` is no longer guaranteed to be emitted after `MainEventsCleared`, it is now platform-specific when the event is emitted after being requested via `redraw_request()`. + - On Windows, `RedrawRequested` is now driven by `WM_PAINT` messages which are requested via `redraw_request()` # 0.29.0-beta.0 diff --git a/examples/child_window.rs b/examples/child_window.rs index 775c180c68..65135b79e0 100644 --- a/examples/child_window.rs +++ b/examples/child_window.rs @@ -3,7 +3,7 @@ mod fill; #[cfg(any(x11_platform, macos_platform, windows_platform))] -fn main() { +fn main() -> Result<(), impl std::error::Error> { use std::collections::HashMap; use raw_window_handle::HasRawWindowHandle; diff --git a/examples/control_flow.rs b/examples/control_flow.rs index fd39d4761b..49f00a0ddf 100644 --- a/examples/control_flow.rs +++ b/examples/control_flow.rs @@ -27,7 +27,7 @@ enum Mode { const WAIT_TIME: time::Duration = time::Duration::from_millis(100); const POLL_SLEEP_TIME: time::Duration = time::Duration::from_millis(100); -fn main() { +fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); println!("Press '1' to switch to Wait mode."); @@ -122,5 +122,5 @@ fn main() { } _ => (), } - }); + }) } diff --git a/examples/cursor.rs b/examples/cursor.rs index c9edf2a0cb..e7d3b9d52c 100644 --- a/examples/cursor.rs +++ b/examples/cursor.rs @@ -10,7 +10,7 @@ use winit::{ #[path = "util/fill.rs"] mod fill; -fn main() { +fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -54,7 +54,7 @@ fn main() { } _ => (), } - }); + }) } const CURSORS: &[CursorIcon] = &[ diff --git a/examples/cursor_grab.rs b/examples/cursor_grab.rs index 72ba14e56c..b69ab47c16 100644 --- a/examples/cursor_grab.rs +++ b/examples/cursor_grab.rs @@ -11,7 +11,7 @@ use winit::{ #[path = "util/fill.rs"] mod fill; -fn main() { +fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -73,5 +73,5 @@ fn main() { Event::RedrawRequested(_) => fill::fill_window(&window), _ => (), } - }); + }) } diff --git a/examples/custom_events.rs b/examples/custom_events.rs index b3a3d2176a..2e8f5472f3 100644 --- a/examples/custom_events.rs +++ b/examples/custom_events.rs @@ -1,7 +1,7 @@ #![allow(clippy::single_match)] #[cfg(not(wasm_platform))] -fn main() { +fn main() -> Result<(), impl std::error::Error> { use simple_logger::SimpleLogger; use winit::{ event::{Event, WindowEvent}, @@ -52,7 +52,7 @@ fn main() { } _ => (), } - }); + }) } #[cfg(wasm_platform)] diff --git a/examples/drag_window.rs b/examples/drag_window.rs index af5c824d43..55056001f7 100644 --- a/examples/drag_window.rs +++ b/examples/drag_window.rs @@ -11,7 +11,7 @@ use winit::{ #[path = "util/fill.rs"] mod fill; -fn main() { +fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -69,7 +69,7 @@ fn main() { } } _ => (), - }); + }) } fn name_windows(window_id: WindowId, switched: bool, window_1: &Window, window_2: &Window) { diff --git a/examples/fullscreen.rs b/examples/fullscreen.rs index e38390ebc8..9bfa3fd6d1 100644 --- a/examples/fullscreen.rs +++ b/examples/fullscreen.rs @@ -12,7 +12,7 @@ use winit::platform::macos::WindowExtMacOS; #[path = "util/fill.rs"] mod fill; -fn main() { +fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -131,5 +131,5 @@ fn main() { } _ => {} } - }); + }) } diff --git a/examples/handling_close.rs b/examples/handling_close.rs index c5d95471a6..5fa90283bd 100644 --- a/examples/handling_close.rs +++ b/examples/handling_close.rs @@ -11,7 +11,7 @@ use winit::{ #[path = "util/fill.rs"] mod fill; -fn main() { +fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -87,5 +87,5 @@ fn main() { } _ => (), } - }); + }) } diff --git a/examples/ime.rs b/examples/ime.rs index d108c8fc6d..319e06158a 100644 --- a/examples/ime.rs +++ b/examples/ime.rs @@ -13,7 +13,7 @@ use winit::{ #[path = "util/fill.rs"] mod fill; -fn main() { +fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new() .with_level(LevelFilter::Trace) .init() @@ -105,5 +105,5 @@ fn main() { } _ => (), } - }); + }) } diff --git a/examples/key_binding.rs b/examples/key_binding.rs index 7d4968f3a1..ef5d531931 100644 --- a/examples/key_binding.rs +++ b/examples/key_binding.rs @@ -17,7 +17,7 @@ fn main() { } #[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))] -fn main() { +fn main() -> Result<(), impl std::error::Error> { #[path = "util/fill.rs"] mod fill; @@ -61,5 +61,5 @@ fn main() { } _ => (), }; - }); + }) } diff --git a/examples/mouse_wheel.rs b/examples/mouse_wheel.rs index ea0c962bbe..38d06aff4d 100644 --- a/examples/mouse_wheel.rs +++ b/examples/mouse_wheel.rs @@ -10,7 +10,7 @@ use winit::{ #[path = "util/fill.rs"] mod fill; -fn main() { +fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -64,5 +64,5 @@ In other words, the deltas indicate the direction in which to move the content ( } _ => (), } - }); + }) } diff --git a/examples/multithreaded.rs b/examples/multithreaded.rs index cc4276364f..bd9f9004dd 100644 --- a/examples/multithreaded.rs +++ b/examples/multithreaded.rs @@ -1,7 +1,7 @@ #![allow(clippy::single_match)] #[cfg(not(wasm_platform))] -fn main() { +fn main() -> Result<(), impl std::error::Error> { use std::{collections::HashMap, sync::mpsc, thread, time::Duration}; use simple_logger::SimpleLogger; diff --git a/examples/multiwindow.rs b/examples/multiwindow.rs index 03812afc6f..7fcb724b2d 100644 --- a/examples/multiwindow.rs +++ b/examples/multiwindow.rs @@ -13,7 +13,7 @@ use winit::{ #[path = "util/fill.rs"] mod fill; -fn main() { +fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); diff --git a/examples/request_redraw.rs b/examples/request_redraw.rs index 16ec6ca7c2..a064cd39bb 100644 --- a/examples/request_redraw.rs +++ b/examples/request_redraw.rs @@ -10,7 +10,7 @@ use winit::{ #[path = "util/fill.rs"] mod fill; -fn main() { +fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -41,5 +41,5 @@ fn main() { } _ => (), } - }); + }) } diff --git a/examples/request_redraw_threaded.rs b/examples/request_redraw_threaded.rs index afe874978a..2010f96eef 100644 --- a/examples/request_redraw_threaded.rs +++ b/examples/request_redraw_threaded.rs @@ -1,7 +1,7 @@ #![allow(clippy::single_match)] #[cfg(not(wasm_platform))] -fn main() { +fn main() -> Result<(), impl std::error::Error> { use std::{sync::Arc, thread, time}; use simple_logger::SimpleLogger; @@ -49,7 +49,7 @@ fn main() { } _ => (), } - }); + }) } #[cfg(wasm_platform)] diff --git a/examples/resizable.rs b/examples/resizable.rs index b0c0f6ea7d..ecec14b228 100644 --- a/examples/resizable.rs +++ b/examples/resizable.rs @@ -12,7 +12,7 @@ use winit::{ #[path = "util/fill.rs"] mod fill; -fn main() { +fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -53,5 +53,5 @@ fn main() { } _ => (), }; - }); + }) } diff --git a/examples/startup_notification.rs b/examples/startup_notification.rs index 5de61401f9..fdd68d51c9 100644 --- a/examples/startup_notification.rs +++ b/examples/startup_notification.rs @@ -1,9 +1,5 @@ //! Demonstrates the use of startup notifications on Linux. -fn main() { - example::main(); -} - #[cfg(any(x11_platform, wayland_platform))] #[path = "./util/fill.rs"] mod fill; @@ -21,7 +17,7 @@ mod example { }; use winit::window::{Window, WindowBuilder, WindowId}; - pub(super) fn main() { + pub(super) fn main() -> Result<(), impl std::error::Error> { // Create the event loop and get the activation token. let event_loop = EventLoop::new(); let mut current_token = match event_loop.read_token_from_env() { @@ -115,13 +111,16 @@ mod example { } flow.set_wait(); - }); + }) } } +#[cfg(any(x11_platform, wayland_platform))] +fn main() -> Result<(), impl std::error::Error> { + example::main() +} + #[cfg(not(any(x11_platform, wayland_platform)))] -mod example { - pub(super) fn main() { - println!("This example is only supported on X11 and Wayland platforms."); - } +fn main() { + println!("This example is only supported on X11 and Wayland platforms."); } diff --git a/examples/theme.rs b/examples/theme.rs index 7b132a9d83..b3dac443ed 100644 --- a/examples/theme.rs +++ b/examples/theme.rs @@ -11,7 +11,7 @@ use winit::{ #[path = "util/fill.rs"] mod fill; -fn main() { +fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -75,5 +75,5 @@ fn main() { } _ => (), } - }); + }) } diff --git a/examples/timer.rs b/examples/timer.rs index 273cc638e1..13fceb51b8 100644 --- a/examples/timer.rs +++ b/examples/timer.rs @@ -16,7 +16,7 @@ use winit::{ #[path = "util/fill.rs"] mod fill; -fn main() { +fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -47,5 +47,5 @@ fn main() { } _ => (), } - }); + }) } diff --git a/examples/touchpad_gestures.rs b/examples/touchpad_gestures.rs index 2ebe83623b..0cca2c2cfd 100644 --- a/examples/touchpad_gestures.rs +++ b/examples/touchpad_gestures.rs @@ -8,7 +8,7 @@ use winit::{ #[path = "util/fill.rs"] mod fill; -fn main() { +fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -47,5 +47,5 @@ fn main() { } else if let Event::RedrawRequested(_) = event { fill::fill_window(&window); } - }); + }) } diff --git a/examples/transparent.rs b/examples/transparent.rs index 134be3adad..b093867d9d 100644 --- a/examples/transparent.rs +++ b/examples/transparent.rs @@ -10,7 +10,7 @@ use winit::{ #[path = "util/fill.rs"] mod fill; -fn main() { +fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -36,5 +36,5 @@ fn main() { } _ => (), } - }); + }) } diff --git a/examples/web.rs b/examples/web.rs index f2033d6d33..1e40676444 100644 --- a/examples/web.rs +++ b/examples/web.rs @@ -7,7 +7,7 @@ use winit::{ window::{Fullscreen, WindowBuilder}, }; -pub fn main() { +pub fn main() -> Result<(), impl std::error::Error> { let event_loop = EventLoop::new(); let builder = WindowBuilder::new().with_title("A fantastic window!"); @@ -56,7 +56,7 @@ pub fn main() { } _ => (), } - }); + }) } #[cfg(wasm_platform)] @@ -72,7 +72,7 @@ mod wasm { console_log::init_with_level(log::Level::Debug).expect("error initializing logger"); #[allow(clippy::main_recursion)] - super::main(); + let _ = super::main(); } pub fn insert_canvas_and_create_log_list(window: &Window) -> web_sys::Element { diff --git a/examples/web_aspect_ratio.rs b/examples/web_aspect_ratio.rs index b965f50728..e0f5ba161f 100644 --- a/examples/web_aspect_ratio.rs +++ b/examples/web_aspect_ratio.rs @@ -47,7 +47,7 @@ This example demonstrates the desired future functionality which will possibly b // Render once with the size info we currently have render_circle(&canvas, window.inner_size()); - event_loop.run(move |event, _, control_flow| { + let _ = event_loop.run(move |event, _, control_flow| { *control_flow = ControlFlow::Wait; match event { diff --git a/examples/window.rs b/examples/window.rs index 4a5d8b0068..d2764c2ad4 100644 --- a/examples/window.rs +++ b/examples/window.rs @@ -10,7 +10,7 @@ use winit::{ #[path = "util/fill.rs"] mod fill; -fn main() { +fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -37,5 +37,5 @@ fn main() { } _ => (), } - }); + }) } diff --git a/examples/window_buttons.rs b/examples/window_buttons.rs index 1f28412c4c..b531531ced 100644 --- a/examples/window_buttons.rs +++ b/examples/window_buttons.rs @@ -14,7 +14,7 @@ use winit::{ #[path = "util/fill.rs"] mod fill; -fn main() { +fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -71,5 +71,5 @@ fn main() { } _ => (), } - }); + }) } diff --git a/examples/window_debug.rs b/examples/window_debug.rs index d1b3ba3d7e..5c7f232671 100644 --- a/examples/window_debug.rs +++ b/examples/window_debug.rs @@ -14,7 +14,7 @@ use winit::{ #[path = "util/fill.rs"] mod fill; -fn main() { +fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -139,5 +139,5 @@ fn main() { } _ => (), } - }); + }) } diff --git a/examples/window_drag_resize.rs b/examples/window_drag_resize.rs index 277e7d4ca7..3232c4c807 100644 --- a/examples/window_drag_resize.rs +++ b/examples/window_drag_resize.rs @@ -13,7 +13,7 @@ const BORDER: f64 = 8.0; #[path = "util/fill.rs"] mod fill; -fn main() { +fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -74,7 +74,7 @@ fn main() { fill::fill_window(&window); } _ => (), - }); + }) } fn cursor_direction_icon(resize_direction: Option) -> CursorIcon { diff --git a/examples/window_icon.rs b/examples/window_icon.rs index e87372c208..6e65f34881 100644 --- a/examples/window_icon.rs +++ b/examples/window_icon.rs @@ -12,7 +12,7 @@ use winit::{ #[path = "util/fill.rs"] mod fill; -fn main() { +fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); // You'll have to choose an icon size at your own discretion. On X11, the desired size varies @@ -48,7 +48,7 @@ fn main() { } else if let Event::RedrawRequested(_) = event { fill::fill_window(&window); } - }); + }) } fn load_icon(path: &Path) -> Icon { diff --git a/examples/window_ondemand.rs b/examples/window_ondemand.rs new file mode 100644 index 0000000000..b1b3c56db2 --- /dev/null +++ b/examples/window_ondemand.rs @@ -0,0 +1,90 @@ +#![allow(clippy::single_match)] + +// Limit this example to only compatible platforms. +#[cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform,))] +fn main() -> Result<(), impl std::error::Error> { + use std::time::Duration; + + use simple_logger::SimpleLogger; + + use winit::{ + error::RunLoopError, + event::{Event, WindowEvent}, + event_loop::EventLoop, + platform::run_ondemand::EventLoopExtRunOnDemand, + window::{Window, WindowBuilder, WindowId}, + }; + + #[path = "util/fill.rs"] + mod fill; + + #[derive(Default)] + struct App { + window_id: Option, + window: Option, + } + + SimpleLogger::new().init().unwrap(); + let mut event_loop = EventLoop::new(); + + fn run_app(event_loop: &mut EventLoop<()>, idx: usize) -> Result<(), RunLoopError> { + let mut app = App::default(); + + event_loop.run_ondemand(move |event, event_loop, control_flow| { + control_flow.set_wait(); + println!("Run {idx}: {:?}", event); + + if let Some(window) = &app.window { + match event { + Event::WindowEvent { + event: WindowEvent::CloseRequested, + window_id, + } if window.id() == window_id => { + println!("--------------------------------------------------------- Window {idx} CloseRequested"); + app.window = None; + } + Event::MainEventsCleared => window.request_redraw(), + Event::RedrawRequested(_) => { + fill::fill_window(window); + } + _ => (), + } + } else if let Some(id) = app.window_id { + match event { + Event::WindowEvent { + event: WindowEvent::Destroyed, + window_id, + } if id == window_id => { + println!("--------------------------------------------------------- Window {idx} Destroyed"); + app.window_id = None; + control_flow.set_exit(); + } + _ => (), + } + } else if let Event::Resumed = event { + let window = WindowBuilder::new() + .with_title("Fantastic window number one!") + .with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0)) + .build(event_loop) + .unwrap(); + app.window_id = Some(window.id()); + app.window = Some(window); + } + }) + } + + run_app(&mut event_loop, 1)?; + + println!("--------------------------------------------------------- Finished first loop"); + println!("--------------------------------------------------------- Waiting 5 seconds"); + std::thread::sleep(Duration::from_secs(5)); + + let ret = run_app(&mut event_loop, 2); + println!("--------------------------------------------------------- Finished second loop"); + ret +} + +#[cfg(not(any(windows_platform, macos_platform, x11_platform, wayland_platform,)))] +fn main() { + println!("This example is not supported on this platform"); +} diff --git a/examples/window_option_as_alt.rs b/examples/window_option_as_alt.rs index fc2e458285..6d7792b540 100644 --- a/examples/window_option_as_alt.rs +++ b/examples/window_option_as_alt.rs @@ -18,7 +18,7 @@ mod fill; /// Prints the keyboard events characters received when option_is_alt is true versus false. /// A left mouse click will toggle option_is_alt. #[cfg(target_os = "macos")] -fn main() { +fn main() -> Result<(), impl std::error::Error> { let event_loop = EventLoop::new(); let window = WindowBuilder::new() @@ -66,7 +66,7 @@ fn main() { } _ => (), } - }); + }) } #[cfg(not(target_os = "macos"))] diff --git a/examples/window_run_return.rs b/examples/window_pump_events.rs similarity index 53% rename from examples/window_run_return.rs rename to examples/window_pump_events.rs index 2a2758d0de..be887bfa0e 100644 --- a/examples/window_run_return.rs +++ b/examples/window_pump_events.rs @@ -7,16 +7,15 @@ x11_platform, wayland_platform, android_platform, - orbital_platform, ))] -fn main() { - use std::{thread::sleep, time::Duration}; +fn main() -> std::process::ExitCode { + use std::{process::ExitCode, thread::sleep, time::Duration}; use simple_logger::SimpleLogger; use winit::{ event::{Event, WindowEvent}, - event_loop::EventLoop, - platform::run_return::EventLoopExtRunReturn, + event_loop::{ControlFlow, EventLoop}, + platform::pump_events::{EventLoopExtPumpEvents, PumpStatus}, window::WindowBuilder, }; @@ -31,11 +30,10 @@ fn main() { .build(&event_loop) .unwrap(); - let mut quit = false; - - while !quit { - event_loop.run_return(|event, _, control_flow| { - control_flow.set_wait(); + 'main: loop { + let timeout = Some(Duration::ZERO); + let status = event_loop.pump_events(timeout, |event, _, control_flow| { + *control_flow = ControlFlow::Wait; if let Event::WindowEvent { event, .. } = &event { // Print only Window events to reduce noise @@ -45,12 +43,10 @@ fn main() { match event { Event::WindowEvent { event: WindowEvent::CloseRequested, - .. - } => { - quit = true; - } + window_id, + } if window_id == window.id() => control_flow.set_exit(), Event::MainEventsCleared => { - control_flow.set_exit(); + window.request_redraw(); } Event::RedrawRequested(_) => { fill::fill_window(&window); @@ -58,14 +54,20 @@ fn main() { _ => (), } }); + if let PumpStatus::Exit(exit_code) = status { + break 'main ExitCode::from(exit_code as u8); + } - // Sleep for 1/60 second to simulate rendering - println!("rendering"); + // Sleep for 1/60 second to simulate application work + // + // Since `pump_events` doesn't block it will be important to + // throttle the loop in the app somehow. + println!("Update()"); sleep(Duration::from_millis(16)); } } -#[cfg(any(ios_platform, wasm_platform))] +#[cfg(any(ios_platform, wasm_platform, orbital_platform))] fn main() { - println!("This platform doesn't support run_return."); + println!("This platform doesn't support pump_events."); } diff --git a/examples/window_resize_increments.rs b/examples/window_resize_increments.rs index 1c713ba72c..42a4201810 100644 --- a/examples/window_resize_increments.rs +++ b/examples/window_resize_increments.rs @@ -11,7 +11,7 @@ use winit::{ #[path = "util/fill.rs"] mod fill; -fn main() { +fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -60,5 +60,5 @@ fn main() { } _ => (), } - }); + }) } diff --git a/examples/window_tabbing.rs b/examples/window_tabbing.rs index d264033597..1989a8f365 100644 --- a/examples/window_tabbing.rs +++ b/examples/window_tabbing.rs @@ -19,7 +19,7 @@ use winit::{ mod fill; #[cfg(target_os = "macos")] -fn main() { +fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); diff --git a/src/error.rs b/src/error.rs index c039f3b868..0a1cda9ed7 100644 --- a/src/error.rs +++ b/src/error.rs @@ -2,7 +2,8 @@ use std::{error, fmt}; use crate::platform_impl; -/// An error whose cause it outside Winit's control. +// TODO: Rename +/// An error that may be generated when requesting Winit state #[derive(Debug)] pub enum ExternalError { /// The operation is not supported by the backend. @@ -25,6 +26,19 @@ pub struct OsError { error: platform_impl::OsError, } +/// A general error that may occur while running the Winit event loop +#[derive(Debug)] +pub enum RunLoopError { + /// The operation is not supported by the backend. + NotSupported(NotSupportedError), + /// The OS cannot perform the operation. + Os(OsError), + /// The event loop can't be re-run while it's already running + AlreadyRunning, + /// Application has exit with an error status. + ExitFailure(i32), +} + impl NotSupportedError { #[inline] #[allow(dead_code)] @@ -77,6 +91,18 @@ impl fmt::Display for NotSupportedError { } } +impl fmt::Display for RunLoopError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + match self { + RunLoopError::AlreadyRunning => write!(f, "EventLoop is already running"), + RunLoopError::NotSupported(e) => e.fmt(f), + RunLoopError::Os(e) => e.fmt(f), + RunLoopError::ExitFailure(status) => write!(f, "Exit Failure: {status}"), + } + } +} + impl error::Error for OsError {} impl error::Error for ExternalError {} impl error::Error for NotSupportedError {} +impl error::Error for RunLoopError {} diff --git a/src/event_loop.rs b/src/event_loop.rs index 1485069d0c..87ec9535c2 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -18,6 +18,7 @@ use std::time::{Duration, Instant}; #[cfg(wasm_platform)] use web_time::{Duration, Instant}; +use crate::error::RunLoopError; use crate::{event::Event, monitor::MonitorHandle, platform_impl}; /// Provides a way to retrieve events from the system and from the windows that were registered to @@ -156,7 +157,7 @@ impl fmt::Debug for EventLoopWindowTarget { /// /// Almost every change is persistent between multiple calls to the event loop closure within a /// given run loop. The only exception to this is [`ExitWithCode`] which, once set, cannot be unset. -/// Changes are **not** persistent between multiple calls to `run_return` - issuing a new call will +/// Changes are **not** persistent between multiple calls to `run_ondemand` - issuing a new call will /// reset the control flow to [`Poll`]. /// /// [`ExitWithCode`]: Self::ExitWithCode @@ -285,23 +286,33 @@ impl EventLoop { EventLoopBuilder::::with_user_event().build() } - /// Hijacks the calling thread and initializes the winit event loop with the provided - /// closure. Since the closure is `'static`, it must be a `move` closure if it needs to + /// Runs the event loop in the calling thread and calls the given `event_handler` closure + /// to dispatch any pending events. + /// + /// Since the closure is `'static`, it must be a `move` closure if it needs to /// access any data from the calling context. /// /// See the [`ControlFlow`] docs for information on how changes to `&mut ControlFlow` impact the /// event loop's behavior. /// - /// Any values not passed to this function will *not* be dropped. - /// /// ## Platform-specific /// /// - **X11 / Wayland:** The program terminates with exit code 1 if the display server /// disconnects. + /// - **iOS:** Will never return to the caller and so values not passed to this function will + /// *not* be dropped before the process exits. + /// - **Web:** Will _act_ as if it never returns to the caller by throwing a Javascript exception + /// (that Rust doesn't see) that will also mean that the rest of the function is never executed + /// and any values not passed to this function will *not* be dropped. + /// + /// Web applications are recommended to use `spawn()` instead of `run()` to avoid the need + /// for the Javascript exception trick, and to make it clearer that the event loop runs + /// asynchronously (via the browser's own, internal, event loop) and doesn't block the + /// current thread of execution like it does on other platforms. /// /// [`ControlFlow`]: crate::event_loop::ControlFlow #[inline] - pub fn run(self, event_handler: F) -> ! + pub fn run(self, event_handler: F) -> Result<(), RunLoopError> where F: 'static + FnMut(Event<'_, T>, &EventLoopWindowTarget, &mut ControlFlow), { diff --git a/src/lib.rs b/src/lib.rs index 9d2a93b82f..ee08ec8cac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,7 +37,7 @@ //! Winit no longer uses a `EventLoop::poll_events() -> impl Iterator`-based event loop //! model, since that can't be implemented properly on some platforms (e.g web, iOS) and works poorly on //! most other platforms. However, this model can be re-implemented to an extent with -//! [`EventLoopExtRunReturn::run_return`]. See that method's documentation for more reasons about why +//! [`EventLoopExtPumpEvents::pump_events`]. See that method's documentation for more reasons about why //! it's discouraged, beyond compatibility reasons. //! //! @@ -109,7 +109,7 @@ //! window visible only once you're ready to render into it. //! //! [`EventLoop`]: event_loop::EventLoop -//! [`EventLoopExtRunReturn::run_return`]: ./platform/run_return/trait.EventLoopExtRunReturn.html#tymethod.run_return +//! [`EventLoopExtPumpEvents::pump_events`]: ./platform/pump_events/trait.EventLoopExtPumpEvents.html#tymethod.pump_events //! [`EventLoop::new()`]: event_loop::EventLoop::new //! [event_loop_run]: event_loop::EventLoop::run //! [`ControlFlow`]: event_loop::ControlFlow diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 9e01c4d993..40c2bce17d 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -9,9 +9,10 @@ //! - `windows` //! - `web` //! -//! And the following platform-specific module: +//! And the following platform-specific modules: //! -//! - `run_return` (available on `windows`, `unix`, `macos`, and `android`) +//! - `run_ondemand` (available on `windows`, `unix`, `macos`, `android`) +//! - `pump_events` (available on `windows`, `unix`, `macos`, `android`) //! //! However only the module corresponding to the platform you're compiling to will be available. @@ -34,14 +35,23 @@ pub mod windows; #[cfg(x11_platform)] pub mod x11; -pub mod modifier_supplement; #[cfg(any( windows_platform, macos_platform, android_platform, x11_platform, - wayland_platform, - orbital_platform + wayland_platform +))] +pub mod run_ondemand; + +#[cfg(any( + windows_platform, + macos_platform, + android_platform, + x11_platform, + wayland_platform ))] -pub mod run_return; +pub mod pump_events; + +pub mod modifier_supplement; pub mod scancode; diff --git a/src/platform/pump_events.rs b/src/platform/pump_events.rs new file mode 100644 index 0000000000..71f49de7e4 --- /dev/null +++ b/src/platform/pump_events.rs @@ -0,0 +1,197 @@ +use std::time::Duration; + +use crate::{ + event::Event, + event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget}, +}; + +/// The return status for `pump_events` +pub enum PumpStatus { + /// Continue running external loop. + Continue, + /// Exit external loop. + Exit(i32), +} + +/// Additional methods on [`EventLoop`] for pumping events within an external event loop +pub trait EventLoopExtPumpEvents { + /// A type provided by the user that can be passed through [`Event::UserEvent`]. + type UserEvent; + + /// Pump the `EventLoop` to check for and dispatch pending events. + /// + /// This API is designed to enable applications to integrate Winit into an + /// external event loop, for platforms that can support this. + /// + /// The given `timeout` limits how long it may block waiting for new events. + /// + /// Passing a `timeout` of `Some(Duration::ZERO)` would ensure your external + /// event loop is never blocked but you would likely need to consider how + /// to throttle your own external loop. + /// + /// Passing a `timeout` of `None` means that it may wait indefinitely for new + /// events before returning control back to the external loop. + /// + /// ## Example + /// + /// ```rust,no_run + /// # // Copied from examples/window_pump_events.rs + /// # #[cfg(any( + /// # windows_platform, + /// # macos_platform, + /// # x11_platform, + /// # wayland_platform, + /// # android_platform, + /// # ))] + /// fn main() -> std::process::ExitCode { + /// # use std::{process::ExitCode, thread::sleep, time::Duration}; + /// # + /// # use simple_logger::SimpleLogger; + /// # use winit::{ + /// # event::{Event, WindowEvent}, + /// # event_loop::EventLoop, + /// # platform::pump_events::{EventLoopExtPumpEvents, PumpStatus}, + /// # window::WindowBuilder, + /// # }; + /// let mut event_loop = EventLoop::new(); + /// # + /// # SimpleLogger::new().init().unwrap(); + /// let window = WindowBuilder::new() + /// .with_title("A fantastic window!") + /// .build(&event_loop) + /// .unwrap(); + /// + /// 'main: loop { + /// let timeout = Some(Duration::ZERO); + /// let status = event_loop.pump_events(timeout, |event, _, control_flow| { + /// # if let Event::WindowEvent { event, .. } = &event { + /// # // Print only Window events to reduce noise + /// # println!("{event:?}"); + /// # } + /// # + /// match event { + /// Event::WindowEvent { + /// event: WindowEvent::CloseRequested, + /// window_id, + /// } if window_id == window.id() => control_flow.set_exit(), + /// Event::MainEventsCleared => { + /// window.request_redraw(); + /// } + /// _ => (), + /// } + /// }); + /// if let PumpStatus::Exit(exit_code) = status { + /// break 'main ExitCode::from(exit_code as u8); + /// } + /// + /// // Sleep for 1/60 second to simulate application work + /// // + /// // Since `pump_events` doesn't block it will be important to + /// // throttle the loop in the app somehow. + /// println!("Update()"); + /// sleep(Duration::from_millis(16)); + /// } + /// } + /// ``` + /// + /// **Note:** This is not a portable API, and its usage involves a number of + /// caveats and trade offs that should be considered before using this API! + /// + /// You almost certainly shouldn't use this API, unless you absolutely know it's + /// the only practical option you have. + /// + /// ## Synchronous events + /// + /// Some events _must_ only be handled synchronously via the closure that + /// is passed to Winit so that the handler will also be synchronized with + /// the window system and operating system. + /// + /// This is because some events are driven by a window system callback + /// where the window systems expects the application to have handled the + /// event before returning. + /// + /// **These events can not be buffered and handled outside of the closure + /// passed to Winit.** + /// + /// As a general rule it is not recommended to ever buffer events to handle + /// them outside of the closure passed to Winit since it's difficult to + /// provide guarantees about which events are safe to buffer across all + /// operating systems. + /// + /// Notable events that will certainly create portability problems if + /// buffered and handled outside of Winit include: + /// - `RedrawRequested` events, used to schedule rendering. + /// + /// macOS for example uses a `drawRect` callback to drive rendering + /// within applications and expects rendering to be finished before + /// the `drawRect` callback returns. + /// + /// For portability it's strongly recommended that applications should + /// keep their rendering inside the closure provided to Winit. + /// - Any lifecycle events, such as `Suspended` / `Resumed`. + /// + /// The handling of these events needs to be synchronized with the + /// operating system and it would never be appropriate to buffer a + /// notification that your application has been suspended or resumed and + /// then handled that later since there would always be a chance that + /// other lifecycle events occur while the event is buffered. + /// + /// ## Supported Platforms + /// - Windows + /// - Linux + /// - MacOS + /// - Android + /// + /// ## Unsupported Platforms + /// - **Web:** This API is fundamentally incompatible with the event-based way in which + /// Web browsers work because it's not possible to have a long-running external + /// loop that would block the browser and there is nothing that can be + /// polled to ask for new new events. Events are delivered via callbacks based + /// on an event loop that is internal to the browser itself. + /// - **iOS:** It's not possible to stop and start an `NSApplication` repeatedly on iOS so + /// there's no way to support the same approach to polling as on MacOS. + /// + /// ## Platform-specific + /// - **Windows**: The implementation will use `PeekMessage` when checking for + /// window messages to avoid blocking your external event loop. + /// + /// - **MacOS**: The implementation works in terms of stopping the global `NSApp` + /// whenever the application `RunLoop` indicates that it is preparing to block + /// and wait for new events. + /// + /// This is very different to the polling APIs that are available on other + /// platforms (the lower level polling primitives on MacOS are private + /// implementation details for `NSApp` which aren't accessible to application + /// developers) + /// + /// It's likely this will be less efficient than polling on other OSs and + /// it also means the `NSApp` is stopped while outside of the Winit + /// event loop - and that's observable (for example to crates like `rfd`) + /// because the `NSApp` is global state. + /// + /// If you render outside of Winit you are likely to see window resizing artifacts + /// since MacOS expects applications to render synchronously during any `drawRect` + /// callback. + fn pump_events(&mut self, timeout: Option, event_handler: F) -> PumpStatus + where + F: FnMut( + Event<'_, Self::UserEvent>, + &EventLoopWindowTarget, + &mut ControlFlow, + ); +} + +impl EventLoopExtPumpEvents for EventLoop { + type UserEvent = T; + + fn pump_events(&mut self, timeout: Option, event_handler: F) -> PumpStatus + where + F: FnMut( + Event<'_, Self::UserEvent>, + &EventLoopWindowTarget, + &mut ControlFlow, + ), + { + self.event_loop.pump_events(timeout, event_handler) + } +} diff --git a/src/platform/run_ondemand.rs b/src/platform/run_ondemand.rs new file mode 100644 index 0000000000..9f4871db45 --- /dev/null +++ b/src/platform/run_ondemand.rs @@ -0,0 +1,82 @@ +use crate::{ + error::RunLoopError, + event::Event, + event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget}, +}; + +#[cfg(doc)] +use crate::{platform::pump_events::EventLoopExtPumpEvents, window::Window}; + +/// Additional methods on [`EventLoop`] to return control flow to the caller. +pub trait EventLoopExtRunOnDemand { + /// A type provided by the user that can be passed through [`Event::UserEvent`]. + type UserEvent; + + /// Runs the event loop in the calling thread and calls the given `event_handler` closure + /// to dispatch any window system events. + /// + /// Unlike [`EventLoop::run`], this function accepts non-`'static` (i.e. non-`move`) closures + /// and it is possible to return control back to the caller without + /// consuming the `EventLoop` (by setting the `control_flow` to [`ControlFlow::Exit`]) and + /// so the event loop can be re-run after it has exit. + /// + /// It's expected that each run of the loop will be for orthogonal instantiations of your + /// Winit application, but internally each instantiation may re-use some common window + /// system resources, such as a display server connection. + /// + /// This API is not designed to run an event loop in bursts that you can exit from and return + /// to while maintaining the full state of your application. (If you need something like this + /// you can look at the [`EventLoopExtPumpEvents::pump_events()`] API) + /// + /// Each time `run_ondemand` is called the `event_handler` can expect to receive a + /// `NewEvents(Init)` and `Resumed` event (even on platforms that have no suspend/resume + /// lifecycle) - which can be used to consistently initialize application state. + /// + /// See the [`ControlFlow`] docs for information on how changes to `&mut ControlFlow` impact the + /// event loop's behavior. + /// + /// # Caveats + /// - This extension isn't available on all platforms, since it's not always possible to + /// return to the caller (specifically this is impossible on iOS and Web - though with + /// the Web backend it is possible to use `spawn()` more than once instead). + /// - No [`Window`] state can be carried between separate runs of the event loop. + /// + /// You are strongly encouraged to use [`EventLoop::run()`] for portability, unless you specifically need + /// the ability to re-run a single event loop more than once + /// + /// # Supported Platforms + /// - Windows + /// - Linux + /// - macOS + /// - Android + /// + /// # Unsupported Platforms + /// - **Web:** This API is fundamentally incompatible with the event-based way in which + /// Web browsers work because it's not possible to have a long-running external + /// loop that would block the browser and there is nothing that can be + /// polled to ask for new events. Events are delivered via callbacks based + /// on an event loop that is internal to the browser itself. + /// - **iOS:** It's not possible to stop and start an `NSApplication` repeatedly on iOS. + fn run_ondemand(&mut self, event_handler: F) -> Result<(), RunLoopError> + where + F: FnMut( + Event<'_, Self::UserEvent>, + &EventLoopWindowTarget, + &mut ControlFlow, + ); +} + +impl EventLoopExtRunOnDemand for EventLoop { + type UserEvent = T; + + fn run_ondemand(&mut self, event_handler: F) -> Result<(), RunLoopError> + where + F: FnMut( + Event<'_, Self::UserEvent>, + &EventLoopWindowTarget, + &mut ControlFlow, + ), + { + self.event_loop.run_ondemand(event_handler) + } +} diff --git a/src/platform/run_return.rs b/src/platform/run_return.rs deleted file mode 100644 index 750b041618..0000000000 --- a/src/platform/run_return.rs +++ /dev/null @@ -1,53 +0,0 @@ -use crate::{ - event::Event, - event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget}, -}; - -/// Additional methods on [`EventLoop`] to return control flow to the caller. -pub trait EventLoopExtRunReturn { - /// A type provided by the user that can be passed through [`Event::UserEvent`]. - type UserEvent; - - /// Initializes the `winit` event loop. - /// - /// Unlike [`EventLoop::run`], this function accepts non-`'static` (i.e. non-`move`) closures - /// and returns control flow to the caller when `control_flow` is set to [`ControlFlow::Exit`]. - /// - /// # Caveats - /// - /// Despite its appearance at first glance, this is *not* a perfect replacement for - /// `poll_events`. For example, this function will not return on Windows or macOS while a - /// window is getting resized, resulting in all application logic outside of the - /// `event_handler` closure not running until the resize operation ends. Other OS operations - /// may also result in such freezes. This behavior is caused by fundamental limitations in the - /// underlying OS APIs, which cannot be hidden by `winit` without severe stability repercussions. - /// - /// You are strongly encouraged to use `run`, unless the use of this is absolutely necessary. - /// - /// ## Platform-specific - /// - /// - **X11 / Wayland:** This function returns `1` upon disconnection from - /// the display server. - fn run_return(&mut self, event_handler: F) -> i32 - where - F: FnMut( - Event<'_, Self::UserEvent>, - &EventLoopWindowTarget, - &mut ControlFlow, - ); -} - -impl EventLoopExtRunReturn for EventLoop { - type UserEvent = T; - - fn run_return(&mut self, event_handler: F) -> i32 - where - F: FnMut( - Event<'_, Self::UserEvent>, - &EventLoopWindowTarget, - &mut ControlFlow, - ), - { - self.event_loop.run_return(event_handler) - } -} diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index cd10d6b82e..e9c2506f33 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -19,22 +19,32 @@ use raw_window_handle::{ AndroidDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle, }; -use crate::platform_impl::Fullscreen; use crate::{ dpi::{PhysicalPosition, PhysicalSize, Position, Size}, error, event::{self, StartCause}, event_loop::{self, ControlFlow, EventLoopWindowTarget as RootELW}, keyboard::NativeKey, + platform::pump_events::PumpStatus, window::{ self, CursorGrabMode, ImePurpose, ResizeDirection, Theme, WindowButtons, WindowLevel, }, }; +use crate::{error::RunLoopError, platform_impl::Fullscreen}; mod keycodes; static HAS_FOCUS: Lazy> = Lazy::new(|| RwLock::new(true)); +/// Returns the minimum `Option`, taking into account that `None` +/// equates to an infinite timeout, not a zero timeout (so can't just use +/// `Option::min`) +fn min_timeout(a: Option, b: Option) -> Option { + a.map_or(b, |a_timeout| { + b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout))) + }) +} + struct PeekableReceiver { recv: mpsc::Receiver, first: Option, @@ -135,7 +145,11 @@ pub struct EventLoop { redraw_flag: SharedFlag, user_events_sender: mpsc::Sender, user_events_receiver: PeekableReceiver, //must wake looper whenever something gets sent + loop_running: bool, // Dispatched `NewEvents` running: bool, + pending_redraw: bool, + control_flow: ControlFlow, + cause: StartCause, ignore_volume_keys: bool, } @@ -171,12 +185,6 @@ fn sticky_exit_callback( } } -struct IterationResult { - deadline: Option, - timeout: Option, - wait_start: Instant, -} - impl EventLoop { pub(crate) fn new(attributes: &PlatformSpecificEventLoopAttributes) -> Self { let (user_events_sender, user_events_receiver) = mpsc::channel(); @@ -200,33 +208,33 @@ impl EventLoop { redraw_flag, user_events_sender, user_events_receiver: PeekableReceiver::from_recv(user_events_receiver), + loop_running: false, running: false, + pending_redraw: false, + control_flow: Default::default(), + cause: StartCause::Init, ignore_volume_keys: attributes.ignore_volume_keys, } } - fn single_iteration( - &mut self, - control_flow: &mut ControlFlow, - main_event: Option>, - pending_redraw: &mut bool, - cause: &mut StartCause, - callback: &mut F, - ) -> IterationResult + fn single_iteration(&mut self, main_event: Option>, callback: &mut F) where F: FnMut(event::Event<'_, T>, &RootELW, &mut ControlFlow), { trace!("Mainloop iteration"); + let cause = self.cause; + let mut control_flow = self.control_flow; + let mut pending_redraw = self.pending_redraw; + let mut resized = false; + sticky_exit_callback( - event::Event::NewEvents(*cause), + event::Event::NewEvents(cause), self.window_target(), - control_flow, + &mut control_flow, callback, ); - let mut resized = false; - if let Some(event) = main_event { trace!("Handling main event {:?}", event); @@ -235,7 +243,7 @@ impl EventLoop { sticky_exit_callback( event::Event::Resumed, self.window_target(), - control_flow, + &mut control_flow, callback, ); } @@ -243,12 +251,12 @@ impl EventLoop { sticky_exit_callback( event::Event::Suspended, self.window_target(), - control_flow, + &mut control_flow, callback, ); } MainEvent::WindowResized { .. } => resized = true, - MainEvent::RedrawNeeded { .. } => *pending_redraw = true, + MainEvent::RedrawNeeded { .. } => pending_redraw = true, MainEvent::ContentRectChanged { .. } => { warn!("TODO: find a way to notify application of content rect change"); } @@ -260,7 +268,7 @@ impl EventLoop { event: event::WindowEvent::Focused(true), }, self.window_target(), - control_flow, + &mut control_flow, callback, ); } @@ -272,7 +280,7 @@ impl EventLoop { event: event::WindowEvent::Focused(false), }, self.window_target(), - control_flow, + &mut control_flow, callback, ); } @@ -289,7 +297,12 @@ impl EventLoop { scale_factor, }, }; - sticky_exit_callback(event, self.window_target(), control_flow, callback); + sticky_exit_callback( + event, + self.window_target(), + &mut control_flow, + callback, + ); } } MainEvent::LowMemory => { @@ -398,7 +411,7 @@ impl EventLoop { sticky_exit_callback( event, self.window_target(), - control_flow, + &mut control_flow, callback ); } @@ -446,7 +459,7 @@ impl EventLoop { sticky_exit_callback( event, self.window_target(), - control_flow, + &mut control_flow, callback, ); } @@ -465,7 +478,7 @@ impl EventLoop { sticky_exit_callback( crate::event::Event::UserEvent(event), self.window_target(), - control_flow, + &mut control_flow, callback, ); } @@ -474,7 +487,7 @@ impl EventLoop { sticky_exit_callback( event::Event::MainEventsCleared, self.window_target(), - control_flow, + &mut control_flow, callback, ); @@ -491,163 +504,179 @@ impl EventLoop { window_id: window::WindowId(WindowId), event: event::WindowEvent::Resized(size), }; - sticky_exit_callback(event, self.window_target(), control_flow, callback); + sticky_exit_callback(event, self.window_target(), &mut control_flow, callback); } - *pending_redraw |= self.redraw_flag.get_and_reset(); - if *pending_redraw { - *pending_redraw = false; + pending_redraw |= self.redraw_flag.get_and_reset(); + if pending_redraw { + pending_redraw = false; let event = event::Event::RedrawRequested(window::WindowId(WindowId)); - sticky_exit_callback(event, self.window_target(), control_flow, callback); + sticky_exit_callback(event, self.window_target(), &mut control_flow, callback); } } sticky_exit_callback( event::Event::RedrawEventsCleared, self.window_target(), - control_flow, + &mut control_flow, callback, ); - let start = Instant::now(); - let (deadline, timeout); - - match control_flow { - ControlFlow::ExitWithCode(_) => { - deadline = None; - timeout = None; - } - ControlFlow::Poll => { - *cause = StartCause::Poll; - deadline = None; - timeout = Some(Duration::from_millis(0)); - } - ControlFlow::Wait => { - *cause = StartCause::WaitCancelled { - start, - requested_resume: None, - }; - deadline = None; - timeout = None; - } - ControlFlow::WaitUntil(wait_deadline) => { - *cause = StartCause::ResumeTimeReached { - start, - requested_resume: *wait_deadline, - }; - timeout = if *wait_deadline > start { - Some(*wait_deadline - start) - } else { - Some(Duration::from_millis(0)) - }; - deadline = Some(*wait_deadline); - } - } - - IterationResult { - wait_start: start, - deadline, - timeout, - } + self.control_flow = control_flow; + self.pending_redraw = pending_redraw; } - pub fn run(mut self, event_handler: F) -> ! + pub fn run(mut self, event_handler: F) -> Result<(), RunLoopError> where F: 'static + FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget, &mut ControlFlow), { - let exit_code = self.run_return(event_handler); - ::std::process::exit(exit_code); + self.run_ondemand(event_handler) } - pub fn run_return(&mut self, mut callback: F) -> i32 + pub fn run_ondemand(&mut self, mut event_handler: F) -> Result<(), RunLoopError> where - F: FnMut(event::Event<'_, T>, &RootELW, &mut ControlFlow), + F: FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget, &mut ControlFlow), { - let mut control_flow = ControlFlow::default(); - let mut cause = StartCause::Init; - let mut pending_redraw = false; - - // run the initial loop iteration - let mut iter_result = self.single_iteration( - &mut control_flow, - None, - &mut pending_redraw, - &mut cause, - &mut callback, - ); + if self.loop_running { + return Err(RunLoopError::AlreadyRunning); + } - let exit_code = loop { - if let ControlFlow::ExitWithCode(code) = control_flow { - break code; + loop { + match self.pump_events(None, &mut event_handler) { + PumpStatus::Exit(0) => { + break Ok(()); + } + PumpStatus::Exit(code) => { + break Err(RunLoopError::ExitFailure(code)); + } + _ => { + continue; + } } + } + } - let mut timeout = iter_result.timeout; + pub fn pump_events(&mut self, timeout: Option, mut callback: F) -> PumpStatus + where + F: FnMut(event::Event<'_, T>, &RootELW, &mut ControlFlow), + { + if !self.loop_running { + self.loop_running = true; + + // Reset the internal state for the loop as we start running to + // ensure consistent behaviour in case the loop runs and exits more + // than once + self.pending_redraw = false; + self.cause = StartCause::Init; + self.control_flow = ControlFlow::Poll; + + // run the initial loop iteration + self.single_iteration(None, &mut callback); + } - // If we already have work to do then we don't want to block on the next poll... - pending_redraw |= self.redraw_flag.get_and_reset(); - if self.running && (pending_redraw || self.user_events_receiver.has_incoming()) { - timeout = Some(Duration::from_millis(0)) - } + // Consider the possibility that the `StartCause::Init` iteration could + // request to Exit + if !matches!(self.control_flow, ControlFlow::ExitWithCode(_)) { + self.poll_events_with_timeout(timeout, &mut callback); + } + if let ControlFlow::ExitWithCode(code) = self.control_flow { + self.loop_running = false; + + let mut dummy = self.control_flow; + sticky_exit_callback( + event::Event::LoopDestroyed, + self.window_target(), + &mut dummy, + &mut callback, + ); + + PumpStatus::Exit(code) + } else { + PumpStatus::Continue + } + } - let app = self.android_app.clone(); // Don't borrow self as part of poll expression - app.poll_events(timeout, |poll_event| { - let mut main_event = None; - - match poll_event { - android_activity::PollEvent::Wake => { - // In the X11 backend it's noted that too many false-positive wake ups - // would cause the event loop to run continuously. They handle this by re-checking - // for pending events (assuming they cover all valid reasons for a wake up). - // - // For now, user_events and redraw_requests are the only reasons to expect - // a wake up here so we can ignore the wake up if there are no events/requests. - // We also ignore wake ups while suspended. - pending_redraw |= self.redraw_flag.get_and_reset(); - if !self.running - || (!pending_redraw && !self.user_events_receiver.has_incoming()) - { - return; - } - } - android_activity::PollEvent::Timeout => {} - android_activity::PollEvent::Main(event) => { - main_event = Some(event); + fn poll_events_with_timeout(&mut self, mut timeout: Option, mut callback: F) + where + F: FnMut(event::Event<'_, T>, &RootELW, &mut ControlFlow), + { + let start = Instant::now(); + + self.pending_redraw |= self.redraw_flag.get_and_reset(); + + timeout = + if self.running && (self.pending_redraw || self.user_events_receiver.has_incoming()) { + // If we already have work to do then we don't want to block on the next poll + Some(Duration::ZERO) + } else { + let control_flow_timeout = match self.control_flow { + ControlFlow::Wait => None, + ControlFlow::Poll => Some(Duration::ZERO), + ControlFlow::WaitUntil(wait_deadline) => { + Some(wait_deadline.saturating_duration_since(start)) } - unknown_event => { - warn!("Unknown poll event {unknown_event:?} (ignored)"); + // `ExitWithCode()` will be reset to `Poll` before polling + ControlFlow::ExitWithCode(_code) => unreachable!(), + }; + + min_timeout(control_flow_timeout, timeout) + }; + + let app = self.android_app.clone(); // Don't borrow self as part of poll expression + app.poll_events(timeout, |poll_event| { + let mut main_event = None; + + match poll_event { + android_activity::PollEvent::Wake => { + // In the X11 backend it's noted that too many false-positive wake ups + // would cause the event loop to run continuously. They handle this by re-checking + // for pending events (assuming they cover all valid reasons for a wake up). + // + // For now, user_events and redraw_requests are the only reasons to expect + // a wake up here so we can ignore the wake up if there are no events/requests. + // We also ignore wake ups while suspended. + self.pending_redraw |= self.redraw_flag.get_and_reset(); + if !self.running + || (!self.pending_redraw && !self.user_events_receiver.has_incoming()) + { + return; } } - - let wait_cancelled = iter_result - .deadline - .map_or(false, |deadline| Instant::now() < deadline); - - if wait_cancelled { - cause = StartCause::WaitCancelled { - start: iter_result.wait_start, - requested_resume: iter_result.deadline, - }; + android_activity::PollEvent::Timeout => {} + android_activity::PollEvent::Main(event) => { + main_event = Some(event); } + unknown_event => { + warn!("Unknown poll event {unknown_event:?} (ignored)"); + } + } - iter_result = self.single_iteration( - &mut control_flow, - main_event, - &mut pending_redraw, - &mut cause, - &mut callback, - ); - }); - }; - - sticky_exit_callback( - event::Event::LoopDestroyed, - self.window_target(), - &mut control_flow, - &mut callback, - ); + self.cause = match self.control_flow { + ControlFlow::Poll => StartCause::Poll, + ControlFlow::Wait => StartCause::WaitCancelled { + start, + requested_resume: None, + }, + ControlFlow::WaitUntil(deadline) => { + if Instant::now() < deadline { + StartCause::WaitCancelled { + start, + requested_resume: Some(deadline), + } + } else { + StartCause::ResumeTimeReached { + start, + requested_resume: deadline, + } + } + } + // `ExitWithCode()` will be reset to `Poll` before polling + ControlFlow::ExitWithCode(_code) => unreachable!(), + }; - exit_code + self.single_iteration(main_event, &mut callback); + }); } pub fn window_target(&self) -> &event_loop::EventLoopWindowTarget { diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 47c57700a5..4c2a43c7c5 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -19,6 +19,7 @@ use std::{ use once_cell::sync::Lazy; use raw_window_handle::{RawDisplayHandle, RawWindowHandle}; use smol_str::SmolStr; +use std::time::Duration; #[cfg(x11_platform)] pub use self::x11::XNotSupported; @@ -28,7 +29,7 @@ use self::x11::{ffi::XVisualInfo, util::WindowType as XWindowType, X11Error, XCo use crate::platform::x11::XlibErrorHook; use crate::{ dpi::{PhysicalPosition, PhysicalSize, Position, Size}, - error::{ExternalError, NotSupportedError, OsError as RootOsError}, + error::{ExternalError, NotSupportedError, OsError as RootOsError, RunLoopError}, event::{Event, KeyEvent}, event_loop::{ AsyncRequestSerial, ControlFlow, DeviceEvents, EventLoopClosed, @@ -36,7 +37,10 @@ use crate::{ }, icon::Icon, keyboard::{Key, KeyCode}, - platform::{modifier_supplement::KeyEventExtModifierSupplement, scancode::KeyCodeExtScancode}, + platform::{ + modifier_supplement::KeyEventExtModifierSupplement, pump_events::PumpStatus, + scancode::KeyCodeExtScancode, + }, window::{ ActivationToken, CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes, WindowButtons, WindowLevel, @@ -826,18 +830,25 @@ impl EventLoop { x11_or_wayland!(match self; EventLoop(evlp) => evlp.create_proxy(); as EventLoopProxy) } - pub fn run_return(&mut self, callback: F) -> i32 + pub fn run(mut self, callback: F) -> Result<(), RunLoopError> where F: FnMut(crate::event::Event<'_, T>, &RootELW, &mut ControlFlow), { - x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_return(callback)) + self.run_ondemand(callback) } - pub fn run(self, callback: F) -> ! + pub fn run_ondemand(&mut self, callback: F) -> Result<(), RunLoopError> where - F: 'static + FnMut(crate::event::Event<'_, T>, &RootELW, &mut ControlFlow), + F: FnMut(crate::event::Event<'_, T>, &RootELW, &mut ControlFlow), { - x11_or_wayland!(match self; EventLoop(evlp) => evlp.run(callback)) + x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_ondemand(callback)) + } + + pub fn pump_events(&mut self, timeout: Option, callback: F) -> PumpStatus + where + F: FnMut(crate::event::Event<'_, T>, &RootELW, &mut ControlFlow), + { + x11_or_wayland!(match self; EventLoop(evlp) => evlp.pump_events(timeout, callback)) } pub fn window_target(&self) -> &crate::event_loop::EventLoopWindowTarget { @@ -933,6 +944,15 @@ fn sticky_exit_callback( } } +/// Returns the minimum `Option`, taking into account that `None` +/// equates to an infinite timeout, not a zero timeout (so can't just use +/// `Option::min`) +fn min_timeout(a: Option, b: Option) -> Option { + a.map_or(b, |a_timeout| { + b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout))) + }) +} + #[cfg(target_os = "linux")] fn is_main_thread() -> bool { rustix::thread::gettid() == rustix::process::getpid() diff --git a/src/platform_impl/linux/wayland/event_loop/mod.rs b/src/platform_impl/linux/wayland/event_loop/mod.rs index 6ddb457115..053b0f26a9 100644 --- a/src/platform_impl/linux/wayland/event_loop/mod.rs +++ b/src/platform_impl/linux/wayland/event_loop/mod.rs @@ -5,7 +5,6 @@ use std::error::Error; use std::io::Result as IOResult; use std::marker::PhantomData; use std::mem; -use std::process; use std::rc::Rc; use std::sync::atomic::Ordering; use std::time::{Duration, Instant}; @@ -17,10 +16,13 @@ use sctk::reexports::client::globals; use sctk::reexports::client::{Connection, Proxy, QueueHandle, WaylandSource}; use crate::dpi::{LogicalSize, PhysicalSize}; +use crate::error::{OsError as RootOsError, RunLoopError}; use crate::event::{Event, StartCause, WindowEvent}; use crate::event_loop::{ControlFlow, EventLoopWindowTarget as RootEventLoopWindowTarget}; +use crate::platform::pump_events::PumpStatus; +use crate::platform_impl::platform::min_timeout; use crate::platform_impl::platform::sticky_exit_callback; -use crate::platform_impl::EventLoopWindowTarget as PlatformEventLoopWindowTarget; +use crate::platform_impl::{EventLoopWindowTarget as PlatformEventLoopWindowTarget, OsError}; mod proxy; pub mod sink; @@ -35,6 +37,16 @@ type WaylandDispatcher = calloop::Dispatcher<'static, WaylandSource, /// The Wayland event loop. pub struct EventLoop { + /// Has `run` or `run_ondemand` been called or a call to `pump_events` that starts the loop + loop_running: bool, + + /// The application's latest control_flow state + control_flow: ControlFlow, + + buffer_sink: EventSink, + compositor_updates: Vec, + window_ids: Vec, + /// Sender of user events. user_events_sender: calloop::channel::Sender, @@ -114,6 +126,11 @@ impl EventLoop { }; let event_loop = Self { + loop_running: false, + control_flow: ControlFlow::default(), + compositor_updates: Vec::new(), + buffer_sink: EventSink::default(), + window_ids: Vec::new(), connection, wayland_dispatcher, user_events_sender, @@ -128,330 +145,398 @@ impl EventLoop { Ok(event_loop) } - pub fn run(mut self, callback: F) -> ! + pub fn run_ondemand(&mut self, mut event_handler: F) -> Result<(), RunLoopError> where - F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget, &mut ControlFlow) + 'static, + F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget, &mut ControlFlow), { - let exit_code = self.run_return(callback); - process::exit(exit_code); + if self.loop_running { + return Err(RunLoopError::AlreadyRunning); + } + + let exit = loop { + match self.pump_events(None, &mut event_handler) { + PumpStatus::Exit(0) => { + break Ok(()); + } + PumpStatus::Exit(code) => { + break Err(RunLoopError::ExitFailure(code)); + } + _ => { + continue; + } + } + }; + + // Applications aren't allowed to carry windows between separate + // `run_ondemand` calls but if they have only just dropped their + // windows we need to make sure those last requests are sent to the + // compositor. + let _ = self.roundtrip().map_err(RunLoopError::Os); + + exit } - pub fn run_return(&mut self, mut callback: F) -> i32 + pub fn pump_events(&mut self, timeout: Option, mut callback: F) -> PumpStatus where F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget, &mut ControlFlow), { - let mut control_flow = ControlFlow::Poll; - - // XXX preallocate certian structures to avoid allocating on each loop iteration. - let mut window_ids = Vec::::new(); - let mut compositor_updates = Vec::::new(); - let mut buffer_sink = EventSink::new(); + if !self.loop_running { + self.loop_running = true; + + // Reset the internal state for the loop as we start running to + // ensure consistent behaviour in case the loop runs and exits more + // than once. + self.control_flow = ControlFlow::Poll; + + // Run the initial loop iteration. + self.single_iteration(&mut callback, StartCause::Init); + } + + // Consider the possibility that the `StartCause::Init` iteration could + // request to Exit. + if !matches!(self.control_flow, ControlFlow::ExitWithCode(_)) { + self.poll_events_with_timeout(timeout, &mut callback); + } + if let ControlFlow::ExitWithCode(code) = self.control_flow { + self.loop_running = false; + + let mut dummy = self.control_flow; + sticky_exit_callback( + Event::LoopDestroyed, + self.window_target(), + &mut dummy, + &mut callback, + ); - callback( - Event::NewEvents(StartCause::Init), - &self.window_target, - &mut control_flow, - ); + PumpStatus::Exit(code) + } else { + PumpStatus::Continue + } + } - // XXX For consistency all platforms must emit a 'Resumed' event even though Wayland - // applications don't themselves have a formal suspend/resume lifecycle. - callback(Event::Resumed, &self.window_target, &mut control_flow); - - // XXX We break on errors from dispatches, since if we've got protocol error - // libwayland-client/wayland-rs will inform us anyway, but crashing downstream is not - // really an option. Instead we inform that the event loop got destroyed. We may - // communicate an error that something was terminated, but winit doesn't provide us - // with an API to do that via some event. - // Still, we set the exit code to the error's OS error code, or to 1 if not possible. - let exit_code = loop { - // Flush the connection. - let _ = self.connection.flush(); - - // During the run of the user callback, some other code monitoring and reading the - // Wayland socket may have been run (mesa for example does this with vsync), if that - // is the case, some events may have been enqueued in our event queue. - // - // If some messages are there, the event loop needs to behave as if it was instantly - // woken up by messages arriving from the Wayland socket, to avoid delaying the - // dispatch of these events until we're woken up again. - let instant_wakeup = { - let mut wayland_source = self.wayland_dispatcher.as_source_mut(); - let queue = wayland_source.queue(); - let state = match &mut self.window_target.p { - PlatformEventLoopWindowTarget::Wayland(window_target) => { - window_target.state.get_mut() - } - #[cfg(x11_platform)] - _ => unreachable!(), - }; - - match queue.dispatch_pending(state) { - Ok(dispatched) => dispatched > 0, - Err(error) => { - error!("Error dispatching wayland queue: {}", error); - break 1; - } + pub fn poll_events_with_timeout(&mut self, mut timeout: Option, mut callback: F) + where + F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget, &mut ControlFlow), + { + let start = Instant::now(); + + // TODO(rib): remove this workaround and instead make sure that the calloop + // WaylandSource correctly implements the cooperative prepare_read protocol + // that support multithreaded wayland clients that may all read from the + // same socket. + // + // During the run of the user callback, some other code monitoring and reading the + // Wayland socket may have been run (mesa for example does this with vsync), if that + // is the case, some events may have been enqueued in our event queue. + // + // If some messages are there, the event loop needs to behave as if it was instantly + // woken up by messages arriving from the Wayland socket, to avoid delaying the + // dispatch of these events until we're woken up again. + let instant_wakeup = { + let mut wayland_source = self.wayland_dispatcher.as_source_mut(); + let queue = wayland_source.queue(); + let state = match &mut self.window_target.p { + PlatformEventLoopWindowTarget::Wayland(window_target) => { + window_target.state.get_mut() } + #[cfg(x11_platform)] + _ => unreachable!(), }; - match control_flow { - ControlFlow::ExitWithCode(code) => break code, - ControlFlow::Poll => { - // Non-blocking dispatch. - let timeout = Duration::ZERO; - if let Err(error) = self.loop_dispatch(Some(timeout)) { - break error.raw_os_error().unwrap_or(1); - } - - callback( - Event::NewEvents(StartCause::Poll), - &self.window_target, - &mut control_flow, - ); + match queue.dispatch_pending(state) { + Ok(dispatched) => dispatched > 0, + Err(error) => { + error!("Error dispatching wayland queue: {}", error); + self.control_flow = ControlFlow::ExitWithCode(1); + return; } - ControlFlow::Wait => { - let timeout = if instant_wakeup { - Some(Duration::ZERO) - } else { - None - }; - - if let Err(error) = self.loop_dispatch(timeout) { - break error.raw_os_error().unwrap_or(1); - } + } + }; - callback( - Event::NewEvents(StartCause::WaitCancelled { - start: Instant::now(), - requested_resume: None, - }), - &self.window_target, - &mut control_flow, - ); + timeout = if instant_wakeup { + Some(Duration::ZERO) + } else { + let control_flow_timeout = match self.control_flow { + ControlFlow::Wait => None, + ControlFlow::Poll => Some(Duration::ZERO), + ControlFlow::WaitUntil(wait_deadline) => { + Some(wait_deadline.saturating_duration_since(start)) } - ControlFlow::WaitUntil(deadline) => { - let start = Instant::now(); - - // Compute the amount of time we'll block for. - let duration = if deadline > start && !instant_wakeup { - deadline - start - } else { - Duration::ZERO - }; - - if let Err(error) = self.loop_dispatch(Some(duration)) { - break error.raw_os_error().unwrap_or(1); - } + // This function shouldn't have to handle any requests to exit + // the application (there should be no need to poll for events + // if the application has requested to exit) so we consider + // it a bug in the backend if we ever see `ExitWithCode` here. + ControlFlow::ExitWithCode(_code) => unreachable!(), + }; + min_timeout(control_flow_timeout, timeout) + }; - let now = Instant::now(); - - if now < deadline { - callback( - Event::NewEvents(StartCause::WaitCancelled { - start, - requested_resume: Some(deadline), - }), - &self.window_target, - &mut control_flow, - ) - } else { - callback( - Event::NewEvents(StartCause::ResumeTimeReached { - start, - requested_resume: deadline, - }), - &self.window_target, - &mut control_flow, - ) + // NOTE Ideally we should flush as the last thing we do before polling + // to wait for events, and this should be done by the calloop + // WaylandSource but we currently need to flush writes manually. + let _ = self.connection.flush(); + + if let Err(error) = self.loop_dispatch(timeout) { + // NOTE We exit on errors from dispatches, since if we've got protocol error + // libwayland-client/wayland-rs will inform us anyway, but crashing downstream is not + // really an option. Instead we inform that the event loop got destroyed. We may + // communicate an error that something was terminated, but winit doesn't provide us + // with an API to do that via some event. + // Still, we set the exit code to the error's OS error code, or to 1 if not possible. + let exit_code = error.raw_os_error().unwrap_or(1); + self.control_flow = ControlFlow::ExitWithCode(exit_code); + return; + } + + // NB: `StartCause::Init` is handled as a special case and doesn't need + // to be considered here + let cause = match self.control_flow { + ControlFlow::Poll => StartCause::Poll, + ControlFlow::Wait => StartCause::WaitCancelled { + start, + requested_resume: None, + }, + ControlFlow::WaitUntil(deadline) => { + if Instant::now() < deadline { + StartCause::WaitCancelled { + start, + requested_resume: Some(deadline), + } + } else { + StartCause::ResumeTimeReached { + start, + requested_resume: deadline, } } } + // This function shouldn't have to handle any requests to exit + // the application (there should be no need to poll for events + // if the application has requested to exit) so we consider + // it a bug in the backend if we ever see `ExitWithCode` here. + ControlFlow::ExitWithCode(_code) => unreachable!(), + }; + + self.single_iteration(&mut callback, cause); + } + + fn single_iteration(&mut self, mut callback: &mut F, cause: StartCause) + where + F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget, &mut ControlFlow), + { + // NOTE currently just indented to simplify the diff + + let mut control_flow = self.control_flow; + + // We retain these grow-only scratch buffers as part of the EventLoop + // for the sake of avoiding lots of reallocs. We take them here to avoid + // trying to mutably borrow `self` more than once and we swap them back + // when finished. + let mut compositor_updates = std::mem::take(&mut self.compositor_updates); + let mut buffer_sink = std::mem::take(&mut self.buffer_sink); + let mut window_ids = std::mem::take(&mut self.window_ids); + + sticky_exit_callback( + Event::NewEvents(cause), + &self.window_target, + &mut control_flow, + callback, + ); + + // NB: For consistency all platforms must emit a 'resumed' event even though Wayland + // applications don't themselves have a formal suspend/resume lifecycle. + if cause == StartCause::Init { + sticky_exit_callback( + Event::Resumed, + &self.window_target, + &mut control_flow, + callback, + ); + } + + // Handle pending user events. We don't need back buffer, since we can't dispatch + // user events indirectly via callback to the user. + for user_event in self.pending_user_events.borrow_mut().drain(..) { + sticky_exit_callback( + Event::UserEvent(user_event), + &self.window_target, + &mut control_flow, + &mut callback, + ); + } + + // Drain the pending compositor updates. + self.with_state(|state| compositor_updates.append(&mut state.window_compositor_updates)); + + for mut compositor_update in compositor_updates.drain(..) { + let window_id = compositor_update.window_id; + if let Some(scale_factor) = compositor_update.scale_factor { + let mut physical_size = self.with_state(|state| { + let windows = state.windows.get_mut(); + let mut window = windows.get(&window_id).unwrap().lock().unwrap(); + + // Set the new scale factor. + window.set_scale_factor(scale_factor); + let window_size = compositor_update.size.unwrap_or(window.inner_size()); + logical_to_physical_rounded(window_size, scale_factor) + }); + + // Stash the old window size. + let old_physical_size = physical_size; - // Handle pending user events. We don't need back buffer, since we can't dispatch - // user events indirectly via callback to the user. - for user_event in self.pending_user_events.borrow_mut().drain(..) { sticky_exit_callback( - Event::UserEvent(user_event), + Event::WindowEvent { + window_id: crate::window::WindowId(window_id), + event: WindowEvent::ScaleFactorChanged { + scale_factor, + new_inner_size: &mut physical_size, + }, + }, &self.window_target, &mut control_flow, &mut callback, ); - } - // Drain the pending compositor updates. - self.with_state(|state| { - compositor_updates.append(&mut state.window_compositor_updates) - }); + let new_logical_size = physical_size.to_logical(scale_factor); - for mut compositor_update in compositor_updates.drain(..) { - let window_id = compositor_update.window_id; - if let Some(scale_factor) = compositor_update.scale_factor { - let mut physical_size = self.with_state(|state| { + // Resize the window when user altered the size. + if old_physical_size != physical_size { + self.with_state(|state| { let windows = state.windows.get_mut(); let mut window = windows.get(&window_id).unwrap().lock().unwrap(); - - // Set the new scale factor. - window.set_scale_factor(scale_factor); - let window_size = compositor_update.size.unwrap_or(window.inner_size()); - logical_to_physical_rounded(window_size, scale_factor) + window.resize(new_logical_size); }); - - // Stash the old window size. - let old_physical_size = physical_size; - - sticky_exit_callback( - Event::WindowEvent { - window_id: crate::window::WindowId(window_id), - event: WindowEvent::ScaleFactorChanged { - scale_factor, - new_inner_size: &mut physical_size, - }, - }, - &self.window_target, - &mut control_flow, - &mut callback, - ); - - let new_logical_size = physical_size.to_logical(scale_factor); - - // Resize the window when user altered the size. - if old_physical_size != physical_size { - self.with_state(|state| { - let windows = state.windows.get_mut(); - let mut window = windows.get(&window_id).unwrap().lock().unwrap(); - window.resize(new_logical_size); - }); - } - - // Make it queue resize. - compositor_update.size = Some(new_logical_size); } - if let Some(size) = compositor_update.size.take() { - let physical_size = self.with_state(|state| { - let windows = state.windows.get_mut(); - let window = windows.get(&window_id).unwrap().lock().unwrap(); + // Make it queue resize. + compositor_update.size = Some(new_logical_size); + } - let scale_factor = window.scale_factor(); - let physical_size = logical_to_physical_rounded(size, scale_factor); + if let Some(size) = compositor_update.size.take() { + let physical_size = self.with_state(|state| { + let windows = state.windows.get_mut(); + let window = windows.get(&window_id).unwrap().lock().unwrap(); - // TODO could probably bring back size reporting optimization. + let scale_factor = window.scale_factor(); + let physical_size = logical_to_physical_rounded(size, scale_factor); - // Mark the window as needed a redraw. - state - .window_requests - .get_mut() - .get_mut(&window_id) - .unwrap() - .redraw_requested - .store(true, Ordering::Relaxed); + // TODO could probably bring back size reporting optimization. - physical_size - }); + // Mark the window as needed a redraw. + state + .window_requests + .get_mut() + .get_mut(&window_id) + .unwrap() + .redraw_requested + .store(true, Ordering::Relaxed); - sticky_exit_callback( - Event::WindowEvent { - window_id: crate::window::WindowId(window_id), - event: WindowEvent::Resized(physical_size), - }, - &self.window_target, - &mut control_flow, - &mut callback, - ); - } - - if compositor_update.close_window { - sticky_exit_callback( - Event::WindowEvent { - window_id: crate::window::WindowId(window_id), - event: WindowEvent::CloseRequested, - }, - &self.window_target, - &mut control_flow, - &mut callback, - ); - } - } + physical_size + }); - // Push the events directly from the window. - self.with_state(|state| { - buffer_sink.append(&mut state.window_events_sink.lock().unwrap()); - }); - for event in buffer_sink.drain() { - let event = event.map_nonuser_event().unwrap(); - sticky_exit_callback(event, &self.window_target, &mut control_flow, &mut callback); + sticky_exit_callback( + Event::WindowEvent { + window_id: crate::window::WindowId(window_id), + event: WindowEvent::Resized(physical_size), + }, + &self.window_target, + &mut control_flow, + &mut callback, + ); } - // Handle non-synthetic events. - self.with_state(|state| { - buffer_sink.append(&mut state.events_sink); - }); - for event in buffer_sink.drain() { - let event = event.map_nonuser_event().unwrap(); - sticky_exit_callback(event, &self.window_target, &mut control_flow, &mut callback); + if compositor_update.close_window { + sticky_exit_callback( + Event::WindowEvent { + window_id: crate::window::WindowId(window_id), + event: WindowEvent::CloseRequested, + }, + &self.window_target, + &mut control_flow, + &mut callback, + ); } + } + + // Push the events directly from the window. + self.with_state(|state| { + buffer_sink.append(&mut state.window_events_sink.lock().unwrap()); + }); + for event in buffer_sink.drain() { + let event = event.map_nonuser_event().unwrap(); + sticky_exit_callback(event, &self.window_target, &mut control_flow, &mut callback); + } + + // Handle non-synthetic events. + self.with_state(|state| { + buffer_sink.append(&mut state.events_sink); + }); + for event in buffer_sink.drain() { + let event = event.map_nonuser_event().unwrap(); + sticky_exit_callback(event, &self.window_target, &mut control_flow, &mut callback); + } + + // Send events cleared. + sticky_exit_callback( + Event::MainEventsCleared, + &self.window_target, + &mut control_flow, + &mut callback, + ); - // Send events cleared. - sticky_exit_callback( - Event::MainEventsCleared, - &self.window_target, - &mut control_flow, - &mut callback, - ); - - // Collect the window ids - self.with_state(|state| { - window_ids.extend(state.window_requests.get_mut().keys()); + // Collect the window ids + self.with_state(|state| { + window_ids.extend(state.window_requests.get_mut().keys()); + }); + + for window_id in window_ids.drain(..) { + let request_redraw = self.with_state(|state| { + let window_requests = state.window_requests.get_mut(); + if window_requests.get(&window_id).unwrap().take_closed() { + mem::drop(window_requests.remove(&window_id)); + mem::drop(state.windows.get_mut().remove(&window_id)); + false + } else { + let mut redraw_requested = window_requests + .get(&window_id) + .unwrap() + .take_redraw_requested(); + + // Redraw the frames while at it. + redraw_requested |= state + .windows + .get_mut() + .get_mut(&window_id) + .unwrap() + .lock() + .unwrap() + .refresh_frame(); + + redraw_requested + } }); - for window_id in window_ids.drain(..) { - let request_redraw = self.with_state(|state| { - let window_requests = state.window_requests.get_mut(); - if window_requests.get(&window_id).unwrap().take_closed() { - mem::drop(window_requests.remove(&window_id)); - mem::drop(state.windows.get_mut().remove(&window_id)); - false - } else { - let mut redraw_requested = window_requests - .get(&window_id) - .unwrap() - .take_redraw_requested(); - - // Redraw the frames while at it. - redraw_requested |= state - .windows - .get_mut() - .get_mut(&window_id) - .unwrap() - .lock() - .unwrap() - .refresh_frame(); - - redraw_requested - } - }); - - if request_redraw { - sticky_exit_callback( - Event::RedrawRequested(crate::window::WindowId(window_id)), - &self.window_target, - &mut control_flow, - &mut callback, - ); - } + if request_redraw { + sticky_exit_callback( + Event::RedrawRequested(crate::window::WindowId(window_id)), + &self.window_target, + &mut control_flow, + &mut callback, + ); } + } - // Send RedrawEventCleared. - sticky_exit_callback( - Event::RedrawEventsCleared, - &self.window_target, - &mut control_flow, - &mut callback, - ); - }; + // Send RedrawEventCleared. + sticky_exit_callback( + Event::RedrawEventsCleared, + &self.window_target, + &mut control_flow, + &mut callback, + ); - callback(Event::LoopDestroyed, &self.window_target, &mut control_flow); - exit_code + self.control_flow = control_flow; + std::mem::swap(&mut self.compositor_updates, &mut compositor_updates); + std::mem::swap(&mut self.buffer_sink, &mut buffer_sink); + std::mem::swap(&mut self.window_ids, &mut window_ids); } #[inline] @@ -486,6 +571,22 @@ impl EventLoop { error.into() }) } + + fn roundtrip(&mut self) -> Result { + let state = match &mut self.window_target.p { + PlatformEventLoopWindowTarget::Wayland(window_target) => window_target.state.get_mut(), + #[cfg(feature = "x11")] + _ => unreachable!(), + }; + + let mut wayland_source = self.wayland_dispatcher.as_source_mut(); + let event_queue = wayland_source.queue(); + event_queue.roundtrip(state).map_err(|_| { + os_error!(OsError::WaylandMisc( + "failed to do a final roundtrip before exiting the loop." + )) + }) + } } pub struct EventLoopWindowTarget { diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index 14e7905695..be7c442f86 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -19,13 +19,13 @@ pub(crate) use self::{ pub use self::xdisplay::{XError, XNotSupported}; -use calloop::channel::{channel, Channel, Event as ChanResult, Sender}; use calloop::generic::Generic; -use calloop::{Dispatcher, EventLoop as Loop}; +use calloop::EventLoop as Loop; +use calloop::{ping::Ping, Readiness}; use std::{ cell::{Cell, RefCell}, - collections::{HashMap, HashSet, VecDeque}, + collections::{HashMap, HashSet}, ffi::CStr, fmt, mem::{self, MaybeUninit}, @@ -37,6 +37,7 @@ use std::{ ptr, rc::Rc, slice, + sync::mpsc::{Receiver, Sender, TryRecvError}, sync::{mpsc, Arc, Weak}, time::{Duration, Instant}, }; @@ -61,13 +62,14 @@ use self::{ event_processor::EventProcessor, ime::{Ime, ImeCreationError, ImeReceiver, ImeRequest, ImeSender}, }; -use super::common::xkb_state::KbdState; +use super::{common::xkb_state::KbdState, OsError}; use crate::{ - error::OsError as RootOsError, + error::{OsError as RootOsError, RunLoopError}, event::{Event, StartCause}, event_loop::{ControlFlow, DeviceEvents, EventLoopClosed, EventLoopWindowTarget as RootELW}, + platform::pump_events::PumpStatus, platform_impl::{ - platform::{sticky_exit_callback, WindowId}, + platform::{min_timeout, sticky_exit_callback, WindowId}, PlatformSpecificWindowBuilderAttributes, }, window::WindowAttributes, @@ -75,6 +77,64 @@ use crate::{ type X11Source = Generic; +struct WakeSender { + sender: Sender, + waker: Ping, +} + +impl Clone for WakeSender { + fn clone(&self) -> Self { + Self { + sender: self.sender.clone(), + waker: self.waker.clone(), + } + } +} + +impl WakeSender { + pub fn send(&self, t: T) -> Result<(), EventLoopClosed> { + let res = self.sender.send(t).map_err(|e| EventLoopClosed(e.0)); + if res.is_ok() { + self.waker.ping(); + } + res + } +} + +struct PeekableReceiver { + recv: Receiver, + first: Option, +} + +impl PeekableReceiver { + pub fn from_recv(recv: Receiver) -> Self { + Self { recv, first: None } + } + pub fn has_incoming(&mut self) -> bool { + if self.first.is_some() { + return true; + } + + match self.recv.try_recv() { + Ok(v) => { + self.first = Some(v); + true + } + Err(TryRecvError::Empty) => false, + Err(TryRecvError::Disconnected) => { + warn!("Channel was disconnected when checking incoming"); + false + } + } + } + pub fn try_recv(&mut self) -> Result { + if let Some(first) = self.first.take() { + return Ok(first); + } + self.recv.try_recv() + } +} + pub struct EventLoopWindowTarget { xconn: Arc, wm_delete_window: xproto::Atom, @@ -83,40 +143,37 @@ pub struct EventLoopWindowTarget { root: xproto::Window, ime: RefCell, windows: RefCell>>, - redraw_sender: Sender, - activation_sender: Sender, + redraw_sender: WakeSender, + activation_sender: WakeSender, device_events: Cell, _marker: ::std::marker::PhantomData, } pub struct EventLoop { - event_loop: Loop<'static, EventLoopState>, + loop_running: bool, + control_flow: ControlFlow, + event_loop: Loop<'static, EventLoopState>, + waker: calloop::ping::Ping, event_processor: EventProcessor, + redraw_receiver: PeekableReceiver, + user_receiver: PeekableReceiver, + activation_receiver: PeekableReceiver, user_sender: Sender, target: Rc>, /// The current state of the event loop. - state: EventLoopState, - - /// Dispatcher for redraw events. - redraw_dispatcher: Dispatcher<'static, Channel, EventLoopState>, + state: EventLoopState, } type ActivationToken = (WindowId, crate::event_loop::AsyncRequestSerial); -struct EventLoopState { - /// Incoming user events. - user_events: VecDeque, - - /// Incoming redraw events. - redraw_events: VecDeque, - - /// Incoming activation tokens. - activation_tokens: VecDeque, +struct EventLoopState { + /// The latest readiness state for the x11 file descriptor + x11_readiness: Readiness, } pub struct EventLoopProxy { - user_sender: Sender, + user_sender: WakeSender, } impl Clone for EventLoopProxy { @@ -242,7 +299,7 @@ impl EventLoop { // Create an event loop. let event_loop = - Loop::>::try_new().expect("Failed to initialize the event loop"); + Loop::::try_new().expect("Failed to initialize the event loop"); let handle = event_loop.handle(); // Create the X11 event dispatcher. @@ -252,48 +309,29 @@ impl EventLoop { calloop::Mode::Level, ); handle - .insert_source(source, |_, _, _| Ok(calloop::PostAction::Continue)) + .insert_source(source, |readiness, _, state| { + state.x11_readiness = readiness; + Ok(calloop::PostAction::Continue) + }) .expect("Failed to register the X11 event dispatcher"); - // Create a channel for sending user events. - let (user_sender, user_channel) = channel(); - handle - .insert_source(user_channel, |ev, _, state| { - if let ChanResult::Msg(user) = ev { - state.user_events.push_back(user); - } + let (waker, waker_source) = + calloop::ping::make_ping().expect("Failed to create event loop waker"); + event_loop + .handle() + .insert_source(waker_source, move |_, _, _| { + // No extra handling is required, we just need to wake-up. }) - .expect("Failed to register the user event channel with the event loop"); + .expect("Failed to register the event loop waker source"); // Create a channel for handling redraw requests. - let (redraw_sender, redraw_channel) = channel(); + let (redraw_sender, redraw_channel) = mpsc::channel(); // Create a channel for sending activation tokens. - let (activation_token_sender, activation_token_channel) = channel(); - - // Create a dispatcher for the redraw channel such that we can dispatch it independent of the - // event loop. - let redraw_dispatcher = - Dispatcher::<_, EventLoopState>::new(redraw_channel, |ev, _, state| { - if let ChanResult::Msg(window_id) = ev { - state.redraw_events.push_back(window_id); - } - }); - handle - .register_dispatcher(redraw_dispatcher.clone()) - .expect("Failed to register the redraw event channel with the event loop"); - - // Create a dispatcher for the activation token channel such that we can dispatch it - // independent of the event loop. - let activation_tokens = - Dispatcher::<_, EventLoopState>::new(activation_token_channel, |ev, _, state| { - if let ChanResult::Msg(token) = ev { - state.activation_tokens.push_back(token); - } - }); - handle - .register_dispatcher(activation_tokens.clone()) - .expect("Failed to register the activation token channel with the event loop"); + let (activation_token_sender, activation_token_channel) = mpsc::channel(); + + // Create a channel for sending user events. + let (user_sender, user_channel) = mpsc::channel(); let kb_state = KbdState::from_x11_xkb(xconn.xcb_connection().get_raw_xcb_connection()).unwrap(); @@ -307,8 +345,14 @@ impl EventLoop { xconn, wm_delete_window, net_wm_ping, - redraw_sender, - activation_sender: activation_token_sender, + redraw_sender: WakeSender { + sender: redraw_sender, // not used again so no clone + waker: waker.clone(), + }, + activation_sender: WakeSender { + sender: activation_token_sender, // not used again so no clone + waker: waker.clone(), + }, device_events: Default::default(), }; @@ -359,22 +403,28 @@ impl EventLoop { event_processor.init_device(ffi::XIAllDevices); EventLoop { + loop_running: false, + control_flow: ControlFlow::default(), event_loop, + waker, event_processor, + redraw_receiver: PeekableReceiver::from_recv(redraw_channel), + activation_receiver: PeekableReceiver::from_recv(activation_token_channel), + user_receiver: PeekableReceiver::from_recv(user_channel), user_sender, target, - redraw_dispatcher, state: EventLoopState { - user_events: VecDeque::new(), - redraw_events: VecDeque::new(), - activation_tokens: VecDeque::new(), + x11_readiness: Readiness::EMPTY, }, } } pub fn create_proxy(&self) -> EventLoopProxy { EventLoopProxy { - user_sender: self.user_sender.clone(), + user_sender: WakeSender { + sender: self.user_sender.clone(), + waker: self.waker.clone(), + }, } } @@ -382,236 +432,271 @@ impl EventLoop { &self.target } - pub fn run_return(&mut self, mut callback: F) -> i32 + pub fn run_ondemand(&mut self, mut event_handler: F) -> Result<(), RunLoopError> where F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), { - struct IterationResult { - deadline: Option, - timeout: Option, - wait_start: Instant, + if self.loop_running { + return Err(RunLoopError::AlreadyRunning); } - fn single_iteration( - this: &mut EventLoop, - control_flow: &mut ControlFlow, - cause: &mut StartCause, - callback: &mut F, - ) -> IterationResult - where - F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), - { - sticky_exit_callback( - crate::event::Event::NewEvents(*cause), - &this.target, - control_flow, - callback, - ); - - // NB: For consistency all platforms must emit a 'resumed' event even though X11 - // applications don't themselves have a formal suspend/resume lifecycle. - if *cause == StartCause::Init { - sticky_exit_callback( - crate::event::Event::Resumed, - &this.target, - control_flow, - callback, - ); - } - // Process all pending events - this.drain_events(callback, control_flow); - - // Empty activation tokens. - while let Some((window_id, serial)) = this.state.activation_tokens.pop_front() { - let token = this - .event_processor - .with_window(window_id.0 as xproto::Window, |window| { - window.generate_activation_token() - }); - - match token { - Some(Ok(token)) => sticky_exit_callback( - crate::event::Event::WindowEvent { - window_id: crate::window::WindowId(window_id), - event: crate::event::WindowEvent::ActivationTokenDone { - serial, - token: crate::window::ActivationToken::_new(token), - }, - }, - &this.target, - control_flow, - callback, - ), - Some(Err(e)) => { - log::error!("Failed to get activation token: {}", e); - } - None => {} + let exit = loop { + match self.pump_events(None, &mut event_handler) { + PumpStatus::Exit(0) => { + break Ok(()); } - } - - // Empty the user event buffer - { - while let Some(event) = this.state.user_events.pop_front() { - sticky_exit_callback( - crate::event::Event::UserEvent(event), - &this.target, - control_flow, - callback, - ); + PumpStatus::Exit(code) => { + break Err(RunLoopError::ExitFailure(code)); + } + _ => { + continue; } } - // send MainEventsCleared - { - sticky_exit_callback( - crate::event::Event::MainEventsCleared, - &this.target, - control_flow, - callback, - ); - } + }; - // Quickly dispatch all redraw events to avoid buffering them. - while let Ok(event) = this.redraw_dispatcher.as_source_mut().try_recv() { - this.state.redraw_events.push_back(event); - } + // Applications aren't allowed to carry windows between separate + // `run_ondemand` calls but if they have only just dropped their + // windows we need to make sure those last requests are sent to the + // X Server. + let wt = get_xtarget(&self.target); + wt.x_connection().sync_with_server().map_err(|x_err| { + RunLoopError::Os(os_error!(OsError::XError(Arc::new(X11Error::Xlib(x_err))))) + })?; - // Empty the redraw requests - { - let mut windows = HashSet::new(); + exit + } - // Empty the channel. + pub fn pump_events(&mut self, timeout: Option, mut callback: F) -> PumpStatus + where + F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), + { + if !self.loop_running { + self.loop_running = true; - while let Some(window_id) = this.state.redraw_events.pop_front() { - windows.insert(window_id); - } + // Reset the internal state for the loop as we start running to + // ensure consistent behaviour in case the loop runs and exits more + // than once. + self.control_flow = ControlFlow::Poll; - for window_id in windows { - let window_id = crate::window::WindowId(window_id); - sticky_exit_callback( - Event::RedrawRequested(window_id), - &this.target, - control_flow, - callback, - ); - } - } - // send RedrawEventsCleared - { - sticky_exit_callback( - crate::event::Event::RedrawEventsCleared, - &this.target, - control_flow, - callback, - ); - } + // run the initial loop iteration + self.single_iteration(&mut callback, StartCause::Init); + } - let start = Instant::now(); - let (deadline, timeout); + // Consider the possibility that the `StartCause::Init` iteration could + // request to Exit. + if !matches!(self.control_flow, ControlFlow::ExitWithCode(_)) { + self.poll_events_with_timeout(timeout, &mut callback); + } + if let ControlFlow::ExitWithCode(code) = self.control_flow { + self.loop_running = false; - match control_flow { - ControlFlow::ExitWithCode(_) => { - return IterationResult { - wait_start: start, - deadline: None, - timeout: None, - }; - } - ControlFlow::Poll => { - *cause = StartCause::Poll; - deadline = None; - timeout = Some(Duration::from_millis(0)); + let mut dummy = self.control_flow; + sticky_exit_callback( + Event::LoopDestroyed, + self.window_target(), + &mut dummy, + &mut callback, + ); + + PumpStatus::Exit(code) + } else { + PumpStatus::Continue + } + } + + fn has_pending(&mut self) -> bool { + self.event_processor.poll() + || self.user_receiver.has_incoming() + || self.redraw_receiver.has_incoming() + } + + pub fn poll_events_with_timeout(&mut self, mut timeout: Option, mut callback: F) + where + F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), + { + let start = Instant::now(); + + let has_pending = self.has_pending(); + + timeout = if has_pending { + // If we already have work to do then we don't want to block on the next poll. + Some(Duration::ZERO) + } else { + let control_flow_timeout = match self.control_flow { + ControlFlow::Wait => None, + ControlFlow::Poll => Some(Duration::ZERO), + ControlFlow::WaitUntil(wait_deadline) => { + Some(wait_deadline.saturating_duration_since(start)) } - ControlFlow::Wait => { - *cause = StartCause::WaitCancelled { + // This function shouldn't have to handle any requests to exit + // the application (there should be no need to poll for events + // if the application has requested to exit) so we consider + // it a bug in the backend if we ever see `ExitWithCode` here. + ControlFlow::ExitWithCode(_code) => unreachable!(), + }; + + min_timeout(control_flow_timeout, timeout) + }; + + self.state.x11_readiness = Readiness::EMPTY; + if let Err(error) = self + .event_loop + .dispatch(timeout, &mut self.state) + .map_err(std::io::Error::from) + { + log::error!("Failed to poll for events: {error:?}"); + let exit_code = error.raw_os_error().unwrap_or(1); + self.control_flow = ControlFlow::ExitWithCode(exit_code); + return; + } + + // False positive / spurious wake ups could lead to us spamming + // redundant iterations of the event loop with no new events to + // dispatch. + // + // If there's no readable event source then we just double check if we + // have any pending `_receiver` events and if not we return without + // running a loop iteration. + // If we don't have any pending `_receiver` + if !self.has_pending() && !self.state.x11_readiness.readable { + return; + } + + // NB: `StartCause::Init` is handled as a special case and doesn't need + // to be considered here + let cause = match self.control_flow { + ControlFlow::Poll => StartCause::Poll, + ControlFlow::Wait => StartCause::WaitCancelled { + start, + requested_resume: None, + }, + ControlFlow::WaitUntil(deadline) => { + if Instant::now() < deadline { + StartCause::WaitCancelled { start, - requested_resume: None, - }; - deadline = None; - timeout = None; - } - ControlFlow::WaitUntil(wait_deadline) => { - *cause = StartCause::ResumeTimeReached { + requested_resume: Some(deadline), + } + } else { + StartCause::ResumeTimeReached { start, - requested_resume: *wait_deadline, - }; - timeout = if *wait_deadline > start { - Some(*wait_deadline - start) - } else { - Some(Duration::from_millis(0)) - }; - deadline = Some(*wait_deadline); + requested_resume: deadline, + } } } + // This function shouldn't have to handle any requests to exit + // the application (there should be no need to poll for events + // if the application has requested to exit) so we consider + // it a bug in the backend if we ever see `ExitWithCode` here. + ControlFlow::ExitWithCode(_code) => unreachable!(), + }; - IterationResult { - wait_start: start, - deadline, - timeout, - } - } + self.single_iteration(&mut callback, cause); + } - let mut control_flow = ControlFlow::default(); - let mut cause = StartCause::Init; + fn single_iteration(&mut self, callback: &mut F, cause: StartCause) + where + F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), + { + let mut control_flow = self.control_flow; - // run the initial loop iteration - let mut iter_result = single_iteration(self, &mut control_flow, &mut cause, &mut callback); + sticky_exit_callback( + crate::event::Event::NewEvents(cause), + &self.target, + &mut control_flow, + callback, + ); - let exit_code = loop { - if let ControlFlow::ExitWithCode(code) = control_flow { - break code; - } - let has_pending = self.event_processor.poll() - || !self.state.user_events.is_empty() - || !self.state.redraw_events.is_empty(); - if !has_pending { - // Wait until - if let Err(error) = self - .event_loop - .dispatch(iter_result.timeout, &mut self.state) - .map_err(std::io::Error::from) - { - break error.raw_os_error().unwrap_or(1); - } + // NB: For consistency all platforms must emit a 'resumed' event even though X11 + // applications don't themselves have a formal suspend/resume lifecycle. + if cause == StartCause::Init { + sticky_exit_callback( + crate::event::Event::Resumed, + &self.target, + &mut control_flow, + callback, + ); + } - if control_flow == ControlFlow::Wait { - // We don't go straight into executing the event loop iteration, we instead go - // to the start of this loop and check again if there's any pending event. We - // must do this because during the execution of the iteration we sometimes wake - // the calloop waker, and if the waker is already awaken before we call poll(), - // then poll doesn't block, but it returns immediately. This caused the event - // loop to run continuously even if the control_flow was `Wait` - continue; + // Process all pending events + self.drain_events(callback, &mut control_flow); + + // Empty activation tokens. + while let Ok((window_id, serial)) = self.activation_receiver.try_recv() { + let token = self + .event_processor + .with_window(window_id.0 as xproto::Window, |window| { + window.generate_activation_token() + }); + + match token { + Some(Ok(token)) => sticky_exit_callback( + crate::event::Event::WindowEvent { + window_id: crate::window::WindowId(window_id), + event: crate::event::WindowEvent::ActivationTokenDone { + serial, + token: crate::window::ActivationToken::_new(token), + }, + }, + &self.target, + &mut control_flow, + callback, + ), + Some(Err(e)) => { + log::error!("Failed to get activation token: {}", e); } + None => {} } + } - let wait_cancelled = iter_result - .deadline - .map_or(false, |deadline| Instant::now() < deadline); - - if wait_cancelled { - cause = StartCause::WaitCancelled { - start: iter_result.wait_start, - requested_resume: iter_result.deadline, - }; + // Empty the user event buffer + { + while let Ok(event) = self.user_receiver.try_recv() { + sticky_exit_callback( + crate::event::Event::UserEvent(event), + &self.target, + &mut control_flow, + callback, + ); } + } + // send MainEventsCleared + { + sticky_exit_callback( + crate::event::Event::MainEventsCleared, + &self.target, + &mut control_flow, + callback, + ); + } + // Empty the redraw requests + { + let mut windows = HashSet::new(); - iter_result = single_iteration(self, &mut control_flow, &mut cause, &mut callback); - }; + while let Ok(window_id) = self.redraw_receiver.try_recv() { + windows.insert(window_id); + } - callback( - crate::event::Event::LoopDestroyed, - &self.target, - &mut control_flow, - ); - exit_code - } + for window_id in windows { + let window_id = crate::window::WindowId(window_id); + sticky_exit_callback( + Event::RedrawRequested(window_id), + &self.target, + &mut control_flow, + callback, + ); + } + } + // send RedrawEventsCleared + { + sticky_exit_callback( + crate::event::Event::RedrawEventsCleared, + &self.target, + &mut control_flow, + callback, + ); + } - pub fn run(mut self, callback: F) -> ! - where - F: 'static + FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), - { - let exit_code = self.run_return(callback); - ::std::process::exit(exit_code); + self.control_flow = control_flow; } fn drain_events(&mut self, callback: &mut F, control_flow: &mut ControlFlow) diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index 7d58fc75e4..0d0f52fb5c 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -23,7 +23,7 @@ use crate::{ error::{ExternalError, NotSupportedError, OsError as RootOsError}, event_loop::AsyncRequestSerial, platform_impl::{ - x11::{atoms::*, MonitorHandle as X11MonitorHandle, X11Error}, + x11::{atoms::*, MonitorHandle as X11MonitorHandle, WakeSender, X11Error}, Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformSpecificWindowBuilderAttributes, VideoMode as PlatformVideoMode, }, @@ -37,7 +37,6 @@ use super::{ ffi, util, CookieResultExt, EventLoopWindowTarget, ImeRequest, ImeSender, VoidCookie, WindowId, XConnection, }; -use calloop::channel::Sender; #[derive(Debug)] pub struct SharedState { @@ -122,8 +121,8 @@ pub(crate) struct UnownedWindow { cursor_visible: Mutex, ime_sender: Mutex, pub shared_state: Mutex, - redraw_sender: Sender, - activation_sender: Sender, + redraw_sender: WakeSender, + activation_sender: WakeSender, } impl UnownedWindow { diff --git a/src/platform_impl/macos/app_state.rs b/src/platform_impl/macos/app_state.rs index 1a85b40f63..18f727314a 100644 --- a/src/platform_impl/macos/app_state.rs +++ b/src/platform_impl/macos/app_state.rs @@ -65,14 +65,17 @@ impl EventLoopHandler { RefMut<'_, dyn FnMut(Event<'_, T>, &RootWindowTarget, &mut ControlFlow)>, ), { + // The `NSApp` and our `HANDLER` are global state and so it's possible that + // we could get a delegate callback after the application has exit an + // `EventLoop`. If the loop has been exit then our weak `self.callback` + // will fail to upgrade. + // + // We don't want to panic or output any verbose logging if we fail to + // upgrade the weak reference since it might be valid that the application + // re-starts the `NSApp` after exiting a Winit `EventLoop` if let Some(callback) = self.callback.upgrade() { let callback = callback.borrow_mut(); (f)(self, callback); - } else { - panic!( - "Tried to dispatch an event, but the event loop that \ - owned the event handler callback seems to be destroyed" - ); } } } @@ -90,6 +93,7 @@ impl EventHandler for EventLoopHandler { fn handle_nonuser_event(&mut self, event: Event<'_, Never>, control_flow: &mut ControlFlow) { self.with_callback(|this, mut callback| { if let ControlFlow::ExitWithCode(code) = *control_flow { + // XXX: why isn't event dispatching simply skipped after control_flow = ExitWithCode? let dummy = &mut ControlFlow::ExitWithCode(code); (callback)(event.userify(), &this.window_target, dummy); } else { @@ -102,6 +106,7 @@ impl EventHandler for EventLoopHandler { self.with_callback(|this, mut callback| { for event in this.window_target.p.receiver.try_iter() { if let ControlFlow::ExitWithCode(code) = *control_flow { + // XXX: why isn't event dispatching simply skipped after control_flow = ExitWithCode? let dummy = &mut ControlFlow::ExitWithCode(code); (callback)(Event::UserEvent(event), &this.window_target, dummy); } else { @@ -114,14 +119,19 @@ impl EventHandler for EventLoopHandler { #[derive(Default)] struct Handler { - ready: AtomicBool, + stop_app_on_launch: AtomicBool, + stop_app_before_wait: AtomicBool, + stop_app_after_wait: AtomicBool, + stop_app_on_redraw: AtomicBool, + launched: AtomicBool, + running: AtomicBool, in_callback: AtomicBool, control_flow: Mutex, - control_flow_prev: Mutex, start_time: Mutex>, callback: Mutex>>, pending_events: Mutex>, pending_redraw: Mutex>, + wait_timeout: Mutex>, waker: Mutex, } @@ -141,12 +151,37 @@ impl Handler { self.waker.lock().unwrap() } - fn is_ready(&self) -> bool { - self.ready.load(Ordering::Acquire) + /// `true` after `ApplicationDelegate::applicationDidFinishLaunching` called + /// + /// NB: This is global / `NSApp` state and since the app will only be launched + /// once but an `EventLoop` may be run more than once then only the first + /// `EventLoop` will observe the `NSApp` before it is launched. + fn is_launched(&self) -> bool { + self.launched.load(Ordering::Acquire) + } + + /// Set via `ApplicationDelegate::applicationDidFinishLaunching` + fn set_launched(&self) { + self.launched.store(true, Ordering::Release); } - fn set_ready(&self) { - self.ready.store(true, Ordering::Release); + /// `true` if an `EventLoop` is currently running + /// + /// NB: This is global / `NSApp` state and may persist beyond the lifetime of + /// a running `EventLoop`. + /// + /// # Caveat + /// This is only intended to be called from the main thread + fn is_running(&self) -> bool { + self.running.load(Ordering::Relaxed) + } + + /// Set when an `EventLoop` starts running, after the `NSApp` is launched + /// + /// # Caveat + /// This is only intended to be called from the main thread + fn set_running(&self) { + self.running.store(true, Ordering::Relaxed); } fn should_exit(&self) -> bool { @@ -156,16 +191,99 @@ impl Handler { ) } - fn get_control_flow_and_update_prev(&self) -> ControlFlow { - let control_flow = self.control_flow.lock().unwrap(); - *self.control_flow_prev.lock().unwrap() = *control_flow; - *control_flow + /// Clears the `running` state and resets the `control_flow` state when an `EventLoop` exits + /// + /// Since an `EventLoop` may be run more than once we need make sure to reset the + /// `control_flow` state back to `Poll` each time the loop exits. + /// + /// Note: that if the `NSApp` has been launched then that state is preserved, and we won't + /// need to re-launch the app if subsequent EventLoops are run. + /// + /// # Caveat + /// This is only intended to be called from the main thread + fn exit(&self) { + // Relaxed ordering because we don't actually have multiple threads involved, we just want + // interiour mutability + // + // XXX: As an aside; having each individual bit of state for `Handler` be atomic or wrapped in a + // `Mutex` for the sake of interior mutability seems a bit odd, and also a potential foot + // gun in case the state is unwittingly accessed across threads because the fine-grained locking + // wouldn't ensure that there's interior consistency. + // + // Maybe the whole thing should just be put in a static `Mutex<>` to make it clear + // the we can mutate more than one peice of state while maintaining consistency. (though it + // looks like there have been recuring re-entrancy issues with callback handling that might + // make that awkward) + self.running.store(false, Ordering::Relaxed); + *self.control_flow.lock().unwrap() = ControlFlow::default(); + self.set_stop_app_on_redraw_requested(false); + self.set_stop_app_before_wait(false); + self.set_stop_app_after_wait(false); + self.set_wait_timeout(None); + } + + pub fn request_stop_app_on_launch(&self) { + // Relaxed ordering because we don't actually have multiple threads involved, we just want + // interior mutability + self.stop_app_on_launch.store(true, Ordering::Relaxed); + } + + pub fn should_stop_app_on_launch(&self) -> bool { + // Relaxed ordering because we don't actually have multiple threads involved, we just want + // interior mutability + self.stop_app_on_launch.load(Ordering::Relaxed) + } + + pub fn set_stop_app_before_wait(&self, stop_before_wait: bool) { + // Relaxed ordering because we don't actually have multiple threads involved, we just want + // interior mutability + self.stop_app_before_wait + .store(stop_before_wait, Ordering::Relaxed); + } + + pub fn should_stop_app_before_wait(&self) -> bool { + // Relaxed ordering because we don't actually have multiple threads involved, we just want + // interior mutability + self.stop_app_before_wait.load(Ordering::Relaxed) + } + + pub fn set_stop_app_after_wait(&self, stop_after_wait: bool) { + // Relaxed ordering because we don't actually have multiple threads involved, we just want + // interior mutability + self.stop_app_after_wait + .store(stop_after_wait, Ordering::Relaxed); + } + + pub fn set_wait_timeout(&self, new_timeout: Option) { + let mut timeout = self.wait_timeout.lock().unwrap(); + *timeout = new_timeout; + } + + pub fn wait_timeout(&self) -> Option { + *self.wait_timeout.lock().unwrap() + } + + pub fn should_stop_app_after_wait(&self) -> bool { + // Relaxed ordering because we don't actually have multiple threads involved, we just want + // interior mutability + self.stop_app_after_wait.load(Ordering::Relaxed) + } + + pub fn set_stop_app_on_redraw_requested(&self, stop_on_redraw: bool) { + // Relaxed ordering because we don't actually have multiple threads involved, we just want + // interior mutability + self.stop_app_on_redraw + .store(stop_on_redraw, Ordering::Relaxed); + } + + pub fn should_stop_app_on_redraw_requested(&self) -> bool { + // Relaxed ordering because we don't actually have multiple threads involved, we just want + // interior mutability + self.stop_app_on_redraw.load(Ordering::Relaxed) } - fn get_old_and_new_control_flow(&self) -> (ControlFlow, ControlFlow) { - let old = *self.control_flow_prev.lock().unwrap(); - let new = *self.control_flow.lock().unwrap(); - (old, new) + fn control_flow(&self) -> ControlFlow { + *self.control_flow.lock().unwrap() } fn get_start_time(&self) -> Option { @@ -192,6 +310,10 @@ impl Handler { self.in_callback.store(in_callback, Ordering::Release); } + fn have_callback(&self) -> bool { + self.callback.lock().unwrap().is_some() + } + fn handle_nonuser_event(&self, wrapper: EventWrapper) { if let Some(ref mut callback) = *self.callback.lock().unwrap() { match wrapper { @@ -253,25 +375,96 @@ impl Handler { pub(crate) enum AppState {} impl AppState { - pub fn set_callback(callback: Weak>, window_target: Rc>) { + /// Associate the application's event callback with the (global static) Handler state + /// + /// # Safety + /// This is ignoring the lifetime of the application callback (which may not be 'static) + /// and can lead to undefined behaviour if the callback is not cleared before the end of + /// its real lifetime. + /// + /// All public APIs that take an event callback (`run`, `run_ondemand`, + /// `pump_events`) _must_ pair a call to `set_callback` with + /// a call to `clear_callback` before returning to avoid undefined behaviour. + pub unsafe fn set_callback( + callback: Weak>, + window_target: Rc>, + ) { *HANDLER.callback.lock().unwrap() = Some(Box::new(EventLoopHandler { callback, window_target, })); } + pub fn clear_callback() { + HANDLER.callback.lock().unwrap().take(); + } + + pub fn is_launched() -> bool { + HANDLER.is_launched() + } + + pub fn is_running() -> bool { + HANDLER.is_running() + } + + // If `pump_events` is called to progress the event loop then we bootstrap the event + // loop via `[NSApp run]` but will use `CFRunLoopRunInMode` for subsequent calls to + // `pump_events` + pub fn request_stop_on_launch() { + HANDLER.request_stop_app_on_launch(); + } + + pub fn set_stop_app_before_wait(stop_before_wait: bool) { + HANDLER.set_stop_app_before_wait(stop_before_wait); + } + + pub fn set_stop_app_after_wait(stop_after_wait: bool) { + HANDLER.set_stop_app_after_wait(stop_after_wait); + } + + pub fn set_wait_timeout(timeout: Option) { + HANDLER.set_wait_timeout(timeout); + } + + pub fn set_stop_app_on_redraw_requested(stop_on_redraw: bool) { + HANDLER.set_stop_app_on_redraw_requested(stop_on_redraw); + } + + pub fn control_flow() -> ControlFlow { + HANDLER.control_flow() + } + pub fn exit() -> i32 { HANDLER.set_in_callback(true); HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::LoopDestroyed)); HANDLER.set_in_callback(false); - HANDLER.callback.lock().unwrap().take(); - if let ControlFlow::ExitWithCode(code) = HANDLER.get_old_and_new_control_flow().1 { + HANDLER.exit(); + Self::clear_callback(); + if let ControlFlow::ExitWithCode(code) = HANDLER.control_flow() { code } else { 0 } } + pub fn dispatch_init_events() { + HANDLER.set_in_callback(true); + HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::NewEvents( + StartCause::Init, + ))); + // NB: For consistency all platforms must emit a 'resumed' event even though macOS + // applications don't themselves have a formal suspend/resume lifecycle. + HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::Resumed)); + HANDLER.set_in_callback(false); + } + + pub fn start_running() { + debug_assert!(HANDLER.is_launched()); + + HANDLER.set_running(); + Self::dispatch_init_events() + } + pub fn launched( activation_policy: NSApplicationActivationPolicy, create_default_menu: bool, @@ -286,34 +479,51 @@ impl AppState { window_activation_hack(&app); app.activateIgnoringOtherApps(activate_ignoring_other_apps); - HANDLER.set_ready(); + HANDLER.set_launched(); HANDLER.waker().start(); if create_default_menu { // The menubar initialization should be before the `NewEvents` event, to allow // overriding of the default menu even if it's created menu::initialize(); } - HANDLER.set_in_callback(true); - HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::NewEvents( - StartCause::Init, - ))); - // NB: For consistency all platforms must emit a 'resumed' event even though macOS - // applications don't themselves have a formal suspend/resume lifecycle. - HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::Resumed)); - HANDLER.set_in_callback(false); + + Self::start_running(); + + // If the `NSApp` is being launched via `EventLoop::pump_events()` then we'll + // want to stop the app once it is launched (and return to the external loop) + // + // In this case we still want to consider Winit's `EventLoop` to be "running", + // so we call `start_running()` above. + if HANDLER.should_stop_app_on_launch() { + // Note: the original idea had been to only stop the underlying `RunLoop` + // for the app but that didn't work as expected (`[NSApp run]` effectively + // ignored the attempt to stop the RunLoop and re-started it.). So we + // return from `pump_events` by stopping the `NSApp` + Self::stop(); + } } + // Called by RunLoopObserver after finishing waiting for new events pub fn wakeup(panic_info: Weak) { let panic_info = panic_info .upgrade() .expect("The panic info must exist here. This failure indicates a developer error."); // Return when in callback due to https://github.com/rust-windowing/winit/issues/1779 - if panic_info.is_panicking() || !HANDLER.is_ready() || HANDLER.get_in_callback() { + if panic_info.is_panicking() + || !HANDLER.have_callback() + || !HANDLER.is_running() + || HANDLER.get_in_callback() + { return; } + + if HANDLER.should_stop_app_after_wait() { + Self::stop(); + } + let start = HANDLER.get_start_time().unwrap(); - let cause = match HANDLER.get_control_flow_and_update_prev() { + let cause = match HANDLER.control_flow() { ControlFlow::Poll => StartCause::Poll, ControlFlow::Wait => StartCause::WaitCancelled { start, @@ -358,6 +568,12 @@ impl AppState { HANDLER .handle_nonuser_event(EventWrapper::StaticEvent(Event::RedrawRequested(window_id))); HANDLER.set_in_callback(false); + + // `pump_events` will request to stop immediately _after_ dispatching RedrawRequested events + // as a way to ensure that `pump_events` can't block an external loop indefinitely + if HANDLER.should_stop_app_on_redraw_requested() { + AppState::stop(); + } } } @@ -368,13 +584,29 @@ impl AppState { HANDLER.events().push_back(wrapper); } + pub fn stop() { + let app = NSApp(); + autoreleasepool(|_| { + app.stop(None); + // To stop event loop immediately, we need to post some event here. + app.postEvent_atStart(&NSEvent::dummy(), true); + }); + } + + // Called by RunLoopObserver before waiting for new events pub fn cleared(panic_info: Weak) { let panic_info = panic_info .upgrade() .expect("The panic info must exist here. This failure indicates a developer error."); // Return when in callback due to https://github.com/rust-windowing/winit/issues/1779 - if panic_info.is_panicking() || !HANDLER.is_ready() || HANDLER.get_in_callback() { + // XXX: how does it make sense that `get_in_callback()` can ever return `true` here if we're + // about to return to the `CFRunLoop` to poll for new events? + if panic_info.is_panicking() + || !HANDLER.have_callback() + || !HANDLER.is_running() + || HANDLER.get_in_callback() + { return; } @@ -384,6 +616,7 @@ impl AppState { HANDLER.handle_nonuser_event(event); } HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::MainEventsCleared)); + for window_id in HANDLER.should_redraw() { HANDLER .handle_nonuser_event(EventWrapper::StaticEvent(Event::RedrawRequested(window_id))); @@ -392,24 +625,34 @@ impl AppState { HANDLER.set_in_callback(false); if HANDLER.should_exit() { - let app = NSApp(); - autoreleasepool(|_| { - app.stop(None); - // To stop event loop immediately, we need to post some event here. - app.postEvent_atStart(&NSEvent::dummy(), true); - }); + Self::stop(); } - HANDLER.update_start_time(); - match HANDLER.get_old_and_new_control_flow() { - (ControlFlow::ExitWithCode(_), _) | (_, ControlFlow::ExitWithCode(_)) => (), - (old, new) if old == new => (), - (_, ControlFlow::Wait) => HANDLER.waker().stop(), - (_, ControlFlow::WaitUntil(instant)) => HANDLER.waker().start_at(instant), - (_, ControlFlow::Poll) => HANDLER.waker().start(), + + if HANDLER.should_stop_app_before_wait() { + Self::stop(); } + HANDLER.update_start_time(); + let wait_timeout = HANDLER.wait_timeout(); // configured by pump_events + let app_timeout = match HANDLER.control_flow() { + ControlFlow::Wait => None, + ControlFlow::Poll | ControlFlow::ExitWithCode(_) => Some(Instant::now()), + ControlFlow::WaitUntil(instant) => Some(instant), + }; + HANDLER + .waker() + .start_at(min_timeout(wait_timeout, app_timeout)); } } +/// Returns the minimum `Option`, taking into account that `None` +/// equates to an infinite timeout, not a zero timeout (so can't just use +/// `Option::min`) +fn min_timeout(a: Option, b: Option) -> Option { + a.map_or(b, |a_timeout| { + b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout))) + }) +} + /// A hack to make activation of multiple windows work when creating them before /// `applicationDidFinishLaunching:` / `Event::Event::NewEvents(StartCause::Init)`. /// diff --git a/src/platform_impl/macos/event_loop.rs b/src/platform_impl/macos/event_loop.rs index 93e893de05..176c90e759 100644 --- a/src/platform_impl/macos/event_loop.rs +++ b/src/platform_impl/macos/event_loop.rs @@ -5,10 +5,11 @@ use std::{ marker::PhantomData, mem, os::raw::c_void, - panic::{catch_unwind, resume_unwind, RefUnwindSafe, UnwindSafe}, - process, ptr, + panic::{catch_unwind, resume_unwind, AssertUnwindSafe, RefUnwindSafe, UnwindSafe}, + ptr, rc::{Rc, Weak}, sync::mpsc, + time::{Duration, Instant}, }; use core_foundation::base::{CFIndex, CFRelease}; @@ -23,9 +24,10 @@ use raw_window_handle::{AppKitDisplayHandle, RawDisplayHandle}; use super::appkit::{NSApp, NSApplicationActivationPolicy, NSEvent, NSWindow}; use crate::{ + error::RunLoopError, event::Event, event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootWindowTarget}, - platform::macos::ActivationPolicy, + platform::{macos::ActivationPolicy, pump_events::PumpStatus}, platform_impl::platform::{ app::WinitApplication, app_delegate::ApplicationDelegate, @@ -191,22 +193,34 @@ impl EventLoop { &self.window_target } - pub fn run(mut self, callback: F) -> ! + pub fn run(mut self, callback: F) -> Result<(), RunLoopError> where F: 'static + FnMut(Event<'_, T>, &RootWindowTarget, &mut ControlFlow), { - let exit_code = self.run_return(callback); - process::exit(exit_code); + self.run_ondemand(callback) } - pub fn run_return(&mut self, callback: F) -> i32 + // NB: we don't base this on `pump_events` because for `MacOs` we can't support + // `pump_events` elegantly (we just ask to run the loop for a "short" amount of + // time and so a layered implementation would end up using a lot of CPU due to + // redundant wake ups. + pub fn run_ondemand(&mut self, callback: F) -> Result<(), RunLoopError> where F: FnMut(Event<'_, T>, &RootWindowTarget, &mut ControlFlow), { - // This transmute is always safe, in case it was reached through `run`, since our - // lifetime will be already 'static. In other cases caller should ensure that all data - // they passed to callback will actually outlive it, some apps just can't move - // everything to event loop, so this is something that they should care about. + if AppState::is_running() { + return Err(RunLoopError::AlreadyRunning); + } + + // # Safety + // We are erasing the lifetime of the application callback here so that we + // can (temporarily) store it within 'static global `AppState` that's + // accessible to objc delegate callbacks. + // + // The safety of this depends on on making sure to also clear the callback + // from the global `AppState` before we return from here, ensuring that + // we don't retain a reference beyond the real lifetime of the callback. + let callback = unsafe { mem::transmute::< Rc, &RootWindowTarget, &mut ControlFlow)>>, @@ -224,18 +238,171 @@ impl EventLoop { let weak_cb: Weak<_> = Rc::downgrade(&callback); drop(callback); - AppState::set_callback(weak_cb, Rc::clone(&self.window_target)); - unsafe { app.run() }; + // # Safety + // We make sure to call `AppState::clear_callback` before returning + unsafe { + AppState::set_callback(weak_cb, Rc::clone(&self.window_target)); + } - if let Some(panic) = self.panic_info.take() { - drop(self._callback.take()); - resume_unwind(panic); + // catch panics to make sure we can't unwind without clearing the set callback + // (which would leave the global `AppState` in an undefined, unsafe state) + let catch_result = catch_unwind(AssertUnwindSafe(|| { + // clear / normalize pump_events state + AppState::set_wait_timeout(None); + AppState::set_stop_app_before_wait(false); + AppState::set_stop_app_after_wait(false); + AppState::set_stop_app_on_redraw_requested(false); + + if AppState::is_launched() { + debug_assert!(!AppState::is_running()); + AppState::start_running(); // Set is_running = true + dispatch `NewEvents(Init)` + `Resumed` + } + unsafe { app.run() }; + + // While the app is running it's possible that we catch a panic + // to avoid unwinding across an objective-c ffi boundary, which + // will lead to us stopping the `NSApp` and saving the + // `PanicInfo` so that we can resume the unwind at a controlled, + // safe point in time. + if let Some(panic) = self.panic_info.take() { + resume_unwind(panic); + } + + AppState::exit() + })); + + // # Safety + // This pairs up with the `unsafe` call to `set_callback` above and ensures that + // we always clear the application callback from the global `AppState` before + // returning + drop(self._callback.take()); + AppState::clear_callback(); + + match catch_result { + Ok(exit_code) => exit_code, + Err(payload) => resume_unwind(payload), } - AppState::exit() }); - drop(self._callback.take()); - exit_code + if exit_code == 0 { + Ok(()) + } else { + Err(RunLoopError::ExitFailure(exit_code)) + } + } + + pub fn pump_events(&mut self, timeout: Option, callback: F) -> PumpStatus + where + F: FnMut(Event<'_, T>, &RootWindowTarget, &mut ControlFlow), + { + // # Safety + // We are erasing the lifetime of the application callback here so that we + // can (temporarily) store it within 'static global `AppState` that's + // accessible to objc delegate callbacks. + // + // The safety of this depends on on making sure to also clear the callback + // from the global `AppState` before we return from here, ensuring that + // we don't retain a reference beyond the real lifetime of the callback. + + let callback = unsafe { + mem::transmute::< + Rc, &RootWindowTarget, &mut ControlFlow)>>, + Rc, &RootWindowTarget, &mut ControlFlow)>>, + >(Rc::new(RefCell::new(callback))) + }; + + self._callback = Some(Rc::clone(&callback)); + + autoreleasepool(|_| { + let app = NSApp(); + + // A bit of juggling with the callback references to make sure + // that `self.callback` is the only owner of the callback. + let weak_cb: Weak<_> = Rc::downgrade(&callback); + drop(callback); + + // # Safety + // We will make sure to call `AppState::clear_callback` before returning + // to ensure that we don't hold on to the callback beyond its (erased) + // lifetime + unsafe { + AppState::set_callback(weak_cb, Rc::clone(&self.window_target)); + } + + // catch panics to make sure we can't unwind without clearing the set callback + // (which would leave the global `AppState` in an undefined, unsafe state) + let catch_result = catch_unwind(AssertUnwindSafe(|| { + // As a special case, if the `NSApp` hasn't been launched yet then we at least run + // the loop until it has fully launched. + if !AppState::is_launched() { + debug_assert!(!AppState::is_running()); + + AppState::request_stop_on_launch(); + unsafe { + app.run(); + } + + // Note: we dispatch `NewEvents(Init)` + `Resumed` events after the `NSApp` has launched + } else if !AppState::is_running() { + // Even though the NSApp may have been launched, it's possible we aren't running + // if the `EventLoop` was run before and has since exited. This indicates that + // we just starting to re-run the same `EventLoop` again. + AppState::start_running(); // Set is_running = true + dispatch `NewEvents(Init)` + `Resumed` + } else { + // Only run the NSApp for as long as the given `Duration` allows so we + // don't block the external loop. + match timeout { + Some(Duration::ZERO) => { + AppState::set_wait_timeout(None); + AppState::set_stop_app_before_wait(true); + } + Some(duration) => { + AppState::set_stop_app_before_wait(false); + let timeout = Instant::now() + duration; + AppState::set_wait_timeout(Some(timeout)); + AppState::set_stop_app_after_wait(true); + } + None => { + AppState::set_wait_timeout(None); + AppState::set_stop_app_before_wait(false); + AppState::set_stop_app_after_wait(true); + } + } + AppState::set_stop_app_on_redraw_requested(true); + unsafe { + app.run(); + } + } + + // While the app is running it's possible that we catch a panic + // to avoid unwinding across an objective-c ffi boundary, which + // will lead to us stopping the `NSApp` and saving the + // `PanicInfo` so that we can resume the unwind at a controlled, + // safe point in time. + if let Some(panic) = self.panic_info.take() { + resume_unwind(panic); + } + + if let ControlFlow::ExitWithCode(code) = AppState::control_flow() { + AppState::exit(); + PumpStatus::Exit(code) + } else { + PumpStatus::Continue + } + })); + + // # Safety + // This pairs up with the `unsafe` call to `set_callback` above and ensures that + // we always clear the application callback from the global `AppState` before + // returning + AppState::clear_callback(); + drop(self._callback.take()); + + match catch_result { + Ok(pump_status) => pump_status, + Err(payload) => resume_unwind(payload), + } + }) } pub fn create_proxy(&self) -> EventLoopProxy { diff --git a/src/platform_impl/macos/mod.rs b/src/platform_impl/macos/mod.rs index 4559e0c9fb..141f03e6ec 100644 --- a/src/platform_impl/macos/mod.rs +++ b/src/platform_impl/macos/mod.rs @@ -35,7 +35,7 @@ use crate::{ use objc2::rc::{autoreleasepool, Id, Shared}; pub(crate) use crate::icon::NoIcon as PlatformIcon; -pub(self) use crate::platform_impl::Fullscreen; +pub(crate) use crate::platform_impl::Fullscreen; #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct DeviceId; diff --git a/src/platform_impl/macos/observer.rs b/src/platform_impl/macos/observer.rs index bce63c87c8..1ff1f80507 100644 --- a/src/platform_impl/macos/observer.rs +++ b/src/platform_impl/macos/observer.rs @@ -141,6 +141,16 @@ pub fn setup_control_flow_observers(panic_info: Weak) { pub struct EventLoopWaker { timer: CFRunLoopTimerRef, + + /// An arbitrary instant in the past, that will trigger an immediate wake + /// We save this as the `next_fire_date` for consistency so we can + /// easily check if the next_fire_date needs updating. + start_instant: Instant, + + /// This is what the `NextFireDate` has been set to. + /// `None` corresponds to `waker.stop()` and `start_instant` is used + /// for `waker.start()` + next_fire_date: Option, } impl Drop for EventLoopWaker { @@ -169,31 +179,50 @@ impl Default for EventLoopWaker { ptr::null_mut(), ); CFRunLoopAddTimer(CFRunLoopGetMain(), timer, kCFRunLoopCommonModes); - EventLoopWaker { timer } + EventLoopWaker { + timer, + start_instant: Instant::now(), + next_fire_date: None, + } } } } impl EventLoopWaker { pub fn stop(&mut self) { - unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MAX) } + if self.next_fire_date.is_some() { + self.next_fire_date = None; + unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MAX) } + } } pub fn start(&mut self) { - unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MIN) } + if self.next_fire_date != Some(self.start_instant) { + self.next_fire_date = Some(self.start_instant); + unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MIN) } + } } - pub fn start_at(&mut self, instant: Instant) { + pub fn start_at(&mut self, instant: Option) { let now = Instant::now(); - if now >= instant { - self.start(); - } else { - unsafe { - let current = CFAbsoluteTimeGetCurrent(); - let duration = instant - now; - let fsecs = - duration.subsec_nanos() as f64 / 1_000_000_000.0 + duration.as_secs() as f64; - CFRunLoopTimerSetNextFireDate(self.timer, current + fsecs) + match instant { + Some(instant) if now >= instant => { + self.start(); + } + Some(instant) => { + if self.next_fire_date != Some(instant) { + self.next_fire_date = Some(instant); + unsafe { + let current = CFAbsoluteTimeGetCurrent(); + let duration = instant - now; + let fsecs = duration.subsec_nanos() as f64 / 1_000_000_000.0 + + duration.as_secs() as f64; + CFRunLoopTimerSetNextFireDate(self.timer, current + fsecs) + } + } + } + None => { + self.stop(); } } } diff --git a/src/platform_impl/orbital/event_loop.rs b/src/platform_impl/orbital/event_loop.rs index 16cf6f4137..365c2d0ff8 100644 --- a/src/platform_impl/orbital/event_loop.rs +++ b/src/platform_impl/orbital/event_loop.rs @@ -12,6 +12,7 @@ use orbclient::{ use raw_window_handle::{OrbitalDisplayHandle, RawDisplayHandle}; use crate::{ + error::RunLoopError, event::{self, Ime, Modifiers, StartCause}, event_loop::{self, ControlFlow}, keyboard::{ @@ -301,15 +302,6 @@ impl EventLoop { } } - pub fn run(mut self, event_handler: F) -> ! - where - F: 'static - + FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget, &mut ControlFlow), - { - let exit_code = self.run_return(event_handler); - ::std::process::exit(exit_code); - } - fn process_event( window_id: WindowId, event_option: EventOption, @@ -451,9 +443,10 @@ impl EventLoop { } } - pub fn run_return(&mut self, mut event_handler_inner: F) -> i32 + pub fn run(mut self, mut event_handler_inner: F) -> Result<(), RunLoopError> where - F: FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget, &mut ControlFlow), + F: 'static + + FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget, &mut ControlFlow), { // Wrapper for event handler function that prevents ExitWithCode from being unset. let mut event_handler = @@ -696,7 +689,11 @@ impl EventLoop { &mut control_flow, ); - code + if code == 0 { + Ok(()) + } else { + Err(RunLoopError::ExitFailure(code)) + } } pub fn window_target(&self) -> &event_loop::EventLoopWindowTarget { diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index d3b4bf6a82..2b0de9236f 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()); @@ -249,38 +243,240 @@ impl EventLoop { &self.window_target } - pub fn run(mut self, event_handler: F) -> ! + pub fn run(mut self, event_handler: F) -> Result<(), RunLoopError> where F: 'static + FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), { - let exit_code = self.run_return(event_handler); - ::std::process::exit(exit_code); + self.run_ondemand(event_handler) } - pub fn run_return(&mut self, mut event_handler: F) -> i32 + pub fn run_ondemand(&mut self, mut event_handler: F) -> Result<(), RunLoopError> where F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), { - let event_loop_windows_ref = &self.window_target; + { + let runner = &self.window_target.p.runner_shared; + if runner.state() != RunnerState::Uninitialized { + return Err(RunLoopError::AlreadyRunning); + } - unsafe { - self.window_target - .p - .runner_shared - .set_event_handler(move |event, control_flow| { + 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(None) { + 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, timeout: Option, 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(); + } + } + + if !matches!( + self.wait_and_dispatch_message(timeout), + ControlFlow::ExitWithCode(_) + ) { + 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(); + + // 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 + fn wait_and_dispatch_message(&mut self, timeout: Option) -> ControlFlow { + let start = Instant::now(); + + let runner = &self.window_target.p.runner_shared; + + let control_flow_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!(), + }; + let timeout = min_timeout(control_flow_timeout, timeout); + + 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; - runner.poll(); - 'main: loop { - if GetMessageW(&mut msg, 0, 0, 0) == false.into() { - break 'main 0; + // 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 +488,24 @@ impl EventLoop { TranslateMessage(&msg); DispatchMessageW(&msg); } + } - if let Err(payload) = runner.take_panic_error() { - runner.reset_runner(); - panic::resume_unwind(payload); - } + 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; - } - } + control_flow = runner.control_flow(); + if let ControlFlow::ExitWithCode(_code) = control_flow { + break; } - }; - unsafe { - runner.loop_destroyed(); + if runner.interrupt_msg_dispatch.get() { + break; + } } - runner.reset_runner(); - exit_code + control_flow } pub fn create_proxy(&self) -> EventLoopProxy { @@ -391,107 +585,13 @@ 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; - } - } - } - } +/// Returns the minimum `Option`, taking into account that `None` +/// equates to an infinite timeout, not a zero timeout (so can't just use +/// `Option::min`) +fn min_timeout(a: Option, b: Option) -> Option { + a.map_or(b, |a_timeout| { + b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout))) + }) } // Implementation taken from https://github.com/rust-lang/rust/blob/db5476571d9b27c862b95c1e64764b0ac8980e23/src/libstd/sys/windows/mod.rs @@ -612,8 +712,6 @@ impl EventLoopProxy { } } -type WaitUntilInstantBox = Box; - /// A lazily-initialized window message ID. pub struct LazyMessageId { /// The ID. @@ -673,13 +771,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 +886,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 +1011,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 +1132,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 +1147,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 +2279,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 +2319,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 c4ac1eb348..c39b3f7004 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 ff07b8eb25..04bd78d384 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 40d43c58c3..017501f6b1 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()