Use Display instead of Debug in the default error handler (#18629)
# Objective Improve error messages for missing resources. The default error handler currently prints the `Debug` representation of the error type instead of `Display`. Most error types use `#[derive(Debug)]`, resulting in a dump of the structure, but will have a user-friendly message for `Display`. Follow-up to #18593 ## Solution Change the default error handler to use `Display` instead of `Debug`. Change `BevyError` to include the backtrace in the `Display` format in addition to `Debug` so that it is still included. ## Showcase Before: ``` Encountered an error in system `system_name`: SystemParamValidationError { skipped: false, message: "Resource does not exist", param: "bevy_ecs::change_detection::Res<app_name::ResourceType>" } Encountered an error in system `other_system_name`: "String message with\nmultiple lines." ``` After ``` Encountered an error in system `system_name`: Parameter `Res<ResourceType>` failed validation: Resource does not exist Encountered an error in system `other_system_name`: String message with multiple lines. ```
This commit is contained in:
parent
6734abe3f5
commit
b4614dadcd
@ -34,48 +34,11 @@ impl BevyError {
|
||||
pub fn downcast_ref<E: Error + 'static>(&self) -> Option<&E> {
|
||||
self.inner.error.downcast_ref::<E>()
|
||||
}
|
||||
}
|
||||
|
||||
/// This type exists (rather than having a `BevyError(Box<dyn InnerBevyError)`) to make [`BevyError`] use a "thin pointer" instead of
|
||||
/// a "fat pointer", which reduces the size of our Result by a usize. This does introduce an extra indirection, but error handling is a "cold path".
|
||||
/// We don't need to optimize it to that degree.
|
||||
/// PERF: We could probably have the best of both worlds with a "custom vtable" impl, but thats not a huge priority right now and the code simplicity
|
||||
/// of the current impl is nice.
|
||||
struct InnerBevyError {
|
||||
error: Box<dyn Error + Send + Sync + 'static>,
|
||||
#[cfg(feature = "backtrace")]
|
||||
backtrace: std::backtrace::Backtrace,
|
||||
}
|
||||
|
||||
// NOTE: writing the impl this way gives us From<&str> ... nice!
|
||||
impl<E> From<E> for BevyError
|
||||
where
|
||||
Box<dyn Error + Send + Sync + 'static>: From<E>,
|
||||
{
|
||||
#[cold]
|
||||
fn from(error: E) -> Self {
|
||||
BevyError {
|
||||
inner: Box::new(InnerBevyError {
|
||||
error: error.into(),
|
||||
#[cfg(feature = "backtrace")]
|
||||
backtrace: std::backtrace::Backtrace::capture(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for BevyError {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
writeln!(f, "{}", self.inner.error)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for BevyError {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
writeln!(f, "{:?}", self.inner.error)?;
|
||||
fn format_backtrace(&self, _f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
#[cfg(feature = "backtrace")]
|
||||
{
|
||||
let f = _f;
|
||||
let backtrace = &self.inner.backtrace;
|
||||
if let std::backtrace::BacktraceStatus::Captured = backtrace.status() {
|
||||
let full_backtrace = std::env::var("BEVY_BACKTRACE").is_ok_and(|val| val == "full");
|
||||
@ -123,7 +86,50 @@ impl Debug for BevyError {
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// This type exists (rather than having a `BevyError(Box<dyn InnerBevyError)`) to make [`BevyError`] use a "thin pointer" instead of
|
||||
/// a "fat pointer", which reduces the size of our Result by a usize. This does introduce an extra indirection, but error handling is a "cold path".
|
||||
/// We don't need to optimize it to that degree.
|
||||
/// PERF: We could probably have the best of both worlds with a "custom vtable" impl, but thats not a huge priority right now and the code simplicity
|
||||
/// of the current impl is nice.
|
||||
struct InnerBevyError {
|
||||
error: Box<dyn Error + Send + Sync + 'static>,
|
||||
#[cfg(feature = "backtrace")]
|
||||
backtrace: std::backtrace::Backtrace,
|
||||
}
|
||||
|
||||
// NOTE: writing the impl this way gives us From<&str> ... nice!
|
||||
impl<E> From<E> for BevyError
|
||||
where
|
||||
Box<dyn Error + Send + Sync + 'static>: From<E>,
|
||||
{
|
||||
#[cold]
|
||||
fn from(error: E) -> Self {
|
||||
BevyError {
|
||||
inner: Box::new(InnerBevyError {
|
||||
error: error.into(),
|
||||
#[cfg(feature = "backtrace")]
|
||||
backtrace: std::backtrace::Backtrace::capture(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for BevyError {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
writeln!(f, "{}", self.inner.error)?;
|
||||
self.format_backtrace(f)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for BevyError {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
writeln!(f, "{:?}", self.inner.error)?;
|
||||
self.format_backtrace(f)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -127,7 +127,7 @@ pub fn default_error_handler() -> fn(BevyError, ErrorContext) {
|
||||
macro_rules! inner {
|
||||
($call:path, $e:ident, $c:ident) => {
|
||||
$call!(
|
||||
"Encountered an error in {} `{}`: {:?}",
|
||||
"Encountered an error in {} `{}`: {}",
|
||||
$c.kind(),
|
||||
$c.name(),
|
||||
$e
|
||||
|
@ -2959,7 +2959,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "Encountered an error in system `bevy_ecs::system::system_param::tests::missing_resource_error::res_system`: SystemParamValidationError { skipped: false, message: \"Resource does not exist\", param: \"bevy_ecs::change_detection::Res<bevy_ecs::system::system_param::tests::missing_resource_error::MissingResource>\" }"]
|
||||
#[should_panic = "Encountered an error in system `bevy_ecs::system::system_param::tests::missing_resource_error::res_system`: Parameter `Res<MissingResource>` failed validation: Resource does not exist"]
|
||||
fn missing_resource_error() {
|
||||
#[derive(Resource)]
|
||||
pub struct MissingResource;
|
||||
|
@ -55,7 +55,7 @@ fn main() {
|
||||
|
||||
// If we run the app, we'll see the following output at startup:
|
||||
//
|
||||
// WARN Encountered an error in system `fallible_systems::failing_system`: "Resource not initialized"
|
||||
// WARN Encountered an error in system `fallible_systems::failing_system`: Resource not initialized
|
||||
// ERROR fallible_systems::failing_system failed: Resource not initialized
|
||||
// INFO captured error: Resource not initialized
|
||||
app.run();
|
||||
|
Loading…
Reference in New Issue
Block a user