summaryrefslogtreecommitdiff
path: root/engine-ecs/src/error.rs
diff options
context:
space:
mode:
Diffstat (limited to 'engine-ecs/src/error.rs')
-rw-r--r--engine-ecs/src/error.rs252
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");
+ }
+ }
+}