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
d5d57bbd26
commit
d5c5de20b1
@ -34,48 +34,11 @@ impl BevyError {
|
|||||||
pub fn downcast_ref<E: Error + 'static>(&self) -> Option<&E> {
|
pub fn downcast_ref<E: Error + 'static>(&self) -> Option<&E> {
|
||||||
self.inner.error.downcast_ref::<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
|
fn format_backtrace(&self, _f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
/// 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)?;
|
|
||||||
#[cfg(feature = "backtrace")]
|
#[cfg(feature = "backtrace")]
|
||||||
{
|
{
|
||||||
|
let f = _f;
|
||||||
let backtrace = &self.inner.backtrace;
|
let backtrace = &self.inner.backtrace;
|
||||||
if let std::backtrace::BacktraceStatus::Captured = backtrace.status() {
|
if let std::backtrace::BacktraceStatus::Captured = backtrace.status() {
|
||||||
let full_backtrace = std::env::var("BEVY_BACKTRACE").is_ok_and(|val| val == "full");
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -127,7 +127,7 @@ pub fn default_error_handler() -> fn(BevyError, ErrorContext) {
|
|||||||
macro_rules! inner {
|
macro_rules! inner {
|
||||||
($call:path, $e:ident, $c:ident) => {
|
($call:path, $e:ident, $c:ident) => {
|
||||||
$call!(
|
$call!(
|
||||||
"Encountered an error in {} `{}`: {:?}",
|
"Encountered an error in {} `{}`: {}",
|
||||||
$c.kind(),
|
$c.kind(),
|
||||||
$c.name(),
|
$c.name(),
|
||||||
$e
|
$e
|
||||||
|
@ -2932,7 +2932,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[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() {
|
fn missing_resource_error() {
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
pub struct MissingResource;
|
pub struct MissingResource;
|
||||||
|
@ -55,7 +55,7 @@ fn main() {
|
|||||||
|
|
||||||
// If we run the app, we'll see the following output at startup:
|
// 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
|
// ERROR fallible_systems::failing_system failed: Resource not initialized
|
||||||
// INFO captured error: Resource not initialized
|
// INFO captured error: Resource not initialized
|
||||||
app.run();
|
app.run();
|
||||||
|
Loading…
Reference in New Issue
Block a user