winit-web: return immediately from run_app on web
This avoids using JavaScript exceptions to support `EventLoop::run_app` on the web, which is a huge hack, and doesn't work with the Exception Handling Proposal for WebAssembly: https://github.com/WebAssembly/exception-handling This needs the application handler passed to `run_app` to be `'static`, but that works better on iOS too anyhow (since you can't accidentally forget to pass in state that then wouldn't be dropped when terminating).
This commit is contained in:
parent
e8bccfff4f
commit
f69b601abb
9 changed files with 81 additions and 102 deletions
|
|
@ -12,7 +12,7 @@ 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`)
|
||||||
/// closures 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 `EventLoop` (by using [`exit()`]) and
|
/// consuming the `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.
|
||||||
///
|
///
|
||||||
|
|
@ -31,8 +31,7 @@ pub trait EventLoopExtRunOnDemand {
|
||||||
///
|
///
|
||||||
/// # Caveats
|
/// # Caveats
|
||||||
/// - This extension isn't available on all platforms, since it's not always possible to return
|
/// - 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
|
/// to the caller (specifically this is impossible on iOS and Web).
|
||||||
/// backend it is possible to use `EventLoopExtWeb::spawn_app()`[^1] more than once instead).
|
|
||||||
/// - No [`Window`] state can be carried between separate runs of the event loop.
|
/// - 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
|
/// You are strongly encouraged to use [`EventLoop::run_app()`] for portability, unless you
|
||||||
|
|
@ -51,8 +50,6 @@ pub trait EventLoopExtRunOnDemand {
|
||||||
/// are delivered via callbacks based on an event loop that is internal to the browser itself.
|
/// 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.
|
/// - **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()
|
/// [`exit()`]: ActiveEventLoop::exit()
|
||||||
/// [`set_control_flow()`]: ActiveEventLoop::set_control_flow()
|
/// [`set_control_flow()`]: ActiveEventLoop::set_control_flow()
|
||||||
fn run_app_on_demand<A: ApplicationHandler>(&mut self, app: A) -> Result<(), EventLoopError>;
|
fn run_app_on_demand<A: ApplicationHandler>(&mut self, app: A) -> Result<(), EventLoopError>;
|
||||||
|
|
|
||||||
|
|
@ -238,7 +238,8 @@ impl EventLoop {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_app<A: ApplicationHandler>(self, app: A) -> ! {
|
// 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) -> ! {
|
||||||
let application: Option<Retained<UIApplication>> =
|
let application: Option<Retained<UIApplication>> =
|
||||||
unsafe { msg_send![UIApplication::class(), sharedApplication] };
|
unsafe { msg_send![UIApplication::class(), sharedApplication] };
|
||||||
assert!(
|
assert!(
|
||||||
|
|
@ -250,6 +251,10 @@ impl EventLoop {
|
||||||
|
|
||||||
// We intentionally override neither the application nor the delegate,
|
// We intentionally override neither the application nor the delegate,
|
||||||
// to allow the user to do so themselves!
|
// 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:
|
||||||
|
// <https://github.com/rust-windowing/winit/pull/4165#issuecomment-2760514167>
|
||||||
app_state::launch(self.mtm, app, || UIApplication::main(None, None, self.mtm))
|
app_state::launch(self.mtm, app, || UIApplication::main(None, None, self.mtm))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,32 +39,9 @@ impl EventLoop {
|
||||||
EVENT_LOOP_CREATED.store(false, Ordering::Relaxed);
|
EVENT_LOOP_CREATED.store(false, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_app<A: ApplicationHandler>(self, app: A) -> ! {
|
pub fn run_app<A: ApplicationHandler + 'static>(self, app: A) -> Result<(), EventLoopError> {
|
||||||
let app = Box::new(app);
|
self.elw.run(Box::new(app));
|
||||||
|
Ok(())
|
||||||
// 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<dyn ApplicationHandler + '_>,
|
|
||||||
Box<dyn ApplicationHandler + 'static>,
|
|
||||||
>(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<A: ApplicationHandler + 'static>(self, app: A) {
|
|
||||||
self.elw.run(Box::new(app), true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn window_target(&self) -> &dyn RootActiveEventLoop {
|
pub fn window_target(&self) -> &dyn RootActiveEventLoop {
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,6 @@ struct Execution {
|
||||||
exit: Cell<bool>,
|
exit: Cell<bool>,
|
||||||
runner: RefCell<RunnerEnum>,
|
runner: RefCell<RunnerEnum>,
|
||||||
suspended: Cell<bool>,
|
suspended: Cell<bool>,
|
||||||
event_loop_recreation: Cell<bool>,
|
|
||||||
events: RefCell<VecDeque<Event>>,
|
events: RefCell<VecDeque<Event>>,
|
||||||
id: Cell<usize>,
|
id: Cell<usize>,
|
||||||
window: web_sys::Window,
|
window: web_sys::Window,
|
||||||
|
|
@ -195,7 +194,6 @@ impl Shared {
|
||||||
exit: Cell::new(false),
|
exit: Cell::new(false),
|
||||||
runner: RefCell::new(RunnerEnum::Pending),
|
runner: RefCell::new(RunnerEnum::Pending),
|
||||||
suspended: Cell::new(false),
|
suspended: Cell::new(false),
|
||||||
event_loop_recreation: Cell::new(false),
|
|
||||||
events: RefCell::new(VecDeque::new()),
|
events: RefCell::new(VecDeque::new()),
|
||||||
window,
|
window,
|
||||||
navigator,
|
navigator,
|
||||||
|
|
@ -772,9 +770,7 @@ impl Shared {
|
||||||
// * For each undropped `Window`:
|
// * For each undropped `Window`:
|
||||||
// * The `register_redraw_request` closure.
|
// * The `register_redraw_request` closure.
|
||||||
// * The `destroy_fn` 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
|
// 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 {
|
pub(crate) fn control_flow(&self) -> ControlFlow {
|
||||||
self.0.control_flow.get()
|
self.0.control_flow.get()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,8 +56,7 @@ impl ActiveEventLoop {
|
||||||
Self { runner: runner::Shared::new(), modifiers: ModifiersShared::default() }
|
Self { runner: runner::Shared::new(), modifiers: ModifiersShared::default() }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn run(&self, app: Box<dyn ApplicationHandler>, event_loop_recreation: bool) {
|
pub(crate) fn run(&self, app: Box<dyn ApplicationHandler>) {
|
||||||
self.runner.event_loop_recreation(event_loop_recreation);
|
|
||||||
self.runner.start(app, self.clone());
|
self.runner.start(app, self.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,6 @@ use std::task::{Context, Poll};
|
||||||
use ::web_sys::HtmlCanvasElement;
|
use ::web_sys::HtmlCanvasElement;
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use winit_core::application::ApplicationHandler;
|
|
||||||
use winit_core::cursor::{CustomCursor, CustomCursorSource};
|
use winit_core::cursor::{CustomCursor, CustomCursorSource};
|
||||||
use winit_core::error::NotSupportedError;
|
use winit_core::error::NotSupportedError;
|
||||||
use winit_core::event_loop::ActiveEventLoop;
|
use winit_core::event_loop::ActiveEventLoop;
|
||||||
|
|
@ -237,30 +236,6 @@ impl Default for WindowAttributesWeb {
|
||||||
|
|
||||||
/// Additional methods on `EventLoop` that are specific to the Web.
|
/// Additional methods on `EventLoop` that are specific to the Web.
|
||||||
pub trait EventLoopExtWeb {
|
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<A: ApplicationHandler + 'static>(self, app: A);
|
|
||||||
|
|
||||||
/// Sets the strategy for [`ControlFlow::Poll`].
|
/// Sets the strategy for [`ControlFlow::Poll`].
|
||||||
///
|
///
|
||||||
/// See [`PollStrategy`].
|
/// See [`PollStrategy`].
|
||||||
|
|
|
||||||
|
|
@ -25,10 +25,6 @@ pub use self::resize_scaling::ResizeScaleHandle;
|
||||||
pub use self::safe_area::SafeAreaHandle;
|
pub use self::safe_area::SafeAreaHandle;
|
||||||
pub use self::schedule::Schedule;
|
pub use self::schedule::Schedule;
|
||||||
|
|
||||||
pub fn throw(msg: &str) {
|
|
||||||
wasm_bindgen::throw_str(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PageTransitionEventHandle {
|
pub struct PageTransitionEventHandle {
|
||||||
_show_listener: event_handle::EventListenerHandle<dyn FnMut(PageTransitionEvent)>,
|
_show_listener: event_handle::EventListenerHandle<dyn FnMut(PageTransitionEvent)>,
|
||||||
_hide_listener: event_handle::EventListenerHandle<dyn FnMut(PageTransitionEvent)>,
|
_hide_listener: event_handle::EventListenerHandle<dyn FnMut(PageTransitionEvent)>,
|
||||||
|
|
|
||||||
|
|
@ -39,3 +39,9 @@ The migration guide could reference other migration examples in the current
|
||||||
changelog entry.
|
changelog entry.
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|
|
||||||
|
|
@ -118,7 +118,7 @@ impl EventLoop {
|
||||||
EventLoopBuilder { platform_specific: Default::default() }
|
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.
|
/// The `app` is dropped when the event loop is shut down.
|
||||||
///
|
///
|
||||||
|
|
@ -173,34 +173,70 @@ impl EventLoop {
|
||||||
/// [`ControlFlow::WaitUntil`] and life-cycle methods like [`ApplicationHandler::resumed`], but
|
/// [`ControlFlow::WaitUntil`] and life-cycle methods like [`ApplicationHandler::resumed`], but
|
||||||
/// it should give you an idea of how things fit together.
|
/// it should give you an idea of how things fit together.
|
||||||
///
|
///
|
||||||
|
/// ## Returns
|
||||||
|
///
|
||||||
|
/// 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 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).
|
||||||
|
///
|
||||||
|
/// 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.
|
||||||
|
///
|
||||||
|
/// 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
|
||||||
|
///
|
||||||
|
/// ## Static
|
||||||
|
///
|
||||||
|
/// 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.
|
||||||
|
///
|
||||||
|
/// 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
|
/// ## Platform-specific
|
||||||
///
|
///
|
||||||
/// - **iOS:** Will never return to the caller and so values not passed to this function will
|
/// - **Web** Once your handler has been dropped, it's possible to reinitialize another event
|
||||||
/// *not* be dropped before the process exits.
|
/// loop by calling this function again. This can be useful if you want to recreate the event
|
||||||
/// - **Web:** Will _act_ as if it never returns to the caller by throwing a Javascript
|
/// loop while the WebAssembly module is still loaded. For example, this can be used to
|
||||||
/// exception (that Rust doesn't see) that will also mean that the rest of the function is
|
/// recreate the event loop when switching between tabs on a single page application.
|
||||||
/// never executed and any values not passed to this function will *not* be dropped.
|
|
||||||
///
|
|
||||||
/// 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.
|
|
||||||
///
|
|
||||||
/// This function won't be available with `target_feature = "exception-handling"`.
|
|
||||||
///
|
|
||||||
/// [^1]: `spawn_app()` is only available on the Web platform.
|
|
||||||
///
|
|
||||||
/// [`set_control_flow()`]: ActiveEventLoop::set_control_flow()
|
|
||||||
/// [`run_app()`]: Self::run_app()
|
|
||||||
#[inline]
|
#[inline]
|
||||||
#[cfg(not(all(web_platform, target_feature = "exception-handling")))]
|
pub fn run_app<A: ApplicationHandler + 'static>(self, app: A) -> Result<(), EventLoopError> {
|
||||||
pub fn run_app<A: ApplicationHandler>(self, app: A) -> Result<(), EventLoopError> {
|
|
||||||
self.event_loop.run_app(app)
|
self.event_loop.run_app(app)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -385,10 +421,6 @@ impl winit_wayland::EventLoopBuilderExtWayland for EventLoopBuilder {
|
||||||
|
|
||||||
#[cfg(web_platform)]
|
#[cfg(web_platform)]
|
||||||
impl winit_web::EventLoopExtWeb for EventLoop {
|
impl winit_web::EventLoopExtWeb for EventLoop {
|
||||||
fn spawn_app<A: ApplicationHandler + 'static>(self, app: A) {
|
|
||||||
self.event_loop.spawn_app(app);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_poll_strategy(&self, strategy: winit_web::PollStrategy) {
|
fn set_poll_strategy(&self, strategy: winit_web::PollStrategy) {
|
||||||
self.event_loop.set_poll_strategy(strategy);
|
self.event_loop.set_poll_strategy(strategy);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue