diff --git a/winit-android/src/event_loop.rs b/winit-android/src/event_loop.rs index 1396574cbc..1a098eae3e 100644 --- a/winit-android/src/event_loop.rs +++ b/winit-android/src/event_loop.rs @@ -500,10 +500,6 @@ impl EventLoop { input_status } - pub fn run_app(mut self, app: A) -> Result<(), EventLoopError> { - self.run_app_on_demand(app) - } - pub fn run_app_on_demand( &mut self, mut app: A, diff --git a/winit-appkit/src/event_loop.rs b/winit-appkit/src/event_loop.rs index 4b5e48dd61..408c53a196 100644 --- a/winit-appkit/src/event_loop.rs +++ b/winit-appkit/src/event_loop.rs @@ -232,10 +232,6 @@ impl EventLoop { &self.window_target } - pub fn run_app(mut self, app: A) -> Result<(), EventLoopError> { - self.run_app_on_demand(app) - } - // 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 diff --git a/winit-core/src/event_loop/mod.rs b/winit-core/src/event_loop/mod.rs index edc06dc647..0337e5c633 100644 --- a/winit-core/src/event_loop/mod.rs +++ b/winit-core/src/event_loop/mod.rs @@ -1,4 +1,6 @@ +pub mod never_return; pub mod pump_events; +pub mod register; pub mod run_on_demand; use std::fmt::{self, Debug}; diff --git a/winit-core/src/event_loop/never_return.rs b/winit-core/src/event_loop/never_return.rs new file mode 100644 index 0000000000..4a94f94ce5 --- /dev/null +++ b/winit-core/src/event_loop/never_return.rs @@ -0,0 +1,14 @@ +use crate::application::ApplicationHandler; + +/// Additional methods on `EventLoop` for platforms whose run method never return. +pub trait EventLoopExtNeverReturn { + /// Run the event loop and call `process::exit` once finished. + /// + /// ## Platform-specific + /// + /// - **iOS**: This registers the callbacks with the system and calls `UIApplicationMain`. + /// - **macOS**: Unimplemented (TODO: Should call `NSApplicationMain`). + /// - **Android/Orbital/Wayland/Windows/X11**: Unsupported. + /// - **Web**: Impossible to support properly. + fn run_app_never_return(self, app: A) -> !; +} diff --git a/winit-core/src/event_loop/register.rs b/winit-core/src/event_loop/register.rs new file mode 100644 index 0000000000..503a746914 --- /dev/null +++ b/winit-core/src/event_loop/register.rs @@ -0,0 +1,17 @@ +use crate::application::ApplicationHandler; + +/// Additional methods on `EventLoop` that registers it with the system event loop. +pub trait EventLoopExtRegister { + /// Initialize and register the application with the system's event loop, such that the + /// callbacks will be run later. + /// + /// ## Platform-specific + /// + /// - **Web**: Once the event loop has been destroyed, it's possible to reinitialize another + /// event loop by calling this function again. This can be useful if you want to recreate the + /// event loop while the WebAssembly module is still loaded. For example, this can be used to + /// recreate the event loop when switching between tabs on a single page application. + /// - **iOS/macOS**: Unimplemented. + /// - **Android/Orbital/Wayland/Windows/X11**: Unsupported. + fn register_app(self, app: A); +} diff --git a/winit-core/src/event_loop/run_on_demand.rs b/winit-core/src/event_loop/run_on_demand.rs index 556be39341..7c53987a6c 100644 --- a/winit-core/src/event_loop/run_on_demand.rs +++ b/winit-core/src/event_loop/run_on_demand.rs @@ -6,15 +6,13 @@ use crate::{ window::Window, }; -#[allow(rustdoc::broken_intra_doc_links)] // FIXME(madsmtm): Fix these. /// Additional methods on [`EventLoop`] to return control flow to the caller. pub trait EventLoopExtRunOnDemand { /// Run the application with the event loop on the calling thread. /// /// Unlike [`EventLoop::run_app`], 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 using [`exit()`]) and - /// so the event loop can be re-run after it has exit. + /// state and it is possible to return control back to the caller without consuming the + /// `EventLoop` (by using [`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 @@ -31,8 +29,7 @@ pub trait EventLoopExtRunOnDemand { /// /// # 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 `EventLoopExtWeb::spawn_app()`[^1] more than once instead). + /// to the caller (specifically this is impossible on iOS and Web). /// - No [`Window`] state can be carried between separate runs of the event loop. /// /// You are strongly encouraged to use [`EventLoop::run_app()`] for portability, unless you @@ -51,8 +48,6 @@ pub trait EventLoopExtRunOnDemand { /// are delivered via callbacks based on an event loop that is internal to the browser itself. /// - **iOS:** It's not possible to stop and start an `UIApplication` repeatedly on iOS. /// - /// [^1]: `spawn_app()` is only available on the Web platforms. - /// /// [`exit()`]: ActiveEventLoop::exit() /// [`set_control_flow()`]: ActiveEventLoop::set_control_flow() fn run_app_on_demand(&mut self, app: A) -> Result<(), EventLoopError>; diff --git a/winit-orbital/src/event_loop.rs b/winit-orbital/src/event_loop.rs index e333134755..8ef97156ce 100644 --- a/winit-orbital/src/event_loop.rs +++ b/winit-orbital/src/event_loop.rs @@ -481,7 +481,10 @@ impl EventLoop { } } - pub fn run_app(mut self, mut app: A) -> Result<(), EventLoopError> { + pub fn run_app_on_demand( + &mut self, + mut app: A, + ) -> Result<(), EventLoopError> { let mut start_cause = StartCause::Init; loop { app.new_events(&self.window_target, start_cause); diff --git a/winit-uikit/src/event_loop.rs b/winit-uikit/src/event_loop.rs index bc3e1fcbf9..c5897edc6b 100644 --- a/winit-uikit/src/event_loop.rs +++ b/winit-uikit/src/event_loop.rs @@ -238,7 +238,8 @@ impl EventLoop { }) } - pub fn run_app(self, app: A) -> ! { + // Require `'static` for correctness, we won't be able to `Drop` the user's state otherwise. + pub fn run_app_never_return(self, app: A) -> ! { let application: Option> = unsafe { msg_send![UIApplication::class(), sharedApplication] }; assert!( @@ -250,6 +251,10 @@ impl EventLoop { // We intentionally override neither the application nor the delegate, // to allow the user to do so themselves! + // + // NOTE: `UIApplicationMain` is _the only way_ to start the event loop on iOS/UIKit, there + // are no other feasible options. See also: + // app_state::launch(self.mtm, app, || UIApplication::main(None, None, self.mtm)) } diff --git a/winit-wayland/src/event_loop/mod.rs b/winit-wayland/src/event_loop/mod.rs index 780239c9c9..3a6ca6982b 100644 --- a/winit-wayland/src/event_loop/mod.rs +++ b/winit-wayland/src/event_loop/mod.rs @@ -169,10 +169,6 @@ impl EventLoop { Ok(event_loop) } - pub fn run_app(mut self, app: A) -> Result<(), EventLoopError> { - self.run_app_on_demand(app) - } - pub fn run_app_on_demand( &mut self, mut app: A, diff --git a/winit-web/src/event_loop/mod.rs b/winit-web/src/event_loop/mod.rs index 156327f1bd..8b722729fc 100644 --- a/winit-web/src/event_loop/mod.rs +++ b/winit-web/src/event_loop/mod.rs @@ -39,32 +39,8 @@ impl EventLoop { EVENT_LOOP_CREATED.store(false, Ordering::Relaxed); } - pub fn run_app(self, app: A) -> ! { - let app = Box::new(app); - - // SAFETY: The `transmute` is necessary because `run()` requires `'static`. This is safe - // because this function will never return and all resources not cleaned up by the point we - // `throw` will leak, making this actually `'static`. - let app = unsafe { - std::mem::transmute::< - Box, - Box, - >(app) - }; - - self.elw.run(app, false); - - // Throw an exception to break out of Rust execution and use unreachable to tell the - // compiler this function won't return, giving it a return type of '!' - backend::throw( - "Using exceptions for control flow, don't mind me. This isn't actually an error!", - ); - - unreachable!(); - } - - pub fn spawn_app(self, app: A) { - self.elw.run(Box::new(app), true); + pub fn register_app(self, app: A) { + self.elw.run(Box::new(app)); } pub fn window_target(&self) -> &dyn RootActiveEventLoop { diff --git a/winit-web/src/event_loop/runner.rs b/winit-web/src/event_loop/runner.rs index 74bddb7e23..608fe540d2 100644 --- a/winit-web/src/event_loop/runner.rs +++ b/winit-web/src/event_loop/runner.rs @@ -48,7 +48,6 @@ struct Execution { exit: Cell, runner: RefCell, suspended: Cell, - event_loop_recreation: Cell, events: RefCell>, id: Cell, window: web_sys::Window, @@ -195,7 +194,6 @@ impl Shared { exit: Cell::new(false), runner: RefCell::new(RunnerEnum::Pending), suspended: Cell::new(false), - event_loop_recreation: Cell::new(false), events: RefCell::new(VecDeque::new()), window, navigator, @@ -772,9 +770,7 @@ impl Shared { // * For each undropped `Window`: // * The `register_redraw_request` closure. // * The `destroy_fn` closure. - if self.0.event_loop_recreation.get() { - EventLoop::allow_event_loop_recreation(); - } + EventLoop::allow_event_loop_recreation(); } // Check if the event loop is currently closed @@ -809,10 +805,6 @@ impl Shared { } } - pub fn event_loop_recreation(&self, allow: bool) { - self.0.event_loop_recreation.set(allow) - } - pub(crate) fn control_flow(&self) -> ControlFlow { self.0.control_flow.get() } diff --git a/winit-web/src/event_loop/window_target.rs b/winit-web/src/event_loop/window_target.rs index 6d2d2be9da..dcfce3bb77 100644 --- a/winit-web/src/event_loop/window_target.rs +++ b/winit-web/src/event_loop/window_target.rs @@ -56,8 +56,7 @@ impl ActiveEventLoop { Self { runner: runner::Shared::new(), modifiers: ModifiersShared::default() } } - pub(crate) fn run(&self, app: Box, event_loop_recreation: bool) { - self.runner.event_loop_recreation(event_loop_recreation); + pub(crate) fn run(&self, app: Box) { self.runner.start(app, self.clone()); } diff --git a/winit-web/src/lib.rs b/winit-web/src/lib.rs index be020e703f..70650846da 100644 --- a/winit-web/src/lib.rs +++ b/winit-web/src/lib.rs @@ -85,7 +85,6 @@ use std::task::{Context, Poll}; use ::web_sys::HtmlCanvasElement; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; -use winit_core::application::ApplicationHandler; use winit_core::cursor::{CustomCursor, CustomCursorSource}; use winit_core::error::NotSupportedError; use winit_core::event_loop::ActiveEventLoop; @@ -237,30 +236,6 @@ impl Default for WindowAttributesWeb { /// Additional methods on `EventLoop` that are specific to the Web. pub trait EventLoopExtWeb { - /// Initializes the winit event loop. - /// - /// Unlike - #[cfg_attr(target_feature = "exception-handling", doc = "`run_app()`")] - #[cfg_attr( - not(target_feature = "exception-handling"), - doc = "[`run_app()`]" - )] - /// [^1], this returns immediately, and doesn't throw an exception in order to - /// satisfy its [`!`] return type. - /// - /// Once the event loop has been destroyed, it's possible to reinitialize another event loop - /// by calling this function again. This can be useful if you want to recreate the event loop - /// while the WebAssembly module is still loaded. For example, this can be used to recreate the - /// event loop when switching between tabs on a single page application. - #[rustfmt::skip] - /// - #[cfg_attr( - not(target_feature = "exception-handling"), - doc = "[`run_app()`]: EventLoop::run_app()" - )] - /// [^1]: `run_app()` is _not_ available on Wasm when the target supports `exception-handling`. - fn spawn_app(self, app: A); - /// Sets the strategy for [`ControlFlow::Poll`]. /// /// See [`PollStrategy`]. diff --git a/winit-web/src/web_sys/mod.rs b/winit-web/src/web_sys/mod.rs index 0b1e5c053f..ffba3ae833 100644 --- a/winit-web/src/web_sys/mod.rs +++ b/winit-web/src/web_sys/mod.rs @@ -25,10 +25,6 @@ pub use self::resize_scaling::ResizeScaleHandle; pub use self::safe_area::SafeAreaHandle; pub use self::schedule::Schedule; -pub fn throw(msg: &str) { - wasm_bindgen::throw_str(msg); -} - pub struct PageTransitionEventHandle { _show_listener: event_handle::EventListenerHandle, _hide_listener: event_handle::EventListenerHandle, diff --git a/winit-win32/src/event_loop.rs b/winit-win32/src/event_loop.rs index 3a5720ec7b..e202281b48 100644 --- a/winit-win32/src/event_loop.rs +++ b/winit-win32/src/event_loop.rs @@ -247,10 +247,6 @@ impl EventLoop { ActiveEventLoop::from_ref(&self.runner) } - pub fn run_app(mut self, app: A) -> Result<(), EventLoopError> { - self.run_app_on_demand(app) - } - pub fn run_app_on_demand( &mut self, mut app: A, diff --git a/winit-x11/src/event_loop.rs b/winit-x11/src/event_loop.rs index b7b084672b..b89a06c1e1 100644 --- a/winit-x11/src/event_loop.rs +++ b/winit-x11/src/event_loop.rs @@ -427,10 +427,6 @@ impl EventLoop { &self.event_processor.target } - pub fn run_app(mut self, app: A) -> Result<(), EventLoopError> { - self.run_app_on_demand(app) - } - pub fn run_app_on_demand( &mut self, mut app: A, diff --git a/winit/examples/run_on_demand.rs b/winit/examples/run_on_demand.rs index f076b43b87..f25bfc5939 100644 --- a/winit/examples/run_on_demand.rs +++ b/winit/examples/run_on_demand.rs @@ -1,7 +1,7 @@ #![allow(clippy::single_match)] // Limit this example to only compatible platforms. -#[cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform,))] +#[cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform, orbital_platform))] fn main() -> Result<(), Box> { use std::time::Duration; @@ -93,7 +93,13 @@ fn main() -> Result<(), Box> { Ok(()) } -#[cfg(not(any(windows_platform, macos_platform, x11_platform, wayland_platform,)))] +#[cfg(not(any( + windows_platform, + macos_platform, + x11_platform, + wayland_platform, + orbital_platform +)))] fn main() { println!("This example is not supported on this platform"); } diff --git a/winit/src/changelog/unreleased.md b/winit/src/changelog/unreleased.md index f3a0f6d2c3..50c1b204a4 100644 --- a/winit/src/changelog/unreleased.md +++ b/winit/src/changelog/unreleased.md @@ -39,3 +39,14 @@ The migration guide could reference other migration examples in the current changelog entry. ## Unreleased + +### Added + +- Add `EventLoopExtRegister::register_app` for being explicit about how the event loop runs on Web. +- Add `EventLoopExtNeverReturn::run_app_never_return` for being explicit about how the event loop runs on iOS. + +### Changed + +- On Web, avoid throwing an exception in `EventLoop::run_app`, instead preferring to return to the caller. + This requires passing a `'static` application to ensure that the application state will live as long as necessary. +- On Web, the event loop can now always be re-created once it has finished running. diff --git a/winit/src/event_loop.rs b/winit/src/event_loop.rs index fc3e15ad4f..6882ebf163 100644 --- a/winit/src/event_loop.rs +++ b/winit/src/event_loop.rs @@ -118,7 +118,7 @@ impl EventLoop { EventLoopBuilder { platform_specific: Default::default() } } - /// Run the application with the event loop on the calling thread. + /// Run the event loop with the given application on the calling thread. /// /// The `app` is dropped when the event loop is shut down. /// @@ -173,35 +173,74 @@ impl EventLoop { /// [`ControlFlow::WaitUntil`] and life-cycle methods like [`ApplicationHandler::resumed`], but /// it should give you an idea of how things fit together. /// - /// ## Platform-specific + /// ## Returns /// - /// - **iOS:** Will never return to the caller and so values not passed to this function will - /// *not* be dropped before the process exits. - /// - **Web:** Will _act_ as if it never returns to the caller by throwing a Javascript - /// exception (that Rust doesn't see) that will also mean that the rest of the function is - /// never executed and any values not passed to this function will *not* be dropped. + /// The semantics of this function can be a bit confusing, because the way different platforms + /// control their event loop varies significantly. /// - /// Web applications are recommended to use - #[cfg_attr( - web_platform, - doc = " [`EventLoopExtWeb::spawn_app()`][crate::platform::web::EventLoopExtWeb::spawn_app()]" - )] - #[cfg_attr(not(web_platform), doc = " `EventLoopExtWeb::spawn_app()`")] - /// [^1] instead of [`run_app()`] 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. + /// On most platforms (Android, macOS, Orbital, X11, Wayland, Windows), this blocks the caller, + /// runs the event loop internally, and then returns once [`ActiveEventLoop::exit`] is called. + /// See [`run_app_on_demand`] for more detailed semantics. + /// + /// On iOS, this will register the application handler, and then call [`UIApplicationMain`] + /// (which is the only way to run the system event loop), which never returns to the caller + /// (the process instead exits after the handler has been dropped). See also + /// [`run_app_never_return`]. + /// + /// On the web, this works by registering the application handler, and then immediately + /// returning to the caller. This is necessary because WebAssembly (and JavaScript) is always + /// executed in the context of the browser's own (internal) event loop, and thus we need to + /// return to avoid blocking that and allow events to later be delivered asynchronously. See + /// also [`register_app`]. + /// + /// If you call this function inside `fn main`, you usually do not need to think about these + /// details. + /// + /// [`UIApplicationMain`]: https://developer.apple.com/documentation/uikit/uiapplicationmain(_:_:_:_:)-1yub7?language=objc + /// [`run_app_on_demand`]: crate::event_loop::run_on_demand::EventLoopExtRunOnDemand::run_app_on_demand + /// [`run_app_never_return`]: crate::event_loop::never_return::EventLoopExtNeverReturn::run_app_never_return + /// [`register_app`]: crate::event_loop::register::EventLoopExtRegister::register_app /// - /// This function won't be available with `target_feature = "exception-handling"`. + /// ## Static /// - /// [^1]: `spawn_app()` is only available on the Web platform. + /// To alleviate the issues noted above, this function requires that you pass in a `'static` + /// handler, to ensure that any state your application uses will be alive as long as the + /// application is running. /// - /// [`set_control_flow()`]: ActiveEventLoop::set_control_flow() - /// [`run_app()`]: Self::run_app() + /// To be clear, you should avoid doing e.g. `event_loop.run_app(&mut app)?`, and prefer + /// `event_loop.run_app(app)?` instead. + /// + /// If this requirement is prohibitive for you, consider using [`run_app_on_demand`] instead + /// (though note that this is not available on iOS and web). #[inline] - #[cfg(not(all(web_platform, target_feature = "exception-handling")))] - pub fn run_app(self, app: A) -> Result<(), EventLoopError> { - self.event_loop.run_app(app) + #[allow(unused_mut)] + pub fn run_app( + mut self, + mut app: A, + ) -> Result<(), EventLoopError> { + #[cfg(any( + windows_platform, + macos_platform, + android_platform, + orbital_platform, + x11_platform, + wayland_platform, + ))] + { + let result = self.event_loop.run_app_on_demand(&mut app); + // SAFETY: unsure that the state is dropped before the exit from the event loop. + drop(app); + result + } + #[cfg(web_platform)] + { + self.event_loop.register_app(app); + Ok(()) + } + #[cfg(ios_platform)] + { + self.event_loop.run_app_never_return(app) + } } /// Creates an [`EventLoopProxy`] that can be used to dispatch user events @@ -306,6 +345,7 @@ impl winit_core::event_loop::pump_events::EventLoopExtPumpEvents for EventLoop { windows_platform, macos_platform, android_platform, + orbital_platform, x11_platform, wayland_platform, docsrs, @@ -316,6 +356,13 @@ impl winit_core::event_loop::run_on_demand::EventLoopExtRunOnDemand for EventLoo } } +#[cfg(any(web_platform, docsrs))] +impl winit_core::event_loop::register::EventLoopExtRegister for EventLoop { + fn register_app(self, app: A) { + self.event_loop.register_app(app) + } +} + #[cfg(android_platform)] impl winit_android::EventLoopExtAndroid for EventLoop { fn android_app(&self) -> &winit_android::activity::AndroidApp { @@ -385,10 +432,6 @@ impl winit_wayland::EventLoopBuilderExtWayland for EventLoopBuilder { #[cfg(web_platform)] impl winit_web::EventLoopExtWeb for EventLoop { - fn spawn_app(self, app: A) { - self.event_loop.spawn_app(app); - } - fn set_poll_strategy(&self, strategy: winit_web::PollStrategy) { self.event_loop.set_poll_strategy(strategy); } diff --git a/winit/src/platform_impl/linux/mod.rs b/winit/src/platform_impl/linux/mod.rs index a771696d1d..877e0dc621 100644 --- a/winit/src/platform_impl/linux/mod.rs +++ b/winit/src/platform_impl/linux/mod.rs @@ -147,10 +147,6 @@ impl EventLoop { } } - pub fn run_app(self, app: A) -> Result<(), EventLoopError> { - x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_app(app)) - } - pub fn run_app_on_demand( &mut self, app: A,