Skip to content

Commit

Permalink
api: unify error handling
Browse files Browse the repository at this point in the history
Make error infrastructure more backend agnostic and let backends
just forward the os errors opaquely.
  • Loading branch information
kchibisov authored Sep 6, 2024
1 parent 8db3e0e commit b674d20
Show file tree
Hide file tree
Showing 35 changed files with 496 additions and 619 deletions.
4 changes: 2 additions & 2 deletions examples/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use rwh_06::{DisplayHandle, HasDisplayHandle};
use softbuffer::{Context, Surface};
use winit::application::ApplicationHandler;
use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize};
use winit::error::ExternalError;
use winit::error::RequestError;
use winit::event::{DeviceEvent, DeviceId, Ime, MouseButton, MouseScrollDelta, WindowEvent};
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::keyboard::{Key, ModifiersState};
Expand Down Expand Up @@ -76,7 +76,7 @@ struct Application {
receiver: Receiver<Action>,
sender: Sender<Action>,
/// Custom cursors assets.
custom_cursors: Result<Vec<CustomCursor>, ExternalError>,
custom_cursors: Result<Vec<CustomCursor>, RequestError>,
/// Application icon.
icon: Icon,
windows: HashMap<WindowId, WindowState>,
Expand Down
202 changes: 110 additions & 92 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -1,45 +1,40 @@
use std::{error, fmt};
use std::error::Error;
use std::fmt::{self, Display};

use crate::platform_impl;

// 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.
NotSupported(NotSupportedError),
/// The operation was ignored.
Ignored,
/// The OS cannot perform the operation.
Os(OsError),
}

/// The error type for when the requested operation is not supported by the backend.
#[derive(Clone, Copy, Default, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct NotSupportedError {
_marker: (),
}

/// The error type for when the OS cannot perform the requested operation.
#[derive(Debug)]
pub struct OsError {
line: u32,
file: &'static str,
error: platform_impl::OsError,
}

/// A general error that may occur while running the Winit event loop
/// A general error that may occur while running or creating
/// the event loop.
#[derive(Debug)]
#[non_exhaustive]
pub enum EventLoopError {
/// 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-created.
RecreationAttempt,
/// Application has exit with an error status.
ExitFailure(i32),
/// Got unspecified OS-specific error during the request.
Os(OsError),
/// Creating the event loop with the requested configuration is not supported.
NotSupported(NotSupportedError),
}

impl fmt::Display for EventLoopError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::RecreationAttempt => write!(f, "EventLoop can't be recreated"),
Self::Os(err) => err.fmt(f),
Self::ExitFailure(status) => write!(f, "Exit Failure: {status}"),
Self::NotSupported(err) => err.fmt(f),
}
}
}

impl Error for EventLoopError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
if let Self::Os(err) = self {
err.source()
} else {
None
}
}
}

impl From<OsError> for EventLoopError {
Expand All @@ -48,85 +43,108 @@ impl From<OsError> for EventLoopError {
}
}

impl NotSupportedError {
#[inline]
#[allow(dead_code)]
pub(crate) fn new() -> NotSupportedError {
NotSupportedError { _marker: () }
impl From<NotSupportedError> for EventLoopError {
fn from(value: NotSupportedError) -> Self {
Self::NotSupported(value)
}
}

impl OsError {
#[allow(dead_code)]
pub(crate) fn new(line: u32, file: &'static str, error: platform_impl::OsError) -> OsError {
OsError { line, file, error }
}
/// A general error that may occur during a request to the windowing system.
#[derive(Debug)]
#[non_exhaustive]
pub enum RequestError {
/// The request is not supported.
NotSupported(NotSupportedError),
/// The request was ignored by the operating system.
Ignored,
/// Got unspecified OS specific error during the request.
Os(OsError),
}

#[allow(unused_macros)]
macro_rules! os_error {
($error:expr) => {{
crate::error::OsError::new(line!(), file!(), $error)
}};
impl Display for RequestError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NotSupported(err) => err.fmt(f),
Self::Ignored => write!(f, "The request was ignored"),
Self::Os(err) => err.fmt(f),
}
}
}
impl Error for RequestError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
if let Self::Os(err) = self {
err.source()
} else {
None
}
}
}

impl fmt::Display for OsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
f.pad(&format!("os error at {}:{}: {}", self.file, self.line, self.error))
impl From<NotSupportedError> for RequestError {
fn from(value: NotSupportedError) -> Self {
Self::NotSupported(value)
}
}

impl fmt::Display for ExternalError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match self {
ExternalError::NotSupported(e) => e.fmt(f),
ExternalError::Ignored => write!(f, "Operation was ignored"),
ExternalError::Os(e) => e.fmt(f),
}
impl From<OsError> for RequestError {
fn from(value: OsError) -> Self {
Self::Os(value)
}
}

impl fmt::Debug for NotSupportedError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
f.debug_struct("NotSupportedError").finish()
/// The requested operation is not supported.
#[derive(Debug)]
pub struct NotSupportedError {
/// The reason why a certain operation is not supported.
reason: &'static str,
}

impl NotSupportedError {
pub(crate) fn new(reason: &'static str) -> Self {
Self { reason }
}
}

impl fmt::Display for NotSupportedError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
f.pad("the requested operation is not supported by Winit")
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Operation is not supported: {}", self.reason)
}
}
impl Error for NotSupportedError {}

impl fmt::Display for EventLoopError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match self {
EventLoopError::RecreationAttempt => write!(f, "EventLoop can't be recreated"),
EventLoopError::NotSupported(e) => e.fmt(f),
EventLoopError::Os(e) => e.fmt(f),
EventLoopError::ExitFailure(status) => write!(f, "Exit Failure: {status}"),
}
/// Unclassified error from the OS.
#[derive(Debug)]
pub struct OsError {
line: u32,
file: &'static str,
error: Box<dyn Error + Send + Sync + 'static>,
}

impl OsError {
#[allow(dead_code)]
pub(crate) fn new(
line: u32,
file: &'static str,
error: impl Into<Box<dyn Error + Send + Sync + 'static>>,
) -> Self {
Self { line, file, error: error.into() }
}
}

impl error::Error for OsError {}
impl error::Error for ExternalError {}
impl error::Error for NotSupportedError {}
impl error::Error for EventLoopError {}

#[cfg(test)]
#[allow(clippy::redundant_clone)]
mod tests {
use super::*;

// Eat attributes for testing
#[test]
fn ensure_fmt_does_not_panic() {
let _ = format!("{:?}, {}", NotSupportedError::new(), NotSupportedError::new().clone());
let _ = format!(
"{:?}, {}",
ExternalError::NotSupported(NotSupportedError::new()),
ExternalError::NotSupported(NotSupportedError::new())
);
impl Display for OsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.pad(&format!("os error at {}:{}: {}", self.file, self.line, self.error))
}
}
impl Error for OsError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
Some(self.error.as_ref())
}
}

#[allow(unused_macros)]
macro_rules! os_error {
($error:expr) => {{
crate::error::OsError::new(line!(), file!(), $error)
}};
}
6 changes: 3 additions & 3 deletions src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ use smol_str::SmolStr;
use web_time::Instant;

use crate::dpi::{PhysicalPosition, PhysicalSize};
use crate::error::ExternalError;
use crate::error::RequestError;
use crate::event_loop::AsyncRequestSerial;
use crate::keyboard::{self, ModifiersKeyState, ModifiersKeys, ModifiersState};
use crate::platform_impl;
Expand Down Expand Up @@ -1016,12 +1016,12 @@ impl SurfaceSizeWriter {
pub fn request_surface_size(
&mut self,
new_surface_size: PhysicalSize<u32>,
) -> Result<(), ExternalError> {
) -> Result<(), RequestError> {
if let Some(inner) = self.new_surface_size.upgrade() {
*inner.lock().unwrap() = new_surface_size;
Ok(())
} else {
Err(ExternalError::Ignored)
Err(RequestError::Ignored)
}
}
}
Expand Down
8 changes: 4 additions & 4 deletions src/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use std::time::{Duration, Instant};
use web_time::{Duration, Instant};

use crate::application::ApplicationHandler;
use crate::error::{EventLoopError, ExternalError, OsError};
use crate::error::{EventLoopError, RequestError};
use crate::monitor::MonitorHandle;
use crate::platform_impl;
use crate::utils::AsAny;
Expand Down Expand Up @@ -268,7 +268,7 @@ impl EventLoop {
pub fn create_custom_cursor(
&self,
custom_cursor: CustomCursorSource,
) -> Result<CustomCursor, ExternalError> {
) -> Result<CustomCursor, RequestError> {
self.event_loop.window_target().create_custom_cursor(custom_cursor)
}
}
Expand Down Expand Up @@ -324,7 +324,7 @@ pub trait ActiveEventLoop: AsAny {
fn create_window(
&self,
window_attributes: WindowAttributes,
) -> Result<Box<dyn Window>, OsError>;
) -> Result<Box<dyn Window>, RequestError>;

/// Create custom cursor.
///
Expand All @@ -334,7 +334,7 @@ pub trait ActiveEventLoop: AsAny {
fn create_custom_cursor(
&self,
custom_cursor: CustomCursorSource,
) -> Result<CustomCursor, ExternalError>;
) -> Result<CustomCursor, RequestError>;

/// Returns the list of all the monitors available on the system.
///
Expand Down
8 changes: 4 additions & 4 deletions src/platform/startup_notify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

use std::env;

use crate::error::NotSupportedError;
use crate::error::{NotSupportedError, RequestError};
use crate::event_loop::{ActiveEventLoop, AsyncRequestSerial};
#[cfg(wayland_platform)]
use crate::platform::wayland::ActiveEventLoopExtWayland;
Expand All @@ -46,7 +46,7 @@ pub trait WindowExtStartupNotify {
/// Request a new activation token.
///
/// The token will be delivered inside
fn request_activation_token(&self) -> Result<AsyncRequestSerial, NotSupportedError>;
fn request_activation_token(&self) -> Result<AsyncRequestSerial, RequestError>;
}

pub trait WindowAttributesExtStartupNotify {
Expand All @@ -73,7 +73,7 @@ impl EventLoopExtStartupNotify for dyn ActiveEventLoop + '_ {
}

impl WindowExtStartupNotify for dyn Window + '_ {
fn request_activation_token(&self) -> Result<AsyncRequestSerial, NotSupportedError> {
fn request_activation_token(&self) -> Result<AsyncRequestSerial, RequestError> {
#[cfg(wayland_platform)]
if let Some(window) = self.as_any().downcast_ref::<crate::platform_impl::wayland::Window>()
{
Expand All @@ -87,7 +87,7 @@ impl WindowExtStartupNotify for dyn Window + '_ {
return window.request_activation_token();
}

Err(NotSupportedError::new())
Err(NotSupportedError::new("startup notify is not supported").into())
}
}

Expand Down
Loading

0 comments on commit b674d20

Please sign in to comment.