diff options
Diffstat (limited to 'engine-ecs')
| -rw-r--r-- | engine-ecs/Cargo.toml | 8 | ||||
| -rw-r--r-- | engine-ecs/src/error.rs | 266 | ||||
| -rw-r--r-- | engine-ecs/src/lib.rs | 2 |
3 files changed, 113 insertions, 163 deletions
diff --git a/engine-ecs/Cargo.toml b/engine-ecs/Cargo.toml index 0c9da60..001cf54 100644 --- a/engine-ecs/Cargo.toml +++ b/engine-ecs/Cargo.toml @@ -14,12 +14,18 @@ tracing = "0.1.39" hashbrown = "0.15.2" parking_lot = "0.12.3" anyhow = "1.0.102" -backtrace = "0.3.76" engine-ecs-macros = { workspace = true } engine-reflection = { workspace = true } util-macros = { workspace = true } vizoxide = { version = "1.0.5", optional = true } +[target.'cfg(windows)'.dependencies.windows-sys] +version = "0.61.2" +features = ["Win32_System_Threading"] + +[target.'cfg(target_os = "linux")'.dependencies.libc] +version = "0.2.186" + [dev-dependencies.criterion] version = "0.5.1" default-features = false 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(()) } } diff --git a/engine-ecs/src/lib.rs b/engine-ecs/src/lib.rs index a426b0b..c502b06 100644 --- a/engine-ecs/src/lib.rs +++ b/engine-ecs/src/lib.rs @@ -110,6 +110,8 @@ impl World world.add_sole(Stats::default()).ok(); + crate::error::set_panic_hook(); + world } |
