Move EventLoopExt* to winit-core (#4228)

* Move EventLoopExtPumpEvents and PumpStatus to winit-core
* Move EventLoopExtRunOnDemand to winit-core
This commit is contained in:
Mads Marquart 2025-05-20 16:56:53 +02:00 committed by GitHub
parent 59e3dda89f
commit eab03dca80
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 68 additions and 74 deletions

View file

@ -1,3 +1,6 @@
pub mod pump_events;
pub mod run_on_demand;
use std::fmt::{self, Debug};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;

View file

@ -0,0 +1,116 @@
use std::time::Duration;
use crate::application::ApplicationHandler;
#[allow(rustdoc::broken_intra_doc_links)] // FIXME(madsmtm): Fix these.
/// Additional methods on [`EventLoop`] for pumping events within an external event loop
pub trait EventLoopExtPumpEvents {
/// Pump the `EventLoop` to check for and dispatch pending events.
///
/// This API is designed to enable applications to integrate Winit into an
/// external event loop, for platforms that can support this.
///
/// The given `timeout` limits how long it may block waiting for new events.
///
/// Passing a `timeout` of `Some(Duration::ZERO)` would ensure your external
/// event loop is never blocked but you would likely need to consider how
/// to throttle your own external loop.
///
/// Passing a `timeout` of `None` means that it may wait indefinitely for new
/// events before returning control back to the external loop.
///
/// **Note:** This is not a portable API, and its usage involves a number of
/// caveats and trade offs that should be considered before using this API!
///
/// You almost certainly shouldn't use this API, unless you absolutely know it's
/// the only practical option you have.
///
/// ## Synchronous events
///
/// Some events _must_ only be handled synchronously via the closure that
/// is passed to Winit so that the handler will also be synchronized with
/// the window system and operating system.
///
/// This is because some events are driven by a window system callback
/// where the window systems expects the application to have handled the
/// event before returning.
///
/// **These events can not be buffered and handled outside of the closure
/// passed to Winit.**
///
/// As a general rule it is not recommended to ever buffer events to handle
/// them outside of the closure passed to Winit since it's difficult to
/// provide guarantees about which events are safe to buffer across all
/// operating systems.
///
/// Notable events that will certainly create portability problems if
/// buffered and handled outside of Winit include:
/// - `RedrawRequested` events, used to schedule rendering.
///
/// macOS for example uses a `drawRect` callback to drive rendering
/// within applications and expects rendering to be finished before
/// the `drawRect` callback returns.
///
/// For portability it's strongly recommended that applications should
/// keep their rendering inside the closure provided to Winit.
/// - Any lifecycle events, such as `Suspended` / `Resumed`.
///
/// The handling of these events needs to be synchronized with the
/// operating system and it would never be appropriate to buffer a
/// notification that your application has been suspended or resumed and
/// then handled that later since there would always be a chance that
/// other lifecycle events occur while the event is buffered.
///
/// ## Supported Platforms
///
/// - Windows
/// - Linux
/// - MacOS
/// - Android
///
/// ## Unsupported Platforms
///
/// - **Web:** This API is fundamentally incompatible with the event-based way in which Web
/// browsers work because it's not possible to have a long-running external loop that would
/// block the browser and there is nothing that can be polled to ask for new new events.
/// Events 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 `NSApplication` repeatedly on iOS so
/// there's no way to support the same approach to polling as on MacOS.
///
/// ## Platform-specific
///
/// - **Windows**: The implementation will use `PeekMessage` when checking for window messages
/// to avoid blocking your external event loop.
///
/// - **MacOS**: The implementation works in terms of stopping the global application whenever
/// the application `RunLoop` indicates that it is preparing to block and wait for new events.
///
/// This is very different to the polling APIs that are available on other
/// platforms (the lower level polling primitives on MacOS are private
/// implementation details for `NSApplication` which aren't accessible to
/// application developers)
///
/// It's likely this will be less efficient than polling on other OSs and
/// it also means the `NSApplication` is stopped while outside of the Winit
/// event loop - and that's observable (for example to crates like `rfd`)
/// because the `NSApplication` is global state.
///
/// If you render outside of Winit you are likely to see window resizing artifacts
/// since MacOS expects applications to render synchronously during any `drawRect`
/// callback.
fn pump_app_events<A: ApplicationHandler>(
&mut self,
timeout: Option<Duration>,
app: A,
) -> PumpStatus;
}
/// The return status for `pump_events`
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum PumpStatus {
/// Continue running external loop.
Continue,
/// Exit external loop.
Exit(i32),
}

View file

@ -0,0 +1,65 @@
use crate::application::ApplicationHandler;
use crate::error::EventLoopError;
#[cfg(doc)]
use crate::{
event_loop::{pump_events::EventLoopExtPumpEvents, ActiveEventLoop},
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.
///
/// 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
/// system resources, such as a display server connection.
///
/// This API is not designed to run an event loop in bursts that you can exit from and return
/// to while maintaining the full state of your application. (If you need something like this
/// you can look at the [`EventLoopExtPumpEvents::pump_app_events()`] API)
///
/// Each time `run_app_on_demand` is called the startup sequence of `init`, followed by
/// `resume` is being preserved.
///
/// See the [`set_control_flow()`] docs on how to change the event loop's behavior.
///
/// # 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
#[cfg_attr(
web_platform,
doc = " [`EventLoopExtWeb::spawn_app()`][crate::platform::web::EventLoopExtWeb::spawn_app()]"
)]
#[cfg_attr(not(web_platform), doc = " `EventLoopExtWeb::spawn_app()`")]
/// [^1] more than once instead).
/// - 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
/// specifically need the ability to re-run a single event loop more than once
///
/// # Supported Platforms
/// - Windows
/// - Linux
/// - macOS
/// - Android
///
/// # Unsupported Platforms
/// - **Web:** This API is fundamentally incompatible with the event-based way in which Web
/// browsers work because it's not possible to have a long-running external loop that would
/// block the browser and there is nothing that can be polled to ask for new events. Events
/// 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<A: ApplicationHandler>(&mut self, app: A) -> Result<(), EventLoopError>;
}