mirror of
https://github.com/danbulant/appit
synced 2026-05-19 04:08:34 +00:00
Generalized app error handling
This commit is contained in:
parent
662cebf193
commit
be6918d5eb
4 changed files with 115 additions and 13 deletions
|
|
@ -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<Self,
|
||||
AppMessage::Error>`.
|
||||
|
||||
To install a hander, use `PendingApp::on_error`.
|
||||
|
||||
### Added
|
||||
|
||||
|
|
|
|||
84
src/lib.rs
84
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<AppMessage>,
|
||||
on_startup: Vec<Box<StartupClosure<AppMessage>>>,
|
||||
pending_windows: Vec<PendingWindow<AppMessage>>,
|
||||
on_error: Option<Box<dyn FnMut(AppMessage::Error)>>,
|
||||
}
|
||||
|
||||
struct PendingWindow<AppMessage>
|
||||
|
|
@ -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<F>(&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<AppMessage>,
|
||||
on_startup: Vec<Box<StartupClosure<AppMessage>>>,
|
||||
pending_windows: Vec<PendingWindow<AppMessage>>,
|
||||
on_error: Option<Box<dyn FnMut(AppMessage::Error)>>,
|
||||
}
|
||||
|
||||
impl<AppMessage> ApplicationHandler<EventLoopMessage<AppMessage>> for RunningApp<AppMessage>
|
||||
|
|
@ -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<AppMessage::Error>> {
|
||||
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<AppMessage::Response>;
|
||||
|
||||
/// 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<AppMessage::Error>>;
|
||||
}
|
||||
|
||||
/// 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<AppMessage> Application<AppMessage> for PendingApp<AppMessage>
|
||||
|
|
@ -508,6 +562,16 @@ where
|
|||
ExecutingApp::new(&self.running.windows, &self.event_loop),
|
||||
))
|
||||
}
|
||||
|
||||
fn send_error(
|
||||
&mut self,
|
||||
error: <AppMessage as Message>::Error,
|
||||
) -> Result<(), EventLoopClosed<<AppMessage as Message>::Error>> {
|
||||
if let Some(on_error) = &mut self.on_error {
|
||||
on_error(error);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<AppMessage> private::ApplicationSealed<AppMessage> for PendingApp<AppMessage>
|
||||
|
|
@ -541,6 +605,14 @@ where
|
|||
let this: &Self = self;
|
||||
this.send(message)
|
||||
}
|
||||
|
||||
fn send_error(
|
||||
&mut self,
|
||||
error: <AppMessage as Message>::Error,
|
||||
) -> Result<(), EventLoopClosed<<AppMessage as Message>::Error>> {
|
||||
let this: &Self = self;
|
||||
this.send_error(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl<AppMessage> private::ApplicationSealed<AppMessage> for App<AppMessage>
|
||||
|
|
@ -735,16 +807,16 @@ struct OpenWindow<User> {
|
|||
}
|
||||
|
||||
/// A guard preventing an [`App`] from shutting down.
|
||||
pub struct ShutdownGuard<Message>
|
||||
pub struct ShutdownGuard<AppMessage>
|
||||
where
|
||||
Message: crate::Message,
|
||||
AppMessage: Message,
|
||||
{
|
||||
app: App<Message>,
|
||||
app: App<AppMessage>,
|
||||
}
|
||||
|
||||
impl<Message> Drop for ShutdownGuard<Message>
|
||||
impl<AppMessage> Drop for ShutdownGuard<AppMessage>
|
||||
where
|
||||
Message: crate::Message,
|
||||
AppMessage: Message,
|
||||
{
|
||||
fn drop(&mut self) {
|
||||
let _ = self.app.proxy.send_event(EventLoopMessage::AllowShutdown);
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ where
|
|||
},
|
||||
PreventShutdown,
|
||||
AllowShutdown,
|
||||
Error(AppMessage::Error),
|
||||
#[cfg(all(target_os = "linux", feature = "xdg"))]
|
||||
ThemeChanged(Theme),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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: <AppMessage as Message>::Error,
|
||||
) -> Result<(), winit::event_loop::EventLoopClosed<<AppMessage as Message>::Error>> {
|
||||
self.app.send_error(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl<AppMessage> private::ApplicationSealed<AppMessage> for RunningWindow<AppMessage>
|
||||
|
|
@ -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<AppMessage>, 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<AppMessage>,
|
||||
context: Self::Context,
|
||||
) -> Result<Self, AppMessage::Error>;
|
||||
|
||||
/// Displays the contents of the window.
|
||||
fn redraw(&mut self, window: &mut RunningWindow<AppMessage>);
|
||||
|
|
|
|||
Loading…
Reference in a new issue