From 54b41ff76036139c1dc47dc7e9279f8647a67132 Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Tue, 11 Apr 2023 12:50:52 +0100 Subject: [PATCH 01/15] Add EventLoopExtPumpEvents and EventLoopExtRunOnDemand This adds two new extensions for running a Winit event loop which will replace `EventLoopExtRunReturn` The `run_return` API is trying to solve multiple problems and address multiple, unrelated, use cases but in doing so it is not succeeding at addressing any of them fully. The notable use cases we have are: 1. Applications want to be able to implement their own external event loop and call some Winit API to poll / pump events, once per iteration of their own loop, without blocking the outer, external loop. Addressing #2706 2. Applications want to be able to re-run separate instantiations of some Winit-based GUI and want to allow the event loop to exit with a status, and then later be able to run the loop again for a new instantiation of their GUI. Addressing #2431 It's very notable that these use cases can't be supported across all platforms and so they are extensions, similar to `EventLoopExtRunReturn` The intention is to support these extensions on: - Windows - Linux (X11 + Wayland) - macOS - Android These extensions aren't compatible with Web or iOS though. Each method of running the loop will behave consistently in terms of how `NewEvents(Init)`, `Resumed` and `LoopDestroyed` events are dispatched (so portable application code wouldn't necessarily need to have any awareness of which method of running the loop was being used) Once all backends have support for these extensions then we can remove `EventLoopExtRunReturn` For simplicity, the extensions are documented with the assumption that the above platforms will be supported. This patch makes no functional change, it only introduces these new extensions so we can then handle adding platform-specific backends in separate pull requests, so the work can be landed in stages. --- src/error.rs | 28 ++++- src/platform/pump_events.rs | 194 +++++++++++++++++++++++++++++++++++ src/platform/run_ondemand.rs | 82 +++++++++++++++ 3 files changed, 303 insertions(+), 1 deletion(-) create mode 100644 src/platform/pump_events.rs create mode 100644 src/platform/run_ondemand.rs diff --git a/src/error.rs b/src/error.rs index c039f3b868..0a1cda9ed7 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 0000000000..e7ca85edeb --- /dev/null +++ b/src/platform/pump_events.rs @@ -0,0 +1,194 @@ +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. + /// + /// 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. + /// + /// ## Example + /// + /// ```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, WindowEvent}, + /// # event_loop::EventLoop, + /// # platform::pump_events::{EventLoopExtPumpEvents, PumpStatus}, + /// # 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: + /// - `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 `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 0000000000..9f4871db45 --- /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) + } +} From 9ff6a42a6c9cc39a141d84de156bc331eb77ac0d Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Sun, 18 Jun 2023 11:10:13 +0100 Subject: [PATCH 02/15] Android: Implement EventLoopExtPumpEvents and EventLoopExtRunOnDemand --- src/platform/mod.rs | 13 +- src/platform_impl/android/mod.rs | 354 ++++++++++++++++++------------- 2 files changed, 216 insertions(+), 151 deletions(-) diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 9e01c4d993..e6c2546d2a 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -9,8 +9,10 @@ //! - `windows` //! - `web` //! -//! And the following platform-specific module: +//! And the following platform-specific modules: //! +//! - `run_ondemand` (available on `android`) +//! - `pump_events` (available on `android`) //! - `run_return` (available on `windows`, `unix`, `macos`, and `android`) //! //! However only the module corresponding to the platform you're compiling to will be available. @@ -34,7 +36,12 @@ pub mod windows; #[cfg(x11_platform)] pub mod x11; -pub mod modifier_supplement; +#[cfg(any(android_platform))] +pub mod run_ondemand; + +#[cfg(any(android_platform,))] +pub mod pump_events; + #[cfg(any( windows_platform, macos_platform, @@ -44,4 +51,6 @@ pub mod modifier_supplement; orbital_platform ))] pub mod run_return; + +pub mod modifier_supplement; pub mod scancode; diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index cd10d6b82e..99af2a5d58 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -19,22 +19,32 @@ use raw_window_handle::{ AndroidDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle, }; -use crate::platform_impl::Fullscreen; use crate::{ dpi::{PhysicalPosition, PhysicalSize, Position, Size}, error, event::{self, StartCause}, event_loop::{self, ControlFlow, EventLoopWindowTarget as RootELW}, keyboard::NativeKey, + platform::pump_events::PumpStatus, window::{ self, CursorGrabMode, ImePurpose, ResizeDirection, Theme, WindowButtons, WindowLevel, }, }; +use crate::{error::RunLoopError, platform_impl::Fullscreen}; mod keycodes; static HAS_FOCUS: Lazy> = Lazy::new(|| RwLock::new(true)); +/// Returns the minimum `Option`, taking into account that `None` +/// equates to an infinite timeout, not a zero timeout (so can't just use +/// `Option::min`) +fn min_timeout(a: Option, b: Option) -> Option { + a.map_or(b, |a_timeout| { + b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout))) + }) +} + struct PeekableReceiver { recv: mpsc::Receiver, first: Option, @@ -135,7 +145,11 @@ pub struct EventLoop { redraw_flag: SharedFlag, user_events_sender: mpsc::Sender, user_events_receiver: PeekableReceiver, //must wake looper whenever something gets sent + loop_running: bool, // Dispatched `NewEvents` running: bool, + pending_redraw: bool, + control_flow: ControlFlow, + cause: StartCause, ignore_volume_keys: bool, } @@ -171,12 +185,6 @@ fn sticky_exit_callback( } } -struct IterationResult { - deadline: Option, - timeout: Option, - wait_start: Instant, -} - impl EventLoop { pub(crate) fn new(attributes: &PlatformSpecificEventLoopAttributes) -> Self { let (user_events_sender, user_events_receiver) = mpsc::channel(); @@ -200,33 +208,33 @@ impl EventLoop { redraw_flag, user_events_sender, user_events_receiver: PeekableReceiver::from_recv(user_events_receiver), + loop_running: false, running: false, + pending_redraw: false, + control_flow: Default::default(), + cause: StartCause::Init, ignore_volume_keys: attributes.ignore_volume_keys, } } - fn single_iteration( - &mut self, - control_flow: &mut ControlFlow, - main_event: Option>, - pending_redraw: &mut bool, - cause: &mut StartCause, - callback: &mut F, - ) -> IterationResult + fn single_iteration(&mut self, main_event: Option>, callback: &mut F) where F: FnMut(event::Event<'_, T>, &RootELW, &mut ControlFlow), { trace!("Mainloop iteration"); + let cause = self.cause; + let mut control_flow = self.control_flow; + let mut pending_redraw = self.pending_redraw; + let mut resized = false; + sticky_exit_callback( - event::Event::NewEvents(*cause), + event::Event::NewEvents(cause), self.window_target(), - control_flow, + &mut control_flow, callback, ); - let mut resized = false; - if let Some(event) = main_event { trace!("Handling main event {:?}", event); @@ -235,7 +243,7 @@ impl EventLoop { sticky_exit_callback( event::Event::Resumed, self.window_target(), - control_flow, + &mut control_flow, callback, ); } @@ -243,12 +251,12 @@ impl EventLoop { sticky_exit_callback( event::Event::Suspended, self.window_target(), - control_flow, + &mut control_flow, callback, ); } MainEvent::WindowResized { .. } => resized = true, - MainEvent::RedrawNeeded { .. } => *pending_redraw = true, + MainEvent::RedrawNeeded { .. } => pending_redraw = true, MainEvent::ContentRectChanged { .. } => { warn!("TODO: find a way to notify application of content rect change"); } @@ -260,7 +268,7 @@ impl EventLoop { event: event::WindowEvent::Focused(true), }, self.window_target(), - control_flow, + &mut control_flow, callback, ); } @@ -272,7 +280,7 @@ impl EventLoop { event: event::WindowEvent::Focused(false), }, self.window_target(), - control_flow, + &mut control_flow, callback, ); } @@ -289,7 +297,12 @@ impl EventLoop { scale_factor, }, }; - sticky_exit_callback(event, self.window_target(), control_flow, callback); + sticky_exit_callback( + event, + self.window_target(), + &mut control_flow, + callback, + ); } } MainEvent::LowMemory => { @@ -398,7 +411,7 @@ impl EventLoop { sticky_exit_callback( event, self.window_target(), - control_flow, + &mut control_flow, callback ); } @@ -446,7 +459,7 @@ impl EventLoop { sticky_exit_callback( event, self.window_target(), - control_flow, + &mut control_flow, callback, ); } @@ -465,7 +478,7 @@ impl EventLoop { sticky_exit_callback( crate::event::Event::UserEvent(event), self.window_target(), - control_flow, + &mut control_flow, callback, ); } @@ -474,7 +487,7 @@ impl EventLoop { sticky_exit_callback( event::Event::MainEventsCleared, self.window_target(), - control_flow, + &mut control_flow, callback, ); @@ -491,64 +504,26 @@ impl EventLoop { window_id: window::WindowId(WindowId), event: event::WindowEvent::Resized(size), }; - sticky_exit_callback(event, self.window_target(), control_flow, callback); + sticky_exit_callback(event, self.window_target(), &mut control_flow, callback); } - *pending_redraw |= self.redraw_flag.get_and_reset(); - if *pending_redraw { - *pending_redraw = false; + pending_redraw |= self.redraw_flag.get_and_reset(); + if pending_redraw { + pending_redraw = false; let event = event::Event::RedrawRequested(window::WindowId(WindowId)); - sticky_exit_callback(event, self.window_target(), control_flow, callback); + sticky_exit_callback(event, self.window_target(), &mut control_flow, callback); } } sticky_exit_callback( event::Event::RedrawEventsCleared, self.window_target(), - control_flow, + &mut control_flow, callback, ); - let start = Instant::now(); - let (deadline, timeout); - - match control_flow { - ControlFlow::ExitWithCode(_) => { - deadline = None; - timeout = None; - } - ControlFlow::Poll => { - *cause = StartCause::Poll; - deadline = None; - timeout = Some(Duration::from_millis(0)); - } - ControlFlow::Wait => { - *cause = StartCause::WaitCancelled { - start, - requested_resume: None, - }; - deadline = None; - timeout = None; - } - ControlFlow::WaitUntil(wait_deadline) => { - *cause = StartCause::ResumeTimeReached { - start, - requested_resume: *wait_deadline, - }; - timeout = if *wait_deadline > start { - Some(*wait_deadline - start) - } else { - Some(Duration::from_millis(0)) - }; - deadline = Some(*wait_deadline); - } - } - - IterationResult { - wait_start: start, - deadline, - timeout, - } + self.control_flow = control_flow; + self.pending_redraw = pending_redraw; } pub fn run(mut self, event_handler: F) -> ! @@ -556,98 +531,179 @@ impl EventLoop { F: 'static + FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget, &mut ControlFlow), { - let exit_code = self.run_return(event_handler); + let exit_code = match self.run_ondemand(event_handler) { + Err(RunLoopError::ExitFailure(code)) => code, + Err(_err) => 1, + Ok(_) => 0, + }; ::std::process::exit(exit_code); } - pub fn run_return(&mut self, mut callback: F) -> i32 + pub fn run_return(&mut self, callback: F) -> i32 where F: FnMut(event::Event<'_, T>, &RootELW, &mut ControlFlow), { - let mut control_flow = ControlFlow::default(); - let mut cause = StartCause::Init; - let mut pending_redraw = false; + match self.run_ondemand(callback) { + Err(RunLoopError::ExitFailure(code)) => code, + Err(_err) => 1, + Ok(_) => 0, + } + } - // run the initial loop iteration - let mut iter_result = self.single_iteration( - &mut control_flow, - None, - &mut pending_redraw, - &mut cause, - &mut callback, - ); + pub fn run_ondemand(&mut self, mut event_handler: F) -> Result<(), RunLoopError> + where + F: FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget, &mut ControlFlow), + { + if self.loop_running { + return Err(RunLoopError::AlreadyRunning); + } - let exit_code = loop { - if let ControlFlow::ExitWithCode(code) = control_flow { - break code; + loop { + match self.pump_events_with_timeout(None, &mut event_handler) { + PumpStatus::Exit(0) => { + break Ok(()); + } + PumpStatus::Exit(code) => { + break Err(RunLoopError::ExitFailure(code)); + } + _ => { + continue; + } } + } + } + + pub fn pump_events(&mut self, event_handler: F) -> PumpStatus + where + F: FnMut(event::Event<'_, T>, &RootELW, &mut ControlFlow), + { + self.pump_events_with_timeout(Some(Duration::ZERO), event_handler) + } - let mut timeout = iter_result.timeout; + fn pump_events_with_timeout( + &mut self, + timeout: Option, + mut callback: F, + ) -> PumpStatus + where + F: FnMut(event::Event<'_, T>, &RootELW, &mut ControlFlow), + { + if !self.loop_running { + self.loop_running = true; + + // Reset the internal state for the loop as we start running to + // ensure consistent behaviour in case the loop runs and exits more + // than once + self.pending_redraw = false; + self.cause = StartCause::Init; + self.control_flow = ControlFlow::Poll; + + // run the initial loop iteration + self.single_iteration(None, &mut callback); + } - // If we already have work to do then we don't want to block on the next poll... - pending_redraw |= self.redraw_flag.get_and_reset(); - if self.running && (pending_redraw || self.user_events_receiver.has_incoming()) { - timeout = Some(Duration::from_millis(0)) - } + // Consider the possibility that the `StartCause::Init` iteration could + // request to Exit + if !matches!(self.control_flow, ControlFlow::ExitWithCode(_)) { + self.poll_events_with_timeout(timeout, &mut callback); + } + if let ControlFlow::ExitWithCode(code) = self.control_flow { + self.loop_running = false; + + let mut dummy = self.control_flow; + sticky_exit_callback( + event::Event::LoopDestroyed, + self.window_target(), + &mut dummy, + &mut callback, + ); + + PumpStatus::Exit(code) + } else { + PumpStatus::Continue + } + } - let app = self.android_app.clone(); // Don't borrow self as part of poll expression - app.poll_events(timeout, |poll_event| { - let mut main_event = None; - - match poll_event { - android_activity::PollEvent::Wake => { - // In the X11 backend it's noted that too many false-positive wake ups - // would cause the event loop to run continuously. They handle this by re-checking - // for pending events (assuming they cover all valid reasons for a wake up). - // - // For now, user_events and redraw_requests are the only reasons to expect - // a wake up here so we can ignore the wake up if there are no events/requests. - // We also ignore wake ups while suspended. - pending_redraw |= self.redraw_flag.get_and_reset(); - if !self.running - || (!pending_redraw && !self.user_events_receiver.has_incoming()) - { - return; - } - } - android_activity::PollEvent::Timeout => {} - android_activity::PollEvent::Main(event) => { - main_event = Some(event); + fn poll_events_with_timeout(&mut self, mut timeout: Option, mut callback: F) + where + F: FnMut(event::Event<'_, T>, &RootELW, &mut ControlFlow), + { + let start = Instant::now(); + + self.pending_redraw |= self.redraw_flag.get_and_reset(); + + timeout = + if self.running && (self.pending_redraw || self.user_events_receiver.has_incoming()) { + // If we already have work to do then we don't want to block on the next poll + Some(Duration::ZERO) + } else { + let control_flow_timeout = match self.control_flow { + ControlFlow::Wait => None, + ControlFlow::Poll => Some(Duration::ZERO), + ControlFlow::WaitUntil(wait_deadline) => { + Some(wait_deadline.saturating_duration_since(start)) } - unknown_event => { - warn!("Unknown poll event {unknown_event:?} (ignored)"); + // `ExitWithCode()` will be reset to `Poll` before polling + ControlFlow::ExitWithCode(_code) => unreachable!(), + }; + + min_timeout(control_flow_timeout, timeout) + }; + + let app = self.android_app.clone(); // Don't borrow self as part of poll expression + app.poll_events(timeout, |poll_event| { + let mut main_event = None; + + match poll_event { + android_activity::PollEvent::Wake => { + // In the X11 backend it's noted that too many false-positive wake ups + // would cause the event loop to run continuously. They handle this by re-checking + // for pending events (assuming they cover all valid reasons for a wake up). + // + // For now, user_events and redraw_requests are the only reasons to expect + // a wake up here so we can ignore the wake up if there are no events/requests. + // We also ignore wake ups while suspended. + self.pending_redraw |= self.redraw_flag.get_and_reset(); + if !self.running + || (!self.pending_redraw && !self.user_events_receiver.has_incoming()) + { + return; } } - - let wait_cancelled = iter_result - .deadline - .map_or(false, |deadline| Instant::now() < deadline); - - if wait_cancelled { - cause = StartCause::WaitCancelled { - start: iter_result.wait_start, - requested_resume: iter_result.deadline, - }; + android_activity::PollEvent::Timeout => {} + android_activity::PollEvent::Main(event) => { + main_event = Some(event); } + unknown_event => { + warn!("Unknown poll event {unknown_event:?} (ignored)"); + } + } - iter_result = self.single_iteration( - &mut control_flow, - main_event, - &mut pending_redraw, - &mut cause, - &mut callback, - ); - }); - }; - - sticky_exit_callback( - event::Event::LoopDestroyed, - self.window_target(), - &mut control_flow, - &mut callback, - ); + self.cause = match self.control_flow { + ControlFlow::Poll => StartCause::Poll, + ControlFlow::Wait => StartCause::WaitCancelled { + start, + requested_resume: None, + }, + ControlFlow::WaitUntil(deadline) => { + if Instant::now() < deadline { + StartCause::WaitCancelled { + start, + requested_resume: Some(deadline), + } + } else { + StartCause::ResumeTimeReached { + start, + requested_resume: deadline, + } + } + } + // `ExitWithCode()` will be reset to `Poll` before polling + ControlFlow::ExitWithCode(_code) => unreachable!(), + }; - exit_code + self.single_iteration(main_event, &mut callback); + }); } pub fn window_target(&self) -> &event_loop::EventLoopWindowTarget { From 80deeb31eb431f6fc35277a63f5172e63b8bf52b Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Sun, 18 Jun 2023 11:42:57 +0100 Subject: [PATCH 03/15] Windows: Implement EventLoopExtPumpEvents and EventLoopExtRunOnDemand A surprising amount of work was required to enable these extensions on Windows. I had originally assumed that pump_events was going to be very similar to run except would use PeekMessageW instead of GetMessageW to avoid blocking the external loop but I found the Windows backend broke several assumptions I had. Overall I think these changes can hopefully be considered a quite a significant simplification (I think it's a net deletion of a fair amount of code) and I think it also helps bring it into slightly closer alignment with other backends too Key changes: - I have removed the `wait_thread` that was a fairly fiddly way of handling `ControlFlow::WaitUntil` timeouts in favor of using `SetTimer` which works with the same messages picked up by `GetMessage` and `PeekMessage`. - I have removed the ordering guarantees between `MainEventsCleared`, `RedrawRequested` and `RedrawEventsCleared` events due to the complexity in maintaining this artificial ordering, which is already not supported consistently across backends anyway (in particular this ordering already isn't compatible with how MacOS / iOS work). - `RedrawRequested` events are now directly dispatched via `WM_PAINT` messages - comparable to how `RedrawRequested` is dispatched via `drawRect` in the MacOS backend. - I have re-worked how `NewEvents`, `MainEventsCleared`, and `RedrawEventsCleared` get dispatched to be more in line with the MacOS backend and also more in line with how we have recently discussed defining them for all platforms. `NewEvents` is conceptually delivered when the event loop "wakes up" and `MainEventsCleared` gets dispatched when the event loop is about to ask the OS to wait for new events. This is a more portable model, and is already how these events work in the MacOS backend. `RedrawEventsCleared` are just delivered after `MainEventsCleared` but this event no longer has a useful meaning. Probably the most controversial thing here is that this "breaks" the ordering rules for redraw event handling, but since my changes interacted with how the order is maintained I was very reluctant to figure out how to continue maintaining something that we have recently been discussing changing: https://github.com/rust-windowing/winit/issues/2640. Additionally, since the MacOS backend already doesn't strictly maintain this order it's somewhat academic to see this as a breakage if Winit applications can't really rely on it already. This updates the documentation for `request_redraw()` to reflect that we no longer guarantee that `RedrawRequested` events must be dispatched after `MainEventsCleared`. --- src/platform/mod.rs | 8 +- src/platform_impl/windows/event_loop.rs | 571 ++++++++---------- .../windows/event_loop/runner.rs | 172 ++---- src/platform_impl/windows/window.rs | 2 - src/window.rs | 18 +- 5 files changed, 339 insertions(+), 432 deletions(-) diff --git a/src/platform/mod.rs b/src/platform/mod.rs index e6c2546d2a..319d3390bc 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -11,8 +11,8 @@ //! //! And the following platform-specific modules: //! -//! - `run_ondemand` (available on `android`) -//! - `pump_events` (available on `android`) +//! - `run_ondemand` (available on `windows`, `android`) +//! - `pump_events` (available on `windows`, `android`) //! - `run_return` (available on `windows`, `unix`, `macos`, and `android`) //! //! However only the module corresponding to the platform you're compiling to will be available. @@ -36,10 +36,10 @@ pub mod windows; #[cfg(x11_platform)] pub mod x11; -#[cfg(any(android_platform))] +#[cfg(any(windows_platform, android_platform))] pub mod run_ondemand; -#[cfg(any(android_platform,))] +#[cfg(any(windows_platform, android_platform,))] pub mod pump_events; #[cfg(any( diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index d3b4bf6a82..5e8c9ca22a 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -14,7 +14,6 @@ use std::{ mpsc::{self, Receiver, Sender}, Arc, Mutex, MutexGuard, }, - thread, time::{Duration, Instant}, }; @@ -23,13 +22,11 @@ use raw_window_handle::{RawDisplayHandle, WindowsDisplayHandle}; use windows_sys::Win32::{ Devices::HumanInterfaceDevice::MOUSE_MOVE_RELATIVE, - Foundation::{BOOL, HANDLE, HWND, LPARAM, LRESULT, POINT, RECT, WAIT_TIMEOUT, WPARAM}, + Foundation::{BOOL, HANDLE, HWND, LPARAM, LRESULT, POINT, RECT, WPARAM}, Graphics::Gdi::{ - GetMonitorInfoW, GetUpdateRect, MonitorFromRect, MonitorFromWindow, RedrawWindow, - ScreenToClient, ValidateRect, MONITORINFO, MONITOR_DEFAULTTONULL, RDW_INTERNALPAINT, - SC_SCREENSAVE, + GetMonitorInfoW, MonitorFromRect, MonitorFromWindow, RedrawWindow, ScreenToClient, + ValidateRect, MONITORINFO, MONITOR_DEFAULTTONULL, RDW_INTERNALPAINT, SC_SCREENSAVE, }, - Media::{timeBeginPeriod, timeEndPeriod, timeGetDevCaps, TIMECAPS, TIMERR_NOERROR}, System::{ Ole::RevokeDragDrop, Threading::{GetCurrentThreadId, INFINITE}, @@ -54,34 +51,35 @@ use windows_sys::Win32::{ }, WindowsAndMessaging::{ CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW, GetCursorPos, - GetMenu, GetMessageW, LoadCursorW, MsgWaitForMultipleObjectsEx, PeekMessageW, - PostMessageW, PostThreadMessageW, RegisterClassExW, RegisterWindowMessageA, SetCursor, - SetWindowPos, TranslateMessage, CREATESTRUCTW, GIDC_ARRIVAL, GIDC_REMOVAL, GWL_STYLE, - GWL_USERDATA, HTCAPTION, HTCLIENT, MINMAXINFO, MNC_CLOSE, MSG, MWMO_INPUTAVAILABLE, - NCCALCSIZE_PARAMS, PM_NOREMOVE, PM_QS_PAINT, PM_REMOVE, PT_PEN, PT_TOUCH, QS_ALLEVENTS, - RI_KEY_E0, RI_KEY_E1, RI_MOUSE_WHEEL, SC_MINIMIZE, SC_RESTORE, SIZE_MAXIMIZED, - SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE, SWP_NOZORDER, WHEEL_DELTA, WINDOWPOS, - WM_CAPTURECHANGED, WM_CLOSE, WM_CREATE, WM_DESTROY, WM_DPICHANGED, WM_ENTERSIZEMOVE, - WM_EXITSIZEMOVE, WM_GETMINMAXINFO, WM_IME_COMPOSITION, WM_IME_ENDCOMPOSITION, - WM_IME_SETCONTEXT, WM_IME_STARTCOMPOSITION, WM_INPUT, WM_INPUT_DEVICE_CHANGE, - WM_KEYDOWN, WM_KEYUP, WM_KILLFOCUS, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, - WM_MBUTTONUP, WM_MENUCHAR, WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCACTIVATE, - WM_NCCALCSIZE, WM_NCCREATE, WM_NCDESTROY, WM_NCLBUTTONDOWN, WM_PAINT, WM_POINTERDOWN, - WM_POINTERUP, WM_POINTERUPDATE, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR, - WM_SETFOCUS, WM_SETTINGCHANGE, WM_SIZE, WM_SYSCOMMAND, WM_SYSKEYDOWN, WM_SYSKEYUP, - WM_TOUCH, WM_WINDOWPOSCHANGED, WM_WINDOWPOSCHANGING, WM_XBUTTONDOWN, WM_XBUTTONUP, - WNDCLASSEXW, WS_EX_LAYERED, WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW, WS_EX_TRANSPARENT, - WS_OVERLAPPED, WS_POPUP, WS_VISIBLE, + GetMenu, GetMessageW, KillTimer, LoadCursorW, PeekMessageW, PostMessageW, + RegisterClassExW, RegisterWindowMessageA, SetCursor, SetTimer, SetWindowPos, + TranslateMessage, CREATESTRUCTW, GIDC_ARRIVAL, GIDC_REMOVAL, GWL_STYLE, GWL_USERDATA, + HTCAPTION, HTCLIENT, MINMAXINFO, MNC_CLOSE, MSG, NCCALCSIZE_PARAMS, PM_REMOVE, PT_PEN, + PT_TOUCH, RI_KEY_E0, RI_KEY_E1, RI_MOUSE_WHEEL, SC_MINIMIZE, SC_RESTORE, + SIZE_MAXIMIZED, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE, SWP_NOZORDER, WHEEL_DELTA, + WINDOWPOS, WM_CAPTURECHANGED, WM_CLOSE, WM_CREATE, WM_DESTROY, WM_DPICHANGED, + WM_ENTERSIZEMOVE, WM_EXITSIZEMOVE, WM_GETMINMAXINFO, WM_IME_COMPOSITION, + WM_IME_ENDCOMPOSITION, WM_IME_SETCONTEXT, WM_IME_STARTCOMPOSITION, WM_INPUT, + WM_INPUT_DEVICE_CHANGE, WM_KEYDOWN, WM_KEYUP, WM_KILLFOCUS, WM_LBUTTONDOWN, + WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MENUCHAR, WM_MOUSEHWHEEL, WM_MOUSEMOVE, + WM_MOUSEWHEEL, WM_NCACTIVATE, WM_NCCALCSIZE, WM_NCCREATE, WM_NCDESTROY, + WM_NCLBUTTONDOWN, WM_PAINT, WM_POINTERDOWN, WM_POINTERUP, WM_POINTERUPDATE, + WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR, WM_SETFOCUS, WM_SETTINGCHANGE, WM_SIZE, + WM_SYSCOMMAND, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_TOUCH, WM_WINDOWPOSCHANGED, + WM_WINDOWPOSCHANGING, WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSEXW, WS_EX_LAYERED, + WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW, WS_EX_TRANSPARENT, WS_OVERLAPPED, WS_POPUP, + WS_VISIBLE, }, }, }; use crate::{ dpi::{PhysicalPosition, PhysicalSize}, + error::RunLoopError, event::{DeviceEvent, Event, Force, Ime, RawKeyEvent, Touch, TouchPhase, WindowEvent}, event_loop::{ControlFlow, DeviceEvents, EventLoopClosed, EventLoopWindowTarget as RootELW}, keyboard::{KeyCode, ModifiersState}, - platform::scancode::KeyCodeExtScancode, + platform::{pump_events::PumpStatus, scancode::KeyCodeExtScancode}, platform_impl::platform::{ dark_mode::try_theme, dpi::{become_dpi_aware, dpi_to_scale_factor}, @@ -99,6 +97,8 @@ use crate::{ }; use runner::{EventLoopRunner, EventLoopRunnerShared}; +use self::runner::RunnerState; + use super::window::set_skip_taskbar; type GetPointerFrameInfoHistory = unsafe extern "system" fn( @@ -216,13 +216,7 @@ impl EventLoop { let thread_msg_target = create_event_target_window::(); - thread::Builder::new() - .name("winit wait thread".to_string()) - .spawn(move || wait_thread(thread_id, thread_msg_target)) - .expect("Failed to spawn winit wait thread"); - let wait_thread_id = get_wait_thread_id(); - - let runner_shared = Rc::new(EventLoopRunner::new(thread_msg_target, wait_thread_id)); + let runner_shared = Rc::new(EventLoopRunner::new(thread_msg_target)); let thread_msg_sender = insert_event_target_window_data::(thread_msg_target, runner_shared.clone()); @@ -253,34 +247,246 @@ impl EventLoop { where F: 'static + FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), { - let exit_code = self.run_return(event_handler); + let exit_code = match self.run_ondemand(event_handler) { + Err(RunLoopError::ExitFailure(code)) => code, + Err(_err) => 1, + Ok(_) => 0, + }; ::std::process::exit(exit_code); } - pub fn run_return(&mut self, mut event_handler: F) -> i32 + pub fn run_return(&mut self, event_handler: F) -> i32 where F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), { - let event_loop_windows_ref = &self.window_target; + match self.run_ondemand(event_handler) { + Err(RunLoopError::ExitFailure(code)) => code, + Err(_err) => 1, + Ok(_) => 0, + } + } - unsafe { - self.window_target - .p - .runner_shared - .set_event_handler(move |event, control_flow| { + pub fn run_ondemand(&mut self, mut event_handler: F) -> Result<(), RunLoopError> + where + F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), + { + { + let runner = &self.window_target.p.runner_shared; + if runner.state() != RunnerState::Uninitialized { + return Err(RunLoopError::AlreadyRunning); + } + + let event_loop_windows_ref = &self.window_target; + // # Safety + // We make sure to call runner.clear_event_handler() before + // returning + unsafe { + runner.set_event_handler(move |event, control_flow| { + event_handler(event, event_loop_windows_ref, control_flow) + }); + } + } + + let exit_code = loop { + if let ControlFlow::ExitWithCode(code) = self.wait_and_dispatch_message() { + break code; + } + + if let ControlFlow::ExitWithCode(code) = self.dispatch_peeked_messages() { + break code; + } + }; + + let runner = &self.window_target.p.runner_shared; + runner.loop_destroyed(); + + // # Safety + // We assume that this will effectively call `runner.clear_event_handler()` + // to meet the safety requirements for calling `runner.set_event_handler()` above. + runner.reset_runner(); + + if exit_code == 0 { + Ok(()) + } else { + Err(RunLoopError::ExitFailure(exit_code)) + } + } + + pub fn pump_events(&mut self, mut event_handler: F) -> PumpStatus + where + F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), + { + { + let runner = &self.window_target.p.runner_shared; + let event_loop_windows_ref = &self.window_target; + + // # Safety + // We make sure to call runner.clear_event_handler() before + // returning + // + // Note: we're currently assuming nothing can panic and unwind + // to leave the runner in an unsound state with an associated + // event handler. + unsafe { + runner.set_event_handler(move |event, control_flow| { event_handler(event, event_loop_windows_ref, control_flow) }); + runner.wakeup(); + } } + self.dispatch_peeked_messages(); + + let runner = &self.window_target.p.runner_shared; + + let status = if let ControlFlow::ExitWithCode(code) = runner.control_flow() { + runner.loop_destroyed(); + + // Immediately reset the internal state for the loop to allow + // the loop to be run more than once. + runner.reset_runner(); + PumpStatus::Exit(code) + } else { + runner.prepare_wait(); + PumpStatus::Continue + }; + + // We wait until we've checked for an exit status before clearing the + // application callback, in case we need to dispatch a LoopDestroyed event + // + // # Safety + // This pairs up with our call to `runner.set_event_handler` and ensures + // the application's callback can't be held beyond its lifetime. + runner.clear_event_handler(); + + status + } + + /// Wait for one message and dispatch it, optionally with a timeout if control_flow is `WaitUntil` + fn wait_and_dispatch_message(&mut self) -> ControlFlow { + let start = Instant::now(); + let runner = &self.window_target.p.runner_shared; - let exit_code = unsafe { - let mut msg = mem::zeroed(); + let timeout = match runner.control_flow() { + ControlFlow::Wait => None, + ControlFlow::Poll => Some(Duration::ZERO), + ControlFlow::WaitUntil(wait_deadline) => { + Some(wait_deadline.saturating_duration_since(start)) + } + ControlFlow::ExitWithCode(_code) => unreachable!(), + }; + + fn get_msg_with_timeout(msg: &mut MSG, timeout: Option) -> PumpStatus { + unsafe { + // A timeout of None means wait indefinitely (so we don't need to call SetTimer) + let timer_id = timeout.map(|timeout| SetTimer(0, 0, dur2timeout(timeout), None)); + let get_status = GetMessageW(msg, 0, 0, 0); + if let Some(timer_id) = timer_id { + KillTimer(0, timer_id); + } + // A return value of 0 implies `WM_QUIT` + if get_status == 0 { + PumpStatus::Exit(0) + } else { + PumpStatus::Continue + } + } + } + + /// Fetch the next MSG either via PeekMessage or GetMessage depending on whether the + /// requested timeout is `ZERO` (and so we don't want to block) + /// + /// Returns `None` if if no MSG was read, else a `Continue` or `Exit` status + fn wait_for_msg(msg: &mut MSG, timeout: Option) -> Option { + if timeout == Some(Duration::ZERO) { + unsafe { + if PeekMessageW(msg, 0, 0, 0, PM_REMOVE) != 0 { + Some(PumpStatus::Continue) + } else { + None + } + } + } else { + Some(get_msg_with_timeout(msg, timeout)) + } + } + + // We aim to be consistent with the MacOS backend which has a RunLoop + // observer that will dispatch MainEventsCleared when about to wait for + // events, and NewEvents after the RunLoop wakes up. + // + // We emulate similar behaviour by treating `GetMessage` as our wait + // point and wake up point (when it returns) and we drain all other + // pending messages via `PeekMessage` until we come back to "wait" via + // `GetMessage` + // + runner.prepare_wait(); + + // # Safety + // The Windows API has no documented requirement for bitwise + // initializing a `MSG` struct (it can be uninitialized memory for the C + // API) and there's no API to construct or initialize a `MSG`. This + // is the simplest way avoid unitialized memory in Rust + let mut msg = unsafe { mem::zeroed() }; + let msg_status = wait_for_msg(&mut msg, timeout); + + // Before we potentially exit, make sure to consistently emit an event for the wake up + runner.wakeup(); + + match msg_status { + None => {} // No MSG to dispatch + Some(PumpStatus::Exit(code)) => { + runner.set_exit_control_flow(code); + return runner.control_flow(); + } + Some(PumpStatus::Continue) => { + unsafe { + let handled = if let Some(callback) = self.msg_hook.as_deref_mut() { + callback(&mut msg as *mut _ as *mut _) + } else { + false + }; + if !handled { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + } + + if let Err(payload) = runner.take_panic_error() { + runner.reset_runner(); + panic::resume_unwind(payload); + } + } + } - runner.poll(); - 'main: loop { - if GetMessageW(&mut msg, 0, 0, 0) == false.into() { - break 'main 0; + runner.control_flow() + } + + /// Dispatch all queued messages via `PeekMessageW` + fn dispatch_peeked_messages(&mut self) -> ControlFlow { + let runner = &self.window_target.p.runner_shared; + + // We generally want to continue dispatching all pending messages + // but we also allow dispatching to be interrupted as a means to + // ensure the `pump_events` won't indefinitely block an external + // event loop if there are too many pending events. This interrupt + // flag will be set after dispatching `RedrawRequested` events. + runner.interrupt_msg_dispatch.set(false); + + // # Safety + // The Windows API has no documented requirement for bitwise + // initializing a `MSG` struct (it can be uninitialized memory for the C + // API) and there's no API to construct or initialize a `MSG`. This + // is the simplest way avoid unitialized memory in Rust + let mut msg = unsafe { mem::zeroed() }; + + let mut control_flow = runner.control_flow(); + + loop { + unsafe { + if PeekMessageW(&mut msg, 0, 0, 0, PM_REMOVE) == false.into() { + break; } let handled = if let Some(callback) = self.msg_hook.as_deref_mut() { @@ -292,26 +498,24 @@ impl EventLoop { TranslateMessage(&msg); DispatchMessageW(&msg); } + } - if let Err(payload) = runner.take_panic_error() { - runner.reset_runner(); - panic::resume_unwind(payload); - } + if let Err(payload) = runner.take_panic_error() { + runner.reset_runner(); + panic::resume_unwind(payload); + } - if let ControlFlow::ExitWithCode(code) = runner.control_flow() { - if !runner.handling_events() { - break 'main code; - } - } + control_flow = runner.control_flow(); + if let ControlFlow::ExitWithCode(_code) = control_flow { + break; } - }; - unsafe { - runner.loop_destroyed(); + if runner.interrupt_msg_dispatch.get() { + break; + } } - runner.reset_runner(); - exit_code + control_flow } pub fn create_proxy(&self) -> EventLoopProxy { @@ -391,109 +595,6 @@ fn main_thread_id() -> u32 { unsafe { MAIN_THREAD_ID } } -fn get_wait_thread_id() -> u32 { - unsafe { - let mut msg = mem::zeroed(); - let result = GetMessageW( - &mut msg, - -1, - SEND_WAIT_THREAD_ID_MSG_ID.get(), - SEND_WAIT_THREAD_ID_MSG_ID.get(), - ); - assert_eq!( - msg.message, - SEND_WAIT_THREAD_ID_MSG_ID.get(), - "this shouldn't be possible. please open an issue with Winit. error code: {result}" - ); - msg.lParam as u32 - } -} - -static WAIT_PERIOD_MIN: Lazy> = Lazy::new(|| unsafe { - let mut caps = TIMECAPS { - wPeriodMin: 0, - wPeriodMax: 0, - }; - if timeGetDevCaps(&mut caps, mem::size_of::() as u32) == TIMERR_NOERROR { - Some(caps.wPeriodMin) - } else { - None - } -}); - -fn wait_thread(parent_thread_id: u32, msg_window_id: HWND) { - unsafe { - let mut msg: MSG; - - let cur_thread_id = GetCurrentThreadId(); - PostThreadMessageW( - parent_thread_id, - SEND_WAIT_THREAD_ID_MSG_ID.get(), - 0, - cur_thread_id as LPARAM, - ); - - let mut wait_until_opt = None; - 'main: loop { - // Zeroing out the message ensures that the `WaitUntilInstantBox` doesn't get - // double-freed if `MsgWaitForMultipleObjectsEx` returns early and there aren't - // additional messages to process. - msg = mem::zeroed(); - - if wait_until_opt.is_some() { - if PeekMessageW(&mut msg, 0, 0, 0, PM_REMOVE) != false.into() { - TranslateMessage(&msg); - DispatchMessageW(&msg); - } - } else if GetMessageW(&mut msg, 0, 0, 0) == false.into() { - break 'main; - } else { - TranslateMessage(&msg); - DispatchMessageW(&msg); - } - - if msg.message == WAIT_UNTIL_MSG_ID.get() { - wait_until_opt = Some(*WaitUntilInstantBox::from_raw(msg.lParam as *mut _)); - } else if msg.message == CANCEL_WAIT_UNTIL_MSG_ID.get() { - wait_until_opt = None; - } - - if let Some(wait_until) = wait_until_opt { - let now = Instant::now(); - if now < wait_until { - // Windows' scheduler has a default accuracy of several ms. This isn't good enough for - // `WaitUntil`, so we request the Windows scheduler to use a higher accuracy if possible. - // If we couldn't query the timer capabilities, then we use the default resolution. - if let Some(period) = *WAIT_PERIOD_MIN { - timeBeginPeriod(period); - } - // `MsgWaitForMultipleObjects` is bound by the granularity of the scheduler period. - // Because of this, we try to reduce the requested time just enough to undershoot `wait_until` - // by the smallest amount possible, and then we busy loop for the remaining time inside the - // NewEvents message handler. - let resume_reason = MsgWaitForMultipleObjectsEx( - 0, - ptr::null(), - dur2timeout(wait_until - now).saturating_sub(WAIT_PERIOD_MIN.unwrap_or(1)), - QS_ALLEVENTS, - MWMO_INPUTAVAILABLE, - ); - if let Some(period) = *WAIT_PERIOD_MIN { - timeEndPeriod(period); - } - if resume_reason == WAIT_TIMEOUT { - PostMessageW(msg_window_id, PROCESS_NEW_EVENTS_MSG_ID.get(), 0, 0); - wait_until_opt = None; - } - } else { - PostMessageW(msg_window_id, PROCESS_NEW_EVENTS_MSG_ID.get(), 0, 0); - wait_until_opt = None; - } - } - } - } -} - // Implementation taken from https://github.com/rust-lang/rust/blob/db5476571d9b27c862b95c1e64764b0ac8980e23/src/libstd/sys/windows/mod.rs fn dur2timeout(dur: Duration) -> u32 { // Note that a duration is a (u64, u32) (seconds, nanoseconds) pair, and the @@ -612,8 +713,6 @@ impl EventLoopProxy { } } -type WaitUntilInstantBox = Box; - /// A lazily-initialized window message ID. pub struct LazyMessageId { /// The ID. @@ -673,13 +772,6 @@ static USER_EVENT_MSG_ID: LazyMessageId = LazyMessageId::new("Winit::WakeupMsg\0 // WPARAM contains a Box> that must be retrieved with `Box::from_raw`, // and LPARAM is unused. static EXEC_MSG_ID: LazyMessageId = LazyMessageId::new("Winit::ExecMsg\0"); -static PROCESS_NEW_EVENTS_MSG_ID: LazyMessageId = LazyMessageId::new("Winit::ProcessNewEvents\0"); -/// lparam is the wait thread's message id. -static SEND_WAIT_THREAD_ID_MSG_ID: LazyMessageId = LazyMessageId::new("Winit::SendWaitThreadId\0"); -/// lparam points to a `Box` signifying the time `PROCESS_NEW_EVENTS_MSG_ID` should -/// be sent. -static WAIT_UNTIL_MSG_ID: LazyMessageId = LazyMessageId::new("Winit::WaitUntil\0"); -static CANCEL_WAIT_UNTIL_MSG_ID: LazyMessageId = LazyMessageId::new("Winit::CancelWaitUntil\0"); // Message sent by a `Window` when it wants to be destroyed by the main thread. // WPARAM and LPARAM are unused. pub static DESTROY_MSG_ID: LazyMessageId = LazyMessageId::new("Winit::DestroyMsg\0"); @@ -795,74 +887,6 @@ fn normalize_pointer_pressure(pressure: u32) -> Option { } } -/// Flush redraw events for Winit's windows. -/// -/// Winit's API guarantees that all redraw events will be clustered together and dispatched all at -/// once, but the standard Windows message loop doesn't always exhibit that behavior. If multiple -/// windows have had redraws scheduled, but an input event is pushed to the message queue between -/// the `WM_PAINT` call for the first window and the `WM_PAINT` call for the second window, Windows -/// will dispatch the input event immediately instead of flushing all the redraw events. This -/// function explicitly pulls all of Winit's redraw events out of the event queue so that they -/// always all get processed in one fell swoop. -/// -/// Returns `true` if this invocation flushed all the redraw events. If this function is re-entrant, -/// it won't flush the redraw events and will return `false`. -#[must_use] -unsafe fn flush_paint_messages( - except: Option, - runner: &EventLoopRunner, -) -> bool { - if !runner.redrawing() { - runner.main_events_cleared(); - let mut msg = mem::zeroed(); - runner.owned_windows(|redraw_window| { - if Some(redraw_window) == except { - return; - } - - if PeekMessageW( - &mut msg, - redraw_window, - WM_PAINT, - WM_PAINT, - PM_REMOVE | PM_QS_PAINT, - ) == false.into() - { - return; - } - - TranslateMessage(&msg); - DispatchMessageW(&msg); - }); - true - } else { - false - } -} - -unsafe fn process_control_flow(runner: &EventLoopRunner) { - match runner.control_flow() { - ControlFlow::Poll => { - PostMessageW( - runner.thread_msg_target(), - PROCESS_NEW_EVENTS_MSG_ID.get(), - 0, - 0, - ); - } - ControlFlow::Wait => (), - ControlFlow::WaitUntil(until) => { - PostThreadMessageW( - runner.wait_thread_id(), - WAIT_UNTIL_MSG_ID.get(), - 0, - Box::into_raw(WaitUntilInstantBox::new(until)) as isize, - ); - } - ControlFlow::ExitWithCode(_) => (), - } -} - /// Emit a `ModifiersChanged` event whenever modifiers have changed. /// Returns the current modifier state fn update_modifiers(window: HWND, userdata: &WindowData) { @@ -988,13 +1012,6 @@ unsafe fn public_window_callback_inner( lparam: LPARAM, userdata: &WindowData, ) -> LRESULT { - RedrawWindow( - userdata.event_loop_runner.thread_msg_target(), - ptr::null(), - 0, - RDW_INTERNALPAINT, - ); - let mut result = ProcResult::DefWindowProc(wparam); // Send new modifiers before sending key events. @@ -1116,7 +1133,6 @@ unsafe fn public_window_callback_inner( window_id: RootWindowId(WindowId(window)), event: Destroyed, }); - userdata.event_loop_runner.remove_window(window); result = ProcResult::Value(0); } @@ -1132,13 +1148,7 @@ unsafe fn public_window_callback_inner( // redraw the window outside the normal flow of the event loop. RedrawWindow(window, ptr::null(), 0, RDW_INTERNALPAINT); } else { - let managing_redraw = - flush_paint_messages(Some(window), &userdata.event_loop_runner); userdata.send_event(Event::RedrawRequested(RootWindowId(WindowId(window)))); - if managing_redraw { - userdata.event_loop_runner.redraw_events_cleared(); - process_control_flow(&userdata.event_loop_runner); - } } result = ProcResult::DefWindowProc(wparam); } @@ -2270,27 +2280,8 @@ unsafe extern "system" fn thread_event_target_callback( userdata_removed = true; 0 } - // Because WM_PAINT comes after all other messages, we use it during modal loops to detect - // when the event queue has been emptied. See `process_event` for more details. WM_PAINT => { ValidateRect(window, ptr::null()); - // If the WM_PAINT handler in `public_window_callback` has already flushed the redraw - // events, `handling_events` will return false and we won't emit a second - // `RedrawEventsCleared` event. - if userdata.event_loop_runner.handling_events() { - if userdata.event_loop_runner.should_buffer() { - // This branch can be triggered when a nested win32 event loop is triggered - // inside of the `event_handler` callback. - RedrawWindow(window, ptr::null(), 0, RDW_INTERNALPAINT); - } else { - // This WM_PAINT handler will never be re-entrant because `flush_paint_messages` - // doesn't call WM_PAINT for the thread event target (i.e. this window). - assert!(flush_paint_messages(None, &userdata.event_loop_runner)); - userdata.event_loop_runner.redraw_events_cleared(); - process_control_flow(&userdata.event_loop_runner); - } - } - // Default WM_PAINT behaviour. This makes sure modals and popups are shown immediatly when opening them. DefWindowProcW(window, msg, wparam, lparam) } @@ -2329,40 +2320,6 @@ unsafe extern "system" fn thread_event_target_callback( function(); 0 } - _ if msg == PROCESS_NEW_EVENTS_MSG_ID.get() => { - PostThreadMessageW( - userdata.event_loop_runner.wait_thread_id(), - CANCEL_WAIT_UNTIL_MSG_ID.get(), - 0, - 0, - ); - - // if the control_flow is WaitUntil, make sure the given moment has actually passed - // before emitting NewEvents - if let ControlFlow::WaitUntil(wait_until) = userdata.event_loop_runner.control_flow() { - let mut msg = mem::zeroed(); - while Instant::now() < wait_until { - if PeekMessageW(&mut msg, 0, 0, 0, PM_NOREMOVE) != false.into() { - // This works around a "feature" in PeekMessageW. If the message PeekMessageW - // gets is a WM_PAINT message that had RDW_INTERNALPAINT set (i.e. doesn't - // have an update region), PeekMessageW will remove that window from the - // redraw queue even though we told it not to remove messages from the - // queue. We fix it by re-dispatching an internal paint message to that - // window. - if msg.message == WM_PAINT { - let mut rect = mem::zeroed(); - if GetUpdateRect(msg.hwnd, &mut rect, false.into()) == false.into() { - RedrawWindow(msg.hwnd, ptr::null(), 0, RDW_INTERNALPAINT); - } - } - - break; - } - } - } - userdata.event_loop_runner.poll(); - 0 - } _ => DefWindowProcW(window, msg, wparam, lparam), }; diff --git a/src/platform_impl/windows/event_loop/runner.rs b/src/platform_impl/windows/event_loop/runner.rs index c4ac1eb348..c39b3f7004 100644 --- a/src/platform_impl/windows/event_loop/runner.rs +++ b/src/platform_impl/windows/event_loop/runner.rs @@ -1,16 +1,13 @@ use std::{ any::Any, cell::{Cell, RefCell}, - collections::{HashSet, VecDeque}, - mem, panic, ptr, + collections::VecDeque, + mem, panic, rc::Rc, time::Instant, }; -use windows_sys::Win32::{ - Foundation::HWND, - Graphics::Gdi::{RedrawWindow, RDW_INTERNALPAINT}, -}; +use windows_sys::Win32::Foundation::HWND; use crate::{ dpi::PhysicalSize, @@ -30,7 +27,11 @@ type EventHandler = Cell, &mut ControlFlow) pub(crate) struct EventLoopRunner { // The event loop's win32 handles pub(super) thread_msg_target: HWND, - wait_thread_id: u32, + + // Setting this will ensure pump_events will return to the external + // loop asap. E.g. set after each RedrawRequested to ensure pump_events + // can't stall an external loop beyond a frame + pub(super) interrupt_msg_dispatch: Cell, control_flow: Cell, runner_state: Cell, @@ -38,8 +39,6 @@ pub(crate) struct EventLoopRunner { event_handler: EventHandler, event_buffer: RefCell>>, - owned_windows: Cell>, - panic_error: Cell>, } @@ -47,7 +46,7 @@ pub type PanicError = Box; /// See `move_state_to` function for details on how the state loop works. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -enum RunnerState { +pub(crate) enum RunnerState { /// The event loop has just been created, and an `Init` event must be sent. Uninitialized, /// The event loop is idling. @@ -55,9 +54,6 @@ enum RunnerState { /// The event loop is handling the OS's events and sending them to the user's callback. /// `NewEvents` has been sent, and `MainEventsCleared` hasn't. HandlingMainEvents, - /// The event loop is handling the redraw events and sending them to the user's callback. - /// `MainEventsCleared` has been sent, and `RedrawEventsCleared` hasn't. - HandlingRedrawEvents, /// The event loop has been destroyed. No other events will be emitted. Destroyed, } @@ -68,20 +64,30 @@ enum BufferedEvent { } impl EventLoopRunner { - pub(crate) fn new(thread_msg_target: HWND, wait_thread_id: u32) -> EventLoopRunner { + pub(crate) fn new(thread_msg_target: HWND) -> EventLoopRunner { EventLoopRunner { thread_msg_target, - wait_thread_id, + interrupt_msg_dispatch: Cell::new(false), runner_state: Cell::new(RunnerState::Uninitialized), control_flow: Cell::new(ControlFlow::Poll), panic_error: Cell::new(None), last_events_cleared: Cell::new(Instant::now()), event_handler: Cell::new(None), event_buffer: RefCell::new(VecDeque::new()), - owned_windows: Cell::new(HashSet::new()), } } + /// Associate the application's event handler with the runner + /// + /// # Safety + /// This is ignoring the lifetime of the application handler (which may not + /// outlive the EventLoopRunner) and can lead to undefined behaviour if + /// the handler is not cleared before the end of real lifetime. + /// + /// All public APIs that take an event handler (`run`, `run_ondemand`, + /// `pump_events`) _must_ pair a call to `set_event_handler` with + /// a call to `clear_event_handler` before returning to avoid + /// undefined behaviour. pub(crate) unsafe fn set_event_handler(&self, f: F) where F: FnMut(Event<'_, T>, &mut ControlFlow), @@ -93,18 +99,22 @@ impl EventLoopRunner { assert!(old_event_handler.is_none()); } + pub(crate) fn clear_event_handler(&self) { + self.event_handler.set(None); + } + pub(crate) fn reset_runner(&self) { let EventLoopRunner { thread_msg_target: _, - wait_thread_id: _, + interrupt_msg_dispatch, runner_state, panic_error, control_flow, last_events_cleared: _, event_handler, event_buffer: _, - owned_windows: _, } = self; + interrupt_msg_dispatch.set(false); runner_state.set(RunnerState::Uninitialized); panic_error.set(None); control_flow.set(ControlFlow::Poll); @@ -114,18 +124,11 @@ impl EventLoopRunner { /// State retrieval functions. impl EventLoopRunner { + #[allow(unused)] pub fn thread_msg_target(&self) -> HWND { self.thread_msg_target } - pub fn wait_thread_id(&self) -> u32 { - self.wait_thread_id - } - - pub fn redrawing(&self) -> bool { - self.runner_state.get() == RunnerState::HandlingRedrawEvents - } - pub fn take_panic_error(&self) -> Result<(), PanicError> { match self.panic_error.take() { Some(err) => Err(err), @@ -133,12 +136,16 @@ impl EventLoopRunner { } } - pub fn control_flow(&self) -> ControlFlow { - self.control_flow.get() + pub fn state(&self) -> RunnerState { + self.runner_state.get() } - pub fn handling_events(&self) -> bool { - self.runner_state.get() != RunnerState::Idle + pub fn set_exit_control_flow(&self, code: i32) { + self.control_flow.set(ControlFlow::ExitWithCode(code)) + } + + pub fn control_flow(&self) -> ControlFlow { + self.control_flow.get() } pub fn should_buffer(&self) -> bool { @@ -177,42 +184,25 @@ impl EventLoopRunner { None } } - pub fn register_window(&self, window: HWND) { - let mut owned_windows = self.owned_windows.take(); - owned_windows.insert(window); - self.owned_windows.set(owned_windows); - } - - pub fn remove_window(&self, window: HWND) { - let mut owned_windows = self.owned_windows.take(); - owned_windows.remove(&window); - self.owned_windows.set(owned_windows); - } - - pub fn owned_windows(&self, mut f: impl FnMut(HWND)) { - let mut owned_windows = self.owned_windows.take(); - for hwnd in &owned_windows { - f(*hwnd); - } - let new_owned_windows = self.owned_windows.take(); - owned_windows.extend(&new_owned_windows); - self.owned_windows.set(owned_windows); - } } /// Event dispatch functions. impl EventLoopRunner { - pub(crate) unsafe fn poll(&self) { + pub(crate) fn prepare_wait(&self) { + self.move_state_to(RunnerState::Idle); + } + + pub(crate) fn wakeup(&self) { self.move_state_to(RunnerState::HandlingMainEvents); } - pub(crate) unsafe fn send_event(&self, event: Event<'_, T>) { + pub(crate) fn send_event(&self, event: Event<'_, T>) { if let Event::RedrawRequested(_) = event { - if self.runner_state.get() != RunnerState::HandlingRedrawEvents { - warn!("RedrawRequested dispatched without explicit MainEventsCleared"); - self.move_state_to(RunnerState::HandlingRedrawEvents); - } self.call_event_handler(event); + // As a rule, to ensure that `pump_events` can't block an external event loop + // for too long, we always guarantee that `pump_events` will return control to + // the external loop asap after a `RedrawRequested` event is dispatched. + self.interrupt_msg_dispatch.set(true); } else if self.should_buffer() { // If the runner is already borrowed, we're in the middle of an event loop invocation. Add // the event to a buffer to be processed later. @@ -220,25 +210,16 @@ impl EventLoopRunner { .borrow_mut() .push_back(BufferedEvent::from_event(event)) } else { - self.move_state_to(RunnerState::HandlingMainEvents); self.call_event_handler(event); self.dispatch_buffered_events(); } } - pub(crate) unsafe fn main_events_cleared(&self) { - self.move_state_to(RunnerState::HandlingRedrawEvents); - } - - pub(crate) unsafe fn redraw_events_cleared(&self) { - self.move_state_to(RunnerState::Idle); - } - - pub(crate) unsafe fn loop_destroyed(&self) { + pub(crate) fn loop_destroyed(&self) { self.move_state_to(RunnerState::Destroyed); } - unsafe fn call_event_handler(&self, event: Event<'_, T>) { + fn call_event_handler(&self, event: Event<'_, T>) { self.catch_unwind(|| { let mut control_flow = self.control_flow.take(); let mut event_handler = self.event_handler.take() @@ -255,7 +236,7 @@ impl EventLoopRunner { }); } - unsafe fn dispatch_buffered_events(&self) { + fn dispatch_buffered_events(&self) { loop { // We do this instead of using a `while let` loop because if we use a `while let` // loop the reference returned `borrow_mut()` doesn't get dropped until the end @@ -278,24 +259,22 @@ impl EventLoopRunner { /// Uninitialized /// | /// V - /// HandlingMainEvents - /// ^ | - /// | V - /// Idle <--- HandlingRedrawEvents - /// | - /// V - /// Destroyed + /// Idle + /// ^ | + /// | V + /// HandlingMainEvents + /// | + /// V + /// Destroyed /// ``` /// /// Attempting to transition back to `Uninitialized` will result in a panic. Attempting to - /// transition *from* `Destroyed` will also reuslt in a panic. Transitioning to the current + /// transition *from* `Destroyed` will also result in a panic. Transitioning to the current /// state is a no-op. Even if the `new_runner_state` isn't the immediate next state in the /// runner state machine (e.g. `self.runner_state == HandlingMainEvents` and /// `new_runner_state == Idle`), the intermediate state transitions will still be executed. - unsafe fn move_state_to(&self, new_runner_state: RunnerState) { - use RunnerState::{ - Destroyed, HandlingMainEvents, HandlingRedrawEvents, Idle, Uninitialized, - }; + fn move_state_to(&self, new_runner_state: RunnerState) { + use RunnerState::{Destroyed, HandlingMainEvents, Idle, Uninitialized}; match ( self.runner_state.replace(new_runner_state), @@ -304,17 +283,12 @@ impl EventLoopRunner { (Uninitialized, Uninitialized) | (Idle, Idle) | (HandlingMainEvents, HandlingMainEvents) - | (HandlingRedrawEvents, HandlingRedrawEvents) | (Destroyed, Destroyed) => (), // State transitions that initialize the event loop. (Uninitialized, HandlingMainEvents) => { self.call_new_events(true); } - (Uninitialized, HandlingRedrawEvents) => { - self.call_new_events(true); - self.call_event_handler(Event::MainEventsCleared); - } (Uninitialized, Idle) => { self.call_new_events(true); self.call_event_handler(Event::MainEventsCleared); @@ -332,19 +306,11 @@ impl EventLoopRunner { (Idle, HandlingMainEvents) => { self.call_new_events(false); } - (Idle, HandlingRedrawEvents) => { - self.call_new_events(false); - self.call_event_handler(Event::MainEventsCleared); - } (Idle, Destroyed) => { self.call_event_handler(Event::LoopDestroyed); } - (HandlingMainEvents, HandlingRedrawEvents) => { - self.call_event_handler(Event::MainEventsCleared); - } (HandlingMainEvents, Idle) => { - warn!("RedrawEventsCleared emitted without explicit MainEventsCleared"); self.call_event_handler(Event::MainEventsCleared); self.call_redraw_events_cleared(); } @@ -354,24 +320,11 @@ impl EventLoopRunner { self.call_event_handler(Event::LoopDestroyed); } - (HandlingRedrawEvents, Idle) => { - self.call_redraw_events_cleared(); - } - (HandlingRedrawEvents, HandlingMainEvents) => { - warn!("NewEvents emitted without explicit RedrawEventsCleared"); - self.call_redraw_events_cleared(); - self.call_new_events(false); - } - (HandlingRedrawEvents, Destroyed) => { - self.call_redraw_events_cleared(); - self.call_event_handler(Event::LoopDestroyed); - } - (Destroyed, _) => panic!("cannot move state from Destroyed"), } } - unsafe fn call_new_events(&self, init: bool) { + fn call_new_events(&self, init: bool) { let start_cause = match (init, self.control_flow()) { (true, _) => StartCause::Init, (false, ControlFlow::Poll) => StartCause::Poll, @@ -402,10 +355,9 @@ impl EventLoopRunner { self.call_event_handler(Event::Resumed); } self.dispatch_buffered_events(); - RedrawWindow(self.thread_msg_target, ptr::null(), 0, RDW_INTERNALPAINT); } - unsafe fn call_redraw_events_cleared(&self) { + fn call_redraw_events_cleared(&self) { self.call_event_handler(Event::RedrawEventsCleared); self.last_events_cleared.set(Instant::now()); } diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index ff07b8eb25..04bd78d384 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -987,8 +987,6 @@ impl<'a, T: 'static> InitData<'a, T> { None }; - self.event_loop.runner_shared.register_window(win.window.0); - event_loop::WindowData { window_state: win.window_state.clone(), event_loop_runner: self.event_loop.runner_shared.clone(), diff --git a/src/window.rs b/src/window.rs index 40d43c58c3..017501f6b1 100644 --- a/src/window.rs +++ b/src/window.rs @@ -524,25 +524,25 @@ impl Window { self.window.scale_factor() } - /// Emits a [`Event::RedrawRequested`] event in the associated event loop after all OS - /// events have been processed by the event loop. + /// Requests a future [`Event::RedrawRequested`] event to be emitted in a way that is + /// synchronized and / or throttled by the windowing system. /// /// This is the **strongly encouraged** method of redrawing windows, as it can integrate with /// OS-requested redraws (e.g. when a window gets resized). /// - /// This function can cause `RedrawRequested` events to be emitted after [`Event::MainEventsCleared`] - /// but before `Event::NewEvents` if called in the following circumstances: - /// * While processing `MainEventsCleared`. - /// * While processing a `RedrawRequested` event that was sent during `MainEventsCleared` or any - /// directly subsequent `RedrawRequested` event. + /// Applications should always aim to redraw whenever they receive a `RedrawRequested` event. + /// + /// There are no strong guarantees about when exactly a `RedrawRequest` event will be emitted + /// with respect to other events, since the requirements can vary significantly between + /// windowing systems. /// /// ## Platform-specific /// + /// - **Windows** This API uses `RedrawWindow` to request a `WM_PAINT` message and `RedrawRequested` + /// is emitted in sync with any `WM_PAINT` messages /// - **iOS:** Can only be called on the main thread. - /// - **Android:** Subsequent calls after `MainEventsCleared` are not handled. /// /// [`Event::RedrawRequested`]: crate::event::Event::RedrawRequested - /// [`Event::MainEventsCleared`]: crate::event::Event::MainEventsCleared #[inline] pub fn request_redraw(&self) { self.window.request_redraw() From 82f91088227f44bae3ee8060226848eef375cbb3 Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Sun, 18 Jun 2023 12:10:09 +0100 Subject: [PATCH 04/15] MacOS: Implement EventLoopExtPumpEvents and EventLoopExtRunOnDemand The implementation of `pump_events` essentially works by hooking into the `RunLoopObserver` and requesting that the app should be stopped the next time that the `RunLoop` prepares to wait for new events. Originally I had thought I would poke the `CFRunLoop` for the app directly and I was originally going to implement `pump_events` based on a timeout which I'd seen SDL doing. I found that `[NSApp run]` wasn't actually being stopped by asking the RunLoop to stop directly and inferred that `NSApp run` will actually catch this and re-start the loop. Hooking into the observer and calling `[NSApp stop]` actually seems like a better solution that doesn't need a hacky constant timeout. The end result is quite similar to what happens with existing apps that call `run_return` inside an external loop and cause the loop to exit for each iteration (that also results in the `NSApp` stopping each iteration). --- src/platform/mod.rs | 8 +- src/platform_impl/macos/app_state.rs | 261 +++++++++++++++++++++++--- src/platform_impl/macos/event_loop.rs | 192 +++++++++++++++++-- 3 files changed, 413 insertions(+), 48 deletions(-) diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 319d3390bc..dd56f58f41 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -11,8 +11,8 @@ //! //! And the following platform-specific modules: //! -//! - `run_ondemand` (available on `windows`, `android`) -//! - `pump_events` (available on `windows`, `android`) +//! - `run_ondemand` (available on `windows`, `macos`, `android`) +//! - `pump_events` (available on `windows`, `macos`, `android`) //! - `run_return` (available on `windows`, `unix`, `macos`, and `android`) //! //! However only the module corresponding to the platform you're compiling to will be available. @@ -36,10 +36,10 @@ pub mod windows; #[cfg(x11_platform)] pub mod x11; -#[cfg(any(windows_platform, android_platform))] +#[cfg(any(windows_platform, macos_platform, android_platform))] pub mod run_ondemand; -#[cfg(any(windows_platform, android_platform,))] +#[cfg(any(windows_platform, macos_platform, android_platform,))] pub mod pump_events; #[cfg(any( diff --git a/src/platform_impl/macos/app_state.rs b/src/platform_impl/macos/app_state.rs index 1a85b40f63..6569a7cfd3 100644 --- a/src/platform_impl/macos/app_state.rs +++ b/src/platform_impl/macos/app_state.rs @@ -65,14 +65,17 @@ impl EventLoopHandler { RefMut<'_, dyn FnMut(Event<'_, T>, &RootWindowTarget, &mut ControlFlow)>, ), { + // The `NSApp` and our `HANDLER` are global state and so it's possible that + // we could get a delegate callback after the application has exit an + // `EventLoop`. If the loop has been exit then our weak `self.callback` + // will fail to upgrade. + // + // We don't want to panic or output any verbose logging if we fail to + // upgrade the weak reference since it might be valid that the application + // re-starts the `NSApp` after exiting a Winit `EventLoop` if let Some(callback) = self.callback.upgrade() { let callback = callback.borrow_mut(); (f)(self, callback); - } else { - panic!( - "Tried to dispatch an event, but the event loop that \ - owned the event handler callback seems to be destroyed" - ); } } } @@ -90,6 +93,7 @@ impl EventHandler for EventLoopHandler { fn handle_nonuser_event(&mut self, event: Event<'_, Never>, control_flow: &mut ControlFlow) { self.with_callback(|this, mut callback| { if let ControlFlow::ExitWithCode(code) = *control_flow { + // XXX: why isn't event dispatching simply skipped after control_flow = ExitWithCode? let dummy = &mut ControlFlow::ExitWithCode(code); (callback)(event.userify(), &this.window_target, dummy); } else { @@ -102,6 +106,7 @@ impl EventHandler for EventLoopHandler { self.with_callback(|this, mut callback| { for event in this.window_target.p.receiver.try_iter() { if let ControlFlow::ExitWithCode(code) = *control_flow { + // XXX: why isn't event dispatching simply skipped after control_flow = ExitWithCode? let dummy = &mut ControlFlow::ExitWithCode(code); (callback)(Event::UserEvent(event), &this.window_target, dummy); } else { @@ -114,7 +119,11 @@ impl EventHandler for EventLoopHandler { #[derive(Default)] struct Handler { - ready: AtomicBool, + stop_app_on_launch: AtomicBool, + stop_app_before_wait: AtomicBool, + stop_app_on_redraw: AtomicBool, + launched: AtomicBool, + running: AtomicBool, in_callback: AtomicBool, control_flow: Mutex, control_flow_prev: Mutex, @@ -141,12 +150,37 @@ impl Handler { self.waker.lock().unwrap() } - fn is_ready(&self) -> bool { - self.ready.load(Ordering::Acquire) + /// `true` after `ApplicationDelegate::applicationDidFinishLaunching` called + /// + /// NB: This is global / `NSApp` state and since the app will only be launched + /// once but an `EventLoop` may be run more than once then only the first + /// `EventLoop` will observe the `NSApp` before it is launched. + fn is_launched(&self) -> bool { + self.launched.load(Ordering::Acquire) + } + + /// Set via `ApplicationDelegate::applicationDidFinishLaunching` + fn set_launched(&self) { + self.launched.store(true, Ordering::Release); + } + + /// `true` if an `EventLoop` is currently running + /// + /// NB: This is global / `NSApp` state and may persist beyond the lifetime of + /// a running `EventLoop`. + /// + /// # Caveat + /// This is only intended to be called from the main thread + fn is_running(&self) -> bool { + self.running.load(Ordering::Relaxed) } - fn set_ready(&self) { - self.ready.store(true, Ordering::Release); + /// Set when an `EventLoop` starts running, after the `NSApp` is launched + /// + /// # Caveat + /// This is only intended to be called from the main thread + fn set_running(&self) { + self.running.store(true, Ordering::Relaxed); } fn should_exit(&self) -> bool { @@ -156,6 +190,74 @@ impl Handler { ) } + /// Clears the `running` state and resets the `control_flow` state when an `EventLoop` exits + /// + /// Since an `EventLoop` may be run more than once we need make sure to reset the + /// `control_flow` state back to `Poll` each time the loop exits. + /// + /// Note: that if the `NSApp` has been launched then that state is preserved, and we won't + /// need to re-launch the app if subsequent EventLoops are run. + /// + /// # Caveat + /// This is only intended to be called from the main thread + fn exit(&self) { + // Relaxed ordering because we don't actually have multiple threads involved, we just want + // interiour mutability + // + // XXX: As an aside; having each individual bit of state for `Handler` be atomic or wrapped in a + // `Mutex` for the sake of interior mutability seems a bit odd, and also a potential foot + // gun in case the state is unwittingly accessed across threads because the fine-grained locking + // wouldn't ensure that there's interior consistency. + // + // Maybe the whole thing should just be put in a static `Mutex<>` to make it clear + // the we can mutate more than one peice of state while maintaining consistency. (though it + // looks like there have been recuring re-entrancy issues with callback handling that might + // make that awkward) + self.running.store(false, Ordering::Relaxed); + *self.control_flow_prev.lock().unwrap() = ControlFlow::default(); + *self.control_flow.lock().unwrap() = ControlFlow::default(); + self.set_stop_app_on_redraw_requested(false); + self.set_stop_app_before_wait(false); + } + + pub fn request_stop_app_on_launch(&self) { + // Relaxed ordering because we don't actually have multiple threads involved, we just want + // interior mutability + self.stop_app_on_launch.store(true, Ordering::Relaxed); + } + + pub fn should_stop_app_on_launch(&self) -> bool { + // Relaxed ordering because we don't actually have multiple threads involved, we just want + // interior mutability + self.stop_app_on_launch.load(Ordering::Relaxed) + } + + pub fn set_stop_app_before_wait(&self, stop_before_wait: bool) { + // Relaxed ordering because we don't actually have multiple threads involved, we just want + // interior mutability + self.stop_app_before_wait + .store(stop_before_wait, Ordering::Relaxed); + } + + pub fn should_stop_app_before_wait(&self) -> bool { + // Relaxed ordering because we don't actually have multiple threads involved, we just want + // interior mutability + self.stop_app_before_wait.load(Ordering::Relaxed) + } + + pub fn set_stop_app_on_redraw_requested(&self, stop_on_redraw: bool) { + // Relaxed ordering because we don't actually have multiple threads involved, we just want + // interior mutability + self.stop_app_on_redraw + .store(stop_on_redraw, Ordering::Relaxed); + } + + pub fn should_stop_app_on_redraw_requested(&self) -> bool { + // Relaxed ordering because we don't actually have multiple threads involved, we just want + // interior mutability + self.stop_app_on_redraw.load(Ordering::Relaxed) + } + fn get_control_flow_and_update_prev(&self) -> ControlFlow { let control_flow = self.control_flow.lock().unwrap(); *self.control_flow_prev.lock().unwrap() = *control_flow; @@ -192,6 +294,10 @@ impl Handler { self.in_callback.store(in_callback, Ordering::Release); } + fn have_callback(&self) -> bool { + self.callback.lock().unwrap().is_some() + } + fn handle_nonuser_event(&self, wrapper: EventWrapper) { if let Some(ref mut callback) = *self.callback.lock().unwrap() { match wrapper { @@ -253,18 +359,63 @@ impl Handler { pub(crate) enum AppState {} impl AppState { - pub fn set_callback(callback: Weak>, window_target: Rc>) { + /// Associate the application's event callback with the (global static) Handler state + /// + /// # Safety + /// This is ignoring the lifetime of the application callback (which may not be 'static) + /// and can lead to undefined behaviour if the callback is not cleared before the end of + /// its real lifetime. + /// + /// All public APIs that take an event callback (`run`, `run_ondemand`, + /// `pump_events`) _must_ pair a call to `set_callback` with + /// a call to `clear_callback` before returning to avoid undefined behaviour. + pub unsafe fn set_callback( + callback: Weak>, + window_target: Rc>, + ) { *HANDLER.callback.lock().unwrap() = Some(Box::new(EventLoopHandler { callback, window_target, })); } + pub fn clear_callback() { + HANDLER.callback.lock().unwrap().take(); + } + + pub fn is_launched() -> bool { + HANDLER.is_launched() + } + + pub fn is_running() -> bool { + HANDLER.is_running() + } + + // If `pump_events` is called to progress the event loop then we bootstrap the event + // loop via `[NSApp run]` but will use `CFRunLoopRunInMode` for subsequent calls to + // `pump_events` + pub fn request_stop_on_launch() { + HANDLER.request_stop_app_on_launch(); + } + + pub fn set_stop_app_before_wait(stop_before_wait: bool) { + HANDLER.set_stop_app_before_wait(stop_before_wait); + } + + pub fn set_stop_app_on_redraw_requested(stop_on_redraw: bool) { + HANDLER.set_stop_app_on_redraw_requested(stop_on_redraw); + } + + pub fn control_flow() -> ControlFlow { + HANDLER.get_old_and_new_control_flow().1 + } + pub fn exit() -> i32 { HANDLER.set_in_callback(true); HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::LoopDestroyed)); HANDLER.set_in_callback(false); - HANDLER.callback.lock().unwrap().take(); + HANDLER.exit(); + Self::clear_callback(); if let ControlFlow::ExitWithCode(code) = HANDLER.get_old_and_new_control_flow().1 { code } else { @@ -272,6 +423,24 @@ impl AppState { } } + pub fn dispatch_init_events() { + HANDLER.set_in_callback(true); + HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::NewEvents( + StartCause::Init, + ))); + // NB: For consistency all platforms must emit a 'resumed' event even though macOS + // applications don't themselves have a formal suspend/resume lifecycle. + HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::Resumed)); + HANDLER.set_in_callback(false); + } + + pub fn start_running() { + debug_assert!(HANDLER.is_launched()); + + HANDLER.set_running(); + Self::dispatch_init_events() + } + pub fn launched( activation_policy: NSApplicationActivationPolicy, create_default_menu: bool, @@ -286,30 +455,42 @@ impl AppState { window_activation_hack(&app); app.activateIgnoringOtherApps(activate_ignoring_other_apps); - HANDLER.set_ready(); + HANDLER.set_launched(); HANDLER.waker().start(); if create_default_menu { // The menubar initialization should be before the `NewEvents` event, to allow // overriding of the default menu even if it's created menu::initialize(); } - HANDLER.set_in_callback(true); - HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::NewEvents( - StartCause::Init, - ))); - // NB: For consistency all platforms must emit a 'resumed' event even though macOS - // applications don't themselves have a formal suspend/resume lifecycle. - HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::Resumed)); - HANDLER.set_in_callback(false); + + Self::start_running(); + + // If the `NSApp` is being launched via `EventLoop::pump_events()` then we'll + // want to stop the app once it is launched (and return to the external loop) + // + // In this case we still want to consider Winit's `EventLoop` to be "running", + // so we call `start_running()` above. + if HANDLER.should_stop_app_on_launch() { + // Note: the original idea had been to only stop the underlying `RunLoop` + // for the app but that didn't work as expected (`[NSApp run]` effectively + // ignored the attempt to stop the RunLoop and re-started it.). So we + // return from `pump_events` by stopping the `NSApp` + Self::stop(); + } } + // Called by RunLoopObserver after finishing waiting for new events pub fn wakeup(panic_info: Weak) { let panic_info = panic_info .upgrade() .expect("The panic info must exist here. This failure indicates a developer error."); // Return when in callback due to https://github.com/rust-windowing/winit/issues/1779 - if panic_info.is_panicking() || !HANDLER.is_ready() || HANDLER.get_in_callback() { + if panic_info.is_panicking() + || !HANDLER.have_callback() + || !HANDLER.is_running() + || HANDLER.get_in_callback() + { return; } let start = HANDLER.get_start_time().unwrap(); @@ -358,6 +539,12 @@ impl AppState { HANDLER .handle_nonuser_event(EventWrapper::StaticEvent(Event::RedrawRequested(window_id))); HANDLER.set_in_callback(false); + + // `pump_events` will request to stop immediately _after_ dispatching RedrawRequested events + // as a way to ensure that `pump_events` can't block an external loop indefinitely + if HANDLER.should_stop_app_on_redraw_requested() { + AppState::stop(); + } } } @@ -368,13 +555,29 @@ impl AppState { HANDLER.events().push_back(wrapper); } + pub fn stop() { + let app = NSApp(); + autoreleasepool(|_| { + app.stop(None); + // To stop event loop immediately, we need to post some event here. + app.postEvent_atStart(&NSEvent::dummy(), true); + }); + } + + // Called by RunLoopObserver before waiting for new events pub fn cleared(panic_info: Weak) { let panic_info = panic_info .upgrade() .expect("The panic info must exist here. This failure indicates a developer error."); // Return when in callback due to https://github.com/rust-windowing/winit/issues/1779 - if panic_info.is_panicking() || !HANDLER.is_ready() || HANDLER.get_in_callback() { + // XXX: how does it make sense that `get_in_callback()` can ever return `true` here if we're + // about to return to the `CFRunLoop` to poll for new events? + if panic_info.is_panicking() + || !HANDLER.have_callback() + || !HANDLER.is_running() + || HANDLER.get_in_callback() + { return; } @@ -384,6 +587,7 @@ impl AppState { HANDLER.handle_nonuser_event(event); } HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::MainEventsCleared)); + for window_id in HANDLER.should_redraw() { HANDLER .handle_nonuser_event(EventWrapper::StaticEvent(Event::RedrawRequested(window_id))); @@ -392,12 +596,11 @@ impl AppState { HANDLER.set_in_callback(false); if HANDLER.should_exit() { - let app = NSApp(); - autoreleasepool(|_| { - app.stop(None); - // To stop event loop immediately, we need to post some event here. - app.postEvent_atStart(&NSEvent::dummy(), true); - }); + Self::stop(); + } + + if HANDLER.should_stop_app_before_wait() { + Self::stop(); } HANDLER.update_start_time(); match HANDLER.get_old_and_new_control_flow() { diff --git a/src/platform_impl/macos/event_loop.rs b/src/platform_impl/macos/event_loop.rs index 93e893de05..00f93c4b27 100644 --- a/src/platform_impl/macos/event_loop.rs +++ b/src/platform_impl/macos/event_loop.rs @@ -5,7 +5,7 @@ use std::{ marker::PhantomData, mem, os::raw::c_void, - panic::{catch_unwind, resume_unwind, RefUnwindSafe, UnwindSafe}, + panic::{catch_unwind, resume_unwind, AssertUnwindSafe, RefUnwindSafe, UnwindSafe}, process, ptr, rc::{Rc, Weak}, sync::mpsc, @@ -23,9 +23,10 @@ use raw_window_handle::{AppKitDisplayHandle, RawDisplayHandle}; use super::appkit::{NSApp, NSApplicationActivationPolicy, NSEvent, NSWindow}; use crate::{ + error::RunLoopError, event::Event, event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootWindowTarget}, - platform::macos::ActivationPolicy, + platform::{macos::ActivationPolicy, pump_events::PumpStatus}, platform_impl::platform::{ app::WinitApplication, app_delegate::ApplicationDelegate, @@ -195,7 +196,11 @@ impl EventLoop { where F: 'static + FnMut(Event<'_, T>, &RootWindowTarget, &mut ControlFlow), { - let exit_code = self.run_return(callback); + let exit_code = match self.run_ondemand(callback) { + Err(RunLoopError::ExitFailure(code)) => code, + Err(_err) => 1, + Ok(_) => 0, + }; process::exit(exit_code); } @@ -203,10 +208,34 @@ impl EventLoop { where F: FnMut(Event<'_, T>, &RootWindowTarget, &mut ControlFlow), { - // This transmute is always safe, in case it was reached through `run`, since our - // lifetime will be already 'static. In other cases caller should ensure that all data - // they passed to callback will actually outlive it, some apps just can't move - // everything to event loop, so this is something that they should care about. + match self.run_ondemand(callback) { + Err(RunLoopError::ExitFailure(code)) => code, + Err(_err) => 1, + Ok(_) => 0, + } + } + + // 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 + // redundant wake ups. + pub fn run_ondemand(&mut self, callback: F) -> Result<(), RunLoopError> + where + F: FnMut(Event<'_, T>, &RootWindowTarget, &mut ControlFlow), + { + if AppState::is_running() { + return Err(RunLoopError::AlreadyRunning); + } + + // # Safety + // We are erasing the lifetime of the application callback here so that we + // can (temporarily) store it within 'static global `AppState` that's + // accessible to objc delegate callbacks. + // + // The safety of this depends on on making sure to also clear the callback + // from the global `AppState` before we return from here, ensuring that + // we don't retain a reference beyond the real lifetime of the callback. + let callback = unsafe { mem::transmute::< Rc, &RootWindowTarget, &mut ControlFlow)>>, @@ -224,18 +253,151 @@ impl EventLoop { let weak_cb: Weak<_> = Rc::downgrade(&callback); drop(callback); - AppState::set_callback(weak_cb, Rc::clone(&self.window_target)); - unsafe { app.run() }; + // # Safety + // We make sure to call `AppState::clear_callback` before returning + unsafe { + AppState::set_callback(weak_cb, Rc::clone(&self.window_target)); + } - if let Some(panic) = self.panic_info.take() { - drop(self._callback.take()); - resume_unwind(panic); + // catch panics to make sure we can't unwind without clearing the set callback + // (which would leave the global `AppState` in an undefined, unsafe state) + let catch_result = catch_unwind(AssertUnwindSafe(|| { + if AppState::is_launched() { + debug_assert!(!AppState::is_running()); + AppState::start_running(); // Set is_running = true + dispatch `NewEvents(Init)` + `Resumed` + } + AppState::set_stop_app_before_wait(false); + unsafe { app.run() }; + + // While the app is running it's possible that we catch a panic + // to avoid unwinding across an objective-c ffi boundary, which + // will lead to us stopping the `NSApp` and saving the + // `PanicInfo` so that we can resume the unwind at a controlled, + // safe point in time. + if let Some(panic) = self.panic_info.take() { + resume_unwind(panic); + } + + AppState::exit() + })); + + // # Safety + // This pairs up with the `unsafe` call to `set_callback` above and ensures that + // we always clear the application callback from the global `AppState` before + // returning + drop(self._callback.take()); + AppState::clear_callback(); + + match catch_result { + Ok(exit_code) => exit_code, + Err(payload) => resume_unwind(payload), } - AppState::exit() }); - drop(self._callback.take()); - exit_code + if exit_code == 0 { + Ok(()) + } else { + Err(RunLoopError::ExitFailure(exit_code)) + } + } + + pub fn pump_events(&mut self, callback: F) -> PumpStatus + where + F: FnMut(Event<'_, T>, &RootWindowTarget, &mut ControlFlow), + { + // # Safety + // We are erasing the lifetime of the application callback here so that we + // can (temporarily) store it within 'static global `AppState` that's + // accessible to objc delegate callbacks. + // + // The safety of this depends on on making sure to also clear the callback + // from the global `AppState` before we return from here, ensuring that + // we don't retain a reference beyond the real lifetime of the callback. + + let callback = unsafe { + mem::transmute::< + Rc, &RootWindowTarget, &mut ControlFlow)>>, + Rc, &RootWindowTarget, &mut ControlFlow)>>, + >(Rc::new(RefCell::new(callback))) + }; + + self._callback = Some(Rc::clone(&callback)); + + autoreleasepool(|_| { + let app = NSApp(); + + // A bit of juggling with the callback references to make sure + // that `self.callback` is the only owner of the callback. + let weak_cb: Weak<_> = Rc::downgrade(&callback); + drop(callback); + + // # Safety + // We will make sure to call `AppState::clear_callback` before returning + // to ensure that we don't hold on to the callback beyond its (erased) + // lifetime + unsafe { + AppState::set_callback(weak_cb, Rc::clone(&self.window_target)); + } + + // catch panics to make sure we can't unwind without clearing the set callback + // (which would leave the global `AppState` in an undefined, unsafe state) + let catch_result = catch_unwind(AssertUnwindSafe(|| { + // As a special case, if the `NSApp` hasn't been launched yet then we at least run + // the loop until it has fully launched. + if !AppState::is_launched() { + debug_assert!(!AppState::is_running()); + + AppState::request_stop_on_launch(); + unsafe { + app.run(); + } + + // Note: we dispatch `NewEvents(Init)` + `Resumed` events after the `NSApp` has launched + } else if !AppState::is_running() { + // Even though the NSApp may have been launched, it's possible we aren't running + // if the `EventLoop` was run before and has since exited. This indicates that + // we just starting to re-run the same `EventLoop` again. + AppState::start_running(); // Set is_running = true + dispatch `NewEvents(Init)` + `Resumed` + } else { + // Make sure we can't block any external loop indefinitely by stopping the NSApp + // and returning after dispatching any `RedrawRequested` event or whenever the + // `RunLoop` needs to wait for new events from the OS + AppState::set_stop_app_on_redraw_requested(true); + AppState::set_stop_app_before_wait(true); + unsafe { + app.run(); + } + } + + // While the app is running it's possible that we catch a panic + // to avoid unwinding across an objective-c ffi boundary, which + // will lead to us stopping the `NSApp` and saving the + // `PanicInfo` so that we can resume the unwind at a controlled, + // safe point in time. + if let Some(panic) = self.panic_info.take() { + resume_unwind(panic); + } + + if let ControlFlow::ExitWithCode(code) = AppState::control_flow() { + AppState::exit(); + PumpStatus::Exit(code) + } else { + PumpStatus::Continue + } + })); + + // # Safety + // This pairs up with the `unsafe` call to `set_callback` above and ensures that + // we always clear the application callback from the global `AppState` before + // returning + AppState::clear_callback(); + drop(self._callback.take()); + + match catch_result { + Ok(pump_status) => pump_status, + Err(payload) => resume_unwind(payload), + } + }) } pub fn create_proxy(&self) -> EventLoopProxy { From 798a6f0120638a59d9ef37746d1cd8c7978c5bf3 Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Sun, 18 Jun 2023 12:40:03 +0100 Subject: [PATCH 05/15] Linux: Implement EventLoopExtPumpEvents and EventLoopExtRunOnDemand Wayland: I found the calloop abstraction a little awkward to work with while I was trying to understand why there was surprising workaround code in the wayland backend for manually dispatching pending events. Investigating this further it looks like there may currently be several issues with the calloop WaylandSource (with how prepare_read is used and with (not) flushing writes before polling) Considering the current minimal needs for polling in all winit backends I do personally tend to think it would be simpler to just own the responsibility for polling more directly, so the logic for wayland-client `prepare_read` wouldn't be in a separate crate (and in this current situation would also be easier to fix) I've tried to maintain the status quo with calloop + workarounds. X11: I found that the recent changes (4ac2006cbc5a) to port the X11 backend from mio to calloop lost the ability to check for pending events before needing to poll/dispatch. (The `has_pending` state being queried before dispatching() was based on state that was filled in during dispatching) As part of the rebase this re-introduces the PeekableReceiver and WakeSender which are small utilities on top of `std::sync::mpsc::channel()`. This adds a calloop `PingSource` so we can use a `Ping` as a generic event loop waker. For taking into account false positive wake ups the X11 source now tracks when the file descriptor is readable so after we poll via calloop we can then specifically check if there are new X11 events or pending redraw/user events when deciding whether to skip the event loop iteration. --- src/platform/mod.rs | 20 +- src/platform_impl/linux/mod.rs | 31 +- .../linux/wayland/event_loop/mod.rs | 661 ++++++++++-------- src/platform_impl/linux/x11/mod.rs | 632 ++++++++++------- src/platform_impl/linux/x11/window.rs | 7 +- 5 files changed, 804 insertions(+), 547 deletions(-) diff --git a/src/platform/mod.rs b/src/platform/mod.rs index dd56f58f41..9dd965beb6 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -11,8 +11,8 @@ //! //! And the following platform-specific modules: //! -//! - `run_ondemand` (available on `windows`, `macos`, `android`) -//! - `pump_events` (available on `windows`, `macos`, `android`) +//! - `run_ondemand` (available on `windows`, `unix`, `macos`, `android`) +//! - `pump_events` (available on `windows`, `unix`, `macos`, `android`) //! - `run_return` (available on `windows`, `unix`, `macos`, and `android`) //! //! However only the module corresponding to the platform you're compiling to will be available. @@ -36,10 +36,22 @@ pub mod windows; #[cfg(x11_platform)] pub mod x11; -#[cfg(any(windows_platform, macos_platform, android_platform))] +#[cfg(any( + windows_platform, + macos_platform, + android_platform, + x11_platform, + wayland_platform +))] pub mod run_ondemand; -#[cfg(any(windows_platform, macos_platform, android_platform,))] +#[cfg(any( + windows_platform, + macos_platform, + android_platform, + x11_platform, + wayland_platform +))] pub mod pump_events; #[cfg(any( diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 47c57700a5..aa9db961cf 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -19,6 +19,7 @@ use std::{ use once_cell::sync::Lazy; use raw_window_handle::{RawDisplayHandle, RawWindowHandle}; use smol_str::SmolStr; +use std::time::Duration; #[cfg(x11_platform)] pub use self::x11::XNotSupported; @@ -28,7 +29,7 @@ use self::x11::{ffi::XVisualInfo, util::WindowType as XWindowType, X11Error, XCo use crate::platform::x11::XlibErrorHook; use crate::{ dpi::{PhysicalPosition, PhysicalSize, Position, Size}, - error::{ExternalError, NotSupportedError, OsError as RootOsError}, + error::{ExternalError, NotSupportedError, OsError as RootOsError, RunLoopError}, event::{Event, KeyEvent}, event_loop::{ AsyncRequestSerial, ControlFlow, DeviceEvents, EventLoopClosed, @@ -36,7 +37,10 @@ use crate::{ }, icon::Icon, keyboard::{Key, KeyCode}, - platform::{modifier_supplement::KeyEventExtModifierSupplement, scancode::KeyCodeExtScancode}, + platform::{ + modifier_supplement::KeyEventExtModifierSupplement, pump_events::PumpStatus, + scancode::KeyCodeExtScancode, + }, window::{ ActivationToken, CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes, WindowButtons, WindowLevel, @@ -840,6 +844,20 @@ impl EventLoop { x11_or_wayland!(match self; EventLoop(evlp) => evlp.run(callback)) } + pub fn run_ondemand(&mut self, callback: F) -> Result<(), RunLoopError> + where + F: FnMut(crate::event::Event<'_, T>, &RootELW, &mut ControlFlow), + { + x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_ondemand(callback)) + } + + pub fn pump_events(&mut self, callback: F) -> PumpStatus + where + F: FnMut(crate::event::Event<'_, T>, &RootELW, &mut ControlFlow), + { + x11_or_wayland!(match self; EventLoop(evlp) => evlp.pump_events(callback)) + } + pub fn window_target(&self) -> &crate::event_loop::EventLoopWindowTarget { x11_or_wayland!(match self; EventLoop(evlp) => evlp.window_target()) } @@ -933,6 +951,15 @@ fn sticky_exit_callback( } } +/// Returns the minimum `Option`, taking into account that `None` +/// equates to an infinite timeout, not a zero timeout (so can't just use +/// `Option::min`) +fn min_timeout(a: Option, b: Option) -> Option { + a.map_or(b, |a_timeout| { + b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout))) + }) +} + #[cfg(target_os = "linux")] fn is_main_thread() -> bool { rustix::thread::gettid() == rustix::process::getpid() diff --git a/src/platform_impl/linux/wayland/event_loop/mod.rs b/src/platform_impl/linux/wayland/event_loop/mod.rs index 6ddb457115..157ebfe0a4 100644 --- a/src/platform_impl/linux/wayland/event_loop/mod.rs +++ b/src/platform_impl/linux/wayland/event_loop/mod.rs @@ -5,7 +5,6 @@ use std::error::Error; use std::io::Result as IOResult; use std::marker::PhantomData; use std::mem; -use std::process; use std::rc::Rc; use std::sync::atomic::Ordering; use std::time::{Duration, Instant}; @@ -17,8 +16,11 @@ use sctk::reexports::client::globals; use sctk::reexports::client::{Connection, Proxy, QueueHandle, WaylandSource}; use crate::dpi::{LogicalSize, PhysicalSize}; +use crate::error::RunLoopError; use crate::event::{Event, StartCause, WindowEvent}; use crate::event_loop::{ControlFlow, EventLoopWindowTarget as RootEventLoopWindowTarget}; +use crate::platform::pump_events::PumpStatus; +use crate::platform_impl::platform::min_timeout; use crate::platform_impl::platform::sticky_exit_callback; use crate::platform_impl::EventLoopWindowTarget as PlatformEventLoopWindowTarget; @@ -35,6 +37,16 @@ type WaylandDispatcher = calloop::Dispatcher<'static, WaylandSource, /// The Wayland event loop. pub struct EventLoop { + /// Has `run` or `run_ondemand` been called or a call to `pump_events` that starts the loop + loop_running: bool, + + /// The application's latest control_flow state + control_flow: ControlFlow, + + buffer_sink: EventSink, + compositor_updates: Vec, + window_ids: Vec, + /// Sender of user events. user_events_sender: calloop::channel::Sender, @@ -114,6 +126,11 @@ impl EventLoop { }; let event_loop = Self { + loop_running: false, + control_flow: ControlFlow::default(), + compositor_updates: Vec::new(), + buffer_sink: EventSink::default(), + window_ids: Vec::new(), connection, wayland_dispatcher, user_events_sender, @@ -132,326 +149,420 @@ impl EventLoop { where F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget, &mut ControlFlow) + 'static, { - let exit_code = self.run_return(callback); - process::exit(exit_code); + let exit_code = match self.run_ondemand(callback) { + Err(RunLoopError::ExitFailure(code)) => code, + Err(_err) => 1, + Ok(_) => 0, + }; + ::std::process::exit(exit_code) } - pub fn run_return(&mut self, mut callback: F) -> i32 + pub fn run_return(&mut self, callback: F) -> i32 where F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget, &mut ControlFlow), { - let mut control_flow = ControlFlow::Poll; + match self.run_ondemand(callback) { + Err(RunLoopError::ExitFailure(code)) => code, + Err(_err) => 1, + Ok(_) => 0, + } + } - // XXX preallocate certian structures to avoid allocating on each loop iteration. - let mut window_ids = Vec::::new(); - let mut compositor_updates = Vec::::new(); - let mut buffer_sink = EventSink::new(); + pub fn run_ondemand(&mut self, mut event_handler: F) -> Result<(), RunLoopError> + where + F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget, &mut ControlFlow), + { + if self.loop_running { + return Err(RunLoopError::AlreadyRunning); + } + + loop { + match self.pump_events_with_timeout(None, &mut event_handler) { + PumpStatus::Exit(0) => { + break Ok(()); + } + PumpStatus::Exit(code) => { + break Err(RunLoopError::ExitFailure(code)); + } + _ => { + continue; + } + } + } + } - callback( - Event::NewEvents(StartCause::Init), - &self.window_target, - &mut control_flow, - ); + pub fn pump_events(&mut self, event_handler: F) -> PumpStatus + where + F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget, &mut ControlFlow), + { + self.pump_events_with_timeout(Some(Duration::ZERO), event_handler) + } - // XXX For consistency all platforms must emit a 'Resumed' event even though Wayland - // applications don't themselves have a formal suspend/resume lifecycle. - callback(Event::Resumed, &self.window_target, &mut control_flow); - - // XXX We break on errors from dispatches, since if we've got protocol error - // libwayland-client/wayland-rs will inform us anyway, but crashing downstream is not - // really an option. Instead we inform that the event loop got destroyed. We may - // communicate an error that something was terminated, but winit doesn't provide us - // with an API to do that via some event. - // Still, we set the exit code to the error's OS error code, or to 1 if not possible. - let exit_code = loop { - // Flush the connection. - let _ = self.connection.flush(); - - // During the run of the user callback, some other code monitoring and reading the - // Wayland socket may have been run (mesa for example does this with vsync), if that - // is the case, some events may have been enqueued in our event queue. - // - // If some messages are there, the event loop needs to behave as if it was instantly - // woken up by messages arriving from the Wayland socket, to avoid delaying the - // dispatch of these events until we're woken up again. - let instant_wakeup = { - let mut wayland_source = self.wayland_dispatcher.as_source_mut(); - let queue = wayland_source.queue(); - let state = match &mut self.window_target.p { - PlatformEventLoopWindowTarget::Wayland(window_target) => { - window_target.state.get_mut() - } - #[cfg(x11_platform)] - _ => unreachable!(), - }; - - match queue.dispatch_pending(state) { - Ok(dispatched) => dispatched > 0, - Err(error) => { - error!("Error dispatching wayland queue: {}", error); - break 1; - } + fn pump_events_with_timeout( + &mut self, + timeout: Option, + mut callback: F, + ) -> PumpStatus + where + F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget, &mut ControlFlow), + { + if !self.loop_running { + self.loop_running = true; + + // Reset the internal state for the loop as we start running to + // ensure consistent behaviour in case the loop runs and exits more + // than once. + self.control_flow = ControlFlow::Poll; + + // Run the initial loop iteration. + self.single_iteration(&mut callback, StartCause::Init); + } + + // Consider the possibility that the `StartCause::Init` iteration could + // request to Exit. + if !matches!(self.control_flow, ControlFlow::ExitWithCode(_)) { + self.poll_events_with_timeout(timeout, &mut callback); + } + if let ControlFlow::ExitWithCode(code) = self.control_flow { + self.loop_running = false; + + let mut dummy = self.control_flow; + sticky_exit_callback( + Event::LoopDestroyed, + self.window_target(), + &mut dummy, + &mut callback, + ); + + PumpStatus::Exit(code) + } else { + PumpStatus::Continue + } + } + + pub fn poll_events_with_timeout(&mut self, mut timeout: Option, mut callback: F) + where + F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget, &mut ControlFlow), + { + let start = Instant::now(); + + // TODO(rib): remove this workaround and instead make sure that the calloop + // WaylandSource correctly implements the cooperative prepare_read protocol + // that support multithreaded wayland clients that may all read from the + // same socket. + // + // During the run of the user callback, some other code monitoring and reading the + // Wayland socket may have been run (mesa for example does this with vsync), if that + // is the case, some events may have been enqueued in our event queue. + // + // If some messages are there, the event loop needs to behave as if it was instantly + // woken up by messages arriving from the Wayland socket, to avoid delaying the + // dispatch of these events until we're woken up again. + let instant_wakeup = { + let mut wayland_source = self.wayland_dispatcher.as_source_mut(); + let queue = wayland_source.queue(); + let state = match &mut self.window_target.p { + PlatformEventLoopWindowTarget::Wayland(window_target) => { + window_target.state.get_mut() } + #[cfg(x11_platform)] + _ => unreachable!(), }; - match control_flow { - ControlFlow::ExitWithCode(code) => break code, - ControlFlow::Poll => { - // Non-blocking dispatch. - let timeout = Duration::ZERO; - if let Err(error) = self.loop_dispatch(Some(timeout)) { - break error.raw_os_error().unwrap_or(1); - } - - callback( - Event::NewEvents(StartCause::Poll), - &self.window_target, - &mut control_flow, - ); + match queue.dispatch_pending(state) { + Ok(dispatched) => dispatched > 0, + Err(error) => { + error!("Error dispatching wayland queue: {}", error); + self.control_flow = ControlFlow::ExitWithCode(1); + return; } - ControlFlow::Wait => { - let timeout = if instant_wakeup { - Some(Duration::ZERO) - } else { - None - }; - - if let Err(error) = self.loop_dispatch(timeout) { - break error.raw_os_error().unwrap_or(1); - } + } + }; - callback( - Event::NewEvents(StartCause::WaitCancelled { - start: Instant::now(), - requested_resume: None, - }), - &self.window_target, - &mut control_flow, - ); + timeout = if instant_wakeup { + Some(Duration::ZERO) + } else { + let control_flow_timeout = match self.control_flow { + ControlFlow::Wait => None, + ControlFlow::Poll => Some(Duration::ZERO), + ControlFlow::WaitUntil(wait_deadline) => { + Some(wait_deadline.saturating_duration_since(start)) } - ControlFlow::WaitUntil(deadline) => { - let start = Instant::now(); - - // Compute the amount of time we'll block for. - let duration = if deadline > start && !instant_wakeup { - deadline - start - } else { - Duration::ZERO - }; - - if let Err(error) = self.loop_dispatch(Some(duration)) { - break error.raw_os_error().unwrap_or(1); - } + // This function shouldn't have to handle any requests to exit + // the application (there should be no need to poll for events + // if the application has requested to exit) so we consider + // it a bug in the backend if we ever see `ExitWithCode` here. + ControlFlow::ExitWithCode(_code) => unreachable!(), + }; + min_timeout(control_flow_timeout, timeout) + }; - let now = Instant::now(); - - if now < deadline { - callback( - Event::NewEvents(StartCause::WaitCancelled { - start, - requested_resume: Some(deadline), - }), - &self.window_target, - &mut control_flow, - ) - } else { - callback( - Event::NewEvents(StartCause::ResumeTimeReached { - start, - requested_resume: deadline, - }), - &self.window_target, - &mut control_flow, - ) + // NOTE Ideally we should flush as the last thing we do before polling + // to wait for events, and this should be done by the calloop + // WaylandSource but we currently need to flush writes manually. + let _ = self.connection.flush(); + + if let Err(error) = self.loop_dispatch(timeout) { + // NOTE We exit on errors from dispatches, since if we've got protocol error + // libwayland-client/wayland-rs will inform us anyway, but crashing downstream is not + // really an option. Instead we inform that the event loop got destroyed. We may + // communicate an error that something was terminated, but winit doesn't provide us + // with an API to do that via some event. + // Still, we set the exit code to the error's OS error code, or to 1 if not possible. + let exit_code = error.raw_os_error().unwrap_or(1); + self.control_flow = ControlFlow::ExitWithCode(exit_code); + return; + } + + // NB: `StartCause::Init` is handled as a special case and doesn't need + // to be considered here + let cause = match self.control_flow { + ControlFlow::Poll => StartCause::Poll, + ControlFlow::Wait => StartCause::WaitCancelled { + start, + requested_resume: None, + }, + ControlFlow::WaitUntil(deadline) => { + if Instant::now() < deadline { + StartCause::WaitCancelled { + start, + requested_resume: Some(deadline), + } + } else { + StartCause::ResumeTimeReached { + start, + requested_resume: deadline, } } } + // This function shouldn't have to handle any requests to exit + // the application (there should be no need to poll for events + // if the application has requested to exit) so we consider + // it a bug in the backend if we ever see `ExitWithCode` here. + ControlFlow::ExitWithCode(_code) => unreachable!(), + }; + + self.single_iteration(&mut callback, cause); + } + + fn single_iteration(&mut self, mut callback: &mut F, cause: StartCause) + where + F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget, &mut ControlFlow), + { + // NOTE currently just indented to simplify the diff + + let mut control_flow = self.control_flow; + + // We retain these grow-only scratch buffers as part of the EventLoop + // for the sake of avoiding lots of reallocs. We take them here to avoid + // trying to mutably borrow `self` more than once and we swap them back + // when finished. + let mut compositor_updates = std::mem::take(&mut self.compositor_updates); + let mut buffer_sink = std::mem::take(&mut self.buffer_sink); + let mut window_ids = std::mem::take(&mut self.window_ids); + + sticky_exit_callback( + Event::NewEvents(cause), + &self.window_target, + &mut control_flow, + callback, + ); + + // NB: For consistency all platforms must emit a 'resumed' event even though Wayland + // applications don't themselves have a formal suspend/resume lifecycle. + if cause == StartCause::Init { + sticky_exit_callback( + Event::Resumed, + &self.window_target, + &mut control_flow, + callback, + ); + } + + // Handle pending user events. We don't need back buffer, since we can't dispatch + // user events indirectly via callback to the user. + for user_event in self.pending_user_events.borrow_mut().drain(..) { + sticky_exit_callback( + Event::UserEvent(user_event), + &self.window_target, + &mut control_flow, + &mut callback, + ); + } + + // Drain the pending compositor updates. + self.with_state(|state| compositor_updates.append(&mut state.window_compositor_updates)); + + for mut compositor_update in compositor_updates.drain(..) { + let window_id = compositor_update.window_id; + if let Some(scale_factor) = compositor_update.scale_factor { + let mut physical_size = self.with_state(|state| { + let windows = state.windows.get_mut(); + let mut window = windows.get(&window_id).unwrap().lock().unwrap(); + + // Set the new scale factor. + window.set_scale_factor(scale_factor); + let window_size = compositor_update.size.unwrap_or(window.inner_size()); + logical_to_physical_rounded(window_size, scale_factor) + }); + + // Stash the old window size. + let old_physical_size = physical_size; - // Handle pending user events. We don't need back buffer, since we can't dispatch - // user events indirectly via callback to the user. - for user_event in self.pending_user_events.borrow_mut().drain(..) { sticky_exit_callback( - Event::UserEvent(user_event), + Event::WindowEvent { + window_id: crate::window::WindowId(window_id), + event: WindowEvent::ScaleFactorChanged { + scale_factor, + new_inner_size: &mut physical_size, + }, + }, &self.window_target, &mut control_flow, &mut callback, ); - } - // Drain the pending compositor updates. - self.with_state(|state| { - compositor_updates.append(&mut state.window_compositor_updates) - }); + let new_logical_size = physical_size.to_logical(scale_factor); - for mut compositor_update in compositor_updates.drain(..) { - let window_id = compositor_update.window_id; - if let Some(scale_factor) = compositor_update.scale_factor { - let mut physical_size = self.with_state(|state| { + // Resize the window when user altered the size. + if old_physical_size != physical_size { + self.with_state(|state| { let windows = state.windows.get_mut(); let mut window = windows.get(&window_id).unwrap().lock().unwrap(); - - // Set the new scale factor. - window.set_scale_factor(scale_factor); - let window_size = compositor_update.size.unwrap_or(window.inner_size()); - logical_to_physical_rounded(window_size, scale_factor) + window.resize(new_logical_size); }); - - // Stash the old window size. - let old_physical_size = physical_size; - - sticky_exit_callback( - Event::WindowEvent { - window_id: crate::window::WindowId(window_id), - event: WindowEvent::ScaleFactorChanged { - scale_factor, - new_inner_size: &mut physical_size, - }, - }, - &self.window_target, - &mut control_flow, - &mut callback, - ); - - let new_logical_size = physical_size.to_logical(scale_factor); - - // Resize the window when user altered the size. - if old_physical_size != physical_size { - self.with_state(|state| { - let windows = state.windows.get_mut(); - let mut window = windows.get(&window_id).unwrap().lock().unwrap(); - window.resize(new_logical_size); - }); - } - - // Make it queue resize. - compositor_update.size = Some(new_logical_size); } - if let Some(size) = compositor_update.size.take() { - let physical_size = self.with_state(|state| { - let windows = state.windows.get_mut(); - let window = windows.get(&window_id).unwrap().lock().unwrap(); - - let scale_factor = window.scale_factor(); - let physical_size = logical_to_physical_rounded(size, scale_factor); + // Make it queue resize. + compositor_update.size = Some(new_logical_size); + } - // TODO could probably bring back size reporting optimization. + if let Some(size) = compositor_update.size.take() { + let physical_size = self.with_state(|state| { + let windows = state.windows.get_mut(); + let window = windows.get(&window_id).unwrap().lock().unwrap(); - // Mark the window as needed a redraw. - state - .window_requests - .get_mut() - .get_mut(&window_id) - .unwrap() - .redraw_requested - .store(true, Ordering::Relaxed); + let scale_factor = window.scale_factor(); + let physical_size = logical_to_physical_rounded(size, scale_factor); - physical_size - }); + // TODO could probably bring back size reporting optimization. - sticky_exit_callback( - Event::WindowEvent { - window_id: crate::window::WindowId(window_id), - event: WindowEvent::Resized(physical_size), - }, - &self.window_target, - &mut control_flow, - &mut callback, - ); - } + // Mark the window as needed a redraw. + state + .window_requests + .get_mut() + .get_mut(&window_id) + .unwrap() + .redraw_requested + .store(true, Ordering::Relaxed); - if compositor_update.close_window { - sticky_exit_callback( - Event::WindowEvent { - window_id: crate::window::WindowId(window_id), - event: WindowEvent::CloseRequested, - }, - &self.window_target, - &mut control_flow, - &mut callback, - ); - } - } + physical_size + }); - // Push the events directly from the window. - self.with_state(|state| { - buffer_sink.append(&mut state.window_events_sink.lock().unwrap()); - }); - for event in buffer_sink.drain() { - let event = event.map_nonuser_event().unwrap(); - sticky_exit_callback(event, &self.window_target, &mut control_flow, &mut callback); + sticky_exit_callback( + Event::WindowEvent { + window_id: crate::window::WindowId(window_id), + event: WindowEvent::Resized(physical_size), + }, + &self.window_target, + &mut control_flow, + &mut callback, + ); } - // Handle non-synthetic events. - self.with_state(|state| { - buffer_sink.append(&mut state.events_sink); - }); - for event in buffer_sink.drain() { - let event = event.map_nonuser_event().unwrap(); - sticky_exit_callback(event, &self.window_target, &mut control_flow, &mut callback); + if compositor_update.close_window { + sticky_exit_callback( + Event::WindowEvent { + window_id: crate::window::WindowId(window_id), + event: WindowEvent::CloseRequested, + }, + &self.window_target, + &mut control_flow, + &mut callback, + ); } + } + + // Push the events directly from the window. + self.with_state(|state| { + buffer_sink.append(&mut state.window_events_sink.lock().unwrap()); + }); + for event in buffer_sink.drain() { + let event = event.map_nonuser_event().unwrap(); + sticky_exit_callback(event, &self.window_target, &mut control_flow, &mut callback); + } + + // Handle non-synthetic events. + self.with_state(|state| { + buffer_sink.append(&mut state.events_sink); + }); + for event in buffer_sink.drain() { + let event = event.map_nonuser_event().unwrap(); + sticky_exit_callback(event, &self.window_target, &mut control_flow, &mut callback); + } + + // Send events cleared. + sticky_exit_callback( + Event::MainEventsCleared, + &self.window_target, + &mut control_flow, + &mut callback, + ); - // Send events cleared. - sticky_exit_callback( - Event::MainEventsCleared, - &self.window_target, - &mut control_flow, - &mut callback, - ); - - // Collect the window ids - self.with_state(|state| { - window_ids.extend(state.window_requests.get_mut().keys()); + // Collect the window ids + self.with_state(|state| { + window_ids.extend(state.window_requests.get_mut().keys()); + }); + + for window_id in window_ids.drain(..) { + let request_redraw = self.with_state(|state| { + let window_requests = state.window_requests.get_mut(); + if window_requests.get(&window_id).unwrap().take_closed() { + mem::drop(window_requests.remove(&window_id)); + mem::drop(state.windows.get_mut().remove(&window_id)); + false + } else { + let mut redraw_requested = window_requests + .get(&window_id) + .unwrap() + .take_redraw_requested(); + + // Redraw the frames while at it. + redraw_requested |= state + .windows + .get_mut() + .get_mut(&window_id) + .unwrap() + .lock() + .unwrap() + .refresh_frame(); + + redraw_requested + } }); - for window_id in window_ids.drain(..) { - let request_redraw = self.with_state(|state| { - let window_requests = state.window_requests.get_mut(); - if window_requests.get(&window_id).unwrap().take_closed() { - mem::drop(window_requests.remove(&window_id)); - mem::drop(state.windows.get_mut().remove(&window_id)); - false - } else { - let mut redraw_requested = window_requests - .get(&window_id) - .unwrap() - .take_redraw_requested(); - - // Redraw the frames while at it. - redraw_requested |= state - .windows - .get_mut() - .get_mut(&window_id) - .unwrap() - .lock() - .unwrap() - .refresh_frame(); - - redraw_requested - } - }); - - if request_redraw { - sticky_exit_callback( - Event::RedrawRequested(crate::window::WindowId(window_id)), - &self.window_target, - &mut control_flow, - &mut callback, - ); - } + if request_redraw { + sticky_exit_callback( + Event::RedrawRequested(crate::window::WindowId(window_id)), + &self.window_target, + &mut control_flow, + &mut callback, + ); } + } - // Send RedrawEventCleared. - sticky_exit_callback( - Event::RedrawEventsCleared, - &self.window_target, - &mut control_flow, - &mut callback, - ); - }; + // Send RedrawEventCleared. + sticky_exit_callback( + Event::RedrawEventsCleared, + &self.window_target, + &mut control_flow, + &mut callback, + ); - callback(Event::LoopDestroyed, &self.window_target, &mut control_flow); - exit_code + self.control_flow = control_flow; + std::mem::swap(&mut self.compositor_updates, &mut compositor_updates); + std::mem::swap(&mut self.buffer_sink, &mut buffer_sink); + std::mem::swap(&mut self.window_ids, &mut window_ids); } #[inline] diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index 14e7905695..fd374b2181 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -19,13 +19,13 @@ pub(crate) use self::{ pub use self::xdisplay::{XError, XNotSupported}; -use calloop::channel::{channel, Channel, Event as ChanResult, Sender}; use calloop::generic::Generic; -use calloop::{Dispatcher, EventLoop as Loop}; +use calloop::EventLoop as Loop; +use calloop::{ping::Ping, Readiness}; use std::{ cell::{Cell, RefCell}, - collections::{HashMap, HashSet, VecDeque}, + collections::{HashMap, HashSet}, ffi::CStr, fmt, mem::{self, MaybeUninit}, @@ -37,6 +37,7 @@ use std::{ ptr, rc::Rc, slice, + sync::mpsc::{Receiver, Sender, TryRecvError}, sync::{mpsc, Arc, Weak}, time::{Duration, Instant}, }; @@ -63,11 +64,12 @@ use self::{ }; use super::common::xkb_state::KbdState; use crate::{ - error::OsError as RootOsError, + error::{OsError as RootOsError, RunLoopError}, event::{Event, StartCause}, event_loop::{ControlFlow, DeviceEvents, EventLoopClosed, EventLoopWindowTarget as RootELW}, + platform::pump_events::PumpStatus, platform_impl::{ - platform::{sticky_exit_callback, WindowId}, + platform::{min_timeout, sticky_exit_callback, WindowId}, PlatformSpecificWindowBuilderAttributes, }, window::WindowAttributes, @@ -75,6 +77,64 @@ use crate::{ type X11Source = Generic; +struct WakeSender { + sender: Sender, + waker: Ping, +} + +impl Clone for WakeSender { + fn clone(&self) -> Self { + Self { + sender: self.sender.clone(), + waker: self.waker.clone(), + } + } +} + +impl WakeSender { + pub fn send(&self, t: T) -> Result<(), EventLoopClosed> { + let res = self.sender.send(t).map_err(|e| EventLoopClosed(e.0)); + if res.is_ok() { + self.waker.ping(); + } + res + } +} + +struct PeekableReceiver { + recv: Receiver, + first: Option, +} + +impl PeekableReceiver { + pub fn from_recv(recv: Receiver) -> Self { + Self { recv, first: None } + } + pub fn has_incoming(&mut self) -> bool { + if self.first.is_some() { + return true; + } + + match self.recv.try_recv() { + Ok(v) => { + self.first = Some(v); + true + } + Err(TryRecvError::Empty) => false, + Err(TryRecvError::Disconnected) => { + warn!("Channel was disconnected when checking incoming"); + false + } + } + } + pub fn try_recv(&mut self) -> Result { + if let Some(first) = self.first.take() { + return Ok(first); + } + self.recv.try_recv() + } +} + pub struct EventLoopWindowTarget { xconn: Arc, wm_delete_window: xproto::Atom, @@ -83,40 +143,37 @@ pub struct EventLoopWindowTarget { root: xproto::Window, ime: RefCell, windows: RefCell>>, - redraw_sender: Sender, - activation_sender: Sender, + redraw_sender: WakeSender, + activation_sender: WakeSender, device_events: Cell, _marker: ::std::marker::PhantomData, } pub struct EventLoop { - event_loop: Loop<'static, EventLoopState>, + loop_running: bool, + control_flow: ControlFlow, + event_loop: Loop<'static, EventLoopState>, + waker: calloop::ping::Ping, event_processor: EventProcessor, + redraw_receiver: PeekableReceiver, + user_receiver: PeekableReceiver, + activation_receiver: PeekableReceiver, user_sender: Sender, target: Rc>, /// The current state of the event loop. - state: EventLoopState, - - /// Dispatcher for redraw events. - redraw_dispatcher: Dispatcher<'static, Channel, EventLoopState>, + state: EventLoopState, } type ActivationToken = (WindowId, crate::event_loop::AsyncRequestSerial); -struct EventLoopState { - /// Incoming user events. - user_events: VecDeque, - - /// Incoming redraw events. - redraw_events: VecDeque, - - /// Incoming activation tokens. - activation_tokens: VecDeque, +struct EventLoopState { + /// The latest readiness state for the x11 file descriptor + x11_readiness: Readiness, } pub struct EventLoopProxy { - user_sender: Sender, + user_sender: WakeSender, } impl Clone for EventLoopProxy { @@ -242,7 +299,7 @@ impl EventLoop { // Create an event loop. let event_loop = - Loop::>::try_new().expect("Failed to initialize the event loop"); + Loop::::try_new().expect("Failed to initialize the event loop"); let handle = event_loop.handle(); // Create the X11 event dispatcher. @@ -252,48 +309,29 @@ impl EventLoop { calloop::Mode::Level, ); handle - .insert_source(source, |_, _, _| Ok(calloop::PostAction::Continue)) + .insert_source(source, |readiness, _, state| { + state.x11_readiness = readiness; + Ok(calloop::PostAction::Continue) + }) .expect("Failed to register the X11 event dispatcher"); - // Create a channel for sending user events. - let (user_sender, user_channel) = channel(); - handle - .insert_source(user_channel, |ev, _, state| { - if let ChanResult::Msg(user) = ev { - state.user_events.push_back(user); - } + let (waker, waker_source) = + calloop::ping::make_ping().expect("Failed to create event loop waker"); + event_loop + .handle() + .insert_source(waker_source, move |_, _, _| { + // No extra handling is required, we just need to wake-up. }) - .expect("Failed to register the user event channel with the event loop"); + .expect("Failed to register the event loop waker source"); // Create a channel for handling redraw requests. - let (redraw_sender, redraw_channel) = channel(); + let (redraw_sender, redraw_channel) = mpsc::channel(); // Create a channel for sending activation tokens. - let (activation_token_sender, activation_token_channel) = channel(); - - // Create a dispatcher for the redraw channel such that we can dispatch it independent of the - // event loop. - let redraw_dispatcher = - Dispatcher::<_, EventLoopState>::new(redraw_channel, |ev, _, state| { - if let ChanResult::Msg(window_id) = ev { - state.redraw_events.push_back(window_id); - } - }); - handle - .register_dispatcher(redraw_dispatcher.clone()) - .expect("Failed to register the redraw event channel with the event loop"); - - // Create a dispatcher for the activation token channel such that we can dispatch it - // independent of the event loop. - let activation_tokens = - Dispatcher::<_, EventLoopState>::new(activation_token_channel, |ev, _, state| { - if let ChanResult::Msg(token) = ev { - state.activation_tokens.push_back(token); - } - }); - handle - .register_dispatcher(activation_tokens.clone()) - .expect("Failed to register the activation token channel with the event loop"); + let (activation_token_sender, activation_token_channel) = mpsc::channel(); + + // Create a channel for sending user events. + let (user_sender, user_channel) = mpsc::channel(); let kb_state = KbdState::from_x11_xkb(xconn.xcb_connection().get_raw_xcb_connection()).unwrap(); @@ -307,8 +345,14 @@ impl EventLoop { xconn, wm_delete_window, net_wm_ping, - redraw_sender, - activation_sender: activation_token_sender, + redraw_sender: WakeSender { + sender: redraw_sender, // not used again so no clone + waker: waker.clone(), + }, + activation_sender: WakeSender { + sender: activation_token_sender, // not used again so no clone + waker: waker.clone(), + }, device_events: Default::default(), }; @@ -359,22 +403,28 @@ impl EventLoop { event_processor.init_device(ffi::XIAllDevices); EventLoop { + loop_running: false, + control_flow: ControlFlow::default(), event_loop, + waker, event_processor, + redraw_receiver: PeekableReceiver::from_recv(redraw_channel), + activation_receiver: PeekableReceiver::from_recv(activation_token_channel), + user_receiver: PeekableReceiver::from_recv(user_channel), user_sender, target, - redraw_dispatcher, state: EventLoopState { - user_events: VecDeque::new(), - redraw_events: VecDeque::new(), - activation_tokens: VecDeque::new(), + x11_readiness: Readiness::EMPTY, }, } } pub fn create_proxy(&self) -> EventLoopProxy { EventLoopProxy { - user_sender: self.user_sender.clone(), + user_sender: WakeSender { + sender: self.user_sender.clone(), + waker: self.waker.clone(), + }, } } @@ -382,236 +432,294 @@ impl EventLoop { &self.target } - pub fn run_return(&mut self, mut callback: F) -> i32 + pub fn run(mut self, callback: F) -> ! + where + F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow) + 'static, + { + let exit_code = match self.run_ondemand(callback) { + Err(RunLoopError::ExitFailure(code)) => code, + Err(_err) => 1, + Ok(_) => 0, + }; + ::std::process::exit(exit_code) + } + + pub fn run_return(&mut self, callback: F) -> i32 where F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), { - struct IterationResult { - deadline: Option, - timeout: Option, - wait_start: Instant, + match self.run_ondemand(callback) { + Err(RunLoopError::ExitFailure(code)) => code, + Err(_err) => 1, + Ok(_) => 0, } - fn single_iteration( - this: &mut EventLoop, - control_flow: &mut ControlFlow, - cause: &mut StartCause, - callback: &mut F, - ) -> IterationResult - where - F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), - { - sticky_exit_callback( - crate::event::Event::NewEvents(*cause), - &this.target, - control_flow, - callback, - ); + } - // NB: For consistency all platforms must emit a 'resumed' event even though X11 - // applications don't themselves have a formal suspend/resume lifecycle. - if *cause == StartCause::Init { - sticky_exit_callback( - crate::event::Event::Resumed, - &this.target, - control_flow, - callback, - ); - } + pub fn run_ondemand(&mut self, mut event_handler: F) -> Result<(), RunLoopError> + where + F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), + { + if self.loop_running { + return Err(RunLoopError::AlreadyRunning); + } - // Process all pending events - this.drain_events(callback, control_flow); - - // Empty activation tokens. - while let Some((window_id, serial)) = this.state.activation_tokens.pop_front() { - let token = this - .event_processor - .with_window(window_id.0 as xproto::Window, |window| { - window.generate_activation_token() - }); - - match token { - Some(Ok(token)) => sticky_exit_callback( - crate::event::Event::WindowEvent { - window_id: crate::window::WindowId(window_id), - event: crate::event::WindowEvent::ActivationTokenDone { - serial, - token: crate::window::ActivationToken::_new(token), - }, - }, - &this.target, - control_flow, - callback, - ), - Some(Err(e)) => { - log::error!("Failed to get activation token: {}", e); - } - None => {} + loop { + match self.pump_events_with_timeout(None, &mut event_handler) { + PumpStatus::Exit(0) => { + break Ok(()); } - } - - // Empty the user event buffer - { - while let Some(event) = this.state.user_events.pop_front() { - sticky_exit_callback( - crate::event::Event::UserEvent(event), - &this.target, - control_flow, - callback, - ); + PumpStatus::Exit(code) => { + break Err(RunLoopError::ExitFailure(code)); + } + _ => { + continue; } } - // send MainEventsCleared - { - sticky_exit_callback( - crate::event::Event::MainEventsCleared, - &this.target, - control_flow, - callback, - ); - } + } + } - // Quickly dispatch all redraw events to avoid buffering them. - while let Ok(event) = this.redraw_dispatcher.as_source_mut().try_recv() { - this.state.redraw_events.push_back(event); - } + pub fn pump_events(&mut self, event_handler: F) -> PumpStatus + where + F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), + { + self.pump_events_with_timeout(Some(Duration::ZERO), event_handler) + } - // Empty the redraw requests - { - let mut windows = HashSet::new(); + fn pump_events_with_timeout( + &mut self, + timeout: Option, + mut callback: F, + ) -> PumpStatus + where + F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), + { + if !self.loop_running { + self.loop_running = true; - // Empty the channel. + // Reset the internal state for the loop as we start running to + // ensure consistent behaviour in case the loop runs and exits more + // than once. + self.control_flow = ControlFlow::Poll; - while let Some(window_id) = this.state.redraw_events.pop_front() { - windows.insert(window_id); - } + // run the initial loop iteration + self.single_iteration(&mut callback, StartCause::Init); + } - for window_id in windows { - let window_id = crate::window::WindowId(window_id); - sticky_exit_callback( - Event::RedrawRequested(window_id), - &this.target, - control_flow, - callback, - ); - } - } - // send RedrawEventsCleared - { - sticky_exit_callback( - crate::event::Event::RedrawEventsCleared, - &this.target, - control_flow, - callback, - ); - } + // Consider the possibility that the `StartCause::Init` iteration could + // request to Exit. + if !matches!(self.control_flow, ControlFlow::ExitWithCode(_)) { + self.poll_events_with_timeout(timeout, &mut callback); + } + if let ControlFlow::ExitWithCode(code) = self.control_flow { + self.loop_running = false; - let start = Instant::now(); - let (deadline, timeout); + let mut dummy = self.control_flow; + sticky_exit_callback( + Event::LoopDestroyed, + self.window_target(), + &mut dummy, + &mut callback, + ); - match control_flow { - ControlFlow::ExitWithCode(_) => { - return IterationResult { - wait_start: start, - deadline: None, - timeout: None, - }; - } - ControlFlow::Poll => { - *cause = StartCause::Poll; - deadline = None; - timeout = Some(Duration::from_millis(0)); + PumpStatus::Exit(code) + } else { + PumpStatus::Continue + } + } + + fn has_pending(&mut self) -> bool { + self.event_processor.poll() + || self.user_receiver.has_incoming() + || self.redraw_receiver.has_incoming() + } + + pub fn poll_events_with_timeout(&mut self, mut timeout: Option, mut callback: F) + where + F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), + { + let start = Instant::now(); + + let has_pending = self.has_pending(); + + timeout = if has_pending { + // If we already have work to do then we don't want to block on the next poll. + Some(Duration::ZERO) + } else { + let control_flow_timeout = match self.control_flow { + ControlFlow::Wait => None, + ControlFlow::Poll => Some(Duration::ZERO), + ControlFlow::WaitUntil(wait_deadline) => { + Some(wait_deadline.saturating_duration_since(start)) } - ControlFlow::Wait => { - *cause = StartCause::WaitCancelled { + // This function shouldn't have to handle any requests to exit + // the application (there should be no need to poll for events + // if the application has requested to exit) so we consider + // it a bug in the backend if we ever see `ExitWithCode` here. + ControlFlow::ExitWithCode(_code) => unreachable!(), + }; + + min_timeout(control_flow_timeout, timeout) + }; + + self.state.x11_readiness = Readiness::EMPTY; + if let Err(error) = self + .event_loop + .dispatch(timeout, &mut self.state) + .map_err(std::io::Error::from) + { + log::error!("Failed to poll for events: {error:?}"); + let exit_code = error.raw_os_error().unwrap_or(1); + self.control_flow = ControlFlow::ExitWithCode(exit_code); + return; + } + + // False positive / spurious wake ups could lead to us spamming + // redundant iterations of the event loop with no new events to + // dispatch. + // + // If there's no readable event source then we just double check if we + // have any pending `_receiver` events and if not we return without + // running a loop iteration. + // If we don't have any pending `_receiver` + if !self.has_pending() && !self.state.x11_readiness.readable { + return; + } + + // NB: `StartCause::Init` is handled as a special case and doesn't need + // to be considered here + let cause = match self.control_flow { + ControlFlow::Poll => StartCause::Poll, + ControlFlow::Wait => StartCause::WaitCancelled { + start, + requested_resume: None, + }, + ControlFlow::WaitUntil(deadline) => { + if Instant::now() < deadline { + StartCause::WaitCancelled { start, - requested_resume: None, - }; - deadline = None; - timeout = None; - } - ControlFlow::WaitUntil(wait_deadline) => { - *cause = StartCause::ResumeTimeReached { + requested_resume: Some(deadline), + } + } else { + StartCause::ResumeTimeReached { start, - requested_resume: *wait_deadline, - }; - timeout = if *wait_deadline > start { - Some(*wait_deadline - start) - } else { - Some(Duration::from_millis(0)) - }; - deadline = Some(*wait_deadline); + requested_resume: deadline, + } } } + // This function shouldn't have to handle any requests to exit + // the application (there should be no need to poll for events + // if the application has requested to exit) so we consider + // it a bug in the backend if we ever see `ExitWithCode` here. + ControlFlow::ExitWithCode(_code) => unreachable!(), + }; - IterationResult { - wait_start: start, - deadline, - timeout, - } - } + self.single_iteration(&mut callback, cause); + } - let mut control_flow = ControlFlow::default(); - let mut cause = StartCause::Init; + fn single_iteration(&mut self, callback: &mut F, cause: StartCause) + where + F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), + { + let mut control_flow = self.control_flow; - // run the initial loop iteration - let mut iter_result = single_iteration(self, &mut control_flow, &mut cause, &mut callback); + sticky_exit_callback( + crate::event::Event::NewEvents(cause), + &self.target, + &mut control_flow, + callback, + ); - let exit_code = loop { - if let ControlFlow::ExitWithCode(code) = control_flow { - break code; - } - let has_pending = self.event_processor.poll() - || !self.state.user_events.is_empty() - || !self.state.redraw_events.is_empty(); - if !has_pending { - // Wait until - if let Err(error) = self - .event_loop - .dispatch(iter_result.timeout, &mut self.state) - .map_err(std::io::Error::from) - { - break error.raw_os_error().unwrap_or(1); - } + // NB: For consistency all platforms must emit a 'resumed' event even though X11 + // applications don't themselves have a formal suspend/resume lifecycle. + if cause == StartCause::Init { + sticky_exit_callback( + crate::event::Event::Resumed, + &self.target, + &mut control_flow, + callback, + ); + } - if control_flow == ControlFlow::Wait { - // We don't go straight into executing the event loop iteration, we instead go - // to the start of this loop and check again if there's any pending event. We - // must do this because during the execution of the iteration we sometimes wake - // the calloop waker, and if the waker is already awaken before we call poll(), - // then poll doesn't block, but it returns immediately. This caused the event - // loop to run continuously even if the control_flow was `Wait` - continue; + // Process all pending events + self.drain_events(callback, &mut control_flow); + + // Empty activation tokens. + while let Ok((window_id, serial)) = self.activation_receiver.try_recv() { + let token = self + .event_processor + .with_window(window_id.0 as xproto::Window, |window| { + window.generate_activation_token() + }); + + match token { + Some(Ok(token)) => sticky_exit_callback( + crate::event::Event::WindowEvent { + window_id: crate::window::WindowId(window_id), + event: crate::event::WindowEvent::ActivationTokenDone { + serial, + token: crate::window::ActivationToken::_new(token), + }, + }, + &self.target, + &mut control_flow, + callback, + ), + Some(Err(e)) => { + log::error!("Failed to get activation token: {}", e); } + None => {} } + } - let wait_cancelled = iter_result - .deadline - .map_or(false, |deadline| Instant::now() < deadline); - - if wait_cancelled { - cause = StartCause::WaitCancelled { - start: iter_result.wait_start, - requested_resume: iter_result.deadline, - }; + // Empty the user event buffer + { + while let Ok(event) = self.user_receiver.try_recv() { + sticky_exit_callback( + crate::event::Event::UserEvent(event), + &self.target, + &mut control_flow, + callback, + ); } + } + // send MainEventsCleared + { + sticky_exit_callback( + crate::event::Event::MainEventsCleared, + &self.target, + &mut control_flow, + callback, + ); + } + // Empty the redraw requests + { + let mut windows = HashSet::new(); - iter_result = single_iteration(self, &mut control_flow, &mut cause, &mut callback); - }; + while let Ok(window_id) = self.redraw_receiver.try_recv() { + windows.insert(window_id); + } - callback( - crate::event::Event::LoopDestroyed, - &self.target, - &mut control_flow, - ); - exit_code - } + for window_id in windows { + let window_id = crate::window::WindowId(window_id); + sticky_exit_callback( + Event::RedrawRequested(window_id), + &self.target, + &mut control_flow, + callback, + ); + } + } + // send RedrawEventsCleared + { + sticky_exit_callback( + crate::event::Event::RedrawEventsCleared, + &self.target, + &mut control_flow, + callback, + ); + } - pub fn run(mut self, callback: F) -> ! - where - F: 'static + FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), - { - let exit_code = self.run_return(callback); - ::std::process::exit(exit_code); + self.control_flow = control_flow; } fn drain_events(&mut self, callback: &mut F, control_flow: &mut ControlFlow) diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index 7d58fc75e4..0d0f52fb5c 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -23,7 +23,7 @@ use crate::{ error::{ExternalError, NotSupportedError, OsError as RootOsError}, event_loop::AsyncRequestSerial, platform_impl::{ - x11::{atoms::*, MonitorHandle as X11MonitorHandle, X11Error}, + x11::{atoms::*, MonitorHandle as X11MonitorHandle, WakeSender, X11Error}, Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformSpecificWindowBuilderAttributes, VideoMode as PlatformVideoMode, }, @@ -37,7 +37,6 @@ use super::{ ffi, util, CookieResultExt, EventLoopWindowTarget, ImeRequest, ImeSender, VoidCookie, WindowId, XConnection, }; -use calloop::channel::Sender; #[derive(Debug)] pub struct SharedState { @@ -122,8 +121,8 @@ pub(crate) struct UnownedWindow { cursor_visible: Mutex, ime_sender: Mutex, pub shared_state: Mutex, - redraw_sender: Sender, - activation_sender: Sender, + redraw_sender: WakeSender, + activation_sender: WakeSender, } impl UnownedWindow { From 5a80523534d0fbad7cf34efb94acb32f46433fd0 Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Sun, 18 Jun 2023 12:59:53 +0100 Subject: [PATCH 06/15] Remove EventLoopExtRunReturn --- examples/window_run_return.rs | 71 ------------------- src/event_loop.rs | 2 +- src/lib.rs | 4 +- src/platform/mod.rs | 11 --- src/platform/run_return.rs | 53 -------------- src/platform_impl/android/mod.rs | 11 --- src/platform_impl/linux/mod.rs | 7 -- .../linux/wayland/event_loop/mod.rs | 11 --- src/platform_impl/linux/x11/mod.rs | 11 --- src/platform_impl/macos/event_loop.rs | 11 --- src/platform_impl/orbital/event_loop.rs | 16 ++--- src/platform_impl/windows/event_loop.rs | 11 --- 12 files changed, 7 insertions(+), 212 deletions(-) delete mode 100644 examples/window_run_return.rs delete mode 100644 src/platform/run_return.rs diff --git a/examples/window_run_return.rs b/examples/window_run_return.rs deleted file mode 100644 index 2a2758d0de..0000000000 --- a/examples/window_run_return.rs +++ /dev/null @@ -1,71 +0,0 @@ -#![allow(clippy::single_match)] - -// Limit this example to only compatible platforms. -#[cfg(any( - windows_platform, - macos_platform, - x11_platform, - wayland_platform, - android_platform, - orbital_platform, -))] -fn main() { - use std::{thread::sleep, time::Duration}; - - use simple_logger::SimpleLogger; - use winit::{ - event::{Event, WindowEvent}, - event_loop::EventLoop, - platform::run_return::EventLoopExtRunReturn, - window::WindowBuilder, - }; - - #[path = "util/fill.rs"] - mod fill; - - let mut event_loop = EventLoop::new(); - - SimpleLogger::new().init().unwrap(); - let window = WindowBuilder::new() - .with_title("A fantastic window!") - .build(&event_loop) - .unwrap(); - - let mut quit = false; - - while !quit { - event_loop.run_return(|event, _, control_flow| { - control_flow.set_wait(); - - if let Event::WindowEvent { event, .. } = &event { - // Print only Window events to reduce noise - println!("{event:?}"); - } - - match event { - Event::WindowEvent { - event: WindowEvent::CloseRequested, - .. - } => { - quit = true; - } - Event::MainEventsCleared => { - control_flow.set_exit(); - } - Event::RedrawRequested(_) => { - fill::fill_window(&window); - } - _ => (), - } - }); - - // Sleep for 1/60 second to simulate rendering - println!("rendering"); - sleep(Duration::from_millis(16)); - } -} - -#[cfg(any(ios_platform, wasm_platform))] -fn main() { - println!("This platform doesn't support run_return."); -} diff --git a/src/event_loop.rs b/src/event_loop.rs index 1485069d0c..7e5a66f73f 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -156,7 +156,7 @@ impl fmt::Debug for EventLoopWindowTarget { /// /// Almost every change is persistent between multiple calls to the event loop closure within a /// given run loop. The only exception to this is [`ExitWithCode`] which, once set, cannot be unset. -/// Changes are **not** persistent between multiple calls to `run_return` - issuing a new call will +/// Changes are **not** persistent between multiple calls to `run_ondemand` - issuing a new call will /// reset the control flow to [`Poll`]. /// /// [`ExitWithCode`]: Self::ExitWithCode diff --git a/src/lib.rs b/src/lib.rs index 9d2a93b82f..ee08ec8cac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,7 +37,7 @@ //! Winit no longer uses a `EventLoop::poll_events() -> impl Iterator`-based event loop //! model, since that can't be implemented properly on some platforms (e.g web, iOS) and works poorly on //! most other platforms. However, this model can be re-implemented to an extent with -//! [`EventLoopExtRunReturn::run_return`]. See that method's documentation for more reasons about why +//! [`EventLoopExtPumpEvents::pump_events`]. See that method's documentation for more reasons about why //! it's discouraged, beyond compatibility reasons. //! //! @@ -109,7 +109,7 @@ //! window visible only once you're ready to render into it. //! //! [`EventLoop`]: event_loop::EventLoop -//! [`EventLoopExtRunReturn::run_return`]: ./platform/run_return/trait.EventLoopExtRunReturn.html#tymethod.run_return +//! [`EventLoopExtPumpEvents::pump_events`]: ./platform/pump_events/trait.EventLoopExtPumpEvents.html#tymethod.pump_events //! [`EventLoop::new()`]: event_loop::EventLoop::new //! [event_loop_run]: event_loop::EventLoop::run //! [`ControlFlow`]: event_loop::ControlFlow diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 9dd965beb6..40c2bce17d 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -13,7 +13,6 @@ //! //! - `run_ondemand` (available on `windows`, `unix`, `macos`, `android`) //! - `pump_events` (available on `windows`, `unix`, `macos`, `android`) -//! - `run_return` (available on `windows`, `unix`, `macos`, and `android`) //! //! However only the module corresponding to the platform you're compiling to will be available. @@ -54,15 +53,5 @@ pub mod run_ondemand; ))] pub mod pump_events; -#[cfg(any( - windows_platform, - macos_platform, - android_platform, - x11_platform, - wayland_platform, - orbital_platform -))] -pub mod run_return; - pub mod modifier_supplement; pub mod scancode; diff --git a/src/platform/run_return.rs b/src/platform/run_return.rs deleted file mode 100644 index 750b041618..0000000000 --- a/src/platform/run_return.rs +++ /dev/null @@ -1,53 +0,0 @@ -use crate::{ - event::Event, - event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget}, -}; - -/// Additional methods on [`EventLoop`] to return control flow to the caller. -pub trait EventLoopExtRunReturn { - /// A type provided by the user that can be passed through [`Event::UserEvent`]. - type UserEvent; - - /// Initializes the `winit` event loop. - /// - /// Unlike [`EventLoop::run`], this function accepts non-`'static` (i.e. non-`move`) closures - /// and returns control flow to the caller when `control_flow` is set to [`ControlFlow::Exit`]. - /// - /// # Caveats - /// - /// Despite its appearance at first glance, this is *not* a perfect replacement for - /// `poll_events`. For example, this function will not return on Windows or macOS while a - /// window is getting resized, resulting in all application logic outside of the - /// `event_handler` closure not running until the resize operation ends. Other OS operations - /// may also result in such freezes. This behavior is caused by fundamental limitations in the - /// underlying OS APIs, which cannot be hidden by `winit` without severe stability repercussions. - /// - /// You are strongly encouraged to use `run`, unless the use of this is absolutely necessary. - /// - /// ## Platform-specific - /// - /// - **X11 / Wayland:** This function returns `1` upon disconnection from - /// the display server. - fn run_return(&mut self, event_handler: F) -> i32 - where - F: FnMut( - Event<'_, Self::UserEvent>, - &EventLoopWindowTarget, - &mut ControlFlow, - ); -} - -impl EventLoopExtRunReturn for EventLoop { - type UserEvent = T; - - fn run_return(&mut self, event_handler: F) -> i32 - where - F: FnMut( - Event<'_, Self::UserEvent>, - &EventLoopWindowTarget, - &mut ControlFlow, - ), - { - self.event_loop.run_return(event_handler) - } -} diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 99af2a5d58..cb45db05be 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -539,17 +539,6 @@ impl EventLoop { ::std::process::exit(exit_code); } - pub fn run_return(&mut self, callback: F) -> i32 - where - F: FnMut(event::Event<'_, T>, &RootELW, &mut ControlFlow), - { - match self.run_ondemand(callback) { - Err(RunLoopError::ExitFailure(code)) => code, - Err(_err) => 1, - Ok(_) => 0, - } - } - pub fn run_ondemand(&mut self, mut event_handler: F) -> Result<(), RunLoopError> where F: FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget, &mut ControlFlow), diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index aa9db961cf..b4e77dc923 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -830,13 +830,6 @@ impl EventLoop { x11_or_wayland!(match self; EventLoop(evlp) => evlp.create_proxy(); as EventLoopProxy) } - pub fn run_return(&mut self, callback: F) -> i32 - where - F: FnMut(crate::event::Event<'_, T>, &RootELW, &mut ControlFlow), - { - x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_return(callback)) - } - pub fn run(self, callback: F) -> ! where F: 'static + FnMut(crate::event::Event<'_, T>, &RootELW, &mut ControlFlow), diff --git a/src/platform_impl/linux/wayland/event_loop/mod.rs b/src/platform_impl/linux/wayland/event_loop/mod.rs index 157ebfe0a4..7bd04d9ba6 100644 --- a/src/platform_impl/linux/wayland/event_loop/mod.rs +++ b/src/platform_impl/linux/wayland/event_loop/mod.rs @@ -157,17 +157,6 @@ impl EventLoop { ::std::process::exit(exit_code) } - pub fn run_return(&mut self, callback: F) -> i32 - where - F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget, &mut ControlFlow), - { - match self.run_ondemand(callback) { - Err(RunLoopError::ExitFailure(code)) => code, - Err(_err) => 1, - Ok(_) => 0, - } - } - pub fn run_ondemand(&mut self, mut event_handler: F) -> Result<(), RunLoopError> where F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget, &mut ControlFlow), diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index fd374b2181..81929519d1 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -444,17 +444,6 @@ impl EventLoop { ::std::process::exit(exit_code) } - pub fn run_return(&mut self, callback: F) -> i32 - where - F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), - { - match self.run_ondemand(callback) { - Err(RunLoopError::ExitFailure(code)) => code, - Err(_err) => 1, - Ok(_) => 0, - } - } - pub fn run_ondemand(&mut self, mut event_handler: F) -> Result<(), RunLoopError> where F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), diff --git a/src/platform_impl/macos/event_loop.rs b/src/platform_impl/macos/event_loop.rs index 00f93c4b27..e058259a25 100644 --- a/src/platform_impl/macos/event_loop.rs +++ b/src/platform_impl/macos/event_loop.rs @@ -204,17 +204,6 @@ impl EventLoop { process::exit(exit_code); } - pub fn run_return(&mut self, callback: F) -> i32 - where - F: FnMut(Event<'_, T>, &RootWindowTarget, &mut ControlFlow), - { - match self.run_ondemand(callback) { - Err(RunLoopError::ExitFailure(code)) => code, - Err(_err) => 1, - Ok(_) => 0, - } - } - // 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 diff --git a/src/platform_impl/orbital/event_loop.rs b/src/platform_impl/orbital/event_loop.rs index 16cf6f4137..0b23e5d5bb 100644 --- a/src/platform_impl/orbital/event_loop.rs +++ b/src/platform_impl/orbital/event_loop.rs @@ -301,15 +301,6 @@ impl EventLoop { } } - pub fn run(mut self, event_handler: F) -> ! - where - F: 'static - + FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget, &mut ControlFlow), - { - let exit_code = self.run_return(event_handler); - ::std::process::exit(exit_code); - } - fn process_event( window_id: WindowId, event_option: EventOption, @@ -451,9 +442,10 @@ impl EventLoop { } } - pub fn run_return(&mut self, mut event_handler_inner: F) -> i32 + pub fn run(mut self, event_handler: F) -> ! where - F: FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget, &mut ControlFlow), + F: 'static + + FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget, &mut ControlFlow), { // Wrapper for event handler function that prevents ExitWithCode from being unset. let mut event_handler = @@ -696,7 +688,7 @@ impl EventLoop { &mut control_flow, ); - code + ::std::process::exit(code); } pub fn window_target(&self) -> &event_loop::EventLoopWindowTarget { diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 5e8c9ca22a..7c45cbaca9 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -255,17 +255,6 @@ impl EventLoop { ::std::process::exit(exit_code); } - pub fn run_return(&mut self, event_handler: F) -> i32 - where - F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), - { - match self.run_ondemand(event_handler) { - Err(RunLoopError::ExitFailure(code)) => code, - Err(_err) => 1, - Ok(_) => 0, - } - } - pub fn run_ondemand(&mut self, mut event_handler: F) -> Result<(), RunLoopError> where F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), From 8a1a815b7b794fa979f64ed74a7a891fc7ab7317 Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Tue, 11 Apr 2023 12:50:52 +0100 Subject: [PATCH 07/15] Re-work event loop run() API so it can return a Result This re-works the portable `run()` API that consumes the `EventLoop` and runs the loop on the calling thread until the app exits. This can be supported across _all_ platforms and compared to the previous `run() -> !` API is now able to return a `Result` status on all platforms except iOS and Web. Fixes: #2709 By moving away from `run() -> !` we stop calling `std::process::exit()` internally as a means to kill the process without returning which means it's possible to return an exit status and applications can return from their `main()` function normally. This also fixes Android support where an Activity runs in a thread but we can't assume to have full ownership of the process (other services could be running in separate threads). Additionally all examples have generally been updated so that `main()` returns a `Result` from `run()` Fixes: #2709 --- examples/child_window.rs | 2 +- examples/control_flow.rs | 4 ++-- examples/cursor.rs | 4 ++-- examples/cursor_grab.rs | 4 ++-- examples/custom_events.rs | 4 ++-- examples/drag_window.rs | 4 ++-- examples/fullscreen.rs | 4 ++-- examples/handling_close.rs | 4 ++-- examples/ime.rs | 4 ++-- examples/key_binding.rs | 4 ++-- examples/mouse_wheel.rs | 4 ++-- examples/multithreaded.rs | 2 +- examples/multiwindow.rs | 2 +- examples/request_redraw.rs | 4 ++-- examples/request_redraw_threaded.rs | 4 ++-- examples/resizable.rs | 4 ++-- examples/startup_notification.rs | 19 ++++++++--------- examples/theme.rs | 4 ++-- examples/timer.rs | 4 ++-- examples/touchpad_gestures.rs | 4 ++-- examples/transparent.rs | 4 ++-- examples/web.rs | 6 +++--- examples/web_aspect_ratio.rs | 2 +- examples/window.rs | 4 ++-- examples/window_buttons.rs | 4 ++-- examples/window_debug.rs | 4 ++-- examples/window_drag_resize.rs | 4 ++-- examples/window_icon.rs | 4 ++-- examples/window_option_as_alt.rs | 4 ++-- examples/window_resize_increments.rs | 4 ++-- examples/window_tabbing.rs | 2 +- src/event_loop.rs | 21 ++++++++++++++----- src/platform_impl/android/mod.rs | 9 ++------ src/platform_impl/linux/mod.rs | 6 +++--- .../linux/wayland/event_loop/mod.rs | 12 ----------- src/platform_impl/linux/x11/mod.rs | 12 ----------- src/platform_impl/macos/event_loop.rs | 11 +++------- src/platform_impl/orbital/event_loop.rs | 9 ++++++-- src/platform_impl/windows/event_loop.rs | 9 ++------ 39 files changed, 98 insertions(+), 122 deletions(-) diff --git a/examples/child_window.rs b/examples/child_window.rs index 775c180c68..65135b79e0 100644 --- a/examples/child_window.rs +++ b/examples/child_window.rs @@ -3,7 +3,7 @@ mod fill; #[cfg(any(x11_platform, macos_platform, windows_platform))] -fn main() { +fn main() -> Result<(), impl std::error::Error> { use std::collections::HashMap; use raw_window_handle::HasRawWindowHandle; diff --git a/examples/control_flow.rs b/examples/control_flow.rs index fd39d4761b..49f00a0ddf 100644 --- a/examples/control_flow.rs +++ b/examples/control_flow.rs @@ -27,7 +27,7 @@ enum Mode { const WAIT_TIME: time::Duration = time::Duration::from_millis(100); const POLL_SLEEP_TIME: time::Duration = time::Duration::from_millis(100); -fn main() { +fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); println!("Press '1' to switch to Wait mode."); @@ -122,5 +122,5 @@ fn main() { } _ => (), } - }); + }) } diff --git a/examples/cursor.rs b/examples/cursor.rs index c9edf2a0cb..e7d3b9d52c 100644 --- a/examples/cursor.rs +++ b/examples/cursor.rs @@ -10,7 +10,7 @@ use winit::{ #[path = "util/fill.rs"] mod fill; -fn main() { +fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -54,7 +54,7 @@ fn main() { } _ => (), } - }); + }) } const CURSORS: &[CursorIcon] = &[ diff --git a/examples/cursor_grab.rs b/examples/cursor_grab.rs index 72ba14e56c..b69ab47c16 100644 --- a/examples/cursor_grab.rs +++ b/examples/cursor_grab.rs @@ -11,7 +11,7 @@ use winit::{ #[path = "util/fill.rs"] mod fill; -fn main() { +fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -73,5 +73,5 @@ fn main() { Event::RedrawRequested(_) => fill::fill_window(&window), _ => (), } - }); + }) } diff --git a/examples/custom_events.rs b/examples/custom_events.rs index b3a3d2176a..2e8f5472f3 100644 --- a/examples/custom_events.rs +++ b/examples/custom_events.rs @@ -1,7 +1,7 @@ #![allow(clippy::single_match)] #[cfg(not(wasm_platform))] -fn main() { +fn main() -> Result<(), impl std::error::Error> { use simple_logger::SimpleLogger; use winit::{ event::{Event, WindowEvent}, @@ -52,7 +52,7 @@ fn main() { } _ => (), } - }); + }) } #[cfg(wasm_platform)] diff --git a/examples/drag_window.rs b/examples/drag_window.rs index af5c824d43..55056001f7 100644 --- a/examples/drag_window.rs +++ b/examples/drag_window.rs @@ -11,7 +11,7 @@ use winit::{ #[path = "util/fill.rs"] mod fill; -fn main() { +fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -69,7 +69,7 @@ fn main() { } } _ => (), - }); + }) } fn name_windows(window_id: WindowId, switched: bool, window_1: &Window, window_2: &Window) { diff --git a/examples/fullscreen.rs b/examples/fullscreen.rs index e38390ebc8..9bfa3fd6d1 100644 --- a/examples/fullscreen.rs +++ b/examples/fullscreen.rs @@ -12,7 +12,7 @@ use winit::platform::macos::WindowExtMacOS; #[path = "util/fill.rs"] mod fill; -fn main() { +fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -131,5 +131,5 @@ fn main() { } _ => {} } - }); + }) } diff --git a/examples/handling_close.rs b/examples/handling_close.rs index c5d95471a6..5fa90283bd 100644 --- a/examples/handling_close.rs +++ b/examples/handling_close.rs @@ -11,7 +11,7 @@ use winit::{ #[path = "util/fill.rs"] mod fill; -fn main() { +fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -87,5 +87,5 @@ fn main() { } _ => (), } - }); + }) } diff --git a/examples/ime.rs b/examples/ime.rs index d108c8fc6d..319e06158a 100644 --- a/examples/ime.rs +++ b/examples/ime.rs @@ -13,7 +13,7 @@ use winit::{ #[path = "util/fill.rs"] mod fill; -fn main() { +fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new() .with_level(LevelFilter::Trace) .init() @@ -105,5 +105,5 @@ fn main() { } _ => (), } - }); + }) } diff --git a/examples/key_binding.rs b/examples/key_binding.rs index 7d4968f3a1..ef5d531931 100644 --- a/examples/key_binding.rs +++ b/examples/key_binding.rs @@ -17,7 +17,7 @@ fn main() { } #[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))] -fn main() { +fn main() -> Result<(), impl std::error::Error> { #[path = "util/fill.rs"] mod fill; @@ -61,5 +61,5 @@ fn main() { } _ => (), }; - }); + }) } diff --git a/examples/mouse_wheel.rs b/examples/mouse_wheel.rs index ea0c962bbe..38d06aff4d 100644 --- a/examples/mouse_wheel.rs +++ b/examples/mouse_wheel.rs @@ -10,7 +10,7 @@ use winit::{ #[path = "util/fill.rs"] mod fill; -fn main() { +fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -64,5 +64,5 @@ In other words, the deltas indicate the direction in which to move the content ( } _ => (), } - }); + }) } diff --git a/examples/multithreaded.rs b/examples/multithreaded.rs index cc4276364f..bd9f9004dd 100644 --- a/examples/multithreaded.rs +++ b/examples/multithreaded.rs @@ -1,7 +1,7 @@ #![allow(clippy::single_match)] #[cfg(not(wasm_platform))] -fn main() { +fn main() -> Result<(), impl std::error::Error> { use std::{collections::HashMap, sync::mpsc, thread, time::Duration}; use simple_logger::SimpleLogger; diff --git a/examples/multiwindow.rs b/examples/multiwindow.rs index 03812afc6f..7fcb724b2d 100644 --- a/examples/multiwindow.rs +++ b/examples/multiwindow.rs @@ -13,7 +13,7 @@ use winit::{ #[path = "util/fill.rs"] mod fill; -fn main() { +fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); diff --git a/examples/request_redraw.rs b/examples/request_redraw.rs index 16ec6ca7c2..a064cd39bb 100644 --- a/examples/request_redraw.rs +++ b/examples/request_redraw.rs @@ -10,7 +10,7 @@ use winit::{ #[path = "util/fill.rs"] mod fill; -fn main() { +fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -41,5 +41,5 @@ fn main() { } _ => (), } - }); + }) } diff --git a/examples/request_redraw_threaded.rs b/examples/request_redraw_threaded.rs index afe874978a..2010f96eef 100644 --- a/examples/request_redraw_threaded.rs +++ b/examples/request_redraw_threaded.rs @@ -1,7 +1,7 @@ #![allow(clippy::single_match)] #[cfg(not(wasm_platform))] -fn main() { +fn main() -> Result<(), impl std::error::Error> { use std::{sync::Arc, thread, time}; use simple_logger::SimpleLogger; @@ -49,7 +49,7 @@ fn main() { } _ => (), } - }); + }) } #[cfg(wasm_platform)] diff --git a/examples/resizable.rs b/examples/resizable.rs index b0c0f6ea7d..ecec14b228 100644 --- a/examples/resizable.rs +++ b/examples/resizable.rs @@ -12,7 +12,7 @@ use winit::{ #[path = "util/fill.rs"] mod fill; -fn main() { +fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -53,5 +53,5 @@ fn main() { } _ => (), }; - }); + }) } diff --git a/examples/startup_notification.rs b/examples/startup_notification.rs index 5de61401f9..fdd68d51c9 100644 --- a/examples/startup_notification.rs +++ b/examples/startup_notification.rs @@ -1,9 +1,5 @@ //! Demonstrates the use of startup notifications on Linux. -fn main() { - example::main(); -} - #[cfg(any(x11_platform, wayland_platform))] #[path = "./util/fill.rs"] mod fill; @@ -21,7 +17,7 @@ mod example { }; use winit::window::{Window, WindowBuilder, WindowId}; - pub(super) fn main() { + pub(super) fn main() -> Result<(), impl std::error::Error> { // Create the event loop and get the activation token. let event_loop = EventLoop::new(); let mut current_token = match event_loop.read_token_from_env() { @@ -115,13 +111,16 @@ mod example { } flow.set_wait(); - }); + }) } } +#[cfg(any(x11_platform, wayland_platform))] +fn main() -> Result<(), impl std::error::Error> { + example::main() +} + #[cfg(not(any(x11_platform, wayland_platform)))] -mod example { - pub(super) fn main() { - println!("This example is only supported on X11 and Wayland platforms."); - } +fn main() { + println!("This example is only supported on X11 and Wayland platforms."); } diff --git a/examples/theme.rs b/examples/theme.rs index 7b132a9d83..b3dac443ed 100644 --- a/examples/theme.rs +++ b/examples/theme.rs @@ -11,7 +11,7 @@ use winit::{ #[path = "util/fill.rs"] mod fill; -fn main() { +fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -75,5 +75,5 @@ fn main() { } _ => (), } - }); + }) } diff --git a/examples/timer.rs b/examples/timer.rs index 273cc638e1..13fceb51b8 100644 --- a/examples/timer.rs +++ b/examples/timer.rs @@ -16,7 +16,7 @@ use winit::{ #[path = "util/fill.rs"] mod fill; -fn main() { +fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -47,5 +47,5 @@ fn main() { } _ => (), } - }); + }) } diff --git a/examples/touchpad_gestures.rs b/examples/touchpad_gestures.rs index 2ebe83623b..0cca2c2cfd 100644 --- a/examples/touchpad_gestures.rs +++ b/examples/touchpad_gestures.rs @@ -8,7 +8,7 @@ use winit::{ #[path = "util/fill.rs"] mod fill; -fn main() { +fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -47,5 +47,5 @@ fn main() { } else if let Event::RedrawRequested(_) = event { fill::fill_window(&window); } - }); + }) } diff --git a/examples/transparent.rs b/examples/transparent.rs index 134be3adad..b093867d9d 100644 --- a/examples/transparent.rs +++ b/examples/transparent.rs @@ -10,7 +10,7 @@ use winit::{ #[path = "util/fill.rs"] mod fill; -fn main() { +fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -36,5 +36,5 @@ fn main() { } _ => (), } - }); + }) } diff --git a/examples/web.rs b/examples/web.rs index f2033d6d33..1e40676444 100644 --- a/examples/web.rs +++ b/examples/web.rs @@ -7,7 +7,7 @@ use winit::{ window::{Fullscreen, WindowBuilder}, }; -pub fn main() { +pub fn main() -> Result<(), impl std::error::Error> { let event_loop = EventLoop::new(); let builder = WindowBuilder::new().with_title("A fantastic window!"); @@ -56,7 +56,7 @@ pub fn main() { } _ => (), } - }); + }) } #[cfg(wasm_platform)] @@ -72,7 +72,7 @@ mod wasm { console_log::init_with_level(log::Level::Debug).expect("error initializing logger"); #[allow(clippy::main_recursion)] - super::main(); + let _ = super::main(); } pub fn insert_canvas_and_create_log_list(window: &Window) -> web_sys::Element { diff --git a/examples/web_aspect_ratio.rs b/examples/web_aspect_ratio.rs index b965f50728..e0f5ba161f 100644 --- a/examples/web_aspect_ratio.rs +++ b/examples/web_aspect_ratio.rs @@ -47,7 +47,7 @@ This example demonstrates the desired future functionality which will possibly b // Render once with the size info we currently have render_circle(&canvas, window.inner_size()); - event_loop.run(move |event, _, control_flow| { + let _ = event_loop.run(move |event, _, control_flow| { *control_flow = ControlFlow::Wait; match event { diff --git a/examples/window.rs b/examples/window.rs index 4a5d8b0068..d2764c2ad4 100644 --- a/examples/window.rs +++ b/examples/window.rs @@ -10,7 +10,7 @@ use winit::{ #[path = "util/fill.rs"] mod fill; -fn main() { +fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -37,5 +37,5 @@ fn main() { } _ => (), } - }); + }) } diff --git a/examples/window_buttons.rs b/examples/window_buttons.rs index 1f28412c4c..b531531ced 100644 --- a/examples/window_buttons.rs +++ b/examples/window_buttons.rs @@ -14,7 +14,7 @@ use winit::{ #[path = "util/fill.rs"] mod fill; -fn main() { +fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -71,5 +71,5 @@ fn main() { } _ => (), } - }); + }) } diff --git a/examples/window_debug.rs b/examples/window_debug.rs index d1b3ba3d7e..5c7f232671 100644 --- a/examples/window_debug.rs +++ b/examples/window_debug.rs @@ -14,7 +14,7 @@ use winit::{ #[path = "util/fill.rs"] mod fill; -fn main() { +fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -139,5 +139,5 @@ fn main() { } _ => (), } - }); + }) } diff --git a/examples/window_drag_resize.rs b/examples/window_drag_resize.rs index 277e7d4ca7..3232c4c807 100644 --- a/examples/window_drag_resize.rs +++ b/examples/window_drag_resize.rs @@ -13,7 +13,7 @@ const BORDER: f64 = 8.0; #[path = "util/fill.rs"] mod fill; -fn main() { +fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -74,7 +74,7 @@ fn main() { fill::fill_window(&window); } _ => (), - }); + }) } fn cursor_direction_icon(resize_direction: Option) -> CursorIcon { diff --git a/examples/window_icon.rs b/examples/window_icon.rs index e87372c208..6e65f34881 100644 --- a/examples/window_icon.rs +++ b/examples/window_icon.rs @@ -12,7 +12,7 @@ use winit::{ #[path = "util/fill.rs"] mod fill; -fn main() { +fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); // You'll have to choose an icon size at your own discretion. On X11, the desired size varies @@ -48,7 +48,7 @@ fn main() { } else if let Event::RedrawRequested(_) = event { fill::fill_window(&window); } - }); + }) } fn load_icon(path: &Path) -> Icon { diff --git a/examples/window_option_as_alt.rs b/examples/window_option_as_alt.rs index fc2e458285..6d7792b540 100644 --- a/examples/window_option_as_alt.rs +++ b/examples/window_option_as_alt.rs @@ -18,7 +18,7 @@ mod fill; /// Prints the keyboard events characters received when option_is_alt is true versus false. /// A left mouse click will toggle option_is_alt. #[cfg(target_os = "macos")] -fn main() { +fn main() -> Result<(), impl std::error::Error> { let event_loop = EventLoop::new(); let window = WindowBuilder::new() @@ -66,7 +66,7 @@ fn main() { } _ => (), } - }); + }) } #[cfg(not(target_os = "macos"))] diff --git a/examples/window_resize_increments.rs b/examples/window_resize_increments.rs index 1c713ba72c..42a4201810 100644 --- a/examples/window_resize_increments.rs +++ b/examples/window_resize_increments.rs @@ -11,7 +11,7 @@ use winit::{ #[path = "util/fill.rs"] mod fill; -fn main() { +fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); @@ -60,5 +60,5 @@ fn main() { } _ => (), } - }); + }) } diff --git a/examples/window_tabbing.rs b/examples/window_tabbing.rs index d264033597..1989a8f365 100644 --- a/examples/window_tabbing.rs +++ b/examples/window_tabbing.rs @@ -19,7 +19,7 @@ use winit::{ mod fill; #[cfg(target_os = "macos")] -fn main() { +fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); diff --git a/src/event_loop.rs b/src/event_loop.rs index 7e5a66f73f..87ec9535c2 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -18,6 +18,7 @@ use std::time::{Duration, Instant}; #[cfg(wasm_platform)] use web_time::{Duration, Instant}; +use crate::error::RunLoopError; use crate::{event::Event, monitor::MonitorHandle, platform_impl}; /// Provides a way to retrieve events from the system and from the windows that were registered to @@ -285,23 +286,33 @@ impl EventLoop { EventLoopBuilder::::with_user_event().build() } - /// Hijacks the calling thread and initializes the winit event loop with the provided - /// closure. Since the closure is `'static`, it must be a `move` closure if it needs to + /// Runs the event loop in the calling thread and calls the given `event_handler` closure + /// to dispatch any pending events. + /// + /// Since the closure is `'static`, it must be a `move` closure if it needs to /// access any data from the calling context. /// /// See the [`ControlFlow`] docs for information on how changes to `&mut ControlFlow` impact the /// event loop's behavior. /// - /// Any values not passed to this function will *not* be dropped. - /// /// ## Platform-specific /// /// - **X11 / Wayland:** The program terminates with exit code 1 if the display server /// disconnects. + /// - **iOS:** Will never return to the caller and so values not passed to this function will + /// *not* be dropped before the process exits. + /// - **Web:** Will _act_ as if it never returns to the caller by throwing a Javascript exception + /// (that Rust doesn't see) that will also mean that the rest of the function is never executed + /// and any values not passed to this function will *not* be dropped. + /// + /// Web applications are recommended to use `spawn()` instead of `run()` 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. /// /// [`ControlFlow`]: crate::event_loop::ControlFlow #[inline] - pub fn run(self, event_handler: F) -> ! + pub fn run(self, event_handler: F) -> Result<(), RunLoopError> where F: 'static + FnMut(Event<'_, T>, &EventLoopWindowTarget, &mut ControlFlow), { diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index cb45db05be..3d449bdb25 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -526,17 +526,12 @@ impl EventLoop { self.pending_redraw = pending_redraw; } - pub fn run(mut self, event_handler: F) -> ! + pub fn run(mut self, event_handler: F) -> Result<(), RunLoopError> where F: 'static + FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget, &mut ControlFlow), { - let exit_code = match self.run_ondemand(event_handler) { - Err(RunLoopError::ExitFailure(code)) => code, - Err(_err) => 1, - Ok(_) => 0, - }; - ::std::process::exit(exit_code); + self.run_ondemand(event_handler) } pub fn run_ondemand(&mut self, mut event_handler: F) -> Result<(), RunLoopError> diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index b4e77dc923..35cead75e5 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -830,11 +830,11 @@ impl EventLoop { x11_or_wayland!(match self; EventLoop(evlp) => evlp.create_proxy(); as EventLoopProxy) } - pub fn run(self, callback: F) -> ! + pub fn run(mut self, callback: F) -> Result<(), RunLoopError> where - F: 'static + FnMut(crate::event::Event<'_, T>, &RootELW, &mut ControlFlow), + F: FnMut(crate::event::Event<'_, T>, &RootELW, &mut ControlFlow), { - x11_or_wayland!(match self; EventLoop(evlp) => evlp.run(callback)) + self.run_ondemand(callback) } pub fn run_ondemand(&mut self, callback: F) -> Result<(), RunLoopError> diff --git a/src/platform_impl/linux/wayland/event_loop/mod.rs b/src/platform_impl/linux/wayland/event_loop/mod.rs index 7bd04d9ba6..afe7c3e577 100644 --- a/src/platform_impl/linux/wayland/event_loop/mod.rs +++ b/src/platform_impl/linux/wayland/event_loop/mod.rs @@ -145,18 +145,6 @@ impl EventLoop { Ok(event_loop) } - pub fn run(mut self, callback: F) -> ! - where - F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget, &mut ControlFlow) + 'static, - { - let exit_code = match self.run_ondemand(callback) { - Err(RunLoopError::ExitFailure(code)) => code, - Err(_err) => 1, - Ok(_) => 0, - }; - ::std::process::exit(exit_code) - } - pub fn run_ondemand(&mut self, mut event_handler: F) -> Result<(), RunLoopError> where F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget, &mut ControlFlow), diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index 81929519d1..c638128833 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -432,18 +432,6 @@ impl EventLoop { &self.target } - pub fn run(mut self, callback: F) -> ! - where - F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow) + 'static, - { - let exit_code = match self.run_ondemand(callback) { - Err(RunLoopError::ExitFailure(code)) => code, - Err(_err) => 1, - Ok(_) => 0, - }; - ::std::process::exit(exit_code) - } - pub fn run_ondemand(&mut self, mut event_handler: F) -> Result<(), RunLoopError> where F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), diff --git a/src/platform_impl/macos/event_loop.rs b/src/platform_impl/macos/event_loop.rs index e058259a25..23743859df 100644 --- a/src/platform_impl/macos/event_loop.rs +++ b/src/platform_impl/macos/event_loop.rs @@ -6,7 +6,7 @@ use std::{ mem, os::raw::c_void, panic::{catch_unwind, resume_unwind, AssertUnwindSafe, RefUnwindSafe, UnwindSafe}, - process, ptr, + ptr, rc::{Rc, Weak}, sync::mpsc, }; @@ -192,16 +192,11 @@ impl EventLoop { &self.window_target } - pub fn run(mut self, callback: F) -> ! + pub fn run(mut self, callback: F) -> Result<(), RunLoopError> where F: 'static + FnMut(Event<'_, T>, &RootWindowTarget, &mut ControlFlow), { - let exit_code = match self.run_ondemand(callback) { - Err(RunLoopError::ExitFailure(code)) => code, - Err(_err) => 1, - Ok(_) => 0, - }; - process::exit(exit_code); + self.run_ondemand(callback) } // NB: we don't base this on `pump_events` because for `MacOs` we can't support diff --git a/src/platform_impl/orbital/event_loop.rs b/src/platform_impl/orbital/event_loop.rs index 0b23e5d5bb..365c2d0ff8 100644 --- a/src/platform_impl/orbital/event_loop.rs +++ b/src/platform_impl/orbital/event_loop.rs @@ -12,6 +12,7 @@ use orbclient::{ use raw_window_handle::{OrbitalDisplayHandle, RawDisplayHandle}; use crate::{ + error::RunLoopError, event::{self, Ime, Modifiers, StartCause}, event_loop::{self, ControlFlow}, keyboard::{ @@ -442,7 +443,7 @@ impl EventLoop { } } - pub fn run(mut self, event_handler: F) -> ! + pub fn run(mut self, mut event_handler_inner: F) -> Result<(), RunLoopError> where F: 'static + FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget, &mut ControlFlow), @@ -688,7 +689,11 @@ impl EventLoop { &mut control_flow, ); - ::std::process::exit(code); + if code == 0 { + Ok(()) + } else { + Err(RunLoopError::ExitFailure(code)) + } } pub fn window_target(&self) -> &event_loop::EventLoopWindowTarget { diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 7c45cbaca9..3fa14cbba1 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -243,16 +243,11 @@ impl EventLoop { &self.window_target } - pub fn run(mut self, event_handler: F) -> ! + pub fn run(mut self, event_handler: F) -> Result<(), RunLoopError> where F: 'static + FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), { - let exit_code = match self.run_ondemand(event_handler) { - Err(RunLoopError::ExitFailure(code)) => code, - Err(_err) => 1, - Ok(_) => 0, - }; - ::std::process::exit(exit_code); + self.run_ondemand(event_handler) } pub fn run_ondemand(&mut self, mut event_handler: F) -> Result<(), RunLoopError> From dc75dab943ab42367377a9c3ec65a0c93b39b749 Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Sun, 18 Jun 2023 13:50:13 +0100 Subject: [PATCH 08/15] Add examples/window_pump_events A minimal example of an application based on an external event loop that calls `pump_events` for each iteration of the external loop. --- examples/window_pump_events.rs | 70 ++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 examples/window_pump_events.rs diff --git a/examples/window_pump_events.rs b/examples/window_pump_events.rs new file mode 100644 index 0000000000..937b217d93 --- /dev/null +++ b/examples/window_pump_events.rs @@ -0,0 +1,70 @@ +#![allow(clippy::single_match)] + +// Limit this example to only compatible platforms. +#[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, WindowEvent}, + event_loop::EventLoop, + platform::pump_events::{EventLoopExtPumpEvents, PumpStatus}, + window::WindowBuilder, + }; + + #[path = "util/fill.rs"] + mod fill; + + 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(); + } + Event::RedrawRequested(_) => { + fill::fill_window(&window); + } + _ => (), + } + }); + 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)); + } +} + +#[cfg(any(ios_platform, wasm_platform, orbital_platform))] +fn main() { + println!("This platform doesn't support pump_events."); +} From d32841b0319cc4d1392840713c9b5e2cced3ee7d Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Sun, 18 Jun 2023 13:50:26 +0100 Subject: [PATCH 09/15] Add examples/window_ondemand A minimal example that shows an application running the event loop more than once via `run_ondemand` There is a 5 second delay between each run to help highlight problems with destroying the window from the first loop. --- examples/window_ondemand.rs | 105 ++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 examples/window_ondemand.rs diff --git a/examples/window_ondemand.rs b/examples/window_ondemand.rs new file mode 100644 index 0000000000..1960b163d4 --- /dev/null +++ b/examples/window_ondemand.rs @@ -0,0 +1,105 @@ +#![allow(clippy::single_match)] + +// Limit this example to only compatible platforms. +#[cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform,))] +fn main() -> Result<(), impl std::error::Error> { + use simple_logger::SimpleLogger; + + use winit::{ + event::{Event, WindowEvent}, + event_loop::EventLoop, + platform::run_ondemand::EventLoopExtRunOnDemand, + window::{Window, WindowBuilder}, + }; + + #[path = "util/fill.rs"] + mod fill; + + #[derive(Default)] + struct App { + window: Option, + } + + SimpleLogger::new().init().unwrap(); + let mut event_loop = EventLoop::new(); + + { + let mut app = App::default(); + + event_loop.run_ondemand(move |event, event_loop, control_flow| { + control_flow.set_wait(); + println!("Run 1: {:?}", event); + + if let Some(window) = &app.window { + match event { + Event::WindowEvent { + event: WindowEvent::CloseRequested, + window_id, + } if window.id() == window_id => { + app.window = None; + control_flow.set_exit(); + } + Event::MainEventsCleared => window.request_redraw(), + Event::RedrawRequested(_) => { + fill::fill_window(window); + } + _ => (), + } + } else if let Event::Resumed = event { + app.window = Some( + WindowBuilder::new() + .with_title("Fantastic window number one!") + .with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0)) + .build(event_loop) + .unwrap(), + ); + } + })?; + } + + println!("--------------------------------------------------------- Finished first loop"); + println!("--------------------------------------------------------- Waiting 5 seconds"); + std::thread::sleep_ms(5000); + + let ret = { + let mut app = App::default(); + + event_loop.run_ondemand(move |event, event_loop, control_flow| { + control_flow.set_wait(); + println!("Run 2: {:?}", event); + + if let Some(window) = &app.window { + match event { + Event::WindowEvent { + event: WindowEvent::CloseRequested, + window_id, + } if window.id() == window_id => { + app.window = None; + control_flow.set_exit(); + } + Event::MainEventsCleared => window.request_redraw(), + Event::RedrawRequested(_) => { + fill::fill_window(window); + } + _ => (), + } + } else if let Event::Resumed = event { + app.window = Some( + WindowBuilder::new() + .with_title("Fantastic window number two!") + .with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0)) + .build(event_loop) + .unwrap(), + ); + } + }) + }; + + println!("--------------------------------------------------------- Finished second loop"); + ret +} + +#[cfg(not(any(windows_platform, macos_platform, x11_platform, wayland_platform,)))] +fn main() { + println!("This example is not supported on this platform"); +} From 72158433b55e6e2e74217d09c691b025b1f63357 Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Thu, 22 Jun 2023 10:55:43 +0100 Subject: [PATCH 10/15] Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4aa0b6de4e..38f8156e81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,10 @@ And please only add new entries to the top of this list, right below the `# Unre - On Web, remove unnecessary `Window::is_dark_mode()`, which was replaced with `Window::theme()`. - On Web, add `WindowBuilderExtWebSys::with_append()` to append the canvas element to the web page on creation. - On Windows, add `drag_resize_window` method support. +- **Breaking** `run() ->!` has been replaced by `run() -> Result<(), RunLoopError>` for returning errors without calling `std::process::exit()` ([#2767](https://github.com/rust-windowing/winit/pull/2767)) +- **Breaking** Removed `EventLoopExtRunReturn` / `run_return` in favor of `EventLoopExtPumpEvents` / `pump_events` and `EventLoopExtRunOnDemand` / `run_ondemand` ([#2767](https://github.com/rust-windowing/winit/pull/2767)) +- `RedrawRequested` is no longer guaranteed to be emitted after `MainEventsCleared`, it is now platform-specific when the event is emitted after being requested via `redraw_request()`. + - On Windows, `RedrawRequested` is now driven by `WM_PAINT` messages which are requested via `redraw_request()` # 0.29.0-beta.0 From c4eb69208717b305828b91e218fe3e9844c4611f Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Tue, 4 Jul 2023 22:53:17 +0100 Subject: [PATCH 11/15] Linux: Sync with server/compositor before exiting run_ondemand Although we document that applications can't keep windows between separate run_ondemand calls it's possible that the application has only just dropped their windows and we need to flush these requests to the server/compositor. This fixes the window_ondemand example - by ensuring the window from the first loop really is destroyed before waiting for 5 seconds and starting the second loop. --- .../linux/wayland/event_loop/mod.rs | 32 ++++++++++++++++--- src/platform_impl/linux/x11/mod.rs | 17 ++++++++-- 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/src/platform_impl/linux/wayland/event_loop/mod.rs b/src/platform_impl/linux/wayland/event_loop/mod.rs index afe7c3e577..2a00afa626 100644 --- a/src/platform_impl/linux/wayland/event_loop/mod.rs +++ b/src/platform_impl/linux/wayland/event_loop/mod.rs @@ -16,13 +16,13 @@ use sctk::reexports::client::globals; use sctk::reexports::client::{Connection, Proxy, QueueHandle, WaylandSource}; use crate::dpi::{LogicalSize, PhysicalSize}; -use crate::error::RunLoopError; +use crate::error::{OsError as RootOsError, RunLoopError}; use crate::event::{Event, StartCause, WindowEvent}; use crate::event_loop::{ControlFlow, EventLoopWindowTarget as RootEventLoopWindowTarget}; use crate::platform::pump_events::PumpStatus; use crate::platform_impl::platform::min_timeout; use crate::platform_impl::platform::sticky_exit_callback; -use crate::platform_impl::EventLoopWindowTarget as PlatformEventLoopWindowTarget; +use crate::platform_impl::{EventLoopWindowTarget as PlatformEventLoopWindowTarget, OsError}; mod proxy; pub mod sink; @@ -153,7 +153,7 @@ impl EventLoop { return Err(RunLoopError::AlreadyRunning); } - loop { + let exit = loop { match self.pump_events_with_timeout(None, &mut event_handler) { PumpStatus::Exit(0) => { break Ok(()); @@ -165,7 +165,15 @@ impl EventLoop { continue; } } - } + }; + + // Applications aren't allowed to carry windows between separate + // `run_ondemand` calls but if they have only just dropped their + // windows we need to make sure those last requests are sent to the + // compositor. + let _ = self.roundtrip().map_err(RunLoopError::Os); + + exit } pub fn pump_events(&mut self, event_handler: F) -> PumpStatus @@ -574,6 +582,22 @@ impl EventLoop { error.into() }) } + + fn roundtrip(&mut self) -> Result { + let state = match &mut self.window_target.p { + PlatformEventLoopWindowTarget::Wayland(window_target) => window_target.state.get_mut(), + #[cfg(feature = "x11")] + _ => unreachable!(), + }; + + let mut wayland_source = self.wayland_dispatcher.as_source_mut(); + let event_queue = wayland_source.queue(); + event_queue.roundtrip(state).map_err(|_| { + os_error!(OsError::WaylandMisc( + "failed to do a final roundtrip before exiting the loop." + )) + }) + } } pub struct EventLoopWindowTarget { diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index c638128833..897fded395 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -62,7 +62,7 @@ use self::{ event_processor::EventProcessor, ime::{Ime, ImeCreationError, ImeReceiver, ImeRequest, ImeSender}, }; -use super::common::xkb_state::KbdState; +use super::{common::xkb_state::KbdState, OsError}; use crate::{ error::{OsError as RootOsError, RunLoopError}, event::{Event, StartCause}, @@ -440,7 +440,7 @@ impl EventLoop { return Err(RunLoopError::AlreadyRunning); } - loop { + let exit = loop { match self.pump_events_with_timeout(None, &mut event_handler) { PumpStatus::Exit(0) => { break Ok(()); @@ -452,7 +452,18 @@ impl EventLoop { continue; } } - } + }; + + // Applications aren't allowed to carry windows between separate + // `run_ondemand` calls but if they have only just dropped their + // windows we need to make sure those last requests are sent to the + // X Server. + let wt = get_xtarget(&self.target); + wt.x_connection().sync_with_server().map_err(|x_err| { + RunLoopError::Os(os_error!(OsError::XError(Arc::new(X11Error::Xlib(x_err))))) + })?; + + exit } pub fn pump_events(&mut self, event_handler: F) -> PumpStatus From c36bee8c77d362553292f94427bba0e8bd3b34f0 Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Tue, 4 Jul 2023 23:28:43 +0100 Subject: [PATCH 12/15] window_ondemand: wait for Destroyed event before exiting app Considering the strict requirement that applications can't keep windows across run_ondemand calls, this tries to make the window_ondemand example explicitly wait for its Window to be destroyed before exiting each run_ondemand iteration. This updates the example to only `.set_exit()` after it gets a `Destroyed` event after the Window has been dropped. On Windows this works to ensure the Window is destroyed before the example waits for 5 seconds. Unfortunately though: 1. The Wayland backend doesn't emit `Destroyed` events for windows 2. The macOS backend emits `Destroyed` events before the window is really destroyed. and so the example isn't currently portable. --- examples/window_ondemand.rs | 67 ++++++++++++++----------------------- 1 file changed, 26 insertions(+), 41 deletions(-) diff --git a/examples/window_ondemand.rs b/examples/window_ondemand.rs index 1960b163d4..b1b3c56db2 100644 --- a/examples/window_ondemand.rs +++ b/examples/window_ondemand.rs @@ -3,13 +3,16 @@ // Limit this example to only compatible platforms. #[cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform,))] fn main() -> Result<(), impl std::error::Error> { + use std::time::Duration; + use simple_logger::SimpleLogger; use winit::{ + error::RunLoopError, event::{Event, WindowEvent}, event_loop::EventLoop, platform::run_ondemand::EventLoopExtRunOnDemand, - window::{Window, WindowBuilder}, + window::{Window, WindowBuilder, WindowId}, }; #[path = "util/fill.rs"] @@ -17,18 +20,19 @@ fn main() -> Result<(), impl std::error::Error> { #[derive(Default)] struct App { + window_id: Option, window: Option, } SimpleLogger::new().init().unwrap(); let mut event_loop = EventLoop::new(); - { + fn run_app(event_loop: &mut EventLoop<()>, idx: usize) -> Result<(), RunLoopError> { let mut app = App::default(); event_loop.run_ondemand(move |event, event_loop, control_flow| { control_flow.set_wait(); - println!("Run 1: {:?}", event); + println!("Run {idx}: {:?}", event); if let Some(window) = &app.window { match event { @@ -36,8 +40,8 @@ fn main() -> Result<(), impl std::error::Error> { event: WindowEvent::CloseRequested, window_id, } if window.id() == window_id => { + println!("--------------------------------------------------------- Window {idx} CloseRequested"); app.window = None; - control_flow.set_exit(); } Event::MainEventsCleared => window.request_redraw(), Event::RedrawRequested(_) => { @@ -45,56 +49,37 @@ fn main() -> Result<(), impl std::error::Error> { } _ => (), } - } else if let Event::Resumed = event { - app.window = Some( - WindowBuilder::new() - .with_title("Fantastic window number one!") - .with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0)) - .build(event_loop) - .unwrap(), - ); - } - })?; - } - - println!("--------------------------------------------------------- Finished first loop"); - println!("--------------------------------------------------------- Waiting 5 seconds"); - std::thread::sleep_ms(5000); - - let ret = { - let mut app = App::default(); - - event_loop.run_ondemand(move |event, event_loop, control_flow| { - control_flow.set_wait(); - println!("Run 2: {:?}", event); - - if let Some(window) = &app.window { + } else if let Some(id) = app.window_id { match event { Event::WindowEvent { - event: WindowEvent::CloseRequested, + event: WindowEvent::Destroyed, window_id, - } if window.id() == window_id => { - app.window = None; + } if id == window_id => { + println!("--------------------------------------------------------- Window {idx} Destroyed"); + app.window_id = None; control_flow.set_exit(); } - Event::MainEventsCleared => window.request_redraw(), - Event::RedrawRequested(_) => { - fill::fill_window(window); - } _ => (), } } else if let Event::Resumed = event { - app.window = Some( - WindowBuilder::new() - .with_title("Fantastic window number two!") + let window = WindowBuilder::new() + .with_title("Fantastic window number one!") .with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0)) .build(event_loop) - .unwrap(), - ); + .unwrap(); + app.window_id = Some(window.id()); + app.window = Some(window); } }) - }; + } + + run_app(&mut event_loop, 1)?; + + println!("--------------------------------------------------------- Finished first loop"); + println!("--------------------------------------------------------- Waiting 5 seconds"); + std::thread::sleep(Duration::from_secs(5)); + let ret = run_app(&mut event_loop, 2); println!("--------------------------------------------------------- Finished second loop"); ret } From 4f095f0871354ceb34b5116f3406eeab8479e972 Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Sun, 23 Jul 2023 23:27:38 +0100 Subject: [PATCH 13/15] MacOS: implement pump_events_with_timeout internally This layers pump_events on a pump_events_with_timeout API, like we have for Linux and Android. This is just an internal implementation detail for now but we could consider making pump_events_with_timeout public, or just making it so that pump_events() takes the timeout argument. --- src/platform_impl/macos/app_state.rs | 84 ++++++++++++++++++++------- src/platform_impl/macos/event_loop.rs | 38 ++++++++++-- src/platform_impl/macos/mod.rs | 2 +- src/platform_impl/macos/observer.rs | 55 +++++++++++++----- 4 files changed, 138 insertions(+), 41 deletions(-) diff --git a/src/platform_impl/macos/app_state.rs b/src/platform_impl/macos/app_state.rs index 6569a7cfd3..c75bfc6a96 100644 --- a/src/platform_impl/macos/app_state.rs +++ b/src/platform_impl/macos/app_state.rs @@ -121,16 +121,17 @@ impl EventHandler for EventLoopHandler { struct Handler { stop_app_on_launch: AtomicBool, stop_app_before_wait: AtomicBool, + stop_app_after_wait: AtomicBool, stop_app_on_redraw: AtomicBool, launched: AtomicBool, running: AtomicBool, in_callback: AtomicBool, control_flow: Mutex, - control_flow_prev: Mutex, start_time: Mutex>, callback: Mutex>>, pending_events: Mutex>, pending_redraw: Mutex>, + wait_timeout: Mutex>, waker: Mutex, } @@ -214,10 +215,11 @@ impl Handler { // looks like there have been recuring re-entrancy issues with callback handling that might // make that awkward) self.running.store(false, Ordering::Relaxed); - *self.control_flow_prev.lock().unwrap() = ControlFlow::default(); *self.control_flow.lock().unwrap() = ControlFlow::default(); self.set_stop_app_on_redraw_requested(false); self.set_stop_app_before_wait(false); + self.set_stop_app_after_wait(false); + self.set_wait_timeout(None); } pub fn request_stop_app_on_launch(&self) { @@ -245,6 +247,28 @@ impl Handler { self.stop_app_before_wait.load(Ordering::Relaxed) } + pub fn set_stop_app_after_wait(&self, stop_after_wait: bool) { + // Relaxed ordering because we don't actually have multiple threads involved, we just want + // interior mutability + self.stop_app_after_wait + .store(stop_after_wait, Ordering::Relaxed); + } + + pub fn set_wait_timeout(&self, new_timeout: Option) { + let mut timeout = self.wait_timeout.lock().unwrap(); + *timeout = new_timeout; + } + + pub fn wait_timeout(&self) -> Option { + *self.wait_timeout.lock().unwrap() + } + + pub fn should_stop_app_after_wait(&self) -> bool { + // Relaxed ordering because we don't actually have multiple threads involved, we just want + // interior mutability + self.stop_app_after_wait.load(Ordering::Relaxed) + } + pub fn set_stop_app_on_redraw_requested(&self, stop_on_redraw: bool) { // Relaxed ordering because we don't actually have multiple threads involved, we just want // interior mutability @@ -258,16 +282,8 @@ impl Handler { self.stop_app_on_redraw.load(Ordering::Relaxed) } - fn get_control_flow_and_update_prev(&self) -> ControlFlow { - let control_flow = self.control_flow.lock().unwrap(); - *self.control_flow_prev.lock().unwrap() = *control_flow; - *control_flow - } - - fn get_old_and_new_control_flow(&self) -> (ControlFlow, ControlFlow) { - let old = *self.control_flow_prev.lock().unwrap(); - let new = *self.control_flow.lock().unwrap(); - (old, new) + fn control_flow(&self) -> ControlFlow { + *self.control_flow.lock().unwrap() } fn get_start_time(&self) -> Option { @@ -402,12 +418,20 @@ impl AppState { HANDLER.set_stop_app_before_wait(stop_before_wait); } + pub fn set_stop_app_after_wait(stop_after_wait: bool) { + HANDLER.set_stop_app_after_wait(stop_after_wait); + } + + pub fn set_wait_timeout(timeout: Option) { + HANDLER.set_wait_timeout(timeout); + } + pub fn set_stop_app_on_redraw_requested(stop_on_redraw: bool) { HANDLER.set_stop_app_on_redraw_requested(stop_on_redraw); } pub fn control_flow() -> ControlFlow { - HANDLER.get_old_and_new_control_flow().1 + HANDLER.control_flow() } pub fn exit() -> i32 { @@ -416,7 +440,7 @@ impl AppState { HANDLER.set_in_callback(false); HANDLER.exit(); Self::clear_callback(); - if let ControlFlow::ExitWithCode(code) = HANDLER.get_old_and_new_control_flow().1 { + if let ControlFlow::ExitWithCode(code) = HANDLER.control_flow() { code } else { 0 @@ -493,8 +517,13 @@ impl AppState { { return; } + + if HANDLER.should_stop_app_after_wait() { + Self::stop(); + } + let start = HANDLER.get_start_time().unwrap(); - let cause = match HANDLER.get_control_flow_and_update_prev() { + let cause = match HANDLER.control_flow() { ControlFlow::Poll => StartCause::Poll, ControlFlow::Wait => StartCause::WaitCancelled { start, @@ -603,16 +632,27 @@ impl AppState { Self::stop(); } HANDLER.update_start_time(); - match HANDLER.get_old_and_new_control_flow() { - (ControlFlow::ExitWithCode(_), _) | (_, ControlFlow::ExitWithCode(_)) => (), - (old, new) if old == new => (), - (_, ControlFlow::Wait) => HANDLER.waker().stop(), - (_, ControlFlow::WaitUntil(instant)) => HANDLER.waker().start_at(instant), - (_, ControlFlow::Poll) => HANDLER.waker().start(), - } + let wait_timeout = HANDLER.wait_timeout(); // configured by pump_events_with_timeout + let app_timeout = match HANDLER.control_flow() { + ControlFlow::Wait => None, + ControlFlow::Poll | ControlFlow::ExitWithCode(_) => Some(Instant::now()), + ControlFlow::WaitUntil(instant) => Some(instant), + }; + HANDLER + .waker() + .start_at(min_timeout(wait_timeout, app_timeout)); } } +/// Returns the minimum `Option`, taking into account that `None` +/// equates to an infinite timeout, not a zero timeout (so can't just use +/// `Option::min`) +fn min_timeout(a: Option, b: Option) -> Option { + a.map_or(b, |a_timeout| { + b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout))) + }) +} + /// A hack to make activation of multiple windows work when creating them before /// `applicationDidFinishLaunching:` / `Event::Event::NewEvents(StartCause::Init)`. /// diff --git a/src/platform_impl/macos/event_loop.rs b/src/platform_impl/macos/event_loop.rs index 23743859df..2623b3e45a 100644 --- a/src/platform_impl/macos/event_loop.rs +++ b/src/platform_impl/macos/event_loop.rs @@ -9,6 +9,7 @@ use std::{ ptr, rc::{Rc, Weak}, sync::mpsc, + time::{Duration, Instant}, }; use core_foundation::base::{CFIndex, CFRelease}; @@ -246,11 +247,16 @@ impl EventLoop { // catch panics to make sure we can't unwind without clearing the set callback // (which would leave the global `AppState` in an undefined, unsafe state) let catch_result = catch_unwind(AssertUnwindSafe(|| { + // clear / normalize pump_events state + AppState::set_wait_timeout(None); + AppState::set_stop_app_before_wait(false); + AppState::set_stop_app_after_wait(false); + AppState::set_stop_app_on_redraw_requested(false); + if AppState::is_launched() { debug_assert!(!AppState::is_running()); AppState::start_running(); // Set is_running = true + dispatch `NewEvents(Init)` + `Resumed` } - AppState::set_stop_app_before_wait(false); unsafe { app.run() }; // While the app is running it's possible that we catch a panic @@ -286,6 +292,13 @@ impl EventLoop { } pub fn pump_events(&mut self, callback: F) -> PumpStatus + where + F: FnMut(Event<'_, T>, &RootWindowTarget, &mut ControlFlow), + { + self.pump_events_with_timeout(Some(Duration::ZERO), callback) + } + + fn pump_events_with_timeout(&mut self, timeout: Option, callback: F) -> PumpStatus where F: FnMut(Event<'_, T>, &RootWindowTarget, &mut ControlFlow), { @@ -343,11 +356,26 @@ impl EventLoop { // we just starting to re-run the same `EventLoop` again. AppState::start_running(); // Set is_running = true + dispatch `NewEvents(Init)` + `Resumed` } else { - // Make sure we can't block any external loop indefinitely by stopping the NSApp - // and returning after dispatching any `RedrawRequested` event or whenever the - // `RunLoop` needs to wait for new events from the OS + // Only run the NSApp for as long as the given `Duration` allows so we + // don't block the external loop. + match timeout { + Some(Duration::ZERO) => { + AppState::set_wait_timeout(None); + AppState::set_stop_app_before_wait(true); + } + Some(duration) => { + AppState::set_stop_app_before_wait(false); + let timeout = Instant::now() + duration; + AppState::set_wait_timeout(Some(timeout)); + AppState::set_stop_app_after_wait(true); + } + None => { + AppState::set_wait_timeout(None); + AppState::set_stop_app_before_wait(false); + AppState::set_stop_app_after_wait(true); + } + } AppState::set_stop_app_on_redraw_requested(true); - AppState::set_stop_app_before_wait(true); unsafe { app.run(); } diff --git a/src/platform_impl/macos/mod.rs b/src/platform_impl/macos/mod.rs index 4559e0c9fb..141f03e6ec 100644 --- a/src/platform_impl/macos/mod.rs +++ b/src/platform_impl/macos/mod.rs @@ -35,7 +35,7 @@ use crate::{ use objc2::rc::{autoreleasepool, Id, Shared}; pub(crate) use crate::icon::NoIcon as PlatformIcon; -pub(self) use crate::platform_impl::Fullscreen; +pub(crate) use crate::platform_impl::Fullscreen; #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct DeviceId; diff --git a/src/platform_impl/macos/observer.rs b/src/platform_impl/macos/observer.rs index bce63c87c8..1ff1f80507 100644 --- a/src/platform_impl/macos/observer.rs +++ b/src/platform_impl/macos/observer.rs @@ -141,6 +141,16 @@ pub fn setup_control_flow_observers(panic_info: Weak) { pub struct EventLoopWaker { timer: CFRunLoopTimerRef, + + /// An arbitrary instant in the past, that will trigger an immediate wake + /// We save this as the `next_fire_date` for consistency so we can + /// easily check if the next_fire_date needs updating. + start_instant: Instant, + + /// This is what the `NextFireDate` has been set to. + /// `None` corresponds to `waker.stop()` and `start_instant` is used + /// for `waker.start()` + next_fire_date: Option, } impl Drop for EventLoopWaker { @@ -169,31 +179,50 @@ impl Default for EventLoopWaker { ptr::null_mut(), ); CFRunLoopAddTimer(CFRunLoopGetMain(), timer, kCFRunLoopCommonModes); - EventLoopWaker { timer } + EventLoopWaker { + timer, + start_instant: Instant::now(), + next_fire_date: None, + } } } } impl EventLoopWaker { pub fn stop(&mut self) { - unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MAX) } + if self.next_fire_date.is_some() { + self.next_fire_date = None; + unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MAX) } + } } pub fn start(&mut self) { - unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MIN) } + if self.next_fire_date != Some(self.start_instant) { + self.next_fire_date = Some(self.start_instant); + unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MIN) } + } } - pub fn start_at(&mut self, instant: Instant) { + pub fn start_at(&mut self, instant: Option) { let now = Instant::now(); - if now >= instant { - self.start(); - } else { - unsafe { - let current = CFAbsoluteTimeGetCurrent(); - let duration = instant - now; - let fsecs = - duration.subsec_nanos() as f64 / 1_000_000_000.0 + duration.as_secs() as f64; - CFRunLoopTimerSetNextFireDate(self.timer, current + fsecs) + match instant { + Some(instant) if now >= instant => { + self.start(); + } + Some(instant) => { + if self.next_fire_date != Some(instant) { + self.next_fire_date = Some(instant); + unsafe { + let current = CFAbsoluteTimeGetCurrent(); + let duration = instant - now; + let fsecs = duration.subsec_nanos() as f64 / 1_000_000_000.0 + + duration.as_secs() as f64; + CFRunLoopTimerSetNextFireDate(self.timer, current + fsecs) + } + } + } + None => { + self.stop(); } } } From 3f67f94b054712d343307bd770e5a6092db100c3 Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Mon, 24 Jul 2023 00:07:49 +0100 Subject: [PATCH 14/15] Windows: implement pump_events_with_timeout internally --- src/platform_impl/windows/event_loop.rs | 38 +++++++++++++++++++++---- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 3fa14cbba1..09103b818f 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -272,7 +272,7 @@ impl EventLoop { } let exit_code = loop { - if let ControlFlow::ExitWithCode(code) = self.wait_and_dispatch_message() { + if let ControlFlow::ExitWithCode(code) = self.wait_and_dispatch_message(None) { break code; } @@ -296,7 +296,18 @@ impl EventLoop { } } - pub fn pump_events(&mut self, mut event_handler: F) -> PumpStatus + pub fn pump_events(&mut self, event_handler: F) -> PumpStatus + where + F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), + { + self.pump_events_with_timeout(Some(Duration::ZERO), event_handler) + } + + fn pump_events_with_timeout( + &mut self, + timeout: Option, + mut event_handler: F, + ) -> PumpStatus where F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), { @@ -319,7 +330,12 @@ impl EventLoop { } } - self.dispatch_peeked_messages(); + if !matches!( + self.wait_and_dispatch_message(timeout), + ControlFlow::ExitWithCode(_) + ) { + self.dispatch_peeked_messages(); + } let runner = &self.window_target.p.runner_shared; @@ -346,13 +362,13 @@ impl EventLoop { status } - /// Wait for one message and dispatch it, optionally with a timeout if control_flow is `WaitUntil` - fn wait_and_dispatch_message(&mut self) -> ControlFlow { + /// Wait for one message and dispatch it, optionally with a timeout + fn wait_and_dispatch_message(&mut self, timeout: Option) -> ControlFlow { let start = Instant::now(); let runner = &self.window_target.p.runner_shared; - let timeout = match runner.control_flow() { + let control_flow_timeout = match runner.control_flow() { ControlFlow::Wait => None, ControlFlow::Poll => Some(Duration::ZERO), ControlFlow::WaitUntil(wait_deadline) => { @@ -360,6 +376,7 @@ impl EventLoop { } ControlFlow::ExitWithCode(_code) => unreachable!(), }; + let timeout = min_timeout(control_flow_timeout, timeout); fn get_msg_with_timeout(msg: &mut MSG, timeout: Option) -> PumpStatus { unsafe { @@ -579,6 +596,15 @@ fn main_thread_id() -> u32 { unsafe { MAIN_THREAD_ID } } +/// Returns the minimum `Option`, taking into account that `None` +/// equates to an infinite timeout, not a zero timeout (so can't just use +/// `Option::min`) +fn min_timeout(a: Option, b: Option) -> Option { + a.map_or(b, |a_timeout| { + b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout))) + }) +} + // Implementation taken from https://github.com/rust-lang/rust/blob/db5476571d9b27c862b95c1e64764b0ac8980e23/src/libstd/sys/windows/mod.rs fn dur2timeout(dur: Duration) -> u32 { // Note that a duration is a (u64, u32) (seconds, nanoseconds) pair, and the From f2bd7e63d73873302bf5f51fc528541d8ba39c7b Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Mon, 24 Jul 2023 00:37:51 +0100 Subject: [PATCH 15/15] Add timeout argument to pump_events This renames all internal implementations of pump_events_with_timeout to pump_events and makes them public. Since all platforms that support pump_events support timeouts there's no need to have a separate API. --- examples/window_pump_events.rs | 7 +++++-- src/platform/pump_events.rs | 11 +++++++---- src/platform_impl/android/mod.rs | 15 ++------------- src/platform_impl/linux/mod.rs | 4 ++-- src/platform_impl/linux/wayland/event_loop/mod.rs | 15 ++------------- src/platform_impl/linux/x11/mod.rs | 15 ++------------- src/platform_impl/macos/app_state.rs | 2 +- src/platform_impl/macos/event_loop.rs | 9 +-------- src/platform_impl/windows/event_loop.rs | 13 +------------ 9 files changed, 23 insertions(+), 68 deletions(-) diff --git a/examples/window_pump_events.rs b/examples/window_pump_events.rs index 937b217d93..be887bfa0e 100644 --- a/examples/window_pump_events.rs +++ b/examples/window_pump_events.rs @@ -14,7 +14,7 @@ fn main() -> std::process::ExitCode { use simple_logger::SimpleLogger; use winit::{ event::{Event, WindowEvent}, - event_loop::EventLoop, + event_loop::{ControlFlow, EventLoop}, platform::pump_events::{EventLoopExtPumpEvents, PumpStatus}, window::WindowBuilder, }; @@ -31,7 +31,10 @@ fn main() -> std::process::ExitCode { .unwrap(); 'main: loop { - let status = event_loop.pump_events(|event, _, control_flow| { + let timeout = Some(Duration::ZERO); + let status = event_loop.pump_events(timeout, |event, _, control_flow| { + *control_flow = ControlFlow::Wait; + if let Event::WindowEvent { event, .. } = &event { // Print only Window events to reduce noise println!("{event:?}"); diff --git a/src/platform/pump_events.rs b/src/platform/pump_events.rs index e7ca85edeb..71f49de7e4 100644 --- a/src/platform/pump_events.rs +++ b/src/platform/pump_events.rs @@ -1,3 +1,5 @@ +use std::time::Duration; + use crate::{ event::Event, event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget}, @@ -60,7 +62,8 @@ pub trait EventLoopExtPumpEvents { /// .unwrap(); /// /// 'main: loop { - /// let status = event_loop.pump_events(|event, _, control_flow| { + /// let timeout = Some(Duration::ZERO); + /// let status = event_loop.pump_events(timeout, |event, _, control_flow| { /// # if let Event::WindowEvent { event, .. } = &event { /// # // Print only Window events to reduce noise /// # println!("{event:?}"); @@ -169,7 +172,7 @@ pub trait EventLoopExtPumpEvents { /// 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 + fn pump_events(&mut self, timeout: Option, event_handler: F) -> PumpStatus where F: FnMut( Event<'_, Self::UserEvent>, @@ -181,7 +184,7 @@ pub trait EventLoopExtPumpEvents { impl EventLoopExtPumpEvents for EventLoop { type UserEvent = T; - fn pump_events(&mut self, event_handler: F) -> PumpStatus + fn pump_events(&mut self, timeout: Option, event_handler: F) -> PumpStatus where F: FnMut( Event<'_, Self::UserEvent>, @@ -189,6 +192,6 @@ impl EventLoopExtPumpEvents for EventLoop { &mut ControlFlow, ), { - self.event_loop.pump_events(event_handler) + self.event_loop.pump_events(timeout, event_handler) } } diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 3d449bdb25..e9c2506f33 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -543,7 +543,7 @@ impl EventLoop { } loop { - match self.pump_events_with_timeout(None, &mut event_handler) { + match self.pump_events(None, &mut event_handler) { PumpStatus::Exit(0) => { break Ok(()); } @@ -557,18 +557,7 @@ impl EventLoop { } } - pub fn pump_events(&mut self, event_handler: F) -> PumpStatus - where - F: FnMut(event::Event<'_, T>, &RootELW, &mut ControlFlow), - { - self.pump_events_with_timeout(Some(Duration::ZERO), event_handler) - } - - fn pump_events_with_timeout( - &mut self, - timeout: Option, - mut callback: F, - ) -> PumpStatus + pub fn pump_events(&mut self, timeout: Option, mut callback: F) -> PumpStatus where F: FnMut(event::Event<'_, T>, &RootELW, &mut ControlFlow), { diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 35cead75e5..4c2a43c7c5 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -844,11 +844,11 @@ impl EventLoop { x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_ondemand(callback)) } - pub fn pump_events(&mut self, callback: F) -> PumpStatus + pub fn pump_events(&mut self, timeout: Option, callback: F) -> PumpStatus where F: FnMut(crate::event::Event<'_, T>, &RootELW, &mut ControlFlow), { - x11_or_wayland!(match self; EventLoop(evlp) => evlp.pump_events(callback)) + x11_or_wayland!(match self; EventLoop(evlp) => evlp.pump_events(timeout, callback)) } pub fn window_target(&self) -> &crate::event_loop::EventLoopWindowTarget { diff --git a/src/platform_impl/linux/wayland/event_loop/mod.rs b/src/platform_impl/linux/wayland/event_loop/mod.rs index 2a00afa626..053b0f26a9 100644 --- a/src/platform_impl/linux/wayland/event_loop/mod.rs +++ b/src/platform_impl/linux/wayland/event_loop/mod.rs @@ -154,7 +154,7 @@ impl EventLoop { } let exit = loop { - match self.pump_events_with_timeout(None, &mut event_handler) { + match self.pump_events(None, &mut event_handler) { PumpStatus::Exit(0) => { break Ok(()); } @@ -176,18 +176,7 @@ impl EventLoop { exit } - pub fn pump_events(&mut self, event_handler: F) -> PumpStatus - where - F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget, &mut ControlFlow), - { - self.pump_events_with_timeout(Some(Duration::ZERO), event_handler) - } - - fn pump_events_with_timeout( - &mut self, - timeout: Option, - mut callback: F, - ) -> PumpStatus + pub fn pump_events(&mut self, timeout: Option, mut callback: F) -> PumpStatus where F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget, &mut ControlFlow), { diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index 897fded395..be7c442f86 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -441,7 +441,7 @@ impl EventLoop { } let exit = loop { - match self.pump_events_with_timeout(None, &mut event_handler) { + match self.pump_events(None, &mut event_handler) { PumpStatus::Exit(0) => { break Ok(()); } @@ -466,18 +466,7 @@ impl EventLoop { exit } - pub fn pump_events(&mut self, event_handler: F) -> PumpStatus - where - F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), - { - self.pump_events_with_timeout(Some(Duration::ZERO), event_handler) - } - - fn pump_events_with_timeout( - &mut self, - timeout: Option, - mut callback: F, - ) -> PumpStatus + pub fn pump_events(&mut self, timeout: Option, mut callback: F) -> PumpStatus where F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), { diff --git a/src/platform_impl/macos/app_state.rs b/src/platform_impl/macos/app_state.rs index c75bfc6a96..18f727314a 100644 --- a/src/platform_impl/macos/app_state.rs +++ b/src/platform_impl/macos/app_state.rs @@ -632,7 +632,7 @@ impl AppState { Self::stop(); } HANDLER.update_start_time(); - let wait_timeout = HANDLER.wait_timeout(); // configured by pump_events_with_timeout + let wait_timeout = HANDLER.wait_timeout(); // configured by pump_events let app_timeout = match HANDLER.control_flow() { ControlFlow::Wait => None, ControlFlow::Poll | ControlFlow::ExitWithCode(_) => Some(Instant::now()), diff --git a/src/platform_impl/macos/event_loop.rs b/src/platform_impl/macos/event_loop.rs index 2623b3e45a..176c90e759 100644 --- a/src/platform_impl/macos/event_loop.rs +++ b/src/platform_impl/macos/event_loop.rs @@ -291,14 +291,7 @@ impl EventLoop { } } - pub fn pump_events(&mut self, callback: F) -> PumpStatus - where - F: FnMut(Event<'_, T>, &RootWindowTarget, &mut ControlFlow), - { - self.pump_events_with_timeout(Some(Duration::ZERO), callback) - } - - fn pump_events_with_timeout(&mut self, timeout: Option, callback: F) -> PumpStatus + pub fn pump_events(&mut self, timeout: Option, callback: F) -> PumpStatus where F: FnMut(Event<'_, T>, &RootWindowTarget, &mut ControlFlow), { diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 09103b818f..2b0de9236f 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -296,18 +296,7 @@ impl EventLoop { } } - pub fn pump_events(&mut self, event_handler: F) -> PumpStatus - where - F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), - { - self.pump_events_with_timeout(Some(Duration::ZERO), event_handler) - } - - fn pump_events_with_timeout( - &mut self, - timeout: Option, - mut event_handler: F, - ) -> PumpStatus + pub fn pump_events(&mut self, timeout: Option, mut event_handler: F) -> PumpStatus where F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), {