winit/event_loop: Add register_app and run_app_never_return

To allow users to explicitly choose the run semantics that they want.
This commit is contained in:
Mads Marquart 2025-03-17 04:49:58 +01:00 committed by Kirill Chibisov
parent f69b601abb
commit 4c5bf0ee08
16 changed files with 102 additions and 71 deletions

View file

@ -500,10 +500,6 @@ impl EventLoop {
input_status input_status
} }
pub fn run_app<A: ApplicationHandler>(mut self, app: A) -> Result<(), EventLoopError> {
self.run_app_on_demand(app)
}
pub fn run_app_on_demand<A: ApplicationHandler>( pub fn run_app_on_demand<A: ApplicationHandler>(
&mut self, &mut self,
mut app: A, mut app: A,

View file

@ -232,10 +232,6 @@ impl EventLoop {
&self.window_target &self.window_target
} }
pub fn run_app<A: ApplicationHandler>(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 // 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 // `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 // time and so a layered implementation would end up using a lot of CPU due to

View file

@ -1,4 +1,6 @@
pub mod never_return;
pub mod pump_events; pub mod pump_events;
pub mod register;
pub mod run_on_demand; pub mod run_on_demand;
use std::fmt::{self, Debug}; use std::fmt::{self, Debug};

View file

@ -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<A: ApplicationHandler + 'static>(self, app: A) -> !;
}

View file

@ -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<A: ApplicationHandler + 'static>(self, app: A);
}

View file

@ -6,15 +6,13 @@ use crate::{
window::Window, window::Window,
}; };
#[allow(rustdoc::broken_intra_doc_links)] // FIXME(madsmtm): Fix these.
/// Additional methods on [`EventLoop`] to return control flow to the caller. /// Additional methods on [`EventLoop`] to return control flow to the caller.
pub trait EventLoopExtRunOnDemand { pub trait EventLoopExtRunOnDemand {
/// Run the application with the event loop on the calling thread. /// Run the application with the event loop on the calling thread.
/// ///
/// Unlike [`EventLoop::run_app`], this function accepts non-`'static` (i.e. non-`move`) /// Unlike [`EventLoop::run_app`], this function accepts non-`'static` (i.e. non-`move`)
/// state and it is possible to return control back to the caller without /// state and it is possible to return control back to the caller without consuming the
/// consuming the `EventLoop` (by using [`exit()`]) and /// `EventLoop` (by using [`exit()`]) and so the event loop can be re-run after it has exit.
/// 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 /// 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 /// Winit application, but internally each instantiation may re-use some common window

View file

@ -481,7 +481,10 @@ impl EventLoop {
} }
} }
pub fn run_app<A: ApplicationHandler>(mut self, mut app: A) -> Result<(), EventLoopError> { pub fn run_app_on_demand<A: ApplicationHandler>(
&mut self,
mut app: A,
) -> Result<(), EventLoopError> {
let mut start_cause = StartCause::Init; let mut start_cause = StartCause::Init;
loop { loop {
app.new_events(&self.window_target, start_cause); app.new_events(&self.window_target, start_cause);

View file

@ -239,7 +239,7 @@ impl EventLoop {
} }
// Require `'static` for correctness, we won't be able to `Drop` the user's state otherwise. // Require `'static` for correctness, we won't be able to `Drop` the user's state otherwise.
pub fn run_app<A: ApplicationHandler + 'static>(self, app: A) -> ! { pub fn run_app_never_return<A: ApplicationHandler + 'static>(self, app: A) -> ! {
let application: Option<Retained<UIApplication>> = let application: Option<Retained<UIApplication>> =
unsafe { msg_send![UIApplication::class(), sharedApplication] }; unsafe { msg_send![UIApplication::class(), sharedApplication] };
assert!( assert!(

View file

@ -169,10 +169,6 @@ impl EventLoop {
Ok(event_loop) Ok(event_loop)
} }
pub fn run_app<A: ApplicationHandler>(mut self, app: A) -> Result<(), EventLoopError> {
self.run_app_on_demand(app)
}
pub fn run_app_on_demand<A: ApplicationHandler>( pub fn run_app_on_demand<A: ApplicationHandler>(
&mut self, &mut self,
mut app: A, mut app: A,

View file

@ -39,9 +39,8 @@ impl EventLoop {
EVENT_LOOP_CREATED.store(false, Ordering::Relaxed); EVENT_LOOP_CREATED.store(false, Ordering::Relaxed);
} }
pub fn run_app<A: ApplicationHandler + 'static>(self, app: A) -> Result<(), EventLoopError> { pub fn register_app<A: ApplicationHandler + 'static>(self, app: A) {
self.elw.run(Box::new(app)); self.elw.run(Box::new(app));
Ok(())
} }
pub fn window_target(&self) -> &dyn RootActiveEventLoop { pub fn window_target(&self) -> &dyn RootActiveEventLoop {

View file

@ -247,10 +247,6 @@ impl EventLoop {
ActiveEventLoop::from_ref(&self.runner) ActiveEventLoop::from_ref(&self.runner)
} }
pub fn run_app<A: ApplicationHandler>(mut self, app: A) -> Result<(), EventLoopError> {
self.run_app_on_demand(app)
}
pub fn run_app_on_demand<A: ApplicationHandler>( pub fn run_app_on_demand<A: ApplicationHandler>(
&mut self, &mut self,
mut app: A, mut app: A,

View file

@ -427,10 +427,6 @@ impl EventLoop {
&self.event_processor.target &self.event_processor.target
} }
pub fn run_app<A: ApplicationHandler>(mut self, app: A) -> Result<(), EventLoopError> {
self.run_app_on_demand(app)
}
pub fn run_app_on_demand<A: ApplicationHandler>( pub fn run_app_on_demand<A: ApplicationHandler>(
&mut self, &mut self,
mut app: A, mut app: A,

View file

@ -1,7 +1,7 @@
#![allow(clippy::single_match)] #![allow(clippy::single_match)]
// Limit this example to only compatible platforms. // 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<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
use std::time::Duration; use std::time::Duration;
@ -93,7 +93,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
Ok(()) 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() { fn main() {
println!("This example is not supported on this platform"); println!("This example is not supported on this platform");
} }

View file

@ -40,6 +40,11 @@ changelog entry.
## Unreleased ## 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 ### Changed
- On Web, avoid throwing an exception in `EventLoop::run_app`, instead preferring to return to the caller. - On Web, avoid throwing an exception in `EventLoop::run_app`, instead preferring to return to the caller.

View file

@ -178,22 +178,28 @@ impl EventLoop {
/// The semantics of this function can be a bit confusing, because the way different platforms /// The semantics of this function can be a bit confusing, because the way different platforms
/// control their event loop varies significantly. /// control their event loop varies significantly.
/// ///
/// On most platforms (Android, X11, Wayland, Windows, macOS), this blocks the caller, runs the /// On most platforms (Android, macOS, Orbital, X11, Wayland, Windows), this blocks the caller,
/// event loop internally, and then returns once [`ActiveEventLoop::exit`] is called. /// 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`] /// 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 /// (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). /// (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 /// 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 /// 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 /// 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. /// 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 /// If you call this function inside `fn main`, you usually do not need to think about these
/// details. /// details.
/// ///
/// [`UIApplicationMain`]: https://developer.apple.com/documentation/uikit/uiapplicationmain(_:_:_:_:)-1yub7?language=objc /// [`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
/// ///
/// ## Static /// ## Static
/// ///
@ -204,40 +210,37 @@ impl EventLoop {
/// To be clear, you should avoid doing e.g. `event_loop.run_app(&mut app)?`, and prefer /// To be clear, you should avoid doing e.g. `event_loop.run_app(&mut app)?`, and prefer
/// `event_loop.run_app(app)?` instead. /// `event_loop.run_app(app)?` instead.
/// ///
/// If this requirement is prohibitive for you, consider using /// If this requirement is prohibitive for you, consider using [`run_app_on_demand`] instead
#[cfg_attr( /// (though note that this is not available on iOS and web).
any(
windows_platform,
macos_platform,
android_platform,
x11_platform,
wayland_platform,
docsrs,
),
doc = "[`EventLoopExtRunOnDemand::run_app_on_demand`](crate::event_loop::run_on_demand::EventLoopExtRunOnDemand::run_app_on_demand)"
)]
#[cfg_attr(
not(any(
windows_platform,
macos_platform,
android_platform,
x11_platform,
wayland_platform,
docsrs,
)),
doc = "`EventLoopExtRunOnDemand::run_app_on_demand`"
)]
/// instead (though note that this is not available on iOS and web).
///
/// ## Platform-specific
///
/// - **Web** Once your handler has been dropped, 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.
#[inline] #[inline]
pub fn run_app<A: ApplicationHandler + 'static>(self, app: A) -> Result<(), EventLoopError> { #[allow(unused_mut)]
self.event_loop.run_app(app) pub fn run_app<A: ApplicationHandler + 'static>(
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 /// Creates an [`EventLoopProxy`] that can be used to dispatch user events
@ -342,6 +345,7 @@ impl winit_core::event_loop::pump_events::EventLoopExtPumpEvents for EventLoop {
windows_platform, windows_platform,
macos_platform, macos_platform,
android_platform, android_platform,
orbital_platform,
x11_platform, x11_platform,
wayland_platform, wayland_platform,
docsrs, docsrs,
@ -352,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<A: ApplicationHandler + 'static>(self, app: A) {
self.event_loop.register_app(app)
}
}
#[cfg(android_platform)] #[cfg(android_platform)]
impl winit_android::EventLoopExtAndroid for EventLoop { impl winit_android::EventLoopExtAndroid for EventLoop {
fn android_app(&self) -> &winit_android::activity::AndroidApp { fn android_app(&self) -> &winit_android::activity::AndroidApp {

View file

@ -147,10 +147,6 @@ impl EventLoop {
} }
} }
pub fn run_app<A: ApplicationHandler>(self, app: A) -> Result<(), EventLoopError> {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_app(app))
}
pub fn run_app_on_demand<A: ApplicationHandler>( pub fn run_app_on_demand<A: ApplicationHandler>(
&mut self, &mut self,
app: A, app: A,