diff --git a/src/error.rs b/src/error.rs index c039f3b8686..0a1cda9ed73 100644 --- a/src/error.rs +++ b/src/error.rs @@ -2,7 +2,8 @@ use std::{error, fmt}; use crate::platform_impl; -/// An error whose cause it outside Winit's control. +// TODO: Rename +/// An error that may be generated when requesting Winit state #[derive(Debug)] pub enum ExternalError { /// The operation is not supported by the backend. @@ -25,6 +26,19 @@ pub struct OsError { error: platform_impl::OsError, } +/// A general error that may occur while running the Winit event loop +#[derive(Debug)] +pub enum RunLoopError { + /// The operation is not supported by the backend. + NotSupported(NotSupportedError), + /// The OS cannot perform the operation. + Os(OsError), + /// The event loop can't be re-run while it's already running + AlreadyRunning, + /// Application has exit with an error status. + ExitFailure(i32), +} + impl NotSupportedError { #[inline] #[allow(dead_code)] @@ -77,6 +91,18 @@ impl fmt::Display for NotSupportedError { } } +impl fmt::Display for RunLoopError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + match self { + RunLoopError::AlreadyRunning => write!(f, "EventLoop is already running"), + RunLoopError::NotSupported(e) => e.fmt(f), + RunLoopError::Os(e) => e.fmt(f), + RunLoopError::ExitFailure(status) => write!(f, "Exit Failure: {status}"), + } + } +} + impl error::Error for OsError {} impl error::Error for ExternalError {} impl error::Error for NotSupportedError {} +impl error::Error for RunLoopError {} diff --git a/src/platform/pump_events.rs b/src/platform/pump_events.rs new file mode 100644 index 00000000000..5388fffd275 --- /dev/null +++ b/src/platform/pump_events.rs @@ -0,0 +1,183 @@ +use crate::{ + event::Event, + event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget}, +}; + +/// The return status for `pump_events` +pub enum PumpStatus { + /// Continue running external loop + Continue, + /// exit external loop + Exit(i32), +} + +/// Additional methods on [`EventLoop`] for pumping events within an external event loop +pub trait EventLoopExtPumpEvents { + /// A type provided by the user that can be passed through [`Event::UserEvent`]. + type UserEvent; + + /// Pump the `EventLoop` to check for and dispatch pending events, without blocking + /// + /// This API is designed to enable applications to integrate Winit into an + /// external event loop, for platforms that can support this. + /// + /// ```rust,no_run + /// # // Copied from examples/window_pump_events.rs + /// # #[cfg(any( + /// # windows_platform, + /// # macos_platform, + /// # x11_platform, + /// # wayland_platform, + /// # android_platform, + /// # ))] + /// fn main() -> std::process::ExitCode { + /// # use std::{process::ExitCode, thread::sleep, time::Duration}; + /// # + /// # use simple_logger::SimpleLogger; + /// # use winit::{ + /// # event::{Event, PumpStatus, WindowEvent}, + /// # event_loop::EventLoop, + /// # platform::pump_events::EventLoopExtPumpEvents, + /// # window::WindowBuilder, + /// # }; + /// let mut event_loop = EventLoop::new(); + /// # + /// # SimpleLogger::new().init().unwrap(); + /// let window = WindowBuilder::new() + /// .with_title("A fantastic window!") + /// .build(&event_loop) + /// .unwrap(); + /// + /// 'main: loop { + /// let status = event_loop.pump_events(|event, _, control_flow| { + /// # if let Event::WindowEvent { event, .. } = &event { + /// # // Print only Window events to reduce noise + /// # println!("{event:?}"); + /// # } + /// # + /// match event { + /// Event::WindowEvent { + /// event: WindowEvent::CloseRequested, + /// window_id, + /// } if window_id == window.id() => control_flow.set_exit(), + /// Event::MainEventsCleared => { + /// window.request_redraw(); + /// } + /// _ => (), + /// } + /// }); + /// if let PumpStatus::Exit(exit_code) = status { + /// break 'main ExitCode::from(exit_code as u8); + /// } + /// + /// // Sleep for 1/60 second to simulate application work + /// // + /// // Since `pump_events` doesn't block it will be important to + /// // throttle the loop in the app somehow. + /// println!("Update()"); + /// sleep(Duration::from_millis(16)); + /// } + /// } + /// ``` + /// + /// **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: + /// - `RenderRequested` 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 `NSApp` + /// 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 `NSApp` 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 `NSApp` is stopped while outside of the Winit + /// event loop - and that's observable (for example to crates like `rfd`) + /// because the `NSApp` 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_events(&mut self, event_handler: F) -> PumpStatus + where + F: FnMut( + Event<'_, Self::UserEvent>, + &EventLoopWindowTarget, + &mut ControlFlow, + ); +} + +impl EventLoopExtPumpEvents for EventLoop { + type UserEvent = T; + + fn pump_events(&mut self, event_handler: F) -> PumpStatus + where + F: FnMut( + Event<'_, Self::UserEvent>, + &EventLoopWindowTarget, + &mut ControlFlow, + ), + { + self.event_loop.pump_events(event_handler) + } +} diff --git a/src/platform/run_ondemand.rs b/src/platform/run_ondemand.rs new file mode 100644 index 00000000000..9f4871db45d --- /dev/null +++ b/src/platform/run_ondemand.rs @@ -0,0 +1,82 @@ +use crate::{ + error::RunLoopError, + event::Event, + event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget}, +}; + +#[cfg(doc)] +use crate::{platform::pump_events::EventLoopExtPumpEvents, window::Window}; + +/// Additional methods on [`EventLoop`] to return control flow to the caller. +pub trait EventLoopExtRunOnDemand { + /// A type provided by the user that can be passed through [`Event::UserEvent`]. + type UserEvent; + + /// Runs the event loop in the calling thread and calls the given `event_handler` closure + /// to dispatch any window system events. + /// + /// Unlike [`EventLoop::run`], 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 setting the `control_flow` to [`ControlFlow::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_events()`] API) + /// + /// Each time `run_ondemand` is called the `event_handler` can expect to receive a + /// `NewEvents(Init)` and `Resumed` event (even on platforms that have no suspend/resume + /// lifecycle) - which can be used to consistently initialize application state. + /// + /// See the [`ControlFlow`] docs for information on how changes to `&mut ControlFlow` impact 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 `spawn()` 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()`] 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 `NSApplication` repeatedly on iOS. + fn run_ondemand(&mut self, event_handler: F) -> Result<(), RunLoopError> + where + F: FnMut( + Event<'_, Self::UserEvent>, + &EventLoopWindowTarget, + &mut ControlFlow, + ); +} + +impl EventLoopExtRunOnDemand for EventLoop { + type UserEvent = T; + + fn run_ondemand(&mut self, event_handler: F) -> Result<(), RunLoopError> + where + F: FnMut( + Event<'_, Self::UserEvent>, + &EventLoopWindowTarget, + &mut ControlFlow, + ), + { + self.event_loop.run_ondemand(event_handler) + } +}