diff --git a/CHANGELOG.md b/CHANGELOG.md index 464d5fd..7791be5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 underlying window is resized immediately. Notably, this happens on Wayland but may happen on some other platforms as well. - `RunningWindow::winit` now returns an `Arc` wrapped winit window. +- `AppMessage::Error` is a new associated type that is used to communicate + errors from window threads to the event loop. To facilitate this + communication, `App::send_error` and `Application::send_error` have been added. + + Additionally, `WindowBehavior::Initialize` now returns a `Result`. + + To install a hander, use `PendingApp::on_error`. ### Added diff --git a/src/lib.rs b/src/lib.rs index 5efd8a2..ac7bd51 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,7 @@ mod window; mod xdg; use std::collections::HashMap; +use std::convert::Infallible; use std::ops::Deref; use std::process::exit; use std::sync::atomic::{AtomicBool, Ordering}; @@ -24,7 +25,7 @@ use winit::application::ApplicationHandler; use winit::error::{EventLoopError, OsError}; use winit::event::StartCause; use winit::event_loop::{ - ActiveEventLoop, ControlFlow, EventLoop, EventLoopProxy, OwnedDisplayHandle, + ActiveEventLoop, ControlFlow, EventLoop, EventLoopClosed, EventLoopProxy, OwnedDisplayHandle, }; use winit::monitor::MonitorHandle; use winit::window::WindowId; @@ -143,6 +144,7 @@ where running: App, on_startup: Vec>>, pending_windows: Vec>, + on_error: Option>, } struct PendingWindow @@ -197,9 +199,19 @@ where message_callback: Box::new(event_callback), on_startup: Vec::new(), pending_windows: Vec::new(), + on_error: None, } } + /// Sets a handler that is invoked when an app receives an + /// [`AppTypes::Error`]. + pub fn on_error(&mut self, on_error: F) + where + F: FnMut(AppMessage::Error) + 'static, + { + self.on_error = Some(Box::new(on_error)); + } + /// Executes `on_startup` once the app event loop has started. /// /// This is useful because some information provided by winit is only @@ -228,6 +240,7 @@ where running, on_startup, pending_windows, + on_error, } = self; #[cfg(all(target_os = "linux", feature = "xdg"))] @@ -238,6 +251,7 @@ where running, on_startup, pending_windows, + on_error, }) } } @@ -250,6 +264,7 @@ where running: App, on_startup: Vec>>, pending_windows: Vec>, + on_error: Option>, } impl ApplicationHandler> for RunningApp @@ -340,6 +355,11 @@ where exit(0) } } + EventLoopMessage::Error(err) => { + if let Some(handler) = &mut self.on_error { + handler(err); + } + } #[cfg(all(target_os = "linux", feature = "xdg"))] EventLoopMessage::ThemeChanged(theme) => { self.running.windows.theme_changed(theme); @@ -385,6 +405,26 @@ where response_receiver.recv().ok() } + /// Sends an error to the event loop. + /// + /// # Errors + /// + /// Returns an error if the event loop is not currently running. + pub fn send_error( + &self, + error: AppMessage::Error, + ) -> Result<(), EventLoopClosed> { + if !self.started.load(Ordering::Relaxed) { + return Err(EventLoopClosed(error)); + } + + match self.proxy.send_event(EventLoopMessage::Error(error)) { + Ok(()) => Ok(()), + Err(EventLoopClosed(EventLoopMessage::Error(err))) => Err(EventLoopClosed(err)), + _ => unreachable!("returned value should be the same"), + } + } + /// Creates a guard that prevents this app from shutting down. /// /// If the app is not currently running, this function returns None. @@ -428,6 +468,16 @@ where /// running. Otherwise, this function will block until the result of the /// callback has been received. fn send(&mut self, message: AppMessage) -> Option; + + /// Sends an error to the event loop. + /// + /// # Errors + /// + /// Returns an error if the event loop is not currently running. + fn send_error( + &mut self, + error: AppMessage::Error, + ) -> Result<(), EventLoopClosed>; } /// A type that contains a reference to an [`Application`] implementor. @@ -487,11 +537,15 @@ pub trait Message: Send + 'static { type Window: Send; /// The type returned when responding to this message. type Response: Send; + /// The type that is communicated when an error occurs that the event + /// loop/app should handle. + type Error: Send; } impl Message for () { type Response = (); type Window = (); + type Error = Infallible; } impl Application for PendingApp @@ -508,6 +562,16 @@ where ExecutingApp::new(&self.running.windows, &self.event_loop), )) } + + fn send_error( + &mut self, + error: ::Error, + ) -> Result<(), EventLoopClosed<::Error>> { + if let Some(on_error) = &mut self.on_error { + on_error(error); + } + Ok(()) + } } impl private::ApplicationSealed for PendingApp @@ -541,6 +605,14 @@ where let this: &Self = self; this.send(message) } + + fn send_error( + &mut self, + error: ::Error, + ) -> Result<(), EventLoopClosed<::Error>> { + let this: &Self = self; + this.send_error(error) + } } impl private::ApplicationSealed for App @@ -735,16 +807,16 @@ struct OpenWindow { } /// A guard preventing an [`App`] from shutting down. -pub struct ShutdownGuard +pub struct ShutdownGuard where - Message: crate::Message, + AppMessage: Message, { - app: App, + app: App, } -impl Drop for ShutdownGuard +impl Drop for ShutdownGuard where - Message: crate::Message, + AppMessage: Message, { fn drop(&mut self) { let _ = self.app.proxy.send_event(EventLoopMessage::AllowShutdown); diff --git a/src/private.rs b/src/private.rs index 68cd161..4d20933 100644 --- a/src/private.rs +++ b/src/private.rs @@ -62,6 +62,7 @@ where }, PreventShutdown, AllowShutdown, + Error(AppMessage::Error), #[cfg(all(target_os = "linux", feature = "xdg"))] ThemeChanged(Theme), } diff --git a/src/window.rs b/src/window.rs index 8f70dfe..0353f1b 100644 --- a/src/window.rs +++ b/src/window.rs @@ -481,7 +481,7 @@ where // recovery for a panic inside of a window, the only question is whether // the entire app panics or not. let possible_panic = std::panic::catch_unwind(AssertUnwindSafe(move || { - let mut behavior = Behavior::initialize(&mut self, context); + let mut behavior = Behavior::initialize(&mut self, context)?; // When it takes a while for a graphics stack to initialize, we can // avoid showing a blank window due to our multi-threaded event @@ -513,13 +513,20 @@ where // behavior is dropped. This upholds the requirement for RawWindowHandle // by making sure that any resources required by the behavior have had a // chance to be freed. + Ok(()) })); - if let Err(panic) = possible_panic { - let _result = proxy.send_event(EventLoopMessage::WindowPanic(window_id)); - std::panic::resume_unwind(panic) - } else { - let _result = proxy.send_event(EventLoopMessage::CloseWindow(window_id)); + match possible_panic { + Ok(Ok(())) => { + let _result = proxy.send_event(EventLoopMessage::CloseWindow(window_id)); + } + Ok(Err(init_error)) => { + let _result = proxy.send_event(EventLoopMessage::Error(init_error)); + } + Err(panic) => { + let _result = proxy.send_event(EventLoopMessage::WindowPanic(window_id)); + std::panic::resume_unwind(panic) + } } } @@ -801,6 +808,12 @@ where .ok()?; self.responses.1.recv().ok() } + fn send_error( + &mut self, + error: ::Error, + ) -> Result<(), winit::event_loop::EventLoopClosed<::Error>> { + self.app.send_error(error) + } } impl private::ApplicationSealed for RunningWindow @@ -990,7 +1003,15 @@ where /// Returns a new instance of this behavior after initializing itself with /// the window and context. - fn initialize(window: &mut RunningWindow, context: Self::Context) -> Self; + /// + /// # Errors + /// + /// If the window cannot be initialized, this function should return the + /// cause of the failure. + fn initialize( + window: &mut RunningWindow, + context: Self::Context, + ) -> Result; /// Displays the contents of the window. fn redraw(&mut self, window: &mut RunningWindow);