diff options
Diffstat (limited to 'engine-ecs/src/error.rs')
| -rw-r--r-- | engine-ecs/src/error.rs | 252 |
1 files changed, 252 insertions, 0 deletions
diff --git a/engine-ecs/src/error.rs b/engine-ecs/src/error.rs new file mode 100644 index 0000000..5ce5632 --- /dev/null +++ b/engine-ecs/src/error.rs @@ -0,0 +1,252 @@ +use std::fmt::{Debug, Display}; + +use crate::World; + +#[macro_export] +macro_rules! error { + ($lit: literal) => { + $crate::error::Error::message($lit) + }; + + ($lit: literal, $($tt: tt)*) => { + $crate::error::Error::message(std::format!($lit, $($tt)*)) + }; + + ($err: expr) => { + $crate::error::Error::from($err) + }; +} + +pub struct Error +{ + inner: anyhow::Error, +} + +impl Error +{ + pub fn new<Err>(err: Err) -> Self + where + Err: std::error::Error + Send + Sync + 'static, + { + Self::from(err) + } + + pub fn message<Message>(message: Message) -> Self + where + Message: Display + Debug + Send + Sync + 'static, + { + Self { inner: anyhow::Error::msg(message) } + } + + pub fn context<Ctx>(self, context: Ctx) -> Error + where + Ctx: Display + Send + Sync + 'static, + { + Self { inner: self.inner.context(context) } + } + + /// Retrieves the message text of a system-defined Windows Win32 error code and + /// returns it as a `Error`. If this fails (for example if the error code is invalid), + /// the returned `Error` contains the error code number. + #[cfg(windows)] + pub fn from_system_defined_win32_error(error: u32) -> Self + { + use std::ffi::CStr; + use std::ptr::null; + + use windows_sys::Win32::System::Diagnostics::Debug::{ + FormatMessageA, + FORMAT_MESSAGE_FROM_SYSTEM, + FORMAT_MESSAGE_IGNORE_INSERTS, + }; + + let mut buffer = vec![0u8; 256]; + + let len = unsafe { + FormatMessageA( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + null(), + error, + 0, + buffer.as_mut_ptr(), + (buffer.len() - 1) as u32, + null(), + ) + } as usize; + + if len == 0 { + return Self::message(format!("Win32 error {error}")); + } + + let buf = unsafe { CStr::from_bytes_with_nul_unchecked(&buffer[..len + 1]) }; + + Self::message(buf.to_string_lossy().trim().to_owned()) + } +} + +impl Debug for Error +{ + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result + { + <anyhow::Error as Debug>::fmt(&self.inner, formatter) + } +} + +impl Display for Error +{ + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result + { + <anyhow::Error as Display>::fmt(&self.inner, formatter) + } +} + +impl<Err> From<Err> for Error +where + Err: std::error::Error + Send + Sync + 'static, +{ + fn from(err: Err) -> Self + { + Self { inner: err.into() } + } +} + +pub trait Context<T> +{ + fn context<Ctx>(self, context: Ctx) -> Result<T, Error> + where + Ctx: Display + Send + Sync + 'static; + + fn with_context<Ctx>(self, context_func: impl FnOnce() -> Ctx) -> Result<T, Error> + where + Ctx: Display + Send + Sync + 'static; +} + +impl<T, Err> Context<T> for Result<T, Err> +where + Err: std::error::Error + Send + Sync + 'static, +{ + fn context<Ctx>(self, context: Ctx) -> Result<T, Error> + where + Ctx: Display + Send + Sync + 'static, + { + self.map_err(|err| Error::from(err).context(context)) + } + + fn with_context<Ctx>(self, context_func: impl FnOnce() -> Ctx) -> Result<T, Error> + where + Ctx: Display + Send + Sync + 'static, + { + self.map_err(|err| Error::from(err).context(context_func())) + } +} + +impl<T> Context<T> for Result<T, Error> +{ + fn context<Ctx>(self, context: Ctx) -> Result<T, Error> + where + Ctx: Display + Send + Sync + 'static, + { + self.map_err(|err| err.context(context)) + } + + fn with_context<Ctx>(self, context_func: impl FnOnce() -> Ctx) -> Result<T, Error> + where + Ctx: Display + Send + Sync + 'static, + { + self.map_err(|err| err.context(context_func())) + } +} + +pub type HandlerFn = fn(&World, Error, Metadata); + +/// Error metadata. +#[derive(Debug)] +pub struct Metadata +{ + pub source_name: &'static str, + pub source_kind: SourceKind, +} + +/// Error source kind. +#[derive(Debug)] +#[non_exhaustive] +pub enum SourceKind +{ + System, + Observer, +} + +impl Display for SourceKind +{ + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result + { + match self { + SourceKind::System => formatter.write_str("system"), + SourceKind::Observer => formatter.write_str("observer"), + } + } +} + +pub fn err_handler_panic(_world: &World, err: Error, err_metadata: Metadata) +{ + std::panic::panic_any(err.context(format!( + "Error occurred in {} '{}'", + err_metadata.source_kind, err_metadata.source_name + ))); +} + +pub fn err_handler_log_error(_world: &World, err: Error, err_metadata: Metadata) +{ + tracing::error!( + "Error occurred in {} '{}': {err:#}", + err_metadata.source_kind, + err_metadata.source_name + ); +} + +pub(crate) fn set_panic_hook() +{ + let previous_panic_hook = std::panic::take_hook(); + + std::panic::set_hook(Box::new(move |info| { + let Some(err) = info.payload().downcast_ref::<Error>() else { + return previous_panic_hook(info); + }; + + print!( + "Thread '{}' ({:?}) panicked", + std::thread::current().name().unwrap_or("<unnamed>"), + get_current_os_thread_id() + ); + + if let Some(location) = info.location() { + print!(" at {location}"); + } + + print!("\n{err:?}"); + + if err.inner.backtrace().status() == std::backtrace::BacktraceStatus::Disabled { + print!(concat!( + "\nnote: run with `RUST_BACKTRACE=1` environment variable ", + "to display a backtrace" + )); + } + + println!(""); + })); +} + +fn get_current_os_thread_id() -> u64 +{ + cfg_select! { + target_os = "linux" => { + (unsafe { libc::gettid() }) as u64 + } + windows => { + (unsafe { windows_sys::Win32::System::Threading::GetCurrentThreadId() }) as u64 + } + _ => { + compile_error!("Unsupported target OS"); + } + } +} |
