summaryrefslogtreecommitdiff
path: root/engine-ecs/src/error.rs
diff options
context:
space:
mode:
authorHampusM <hampus@hampusmat.com>2026-07-06 21:57:52 +0200
committerHampusM <hampus@hampusmat.com>2026-07-06 22:01:40 +0200
commit5a07a635486c3a072e6578fbb725da1bab9a774c (patch)
tree2f8ef259c792e85be2b5e88e769baec73e7b7c58 /engine-ecs/src/error.rs
parent19276ab6030250bc1c2e034bd8ccf72db61fa21c (diff)
feat(engine): make context addable to errors & results
Diffstat (limited to 'engine-ecs/src/error.rs')
-rw-r--r--engine-ecs/src/error.rs266
1 files changed, 104 insertions, 162 deletions
diff --git a/engine-ecs/src/error.rs b/engine-ecs/src/error.rs
index c3da052..2654dde 100644
--- a/engine-ecs/src/error.rs
+++ b/engine-ecs/src/error.rs
@@ -1,17 +1,15 @@
-use std::fmt::{Debug, Display, Write as _};
-
-use backtrace::Backtrace;
+use std::fmt::{Debug, Display};
use crate::World;
#[macro_export]
macro_rules! error {
($lit: literal) => {
- $crate::error::Error::from($lit)
+ $crate::error::Error::message($lit)
};
($lit: literal, $($tt: tt)*) => {
- $crate::error::Error::from(std::format!($lit, $($tt)*))
+ $crate::error::Error::message(std::format!($lit, $($tt)*))
};
($err: expr) => {
@@ -21,24 +19,30 @@ macro_rules! error {
pub struct Error
{
- inner: Box<dyn std::error::Error + Send + Sync>,
- backtrace: Backtrace,
+ inner: anyhow::Error,
}
impl Error
{
- pub fn resolve_backtrace(&mut self)
+ pub fn new<Err>(err: Err) -> Self
+ where
+ Err: std::error::Error + Send + Sync + 'static,
{
- self.backtrace.resolve();
+ Self::from(err)
}
- fn is_backtrace_resolved(&self) -> bool
+ pub fn message<Message>(message: Message) -> Self
+ where
+ Message: Display + Debug + Send + Sync + 'static,
{
- let Some(first_frame) = self.backtrace.frames().first() else {
- return false;
- };
+ Self { inner: anyhow::Error::msg(message) }
+ }
- !first_frame.symbols().is_empty()
+ pub fn context<Ctx>(self, context: Ctx) -> Error
+ where
+ Ctx: Display + Send + Sync + 'static,
+ {
+ Self { inner: self.inner.context(context) }
}
}
@@ -46,59 +50,7 @@ impl Debug for Error
{
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
{
- let error = &*self.inner;
-
- write!(formatter, "{error}")?;
-
- if let Some(cause) = error.source() {
- write!(formatter, "\n\nCaused by:")?;
- let multiple = cause.source().is_some();
- for (n, error) in anyhow::Chain::new(cause).enumerate() {
- writeln!(formatter)?;
-
- let mut indented = Indented {
- inner: formatter,
- number: if multiple { Some(n) } else { None },
- started: false,
- };
- write!(indented, "{error}")?;
- }
- }
-
- if std::env::var_os("ENGINE_ECS_BACKTRACE")
- .is_none_or(|backtrace_enabled| backtrace_enabled != "1")
- {
- write!(
- formatter,
- concat!(
- "\n\nnote: run with `ENGINE_ECS_BACKTRACE=1` environment variable ",
- "to display a engine backtrace"
- )
- )?;
-
- return Ok(());
- }
-
- let mut cloned_backtrace;
-
- let backtrace = if self.is_backtrace_resolved() {
- &self.backtrace
- } else {
- cloned_backtrace = self.backtrace.clone();
- cloned_backtrace.resolve();
- &cloned_backtrace
- };
-
- write!(
- formatter,
- "\n\nStack backtrace:\n{:?}",
- std::fmt::from_fn(|backtrace_formatter| fmt_backtrace(
- backtrace,
- backtrace_formatter
- ))
- )?;
-
- Ok(())
+ <anyhow::Error as Debug>::fmt(&self.inner, formatter)
}
}
@@ -106,31 +58,64 @@ impl Display for Error
{
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
{
- let error = &*self.inner;
+ <anyhow::Error as Display>::fmt(&self.inner, formatter)
+ }
+}
- write!(formatter, "{error}")?;
+impl<Err> From<Err> for Error
+where
+ Err: std::error::Error + Send + Sync + 'static,
+{
+ fn from(err: Err) -> Self
+ {
+ Self { inner: err.into() }
+ }
+}
- if formatter.alternate() {
- let chain = anyhow::Chain::new(error);
- for cause in chain.skip(1) {
- write!(formatter, ": {}", cause)?;
- }
- }
+pub trait Context<T>
+{
+ fn context<Ctx>(self, context: Ctx) -> Result<T, Error>
+ where
+ Ctx: Display + Send + Sync + 'static;
- Ok(())
- }
+ fn with_context<Ctx>(self, context_func: impl FnOnce() -> Ctx) -> Result<T, Error>
+ where
+ Ctx: Display + Send + Sync + 'static;
}
-impl<Err: Send + Sync + 'static> From<Err> for Error
+impl<T, Err> Context<T> for Result<T, Err>
where
- Box<dyn std::error::Error + Send + Sync>: From<Err>,
+ Err: std::error::Error + Send + Sync + 'static,
{
- fn from(err: Err) -> Self
+ fn context<Ctx>(self, context: Ctx) -> Result<T, Error>
+ where
+ Ctx: Display + Send + Sync + 'static,
{
- Self {
- inner: err.into(),
- backtrace: Backtrace::new_unresolved(),
- }
+ 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()))
}
}
@@ -164,14 +149,12 @@ impl Display for SourceKind
}
}
-pub fn err_handler_panic(_world: &World, mut err: Error, err_metadata: Metadata)
+pub fn err_handler_panic(_world: &World, err: Error, err_metadata: Metadata)
{
- err.resolve_backtrace();
-
- panic!(
- "Error occurred in {} '{}': {err:?}",
+ 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)
@@ -183,90 +166,49 @@ pub fn err_handler_log_error(_world: &World, err: Error, err_metadata: Metadata)
);
}
-fn fmt_backtrace(
- backtrace: &Backtrace,
- fmt: &mut std::fmt::Formatter<'_>,
-) -> std::fmt::Result
+pub(crate) fn set_panic_hook()
{
- let style = if fmt.alternate() {
- backtrace::PrintFmt::Full
- } else {
- backtrace::PrintFmt::Short
- };
+ let previous_panic_hook = std::panic::take_hook();
- // When printing paths we try to strip the cwd if it exists, otherwise
- // we just print the path as-is. Note that we also only do this for the
- // short format, because if it's full we presumably want to print
- // everything.
- let cwd = std::env::current_dir();
- let mut print_path =
- move |fmt: &mut std::fmt::Formatter<'_>,
- path: backtrace::BytesOrWideString<'_>| {
- let path = path.into_path_buf();
- if style != backtrace::PrintFmt::Full {
- if let Ok(cwd) = &cwd {
- if let Ok(suffix) = path.strip_prefix(cwd) {
- return std::fmt::Display::fmt(&suffix.display(), fmt);
- }
- }
- }
- std::fmt::Display::fmt(&path.display(), fmt)
+ std::panic::set_hook(Box::new(move |info| {
+ let Some(err) = info.payload().downcast_ref::<Error>() else {
+ return previous_panic_hook(info);
};
- let mut f = backtrace::BacktraceFmt::new(fmt, style, &mut print_path);
+ print!(
+ "Thread '{}' ({:?}) panicked",
+ std::thread::current().name().unwrap_or("<unnamed>"),
+ get_current_os_thread_id()
+ );
- f.add_context()?;
+ if let Some(location) = info.location() {
+ print!(" at {location}");
+ }
- for frame in backtrace.frames() {
- if frame.symbols().iter().all(|symbol| {
- symbol.name().is_some_and(|symbol_name| {
- let symbol_name = symbol_name.to_string();
+ print!("\n{err:?}");
- symbol_name
- .contains("<ecs::error::Error as core::convert::From<Err>>::from")
- })
- }) {
- continue;
+ if err.inner.backtrace().status() == std::backtrace::BacktraceStatus::Disabled {
+ print!(concat!(
+ "\nnote: run with `RUST_BACKTRACE=1` environment variable ",
+ "to display a backtrace"
+ ));
}
- f.frame().backtrace_frame(frame)?;
- }
- f.finish()?;
- Ok(())
+ println!("");
+ }));
}
-struct Indented<'a, D>
+fn get_current_os_thread_id() -> u64
{
- inner: &'a mut D,
- number: Option<usize>,
- started: bool,
-}
-
-impl<T> std::fmt::Write for Indented<'_, T>
-where
- T: std::fmt::Write,
-{
- fn write_str(&mut self, s: &str) -> std::fmt::Result
- {
- for (i, line) in s.split('\n').enumerate() {
- if !self.started {
- self.started = true;
- match self.number {
- Some(number) => write!(self.inner, "{: >5}: ", number)?,
- None => self.inner.write_str(" ")?,
- }
- } else if i > 0 {
- self.inner.write_char('\n')?;
- if self.number.is_some() {
- self.inner.write_str(" ")?;
- } else {
- self.inner.write_str(" ")?;
- }
- }
-
- self.inner.write_str(line)?;
+ 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");
}
-
- Ok(())
}
}