From c61e90198c2efa5f5315880bce9d321cc6b19e4b Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Sat, 18 Mar 2023 22:42:11 +0000 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 can 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`. Fixes: #2709 Fixes: #2706 Fixes: #2431 TODO: Linux + MacOS support --- Cargo.toml | 3 +- 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 | 4 +- examples/window_buttons.rs | 4 +- examples/window_debug.rs | 4 +- examples/window_icon.rs | 4 +- examples/window_resize_increments.rs | 4 +- examples/window_run_return.rs | 66 ---- src/error.rs | 6 + src/event.rs | 8 + src/event_loop.rs | 24 +- src/platform/mod.rs | 13 +- src/platform/run_return.rs | 53 ---- src/platform_impl/android/mod.rs | 284 +++++++++++------- src/platform_impl/windows/event_loop.rs | 93 +++++- .../windows/event_loop/runner.rs | 6 +- 34 files changed, 350 insertions(+), 298 deletions(-) delete mode 100644 examples/window_run_return.rs delete mode 100644 src/platform/run_return.rs diff --git a/Cargo.toml b/Cargo.toml index f0530cd2c7f..f688cb73925 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,7 +62,8 @@ simple_logger = { version = "2.1.0", default_features = false } [target.'cfg(target_os = "android")'.dependencies] # Coordinate the next winit release with android-ndk-rs: https://github.com/rust-windowing/winit/issues/1995 ndk = "0.7.0" -android-activity = "0.4.0" +#android-activity = "0.4.0" +android-activity = { path = "../android-activity/android-activity" } [target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies] objc2 = "=0.3.0-beta.3" diff --git a/examples/control_flow.rs b/examples/control_flow.rs index 94926d7764e..8882abe83dc 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 cff77885ea3..82700bb17d3 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 c1b7133de6a..5e4adb103f8 100644 --- a/examples/custom_events.rs +++ b/examples/custom_events.rs @@ -1,7 +1,7 @@ #![allow(clippy::single_match)] #[cfg(not(target_arch = "wasm32"))] -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(target_arch = "wasm32")] 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 6bfda4c2c92..a08d65138e3 100644 --- a/examples/fullscreen.rs +++ b/examples/fullscreen.rs @@ -5,7 +5,7 @@ use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEve use winit::event_loop::EventLoop; 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(); @@ -109,5 +109,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 bdf67a8624d..4135aeb11a3 100644 --- a/examples/ime.rs +++ b/examples/ime.rs @@ -9,7 +9,7 @@ use winit::{ window::WindowBuilder, }; -fn main() { +fn main() -> std::result::Result<(), impl std::error::Error> { SimpleLogger::new() .with_level(LevelFilter::Trace) .init() @@ -95,5 +95,5 @@ fn main() { } _ => (), } - }); + }) } diff --git a/examples/mouse_wheel.rs b/examples/mouse_wheel.rs index c1d8f0cb994..986f602faee 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 b999f6c9256..4460de90246 100644 --- a/examples/multithreaded.rs +++ b/examples/multithreaded.rs @@ -1,7 +1,7 @@ #![allow(clippy::single_match)] #[cfg(not(target_arch = "wasm32"))] -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 de87962d047..bb02924bdec 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 93bb43983e0..9adb903aa6b 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 2500a8c02c1..2e8ed3104c6 100644 --- a/examples/request_redraw_threaded.rs +++ b/examples/request_redraw_threaded.rs @@ -1,7 +1,7 @@ #![allow(clippy::single_match)] #[cfg(not(target_arch = "wasm32"))] -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(target_arch = "wasm32")] diff --git a/examples/resizable.rs b/examples/resizable.rs index 9e438a51d99..abf71f48e88 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 6cbbd8b9c3c..a86900774ae 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 c312460b22d..69b721f4e00 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 85d9e967f8b..92653ebb014 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(); @@ -39,5 +39,5 @@ fn main() { _ => (), } } - }); + }) } diff --git a/examples/transparent.rs b/examples/transparent.rs index 9080cbef12e..a6caf7ad7b5 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 82a3971a6e2..cfe27f91c22 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(target_arch = "wasm32")] diff --git a/examples/window.rs b/examples/window.rs index 23a633d2ede..0ebd2f1662f 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(); @@ -31,5 +31,5 @@ fn main() { } _ => (), } - }); + }) } 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 47a130178c5..ba0c72b77d3 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(); @@ -127,5 +127,5 @@ fn main() { } if window_id == window.id() => control_flow.set_exit(), _ => (), } - }); + }) } 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_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/examples/window_run_return.rs b/examples/window_run_return.rs deleted file mode 100644 index e6b3c662111..00000000000 --- a/examples/window_run_return.rs +++ /dev/null @@ -1,66 +0,0 @@ -#![allow(clippy::single_match)] - -// Limit this example to only compatible platforms. -#[cfg(any( - target_os = "windows", - target_os = "macos", - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd", - target_os = "android", -))] -fn main() { - use std::{thread::sleep, time::Duration}; - - use simple_logger::SimpleLogger; - use winit::{ - event::{Event, WindowEvent}, - event_loop::EventLoop, - platform::run_return::EventLoopExtRunReturn, - 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(); - - let mut quit = false; - - while !quit { - event_loop.run_return(|event, _, control_flow| { - control_flow.set_wait(); - - if let Event::WindowEvent { event, .. } = &event { - // Print only Window events to reduce noise - println!("{:?}", event); - } - - match event { - Event::WindowEvent { - event: WindowEvent::CloseRequested, - .. - } => { - quit = true; - } - Event::MainEventsCleared => { - control_flow.set_exit(); - } - _ => (), - } - }); - - // Sleep for 1/60 second to simulate rendering - println!("rendering"); - sleep(Duration::from_millis(16)); - } -} - -#[cfg(any(target_os = "ios", target_arch = "wasm32"))] -fn main() { - println!("This platform doesn't support run_return."); -} diff --git a/src/error.rs b/src/error.rs index c039f3b8686..ab63c48bcc3 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 da237672c78..9ac4aecc6d2 100644 --- a/src/event.rs +++ b/src/event.rs @@ -314,6 +314,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 92861cad9d3..32754324252 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -15,6 +15,7 @@ use instant::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 @@ -265,23 +266,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 f819414eddd..e584a83639d 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -48,6 +48,17 @@ pub mod windows; ))] pub mod x11; +#[cfg(any( + target_os = "windows", + target_os = "macos", + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" +))] +pub mod run_ondemand; + #[cfg(any( target_os = "windows", target_os = "macos", @@ -58,4 +69,4 @@ pub mod x11; target_os = "netbsd", target_os = "openbsd" ))] -pub mod run_return; +pub mod pump_events; \ No newline at end of file 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 48dee0322c3..45a85fd9ea6 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -191,6 +191,10 @@ 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, @@ -288,7 +292,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)] @@ -342,7 +350,11 @@ 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 } } @@ -353,7 +365,7 @@ impl EventLoop { pending_redraw: &mut bool, cause: &mut StartCause, callback: &mut F, - ) -> IterationResult + ) where F: FnMut(event::Event<'_, T>, &RootELW, &mut ControlFlow), { @@ -633,146 +645,186 @@ impl EventLoop { control_flow, callback, ); + } - let start = Instant::now(); - let (deadline, timeout); + 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) + } - 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); - } + // Private for now, since it's hard to imagine applications using this on Android + 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 { + match self.pump_events_with_timeout(None, event_handler) { + PumpStatus::Exit(code) => { + + // On other platforms we would check here that the application has destroyed all windows + // but that's not really meaningful on Android + + if code == 0 { + break Ok(()) + } else { + break Err(ExternalError::ExitFailure(exit_code)) + } + } + _ => {} + } } } - pub fn run(mut self, event_handler: F) -> ! + pub fn pump_events(&mut self, mut 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; - - // run the initial loop iteration - let mut iter_result = self.single_iteration( - &mut control_flow, - None, - &mut pending_redraw, - &mut cause, - &mut callback, - ); + if !self.loop_running { + 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 + let mut iter_result = self.single_iteration( + &mut self.control_flow, + None, + &mut self.pending_redraw, + &mut self.cause, + &mut callback, + ); + } - let exit_code = loop { - if let ControlFlow::ExitWithCode(code) = control_flow { - break code; - } + self.poll_events_with_timeout(timeout, callback); + if let ControlFlow::ExitWithCode(code) = self.control_flow { + self.loop_running = false; - let mut timeout = iter_result.timeout; + sticky_exit_callback( + event::Event::LoopDestroyed, + self.window_target(), + &mut self.control_flow, + &mut callback, + ); - // If we already have work to do then we don't want to block on the next poll... - pending_redraw |= self.redraw_flag.get_and_reset(); - if self.running && (pending_redraw || self.user_events_receiver.has_incoming()) { - timeout = Some(Duration::from_millis(0)) - } + PumpStatus::Exit(code) + } else { + PumpStatus::Continue + } + } + + fn poll_events_with_timeout(&mut self, + 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!(), + }; - let wait_cancelled = iter_result - .deadline - .map_or(false, |deadline| Instant::now() < deadline); + min_timeout(control_flow_timeout, timeout) + }; - if wait_cancelled { - cause = StartCause::WaitCancelled { - start: iter_result.wait_start, - requested_resume: iter_result.deadline, - }; + 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; + } } + 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( + &mut self.control_flow, + main_event, + &mut self.pending_redraw, + &mut self.cause, + &mut callback, + ); + }); } 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 15ff475c7c1..78d17fd7ddb 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -75,7 +75,7 @@ use windows_sys::Win32::{ use crate::{ dpi::{PhysicalPosition, PhysicalSize}, - event::{DeviceEvent, Event, Force, Ime, KeyboardInput, Touch, TouchPhase, WindowEvent}, + event::{DeviceEvent, Event, Force, Ime, KeyboardInput, Touch, TouchPhase, WindowEvent, PumpStatus}, event_loop::{ ControlFlow, DeviceEventFilter, EventLoopClosed, EventLoopWindowTarget as RootELW, }, @@ -91,11 +91,13 @@ use crate::{ window_state::{CursorFlags, ImeState, WindowFlags, WindowState}, wrap_device_id, Fullscreen, WindowId, DEVICE_ID, }, - window::WindowId as RootWindowId, + window::WindowId as RootWindowId, error::ExternalError, }; use runner::{EventLoopRunner, EventLoopRunnerShared}; -use super::window::set_skip_taskbar; +use self::runner::RunnerState; + +use super::{window::set_skip_taskbar}; type GetPointerFrameInfoHistory = unsafe extern "system" fn( pointerId: u32, @@ -234,18 +236,23 @@ 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 runner = &self.window_target.p.runner_shared; + + if runner.state() != RunnerState::Uninitialized { + return Err(ExternalError::AlreadyRunning); + } + let event_loop_windows_ref = &self.window_target; unsafe { @@ -257,7 +264,6 @@ impl EventLoop { }); } - let runner = &self.window_target.p.runner_shared; let exit_code = unsafe { let mut msg = mem::zeroed(); @@ -296,7 +302,76 @@ impl EventLoop { } runner.reset_runner(); - exit_code + + if exit_code == 0 { + Ok(()) + } else { + Err(ExternalError::ExitFailure(exit_code)) + } + } + + pub fn pump_events(&mut self, mut event_handler: F) -> PumpStatus + where + F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), + { + let runner = &self.window_target.p.runner_shared; + + 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.poll(); + } + } + + unsafe { + let mut msg = mem::zeroed(); + + 'pump: loop { + if PeekMessageW(&mut msg, 0, 0, 0, PM_REMOVE) == false.into() { + break 'pump; + } + + 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); + } + + if let ControlFlow::ExitWithCode(_code) = runner.control_flow() { + if !runner.handling_events() { + break 'pump; + } + } + } + }; + + 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 { + PumpStatus::Continue + } } pub fn create_proxy(&self) -> EventLoopProxy { diff --git a/src/platform_impl/windows/event_loop/runner.rs b/src/platform_impl/windows/event_loop/runner.rs index c4ac1eb3484..ebef5b111af 100644 --- a/src/platform_impl/windows/event_loop/runner.rs +++ b/src/platform_impl/windows/event_loop/runner.rs @@ -47,7 +47,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. @@ -133,6 +133,10 @@ impl EventLoopRunner { } } + pub fn state(&self) -> RunnerState { + self.runner_state.get() + } + pub fn control_flow(&self) -> ControlFlow { self.control_flow.get() }