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
}
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>(
&mut self,
mut app: A,

View file

@ -232,10 +232,6 @@ impl EventLoop {
&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
// `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

View file

@ -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};

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,
};
#[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`)
/// 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.
/// 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

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;
loop {
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.
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>> =
unsafe { msg_send![UIApplication::class(), sharedApplication] };
assert!(

View file

@ -169,10 +169,6 @@ impl EventLoop {
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>(
&mut self,
mut app: A,

View file

@ -39,9 +39,8 @@ impl EventLoop {
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));
Ok(())
}
pub fn window_target(&self) -> &dyn RootActiveEventLoop {

View file

@ -247,10 +247,6 @@ impl EventLoop {
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>(
&mut self,
mut app: A,

View file

@ -427,10 +427,6 @@ impl EventLoop {
&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>(
&mut self,
mut app: A,

View file

@ -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<dyn std::error::Error>> {
use std::time::Duration;
@ -93,7 +93,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
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");
}

View file

@ -40,6 +40,11 @@ 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.

View file

@ -178,22 +178,28 @@ impl EventLoop {
/// The semantics of this function can be a bit confusing, because the way different platforms
/// control their event loop varies significantly.
///
/// On most platforms (Android, X11, Wayland, Windows, macOS), this blocks the caller, runs the
/// event loop internally, and then returns once [`ActiveEventLoop::exit`] is called.
/// 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).
/// (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.
/// 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
///
/// ## Static
///
@ -204,40 +210,37 @@ impl EventLoop {
/// 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
#[cfg_attr(
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.
/// 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]
pub fn run_app<A: ApplicationHandler + 'static>(self, app: A) -> Result<(), EventLoopError> {
self.event_loop.run_app(app)
#[allow(unused_mut)]
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
@ -342,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,
@ -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)]
impl winit_android::EventLoopExtAndroid for EventLoop {
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>(
&mut self,
app: A,