mirror of
https://github.com/danbulant/appit
synced 2026-05-24 12:26:14 +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
|
underlying window is resized immediately. Notably, this happens on Wayland but
|
||||||
may happen on some other platforms as well.
|
may happen on some other platforms as well.
|
||||||
- `RunningWindow::winit` now returns an `Arc` wrapped winit window.
|
- `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
|
### Added
|
||||||
|
|
||||||
|
|
|
||||||
84
src/lib.rs
84
src/lib.rs
|
|
@ -11,6 +11,7 @@ mod window;
|
||||||
mod xdg;
|
mod xdg;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::convert::Infallible;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
@ -24,7 +25,7 @@ use winit::application::ApplicationHandler;
|
||||||
use winit::error::{EventLoopError, OsError};
|
use winit::error::{EventLoopError, OsError};
|
||||||
use winit::event::StartCause;
|
use winit::event::StartCause;
|
||||||
use winit::event_loop::{
|
use winit::event_loop::{
|
||||||
ActiveEventLoop, ControlFlow, EventLoop, EventLoopProxy, OwnedDisplayHandle,
|
ActiveEventLoop, ControlFlow, EventLoop, EventLoopClosed, EventLoopProxy, OwnedDisplayHandle,
|
||||||
};
|
};
|
||||||
use winit::monitor::MonitorHandle;
|
use winit::monitor::MonitorHandle;
|
||||||
use winit::window::WindowId;
|
use winit::window::WindowId;
|
||||||
|
|
@ -143,6 +144,7 @@ where
|
||||||
running: App<AppMessage>,
|
running: App<AppMessage>,
|
||||||
on_startup: Vec<Box<StartupClosure<AppMessage>>>,
|
on_startup: Vec<Box<StartupClosure<AppMessage>>>,
|
||||||
pending_windows: Vec<PendingWindow<AppMessage>>,
|
pending_windows: Vec<PendingWindow<AppMessage>>,
|
||||||
|
on_error: Option<Box<dyn FnMut(AppMessage::Error)>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PendingWindow<AppMessage>
|
struct PendingWindow<AppMessage>
|
||||||
|
|
@ -197,9 +199,19 @@ where
|
||||||
message_callback: Box::new(event_callback),
|
message_callback: Box::new(event_callback),
|
||||||
on_startup: Vec::new(),
|
on_startup: Vec::new(),
|
||||||
pending_windows: 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.
|
/// Executes `on_startup` once the app event loop has started.
|
||||||
///
|
///
|
||||||
/// This is useful because some information provided by winit is only
|
/// This is useful because some information provided by winit is only
|
||||||
|
|
@ -228,6 +240,7 @@ where
|
||||||
running,
|
running,
|
||||||
on_startup,
|
on_startup,
|
||||||
pending_windows,
|
pending_windows,
|
||||||
|
on_error,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
#[cfg(all(target_os = "linux", feature = "xdg"))]
|
#[cfg(all(target_os = "linux", feature = "xdg"))]
|
||||||
|
|
@ -238,6 +251,7 @@ where
|
||||||
running,
|
running,
|
||||||
on_startup,
|
on_startup,
|
||||||
pending_windows,
|
pending_windows,
|
||||||
|
on_error,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -250,6 +264,7 @@ where
|
||||||
running: App<AppMessage>,
|
running: App<AppMessage>,
|
||||||
on_startup: Vec<Box<StartupClosure<AppMessage>>>,
|
on_startup: Vec<Box<StartupClosure<AppMessage>>>,
|
||||||
pending_windows: Vec<PendingWindow<AppMessage>>,
|
pending_windows: Vec<PendingWindow<AppMessage>>,
|
||||||
|
on_error: Option<Box<dyn FnMut(AppMessage::Error)>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<AppMessage> ApplicationHandler<EventLoopMessage<AppMessage>> for RunningApp<AppMessage>
|
impl<AppMessage> ApplicationHandler<EventLoopMessage<AppMessage>> for RunningApp<AppMessage>
|
||||||
|
|
@ -340,6 +355,11 @@ where
|
||||||
exit(0)
|
exit(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
EventLoopMessage::Error(err) => {
|
||||||
|
if let Some(handler) = &mut self.on_error {
|
||||||
|
handler(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
#[cfg(all(target_os = "linux", feature = "xdg"))]
|
#[cfg(all(target_os = "linux", feature = "xdg"))]
|
||||||
EventLoopMessage::ThemeChanged(theme) => {
|
EventLoopMessage::ThemeChanged(theme) => {
|
||||||
self.running.windows.theme_changed(theme);
|
self.running.windows.theme_changed(theme);
|
||||||
|
|
@ -385,6 +405,26 @@ where
|
||||||
response_receiver.recv().ok()
|
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.
|
/// Creates a guard that prevents this app from shutting down.
|
||||||
///
|
///
|
||||||
/// If the app is not currently running, this function returns None.
|
/// 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
|
/// running. Otherwise, this function will block until the result of the
|
||||||
/// callback has been received.
|
/// callback has been received.
|
||||||
fn send(&mut self, message: AppMessage) -> Option<AppMessage::Response>;
|
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.
|
/// A type that contains a reference to an [`Application`] implementor.
|
||||||
|
|
@ -487,11 +537,15 @@ pub trait Message: Send + 'static {
|
||||||
type Window: Send;
|
type Window: Send;
|
||||||
/// The type returned when responding to this message.
|
/// The type returned when responding to this message.
|
||||||
type Response: Send;
|
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 () {
|
impl Message for () {
|
||||||
type Response = ();
|
type Response = ();
|
||||||
type Window = ();
|
type Window = ();
|
||||||
|
type Error = Infallible;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<AppMessage> Application<AppMessage> for PendingApp<AppMessage>
|
impl<AppMessage> Application<AppMessage> for PendingApp<AppMessage>
|
||||||
|
|
@ -508,6 +562,16 @@ where
|
||||||
ExecutingApp::new(&self.running.windows, &self.event_loop),
|
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>
|
impl<AppMessage> private::ApplicationSealed<AppMessage> for PendingApp<AppMessage>
|
||||||
|
|
@ -541,6 +605,14 @@ where
|
||||||
let this: &Self = self;
|
let this: &Self = self;
|
||||||
this.send(message)
|
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>
|
impl<AppMessage> private::ApplicationSealed<AppMessage> for App<AppMessage>
|
||||||
|
|
@ -735,16 +807,16 @@ struct OpenWindow<User> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A guard preventing an [`App`] from shutting down.
|
/// A guard preventing an [`App`] from shutting down.
|
||||||
pub struct ShutdownGuard<Message>
|
pub struct ShutdownGuard<AppMessage>
|
||||||
where
|
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
|
where
|
||||||
Message: crate::Message,
|
AppMessage: Message,
|
||||||
{
|
{
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
let _ = self.app.proxy.send_event(EventLoopMessage::AllowShutdown);
|
let _ = self.app.proxy.send_event(EventLoopMessage::AllowShutdown);
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,7 @@ where
|
||||||
},
|
},
|
||||||
PreventShutdown,
|
PreventShutdown,
|
||||||
AllowShutdown,
|
AllowShutdown,
|
||||||
|
Error(AppMessage::Error),
|
||||||
#[cfg(all(target_os = "linux", feature = "xdg"))]
|
#[cfg(all(target_os = "linux", feature = "xdg"))]
|
||||||
ThemeChanged(Theme),
|
ThemeChanged(Theme),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -481,7 +481,7 @@ where
|
||||||
// recovery for a panic inside of a window, the only question is whether
|
// recovery for a panic inside of a window, the only question is whether
|
||||||
// the entire app panics or not.
|
// the entire app panics or not.
|
||||||
let possible_panic = std::panic::catch_unwind(AssertUnwindSafe(move || {
|
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
|
// When it takes a while for a graphics stack to initialize, we can
|
||||||
// avoid showing a blank window due to our multi-threaded event
|
// 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
|
// behavior is dropped. This upholds the requirement for RawWindowHandle
|
||||||
// by making sure that any resources required by the behavior have had a
|
// by making sure that any resources required by the behavior have had a
|
||||||
// chance to be freed.
|
// chance to be freed.
|
||||||
|
Ok(())
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if let Err(panic) = possible_panic {
|
match possible_panic {
|
||||||
let _result = proxy.send_event(EventLoopMessage::WindowPanic(window_id));
|
Ok(Ok(())) => {
|
||||||
std::panic::resume_unwind(panic)
|
let _result = proxy.send_event(EventLoopMessage::CloseWindow(window_id));
|
||||||
} else {
|
}
|
||||||
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()?;
|
.ok()?;
|
||||||
self.responses.1.recv().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>
|
impl<AppMessage> private::ApplicationSealed<AppMessage> for RunningWindow<AppMessage>
|
||||||
|
|
@ -990,7 +1003,15 @@ where
|
||||||
|
|
||||||
/// Returns a new instance of this behavior after initializing itself with
|
/// Returns a new instance of this behavior after initializing itself with
|
||||||
/// the window and context.
|
/// 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.
|
/// Displays the contents of the window.
|
||||||
fn redraw(&mut self, window: &mut RunningWindow<AppMessage>);
|
fn redraw(&mut self, window: &mut RunningWindow<AppMessage>);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue