From 128743e627b17194d821eb21c8c1f21d43a84194 Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Tue, 11 Apr 2023 12:50:52 +0100 Subject: [PATCH] Re-work event loop run APIs Overall this re-works the APIs for how an `EventLoop` is run to cover these use-cases, with varying portability caveats: 1. A portable `run()` API that consumes the `EventLoop` and runs the loop on the calling thread until the app exits. This can be supported across _all_ platforms and compared to the previous `run() -> !` API is now able to return a `Result` status on all platforms except iOS and Web. Fixes: #2709 2. A less portable `run_ondmand()` API that covers the use case in #2431 where applications need to be able to re-run a Winit application multiple times against a persistent `EventLoop`. This doesn't allow `Window` state to carry across separate runs of the loop, but does allow orthogonal re-instantiations of a gui application. Available On: Windows, Linux, MacOS Could be supported on Android if there's a use case Incompatible with iOS, Web Fixes: #2431 3. A less portable (and on MacOS, likely less-optimal) `pump_events()` API that covers the use case in #2706 and allows a Winit event loop to be embedded within an external `loop {}`. Applications call `pump_events()` once per iteration of their own external loop to dispatch all pending Winit events, without blocking the external loop. Available On: Windows, Linux, MacOS, Android Incompatible With: iOS, Web Fixes: #2706 Each method of running the loop will behave consistently in terms of how `NewEvents(Init)`, `Resumed` and `LoopDestroyed` events are dispatched (so portable application code wouldn't necessarily need to have any awareness of which method of running the loop was being used) In particular by moving away from `run() -> !` we stop calling `std::process::exit()` internally as a means to kill the process without returning which means it's possible to return an exit status and applications can return from their `main()` function normally. This also fixes Android support where an Activity runs in a thread but we can't assume to have full ownership of the process (other services could be running in separate threads). `run_return` has been removed, and the overlapping use cases that `run_return` previously partially aimed to support have been split across `run_ondemand` and `pump_events`. To help test the changes this adds: examples/window_ondemand examples/window_pump_events examples/window_pump_events_rfd The last _rfd example, tests the interaction between the `rfd` crate and using `pump_events` to embed Winit within an external event loop. Additionally all examples have generally been updated so that `main()` returns a `Result` from `run()` Fixes: #2709 Fixes: #2706 Fixes: #2431 Fixes: #2752 TODO: X11/Wayland backends TODO: ask someone to test orbital changes, made blindly TODO: split patch up --- Cargo.toml | 3 + examples/child_window.rs | 2 +- examples/control_flow.rs | 4 +- examples/cursor.rs | 4 +- examples/cursor_grab.rs | 4 +- examples/custom_events.rs | 4 +- examples/drag_window.rs | 4 +- examples/fullscreen.rs | 4 +- examples/handling_close.rs | 4 +- examples/ime.rs | 4 +- examples/mouse_wheel.rs | 4 +- examples/multithreaded.rs | 2 +- examples/multiwindow.rs | 2 +- examples/request_redraw.rs | 4 +- examples/request_redraw_threaded.rs | 4 +- examples/resizable.rs | 4 +- examples/theme.rs | 4 +- examples/timer.rs | 4 +- examples/touchpad_gestures.rs | 4 +- examples/transparent.rs | 4 +- examples/web.rs | 4 +- examples/window.rs | 9 +- examples/window_buttons.rs | 4 +- examples/window_debug.rs | 4 +- examples/window_drag_resize.rs | 4 +- examples/window_icon.rs | 4 +- examples/window_ondemand.rs | 90 ++++ ...ow_run_return.rs => window_pump_events.rs} | 32 +- examples/window_pump_events_rfd.rs | 61 +++ examples/window_resize_increments.rs | 4 +- src/error.rs | 6 + src/event.rs | 8 + src/event_loop.rs | 24 +- src/platform/mod.rs | 6 +- src/platform/pump_events.rs | 33 ++ src/platform/run_ondemand.rs | 72 +++ src/platform/run_return.rs | 53 -- src/platform_impl/android/mod.rs | 331 ++++++------ src/platform_impl/macos/app_state.rs | 229 ++++++-- src/platform_impl/macos/event_loop.rs | 116 +++- src/platform_impl/orbital/event_loop.rs | 20 +- src/platform_impl/windows/event_loop.rs | 497 +++++++----------- .../windows/event_loop/runner.rs | 106 ++-- 43 files changed, 1087 insertions(+), 703 deletions(-) create mode 100644 examples/window_ondemand.rs rename examples/{window_run_return.rs => window_pump_events.rs} (62%) create mode 100644 examples/window_pump_events_rfd.rs create mode 100644 src/platform/pump_events.rs create mode 100644 src/platform/run_ondemand.rs delete mode 100644 src/platform/run_return.rs diff --git a/Cargo.toml b/Cargo.toml index c86287cf82e..429dee195de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,6 +61,9 @@ serde = { version = "1", optional = true, features = ["serde_derive"] } image = { version = "0.24.0", default-features = false, features = ["png"] } simple_logger = { version = "2.1.0", default_features = false } +[target.'cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))'.dev-dependencies] +rfd = { version = "0.11.0" } + [target.'cfg(target_os = "android")'.dependencies] # Coordinate the next winit release with android-ndk-rs: https://github.com/rust-windowing/winit/issues/1995 android-activity = "0.4.0" diff --git a/examples/child_window.rs b/examples/child_window.rs index b1b8f95e82e..9fb648c58ca 100644 --- a/examples/child_window.rs +++ b/examples/child_window.rs @@ -1,5 +1,5 @@ #[cfg(any(x11_platform, macos_platform, windows_platform))] -fn main() { +fn main() -> std::result::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 bade79d2799..ff8cb90f6b9 100644 --- a/examples/control_flow.rs +++ b/examples/control_flow.rs @@ -19,7 +19,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() -> std::result::Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); println!("Press '1' to switch to Wait mode."); @@ -110,5 +110,5 @@ fn main() { } _ => (), } - }); + }) } diff --git a/examples/cursor.rs b/examples/cursor.rs index fdef7ef9713..e4bf17aa8ac 100644 --- a/examples/cursor.rs +++ b/examples/cursor.rs @@ -7,7 +7,7 @@ use winit::{ window::{CursorIcon, WindowBuilder}, }; -fn main() { +fn main() -> std::result::Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -48,7 +48,7 @@ fn main() { } _ => (), } - }); + }) } const CURSORS: &[CursorIcon] = &[ diff --git a/examples/cursor_grab.rs b/examples/cursor_grab.rs index 79253e106a5..fb673303052 100644 --- a/examples/cursor_grab.rs +++ b/examples/cursor_grab.rs @@ -7,7 +7,7 @@ use winit::{ window::{CursorGrabMode, WindowBuilder}, }; -fn main() { +fn main() -> std::result::Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -66,5 +66,5 @@ fn main() { }, _ => (), } - }); + }) } diff --git a/examples/custom_events.rs b/examples/custom_events.rs index b39353155af..4951999ca6c 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() -> std::result::Result<(), impl std::error::Error> { use simple_logger::SimpleLogger; use winit::{ event::{Event, WindowEvent}, @@ -46,7 +46,7 @@ fn main() { } => control_flow.set_exit(), _ => (), } - }); + }) } #[cfg(wasm_platform)] diff --git a/examples/drag_window.rs b/examples/drag_window.rs index 813e9b00c99..ee621a0542c 100644 --- a/examples/drag_window.rs +++ b/examples/drag_window.rs @@ -9,7 +9,7 @@ use winit::{ window::{Window, WindowBuilder, WindowId}, }; -fn main() { +fn main() -> std::result::Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -60,7 +60,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 a71d079f284..bf7b8b2ae17 100644 --- a/examples/fullscreen.rs +++ b/examples/fullscreen.rs @@ -8,7 +8,7 @@ use winit::window::{Fullscreen, WindowBuilder}; #[cfg(target_os = "macos")] use winit::platform::macos::WindowExtMacOS; -fn main() { +fn main() -> std::result::Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -118,5 +118,5 @@ fn main() { }, _ => {} } - }); + }) } diff --git a/examples/handling_close.rs b/examples/handling_close.rs index 1fe4ad37083..4f65acaa9d1 100644 --- a/examples/handling_close.rs +++ b/examples/handling_close.rs @@ -7,7 +7,7 @@ use winit::{ window::WindowBuilder, }; -fn main() { +fn main() -> std::result::Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -82,5 +82,5 @@ fn main() { } _ => (), } - }); + }) } diff --git a/examples/ime.rs b/examples/ime.rs index 59f43d4a334..6bb982b4f2f 100644 --- a/examples/ime.rs +++ b/examples/ime.rs @@ -9,7 +9,7 @@ use winit::{ window::{ImePurpose, WindowBuilder}, }; -fn main() { +fn main() -> std::result::Result<(), impl std::error::Error> { SimpleLogger::new() .with_level(LevelFilter::Trace) .init() @@ -108,5 +108,5 @@ fn main() { } _ => (), } - }); + }) } diff --git a/examples/mouse_wheel.rs b/examples/mouse_wheel.rs index 239f5a4ec1f..bd64a784f34 100644 --- a/examples/mouse_wheel.rs +++ b/examples/mouse_wheel.rs @@ -7,7 +7,7 @@ use winit::{ window::WindowBuilder, }; -fn main() { +fn main() -> std::result::Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -58,5 +58,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 7e03329d837..155fa430b36 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() -> std::result::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 3409af5a656..26a1f55b0f5 100644 --- a/examples/multiwindow.rs +++ b/examples/multiwindow.rs @@ -9,7 +9,7 @@ use winit::{ window::Window, }; -fn main() { +fn main() -> std::result::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 aa53050d026..b42b5ef45fa 100644 --- a/examples/request_redraw.rs +++ b/examples/request_redraw.rs @@ -7,7 +7,7 @@ use winit::{ window::WindowBuilder, }; -fn main() { +fn main() -> std::result::Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -37,5 +37,5 @@ fn main() { } _ => (), } - }); + }) } diff --git a/examples/request_redraw_threaded.rs b/examples/request_redraw_threaded.rs index d92389c5563..31f83f70f0b 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() -> std::result::Result<(), impl std::error::Error> { use std::{thread, time}; use simple_logger::SimpleLogger; @@ -39,7 +39,7 @@ fn main() { } _ => (), } - }); + }) } #[cfg(wasm_platform)] diff --git a/examples/resizable.rs b/examples/resizable.rs index 8f16172fd72..2dc839c3a0c 100644 --- a/examples/resizable.rs +++ b/examples/resizable.rs @@ -8,7 +8,7 @@ use winit::{ window::WindowBuilder, }; -fn main() { +fn main() -> std::result::Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -46,5 +46,5 @@ fn main() { }, _ => (), }; - }); + }) } diff --git a/examples/theme.rs b/examples/theme.rs index ac8854e2e34..346bed499c1 100644 --- a/examples/theme.rs +++ b/examples/theme.rs @@ -7,7 +7,7 @@ use winit::{ window::{Theme, WindowBuilder}, }; -fn main() { +fn main() -> std::result::Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -67,5 +67,5 @@ fn main() { }, _ => (), } - }); + }) } diff --git a/examples/timer.rs b/examples/timer.rs index 4d609bef23e..138a78e233b 100644 --- a/examples/timer.rs +++ b/examples/timer.rs @@ -10,7 +10,7 @@ use winit::{ window::WindowBuilder, }; -fn main() { +fn main() -> std::result::Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -38,5 +38,5 @@ fn main() { } => control_flow.set_exit(), _ => (), } - }); + }) } diff --git a/examples/touchpad_gestures.rs b/examples/touchpad_gestures.rs index 1a749a19a60..f38a1ff67a4 100644 --- a/examples/touchpad_gestures.rs +++ b/examples/touchpad_gestures.rs @@ -5,7 +5,7 @@ use winit::{ window::WindowBuilder, }; -fn main() { +fn main() -> std::result::Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -42,5 +42,5 @@ fn main() { _ => (), } } - }); + }) } diff --git a/examples/transparent.rs b/examples/transparent.rs index 88c69d1b302..16ebb0d6a37 100644 --- a/examples/transparent.rs +++ b/examples/transparent.rs @@ -7,7 +7,7 @@ use winit::{ window::WindowBuilder, }; -fn main() { +fn main() -> std::result::Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -30,5 +30,5 @@ fn main() { } => control_flow.set_exit(), _ => (), } - }); + }) } diff --git a/examples/web.rs b/examples/web.rs index 5be8af29de4..97a4399669d 100644 --- a/examples/web.rs +++ b/examples/web.rs @@ -6,7 +6,7 @@ use winit::{ window::WindowBuilder, }; -pub fn main() { +pub fn main() -> std::result::Result<(), impl std::error::Error> { let event_loop = EventLoop::new(); let window = WindowBuilder::new() @@ -33,7 +33,7 @@ pub fn main() { } _ => (), } - }); + }) } #[cfg(wasm_platform)] diff --git a/examples/window.rs b/examples/window.rs index e54a2f661c4..209b2f19888 100644 --- a/examples/window.rs +++ b/examples/window.rs @@ -7,7 +7,7 @@ use winit::{ window::WindowBuilder, }; -fn main() { +fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -25,11 +25,14 @@ fn main() { Event::WindowEvent { event: WindowEvent::CloseRequested, window_id, - } if window_id == window.id() => control_flow.set_exit(), + } if window_id == window.id() => { + log::warn!("Close button pressed"); + control_flow.set_exit() + } Event::MainEventsCleared => { window.request_redraw(); } _ => (), } - }); + }) } diff --git a/examples/window_buttons.rs b/examples/window_buttons.rs index 5d41144dbd0..92137ca6179 100644 --- a/examples/window_buttons.rs +++ b/examples/window_buttons.rs @@ -10,7 +10,7 @@ use winit::{ window::{WindowBuilder, WindowButtons}, }; -fn main() { +fn main() -> std::result::Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -64,5 +64,5 @@ fn main() { } if window_id == window.id() => control_flow.set_exit(), _ => (), } - }); + }) } diff --git a/examples/window_debug.rs b/examples/window_debug.rs index 01077a8b6e1..2678d0ca435 100644 --- a/examples/window_debug.rs +++ b/examples/window_debug.rs @@ -10,7 +10,7 @@ use winit::{ window::{Fullscreen, WindowBuilder}, }; -fn main() { +fn main() -> std::result::Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -128,5 +128,5 @@ fn main() { } if window_id == window.id() => control_flow.set_exit(), _ => (), } - }); + }) } diff --git a/examples/window_drag_resize.rs b/examples/window_drag_resize.rs index 95a67377916..5441aecf769 100644 --- a/examples/window_drag_resize.rs +++ b/examples/window_drag_resize.rs @@ -11,7 +11,7 @@ use winit::{ const BORDER: f64 = 8.0; -fn main() { +fn main() -> std::result::Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -67,7 +67,7 @@ fn main() { _ => (), }, _ => (), - }); + }) } fn cursor_direction_icon(resize_direction: Option) -> CursorIcon { diff --git a/examples/window_icon.rs b/examples/window_icon.rs index ed98b3d5779..d7c0504a8fb 100644 --- a/examples/window_icon.rs +++ b/examples/window_icon.rs @@ -9,7 +9,7 @@ use winit::{ window::{Icon, WindowBuilder}, }; -fn main() { +fn main() -> std::result::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 @@ -43,7 +43,7 @@ fn main() { _ => (), } } - }); + }) } fn load_icon(path: &Path) -> Icon { diff --git a/examples/window_ondemand.rs b/examples/window_ondemand.rs new file mode 100644 index 00000000000..9bda58d6973 --- /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 simple_logger::SimpleLogger; + + use winit::{ + event::{Event, WindowEvent}, + event_loop::EventLoop, + platform::run_ondemand::EventLoopExtRunOnDemand, + window::{Window, WindowBuilder}, + }; + + #[derive(Default)] + struct App { + window: Option, + } + + SimpleLogger::new().init().unwrap(); + let mut event_loop = EventLoop::new(); + + { + let mut app = App::default(); + + event_loop.run_ondemand(move |event, event_loop, control_flow| { + control_flow.set_wait(); + println!("Run 1: {:?}", event); + + if let Some(window) = &app.window { + match event { + Event::WindowEvent { + event: WindowEvent::CloseRequested, + window_id, + } => { + if window_id == window.id() { + control_flow.set_exit() + } + } + Event::MainEventsCleared => { + window.request_redraw(); + } + _ => (), + } + } else if let Event::Resumed = event { + app.window = Some( + WindowBuilder::new() + .with_title("Fantastic window number one!") + .with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0)) + .build(event_loop) + .unwrap(), + ); + } + })?; + } + + { + let mut app = App::default(); + + event_loop.run_ondemand(move |event, event_loop, control_flow| { + control_flow.set_wait(); + println!("Run 2: {:?}", event); + + if let Some(window) = &app.window { + match event { + Event::WindowEvent { + event: WindowEvent::CloseRequested, + window_id, + } => { + if window_id == window.id() { + control_flow.set_exit() + } + } + Event::MainEventsCleared => { + window.request_redraw(); + } + _ => (), + } + } else if let Event::Resumed = event { + app.window = Some( + WindowBuilder::new() + .with_title("Fantastic window number two!") + .with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0)) + .build(event_loop) + .unwrap(), + ); + } + }) + } +} diff --git a/examples/window_run_return.rs b/examples/window_pump_events.rs similarity index 62% rename from examples/window_run_return.rs rename to examples/window_pump_events.rs index f088a51437a..56ea0d5458a 100644 --- a/examples/window_run_return.rs +++ b/examples/window_pump_events.rs @@ -7,32 +7,27 @@ 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::{Event, PumpStatus, WindowEvent}, event_loop::EventLoop, - platform::run_return::EventLoopExtRunReturn, + platform::pump_events::EventLoopExtPumpEvents, window::WindowBuilder, }; let mut event_loop = EventLoop::new(); SimpleLogger::new().init().unwrap(); - let _window = WindowBuilder::new() + let window = WindowBuilder::new() .with_title("A fantastic window!") .build(&event_loop) .unwrap(); - let mut quit = false; - - while !quit { - event_loop.run_return(|event, _, control_flow| { - control_flow.set_wait(); - + 'main: loop { + let status = event_loop.pump_events(|event, _, control_flow| { if let Event::WindowEvent { event, .. } = &event { // Print only Window events to reduce noise println!("{event:?}"); @@ -41,16 +36,17 @@ 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(); } _ => (), } }); + 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"); @@ -60,5 +56,5 @@ fn main() { #[cfg(any(ios_platform, wasm_platform))] fn main() { - println!("This platform doesn't support run_return."); + println!("This platform doesn't support pump_events."); } diff --git a/examples/window_pump_events_rfd.rs b/examples/window_pump_events_rfd.rs new file mode 100644 index 00000000000..5ae9ed5e821 --- /dev/null +++ b/examples/window_pump_events_rfd.rs @@ -0,0 +1,61 @@ +#![allow(clippy::single_match)] + +// Limit this example to only compatible platforms. +#[cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform,))] +fn main() -> std::process::ExitCode { + use std::{process::ExitCode, thread::sleep, time::Duration}; + + use simple_logger::SimpleLogger; + use winit::{ + event::{Event, PumpStatus, WindowEvent}, + event_loop::EventLoop, + platform::pump_events::EventLoopExtPumpEvents, + 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 status = event_loop.pump_events(|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 rendering + println!("rendering"); + + let _dialog = rfd::MessageDialog::new() + .set_title("Msg!") + .set_description("Description!") + .set_buttons(rfd::MessageButtons::YesNo) + .show(); + + sleep(Duration::from_millis(33)); + } +} + +#[cfg(any(target_os = "ios", target_arch = "wasm32"))] +fn main() { + println!("This platform doesn't support pump_events."); +} diff --git a/examples/window_resize_increments.rs b/examples/window_resize_increments.rs index ce06daf22f9..59c2e411f56 100644 --- a/examples/window_resize_increments.rs +++ b/examples/window_resize_increments.rs @@ -7,7 +7,7 @@ use winit::{ window::WindowBuilder, }; -fn main() { +fn main() -> std::result::Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -53,5 +53,5 @@ fn main() { Event::MainEventsCleared => window.request_redraw(), _ => (), } - }); + }) } diff --git a/src/error.rs b/src/error.rs index c039f3b8686..bfddb7733ea 100644 --- a/src/error.rs +++ b/src/error.rs @@ -9,6 +9,10 @@ pub enum ExternalError { 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), } /// The error type for when the requested operation is not supported by the backend. @@ -59,8 +63,10 @@ impl fmt::Display for OsError { impl fmt::Display for ExternalError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { match self { + ExternalError::AlreadyRunning => write!(f, "EventLoop is already running"), ExternalError::NotSupported(e) => e.fmt(f), ExternalError::Os(e) => e.fmt(f), + ExternalError::ExitFailure(status) => write!(f, "Exit Failure: {status}"), } } } diff --git a/src/event.rs b/src/event.rs index 96123683457..0412c98b01e 100644 --- a/src/event.rs +++ b/src/event.rs @@ -323,6 +323,14 @@ pub enum StartCause { Init, } +/// The return status for `pump_events` +pub enum PumpStatus { + /// Continue running external loop + Continue, + /// exit external loop + Exit(i32), +} + /// Describes an event from a [`Window`]. #[derive(Debug, PartialEq)] pub enum WindowEvent<'a> { diff --git a/src/event_loop.rs b/src/event_loop.rs index 1f00f7d7ac1..ee9bc3049d7 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -15,6 +15,7 @@ use instant::{Duration, Instant}; use once_cell::sync::OnceCell; use raw_window_handle::{HasRawDisplayHandle, RawDisplayHandle}; +use crate::error::ExternalError; 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 @@ -282,23 +283,36 @@ 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 window system 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. + /// + /// iOS applications are recommended to call `run_noreturn()` for added clarity that the + /// function will never return. + /// - **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 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<(), ExternalError> where F: 'static + FnMut(Event<'_, T>, &EventLoopWindowTarget, &mut ControlFlow), { diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 91c50777397..0c21c3ce072 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -32,12 +32,14 @@ pub mod windows; #[cfg(x11_platform)] pub mod x11; +#[cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform, android_platform))] +pub mod run_ondemand; + #[cfg(any( windows_platform, macos_platform, android_platform, x11_platform, wayland_platform, - orbital_platform ))] -pub mod run_return; +pub mod pump_events; diff --git a/src/platform/pump_events.rs b/src/platform/pump_events.rs new file mode 100644 index 00000000000..70c2506c647 --- /dev/null +++ b/src/platform/pump_events.rs @@ -0,0 +1,33 @@ +use crate::{ + event::{Event, PumpStatus}, + event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget}, +}; + +/// 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; + + fn pump_events(&mut self, 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, event_handler: F) -> PumpStatus + where + F: FnMut( + Event<'_, Self::UserEvent>, + &EventLoopWindowTarget, + &mut ControlFlow, + ), + { + self.event_loop.pump_events(event_handler) + } +} diff --git a/src/platform/run_ondemand.rs b/src/platform/run_ondemand.rs new file mode 100644 index 00000000000..b8ce35275c0 --- /dev/null +++ b/src/platform/run_ondemand.rs @@ -0,0 +1,72 @@ +use crate::{ + error::ExternalError, + event::Event, + event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget}, +}; + +/// 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 [`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 `run` for portability, unless you specifically need + /// the ability to re-run a single event loop more than once + /// + /// ## Platform-specific + /// + /// See the platform specific notes for [`EventLoop::run`] + /// + /// - **Android:** Although this API could technically be supported on Android, it's currently + /// not exposed because it's very unclear how it could be used meaningfully. + fn run_ondemand(&mut self, event_handler: F) -> Result<(), ExternalError> + 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<(), ExternalError> + 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 750b0416184..00000000000 --- 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 2e1924e27b3..07862e9a09a 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -19,7 +19,6 @@ use raw_window_handle::{ AndroidDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle, }; -use crate::platform_impl::Fullscreen; use crate::{ dpi::{PhysicalPosition, PhysicalSize, Position, Size}, error, @@ -29,6 +28,7 @@ use crate::{ self, CursorGrabMode, ImePurpose, ResizeDirection, Theme, WindowButtons, WindowLevel, }, }; +use crate::{error::ExternalError, event::PumpStatus, platform_impl::Fullscreen}; static HAS_FOCUS: Lazy> = Lazy::new(|| RwLock::new(true)); @@ -196,6 +196,12 @@ fn ndk_keycode_to_virtualkeycode(keycode: Keycode) -> 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, @@ -293,7 +299,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, } #[derive(Default, Debug, Clone, PartialEq)] @@ -318,12 +328,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(); @@ -347,32 +351,32 @@ 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, } } - 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); @@ -381,7 +385,7 @@ impl EventLoop { sticky_exit_callback( event::Event::Resumed, self.window_target(), - control_flow, + &mut control_flow, callback, ); } @@ -389,12 +393,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"); } @@ -406,7 +410,7 @@ impl EventLoop { event: event::WindowEvent::Focused(true), }, self.window_target(), - control_flow, + &mut control_flow, callback, ); } @@ -418,7 +422,7 @@ impl EventLoop { event: event::WindowEvent::Focused(false), }, self.window_target(), - control_flow, + &mut control_flow, callback, ); } @@ -435,7 +439,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 => { @@ -544,7 +553,7 @@ impl EventLoop { sticky_exit_callback( event, self.window_target(), - control_flow, + &mut control_flow, callback ); } @@ -577,7 +586,7 @@ impl EventLoop { sticky_exit_callback( event, self.window_target(), - control_flow, + &mut control_flow, callback ); } @@ -597,7 +606,7 @@ impl EventLoop { sticky_exit_callback( crate::event::Event::UserEvent(event), self.window_target(), - control_flow, + &mut control_flow, callback, ); } @@ -606,7 +615,7 @@ impl EventLoop { sticky_exit_callback( event::Event::MainEventsCleared, self.window_target(), - control_flow, + &mut control_flow, callback, ); @@ -623,163 +632,187 @@ 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); + self.control_flow = control_flow; + self.pending_redraw = pending_redraw; + } - 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); - } + pub fn run(mut self, event_handler: F) -> Result<(), ExternalError> + where + F: 'static + + FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget, &mut ControlFlow), + { + self.run_ondemand(event_handler) + } + + pub fn run_ondemand(&mut self, mut event_handler: F) -> Result<(), ExternalError> + where + F: FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget, &mut ControlFlow), + { + if self.loop_running { + return Err(ExternalError::AlreadyRunning); } - IterationResult { - wait_start: start, - deadline, - timeout, + loop { + if let PumpStatus::Exit(code) = self.pump_events_with_timeout(None, &mut event_handler) + { + if code == 0 { + break Ok(()); + } else { + break Err(ExternalError::ExitFailure(code)); + } + } } } - pub fn run(mut self, event_handler: F) -> ! + pub fn pump_events(&mut self, event_handler: F) -> PumpStatus where - F: 'static - + FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget, &mut ControlFlow), + F: FnMut(event::Event<'_, T>, &RootELW, &mut ControlFlow), { - let exit_code = self.run_return(event_handler); - ::std::process::exit(exit_code); + self.pump_events_with_timeout(Some(Duration::ZERO), event_handler) } - pub fn run_return(&mut self, mut callback: F) -> i32 + fn pump_events_with_timeout( + &mut self, + timeout: Option, + mut callback: F, + ) -> PumpStatus where F: FnMut(event::Event<'_, T>, &RootELW, &mut ControlFlow), { - let mut control_flow = ControlFlow::default(); - let mut cause = StartCause::Init; - let mut pending_redraw = false; + 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); + } - // run the initial loop iteration - let mut iter_result = self.single_iteration( - &mut control_flow, - None, - &mut pending_redraw, - &mut cause, - &mut callback, - ); + self.poll_events_with_timeout(timeout, &mut callback); + if let ControlFlow::ExitWithCode(code) = self.control_flow { + self.loop_running = false; - let exit_code = loop { - if let ControlFlow::ExitWithCode(code) = control_flow { - break code; - } + let mut dummy = self.control_flow; + sticky_exit_callback( + event::Event::LoopDestroyed, + self.window_target(), + &mut dummy, + &mut callback, + ); - let mut timeout = iter_result.timeout; + PumpStatus::Exit(code) + } else { + PumpStatus::Continue + } + } - // 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)) - } + 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(); - 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; + 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::from_millis(0)) + } else { + let control_flow_timeout = match self.control_flow { + ControlFlow::Wait => None, + ControlFlow::Poll => Some(Duration::from_millis(0)), + ControlFlow::WaitUntil(wait_deadline) => { + if wait_deadline > start { + Some(wait_deadline - start) + } else { + Some(Duration::from_millis(0)) } } - android_activity::PollEvent::Timeout => {} - android_activity::PollEvent::Main(event) => { - main_event = Some(event); - } - 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: None, + } + } else { + StartCause::WaitCancelled { + start, + requested_resume: Some(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/macos/app_state.rs b/src/platform_impl/macos/app_state.rs index 1a85b40f63f..25649352336 100644 --- a/src/platform_impl/macos/app_state.rs +++ b/src/platform_impl/macos/app_state.rs @@ -11,9 +11,15 @@ use std::{ time::Instant, }; -use core_foundation::runloop::{CFRunLoopGetMain, CFRunLoopWakeUp}; -use objc2::foundation::{is_main_thread, NSSize}; +use core_foundation::runloop::{ + CFRunLoopGetCurrent, CFRunLoopGetMain, CFRunLoopStop, CFRunLoopWakeUp, +}; use objc2::rc::autoreleasepool; +use objc2::{ + class, + foundation::{is_main_thread, NSSize}, + msg_send, +}; use once_cell::sync::Lazy; use super::appkit::{NSApp, NSApplication, NSApplicationActivationPolicy, NSEvent}; @@ -65,14 +71,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 +99,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 +112,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,7 +125,10 @@ impl EventHandler for EventLoopHandler { #[derive(Default)] struct Handler { - ready: AtomicBool, + stop_app_on_launch: AtomicBool, + stop_app_before_wait: AtomicBool, + launched: AtomicBool, + running: AtomicBool, in_callback: AtomicBool, control_flow: Mutex, control_flow_prev: Mutex, @@ -141,12 +155,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); + } + + /// `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) } - fn set_ready(&self) { - self.ready.store(true, Ordering::Release); + /// 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,6 +195,59 @@ impl Handler { ) } + /// 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_prev.lock().unwrap() = ControlFlow::default(); + *self.control_flow.lock().unwrap() = ControlFlow::default(); + } + + 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) + } + 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; @@ -192,6 +284,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 { @@ -260,11 +356,39 @@ impl AppState { })); } + 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 control_flow() -> ControlFlow { + HANDLER.get_old_and_new_control_flow().1 + } + + pub fn clear_callback() { + HANDLER.callback.lock().unwrap().take(); + } + 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(); + HANDLER.exit(); + Self::clear_callback(); if let ControlFlow::ExitWithCode(code) = HANDLER.get_old_and_new_control_flow().1 { code } else { @@ -272,6 +396,24 @@ impl AppState { } } + 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,30 +428,42 @@ 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; } let start = HANDLER.get_start_time().unwrap(); @@ -368,13 +522,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; } @@ -392,12 +562,11 @@ 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(); + } + + if HANDLER.should_stop_app_before_wait() { + Self::stop(); } HANDLER.update_start_time(); match HANDLER.get_old_and_new_control_flow() { diff --git a/src/platform_impl/macos/event_loop.rs b/src/platform_impl/macos/event_loop.rs index 72434ac478b..b273b913ee9 100644 --- a/src/platform_impl/macos/event_loop.rs +++ b/src/platform_impl/macos/event_loop.rs @@ -11,10 +11,15 @@ use std::{ sync::mpsc, }; -use core_foundation::base::{CFIndex, CFRelease}; use core_foundation::runloop::{ - kCFRunLoopCommonModes, CFRunLoopAddSource, CFRunLoopGetMain, CFRunLoopSourceContext, - CFRunLoopSourceCreate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp, + kCFRunLoopCommonModes, kCFRunLoopDefaultMode, CFRunLoopAddSource, CFRunLoopGetMain, + CFRunLoopRun, CFRunLoopRunInMode, CFRunLoopSourceContext, CFRunLoopSourceCreate, + CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp, +}; +use core_foundation::{ + base::{CFIndex, CFRelease}, + date::CFTimeInterval, + runloop::{CFRunLoopGetCurrent, CFRunLoopRef}, }; use objc2::foundation::is_main_thread; use objc2::rc::{autoreleasepool, Id, Shared}; @@ -23,7 +28,8 @@ use raw_window_handle::{AppKitDisplayHandle, RawDisplayHandle}; use super::appkit::{NSApp, NSApplicationActivationPolicy, NSEvent}; use crate::{ - event::Event, + error::ExternalError, + event::{Event, PumpStatus}, event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootWindowTarget}, platform::macos::ActivationPolicy, platform_impl::platform::{ @@ -183,18 +189,25 @@ impl EventLoop { &self.window_target } - pub fn run(mut self, callback: F) -> ! + pub fn run(mut self, callback: F) -> Result<(), ExternalError> 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<(), ExternalError> where F: FnMut(Event<'_, T>, &RootWindowTarget, &mut ControlFlow), { + if AppState::is_running() { + return Err(ExternalError::AlreadyRunning); + } + // 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 @@ -217,6 +230,14 @@ impl EventLoop { drop(callback); AppState::set_callback(weak_cb, Rc::clone(&self.window_target)); + + if AppState::is_launched() { + debug_assert!(!AppState::is_running()); + + log::debug!("Starting loop with pre-launched NSApp..."); + AppState::start_running(); // Set is_running = true + dispatch `NewEvents(Init)` + `Resumed` + } + AppState::set_stop_runloop_before_wait(false); unsafe { app.run() }; if let Some(panic) = self.panic_info.take() { @@ -227,7 +248,82 @@ impl EventLoop { }); drop(self._callback.take()); - exit_code + if exit_code == 0 { + Ok(()) + } else { + Err(ExternalError::ExitFailure(exit_code)) + } + } + + pub fn pump_events(&mut self, callback: F) -> PumpStatus + where + F: FnMut(Event<'_, T>, &RootWindowTarget, &mut ControlFlow), + { + log::debug!("Called pump_events"); + + // 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. + 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); + + AppState::set_callback(weak_cb, Rc::clone(&self.window_target)); + + // Note: there are two possible `Init` conditions we have to handle - either the + // `NSApp` is not yet launched, or else the `EventLoop` is not yet running. + + // 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() { + AppState::start_running(); // Set is_running = true + dispatch `NewEvents(Init)` + `Resumed` + } else { + AppState::set_stop_runloop_before_wait(true); + unsafe { + app.run(); + } + } + + if let Some(panic) = self.panic_info.take() { + drop(self._callback.take()); + resume_unwind(panic); + } + AppState::clear_callback(); + }); + drop(self._callback.take()); + + let status = if let ControlFlow::ExitWithCode(code) = AppState::control_flow() { + AppState::exit(); + PumpStatus::Exit(code) + } else { + PumpStatus::Continue + }; + + log::debug!("Finished pump_events"); + status } pub fn create_proxy(&self) -> EventLoopProxy { @@ -245,6 +341,8 @@ pub fn stop_app_on_panic R + UnwindSafe, R>( match catch_unwind(f) { Ok(r) => Some(r), Err(e) => { + log::error!("Stopping NSApp due to a panic!: {:?}", panic_info); + // It's important that we set the panic before requesting a `stop` // because some callback are still called during the `stop` message // and we need to know in those callbacks if the application is currently diff --git a/src/platform_impl/orbital/event_loop.rs b/src/platform_impl/orbital/event_loop.rs index f0ad43d5b3a..5fcfd02bcae 100644 --- a/src/platform_impl/orbital/event_loop.rs +++ b/src/platform_impl/orbital/event_loop.rs @@ -253,15 +253,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, @@ -394,9 +385,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<(), ExternalError> 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 = @@ -639,7 +631,11 @@ impl EventLoop { &mut control_flow, ); - code + if code == 0 { + break Ok(()); + } else { + break Err(ExternalError::ExitFailure(exit_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 6d5f9d44a53..eadf468a291 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -7,14 +7,16 @@ use std::{ collections::VecDeque, ffi::c_void, marker::PhantomData, - mem, panic, ptr, + mem, + panic, + ptr, rc::Rc, sync::{ atomic::{AtomicU32, Ordering}, mpsc::{self, Receiver, Sender}, Arc, Mutex, MutexGuard, }, - thread, + //thread, time::{Duration, Instant}, }; @@ -23,13 +25,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, WindowsProgramming::INFINITE}, UI::{ Controls::{HOVER_DEFAULT, WM_MOUSELEAVE}, @@ -51,14 +51,13 @@ 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_CHAR, WM_CLOSE, WM_CREATE, WM_DESTROY, WM_DPICHANGED, + 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, 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_CHAR, WM_CLOSE, WM_CREATE, WM_DESTROY, WM_DPICHANGED, WM_DROPFILES, 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, @@ -76,7 +75,10 @@ use windows_sys::Win32::{ use crate::{ dpi::{PhysicalPosition, PhysicalSize}, - event::{DeviceEvent, Event, Force, Ime, KeyboardInput, Touch, TouchPhase, WindowEvent}, + error::ExternalError, + event::{ + DeviceEvent, Event, Force, Ime, KeyboardInput, PumpStatus, Touch, TouchPhase, WindowEvent, + }, event_loop::{ ControlFlow, DeviceEventFilter, EventLoopClosed, EventLoopWindowTarget as RootELW, }, @@ -96,6 +98,8 @@ use crate::{ }; use runner::{EventLoopRunner, EventLoopRunnerShared}; +use self::runner::RunnerState; + use super::window::set_skip_taskbar; type GetPointerFrameInfoHistory = unsafe extern "system" fn( @@ -205,13 +209,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()); @@ -238,69 +236,205 @@ impl EventLoop { &self.window_target } - pub fn run(mut self, event_handler: F) -> ! + pub fn run(mut self, event_handler: F) -> Result<(), ExternalError> 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<(), ExternalError> 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(ExternalError::AlreadyRunning); + } - unsafe { - self.window_target - .p - .runner_shared - .set_event_handler(move |event, control_flow| { - event_handler(event, event_loop_windows_ref, control_flow) - }); - } + let event_loop_windows_ref = &self.window_target; - let runner = &self.window_target.p.runner_shared; + unsafe { + self.window_target + .p + .runner_shared + .set_event_handler(move |event, control_flow| { + event_handler(event, event_loop_windows_ref, control_flow) + }); + runner.start(); + } + } let exit_code = unsafe { - let mut msg = mem::zeroed(); - - runner.poll(); 'main: loop { - if GetMessageW(&mut msg, 0, 0, 0) == false.into() { - break 'main 0; + if let ControlFlow::ExitWithCode(code) = self.wait_and_dispatch_message() { + break 'main code; } - 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 ControlFlow::ExitWithCode(code) = self.dispatch_peeked_messages() { + break 'main code; } + } + }; + + let runner = &self.window_target.p.runner_shared; + unsafe { + runner.loop_destroyed(); + } + + runner.reset_runner(); + + if exit_code == 0 { + Ok(()) + } else { + Err(ExternalError::ExitFailure(exit_code)) + } + } - if let Err(payload) = runner.take_panic_error() { - runner.reset_runner(); - panic::resume_unwind(payload); + pub fn pump_events(&mut self, mut event_handler: F) -> PumpStatus + where + F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), + { + { + let runner = &self.window_target.p.runner_shared; + + if runner.state() == RunnerState::Uninitialized { + let event_loop_windows_ref = &self.window_target; + unsafe { + self.window_target.p.runner_shared.set_event_handler( + move |event, control_flow| { + event_handler(event, event_loop_windows_ref, control_flow) + }, + ); + runner.start(); } + } else { + unsafe { runner.wakeup(); } + } + } - if let ControlFlow::ExitWithCode(code) = runner.control_flow() { - if !runner.handling_events() { - break 'main code; - } + unsafe { + self.dispatch_peeked_messages(); + }; + + let runner = &self.window_target.p.runner_shared; + let status = if let ControlFlow::ExitWithCode(code) = runner.control_flow() { + unsafe { + 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 { + unsafe { runner.prepare_wait(); } + PumpStatus::Continue + }; + + status + } + + /// Wait for one message and dispatch it, optionally with a timeout if control_flow is `WaitUntil` + unsafe fn wait_and_dispatch_message(&mut self) -> ControlFlow { + let mut msg = mem::zeroed(); + + let runner = &self.window_target.p.runner_shared; + + // 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(); + + let start = Instant::now(); + + let timeout = match runner.control_flow() { + ControlFlow::Wait => None, + ControlFlow::Poll => Some(Duration::from_millis(0)), + ControlFlow::WaitUntil(wait_deadline) => { + if wait_deadline > start { + Some(wait_deadline - start) + } else { + Some(Duration::from_millis(0)) } } + ControlFlow::ExitWithCode(_code) => unreachable!(), }; + if let Some(timeout) = timeout { + // XXX: how should we pick a timer ID? + SetTimer(0, 0xf00, dur2timeout(timeout), None); + } else { + KillTimer(0, 0xf00); + } - unsafe { - runner.loop_destroyed(); + if GetMessageW(&mut msg, 0, 0, 0) == false.into() { + // A return value of 0 implies `WM_QUIT` + runner.set_exit_control_flow(); + return runner.control_flow(); } - runner.reset_runner(); - exit_code + runner.wakeup(); + + 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` + unsafe fn dispatch_peeked_messages(&mut self) -> ControlFlow { + let runner = &self.window_target.p.runner_shared; + + let mut msg = mem::zeroed(); + + let mut control_flow = runner.control_flow(); + + loop { + if PeekMessageW(&mut msg, 0, 0, 0, PM_REMOVE) == false.into() { + break; + } + + 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); + } + + control_flow = runner.control_flow(); + if let ControlFlow::ExitWithCode(_code) = control_flow { + break; + } + } + + control_flow } pub fn create_proxy(&self) -> EventLoopProxy { @@ -380,109 +514,6 @@ fn main_thread_id() -> u32 { unsafe { MAIN_THREAD_ID } } -fn get_wait_thread_id() -> u32 { - unsafe { - let mut msg = mem::zeroed(); - let result = GetMessageW( - &mut msg, - -1, - SEND_WAIT_THREAD_ID_MSG_ID.get(), - SEND_WAIT_THREAD_ID_MSG_ID.get(), - ); - assert_eq!( - msg.message, - SEND_WAIT_THREAD_ID_MSG_ID.get(), - "this shouldn't be possible. please open an issue with Winit. error code: {result}" - ); - msg.lParam as u32 - } -} - -static WAIT_PERIOD_MIN: Lazy> = Lazy::new(|| unsafe { - let mut caps = TIMECAPS { - wPeriodMin: 0, - wPeriodMax: 0, - }; - if timeGetDevCaps(&mut caps, mem::size_of::() as u32) == TIMERR_NOERROR { - Some(caps.wPeriodMin) - } else { - None - } -}); - -fn wait_thread(parent_thread_id: u32, msg_window_id: HWND) { - unsafe { - let mut msg: MSG; - - let cur_thread_id = GetCurrentThreadId(); - PostThreadMessageW( - parent_thread_id, - SEND_WAIT_THREAD_ID_MSG_ID.get(), - 0, - cur_thread_id as LPARAM, - ); - - let mut wait_until_opt = None; - 'main: loop { - // Zeroing out the message ensures that the `WaitUntilInstantBox` doesn't get - // double-freed if `MsgWaitForMultipleObjectsEx` returns early and there aren't - // additional messages to process. - msg = mem::zeroed(); - - if wait_until_opt.is_some() { - if PeekMessageW(&mut msg, 0, 0, 0, PM_REMOVE) != false.into() { - TranslateMessage(&msg); - DispatchMessageW(&msg); - } - } else if GetMessageW(&mut msg, 0, 0, 0) == false.into() { - break 'main; - } else { - TranslateMessage(&msg); - DispatchMessageW(&msg); - } - - if msg.message == WAIT_UNTIL_MSG_ID.get() { - wait_until_opt = Some(*WaitUntilInstantBox::from_raw(msg.lParam as *mut _)); - } else if msg.message == CANCEL_WAIT_UNTIL_MSG_ID.get() { - wait_until_opt = None; - } - - if let Some(wait_until) = wait_until_opt { - let now = Instant::now(); - if now < wait_until { - // Windows' scheduler has a default accuracy of several ms. This isn't good enough for - // `WaitUntil`, so we request the Windows scheduler to use a higher accuracy if possible. - // If we couldn't query the timer capabilities, then we use the default resolution. - if let Some(period) = *WAIT_PERIOD_MIN { - timeBeginPeriod(period); - } - // `MsgWaitForMultipleObjects` is bound by the granularity of the scheduler period. - // Because of this, we try to reduce the requested time just enough to undershoot `wait_until` - // by the smallest amount possible, and then we busy loop for the remaining time inside the - // NewEvents message handler. - let resume_reason = MsgWaitForMultipleObjectsEx( - 0, - ptr::null(), - dur2timeout(wait_until - now).saturating_sub(WAIT_PERIOD_MIN.unwrap_or(1)), - QS_ALLEVENTS, - MWMO_INPUTAVAILABLE, - ); - if let Some(period) = *WAIT_PERIOD_MIN { - timeEndPeriod(period); - } - if resume_reason == WAIT_TIMEOUT { - PostMessageW(msg_window_id, PROCESS_NEW_EVENTS_MSG_ID.get(), 0, 0); - wait_until_opt = None; - } - } else { - PostMessageW(msg_window_id, PROCESS_NEW_EVENTS_MSG_ID.get(), 0, 0); - wait_until_opt = None; - } - } - } - } -} - // Implementation taken from https://github.com/rust-lang/rust/blob/db5476571d9b27c862b95c1e64764b0ac8980e23/src/libstd/sys/windows/mod.rs fn dur2timeout(dur: Duration) -> u32 { // Note that a duration is a (u64, u32) (seconds, nanoseconds) pair, and the @@ -601,8 +632,6 @@ impl EventLoopProxy { } } -type WaitUntilInstantBox = Box; - /// A lazily-initialized window message ID. pub struct LazyMessageId { /// The ID. @@ -638,6 +667,8 @@ impl LazyMessageId { assert!(self.name.ends_with('\0')); let new_id = unsafe { RegisterWindowMessageA(self.name.as_ptr()) }; + log::debug!("Registered win32 message ID {} = {}", self.name, new_id); + assert_ne!( new_id, 0, @@ -662,13 +693,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"); @@ -784,74 +808,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. fn update_modifiers(window: HWND, userdata: &WindowData) { use crate::event::WindowEvent::ModifiersChanged; @@ -1120,13 +1076,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); - } } DefWindowProcW(window, msg, wparam, lparam) @@ -2352,27 +2302,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) } @@ -2507,40 +2438,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 c4ac1eb3484..2a18abd85d0 100644 --- a/src/platform_impl/windows/event_loop/runner.rs +++ b/src/platform_impl/windows/event_loop/runner.rs @@ -2,15 +2,12 @@ use std::{ any::Any, cell::{Cell, RefCell}, collections::{HashSet, VecDeque}, - mem, panic, ptr, + 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,6 @@ 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, control_flow: Cell, runner_state: Cell, @@ -47,7 +43,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. @@ -57,7 +53,7 @@ enum RunnerState { 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, + //HandlingRedrawEvents, /// The event loop has been destroyed. No other events will be emitted. Destroyed, } @@ -68,10 +64,9 @@ 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, runner_state: Cell::new(RunnerState::Uninitialized), control_flow: Cell::new(ControlFlow::Poll), panic_error: Cell::new(None), @@ -96,7 +91,6 @@ impl EventLoopRunner { pub(crate) fn reset_runner(&self) { let EventLoopRunner { thread_msg_target: _, - wait_thread_id: _, runner_state, panic_error, control_flow, @@ -118,14 +112,6 @@ impl EventLoopRunner { 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 +119,16 @@ impl EventLoopRunner { } } - pub fn control_flow(&self) -> ControlFlow { - self.control_flow.get() + pub fn state(&self) -> RunnerState { + self.runner_state.get() + } + + pub fn set_exit_control_flow(&self) { + self.control_flow.set(ControlFlow::ExitWithCode(0)) } - pub fn handling_events(&self) -> bool { - self.runner_state.get() != RunnerState::Idle + pub fn control_flow(&self) -> ControlFlow { + self.control_flow.get() } pub fn should_buffer(&self) -> bool { @@ -202,16 +192,20 @@ impl EventLoopRunner { /// Event dispatch functions. impl EventLoopRunner { - pub(crate) unsafe fn poll(&self) { + pub(crate) unsafe fn start(&self) { + self.move_state_to(RunnerState::Idle); + } + + pub(crate) unsafe fn prepare_wait(&self) { + self.move_state_to(RunnerState::Idle); + } + + pub(crate) unsafe fn wakeup(&self) { self.move_state_to(RunnerState::HandlingMainEvents); } pub(crate) unsafe 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); } else if self.should_buffer() { // If the runner is already borrowed, we're in the middle of an event loop invocation. Add @@ -220,20 +214,11 @@ 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) { self.move_state_to(RunnerState::Destroyed); } @@ -278,24 +263,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, - }; + use RunnerState::{Destroyed, HandlingMainEvents, Idle, Uninitialized}; match ( self.runner_state.replace(new_runner_state), @@ -304,17 +287,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 +310,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,19 +324,6 @@ 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"), } } @@ -402,7 +359,6 @@ 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) {