
## Objective Fixes #18092 Bevy's current error type is a simple type alias for `Box<dyn Error + Send + Sync + 'static>`. This largely works as a catch-all error, but it is missing a critical feature: the ability to capture a backtrace at the point that the error occurs. The best way to do this is `anyhow`-style error handling: a new error type that takes advantage of the fact that the `?` `From` conversion happens "inline" to capture the backtrace at the point of the error. ## Solution This PR adds a new `BevyError` type (replacing our old `std::error::Error` type alias), which uses the "from conversion backtrace capture" approach: ```rust fn oh_no() -> Result<(), BevyError> { // this fails with Rust's built in ParseIntError, which // is converted into the catch-all BevyError type let number: usize = "hi".parse()?; println!("parsed {number}"); Ok(()) } ``` This also updates our exported `Result` type alias to default to `BevyError`, meaning you can write this instead: ```rust fn oh_no() -> Result { let number: usize = "hi".parse()?; println!("parsed {number}"); Ok(()) } ``` When a BevyError is encountered in a system, it will use Bevy's default system error handler (which panics by default). BevyError does custom "backtrace filtering" by default, meaning we can cut out the _massive_ amount of "rust internals", "async executor internals", and "bevy system scheduler internals" that show up in backtraces. It also trims out the first generally-unnecssary `From` conversion backtrace lines that make it harder to locate the real error location. The result is a blissfully simple backtrace by default:  The full backtrace can be shown by setting the `BEVY_BACKTRACE=full` environment variable. Non-BevyError panics still use the default Rust backtrace behavior. One issue that prevented the truly noise-free backtrace during panics that you see above is that Rust's default panic handler will print the unfiltered (and largely unhelpful real-panic-point) backtrace by default, in _addition_ to our filtered BevyError backtrace (with the helpful backtrace origin) that we capture and print. To resolve this, I have extended Bevy's existing PanicHandlerPlugin to wrap the default panic handler. If we panic from the result of a BevyError, we will skip the default "print full backtrace" panic handler. This behavior can be enabled and disabled using the new `error_panic_hook` cargo feature in `bevy_app` (which is enabled by default). One downside to _not_ using `Box<dyn Error>` directly is that we can no longer take advantage of the built-in `Into` impl for strings to errors. To resolve this, I have added the following: ```rust // Before Err("some error")? // After Err(BevyError::message("some error"))? ``` We can discuss adding shorthand methods or macros for this (similar to anyhow's `anyhow!("some error")` macro), but I'd prefer to discuss that later. I have also added the following extension method: ```rust // Before some_option.ok_or("some error")?; // After some_option.ok_or_message("some error")?; ``` I've also moved all of our existing error infrastructure from `bevy_ecs::result` to `bevy_ecs::error`, as I think that is the better home for it ## Why not anyhow (or eyre)? The biggest reason is that `anyhow` needs to be a "generically useful error type", whereas Bevy is a much narrower scope. By using our own error, we can be significantly more opinionated. For example, anyhow doesn't do the extensive (and invasive) backtrace filtering that BevyError does because it can't operate on Bevy-specific context, and needs to be generically useful. Bevy also has a lot of operational context (ex: system info) that could be useful to attach to errors. If we have control over the error type, we can add whatever context we want to in a structured way. This could be increasingly useful as we add more visual / interactive error handling tools and editor integrations. Additionally, the core approach used is simple and requires almost no code. anyhow clocks in at ~2500 lines of code, but the impl here uses 160. We are able to boil this down to exactly what we need, and by doing so we improve our compile times and the understandability of our code.
108 lines
3.2 KiB
TOML
108 lines
3.2 KiB
TOML
[package]
|
|
name = "bevy_app"
|
|
version = "0.16.0-dev"
|
|
edition = "2024"
|
|
description = "Provides core App functionality for Bevy Engine"
|
|
homepage = "https://bevyengine.org"
|
|
repository = "https://github.com/bevyengine/bevy"
|
|
license = "MIT OR Apache-2.0"
|
|
keywords = ["bevy"]
|
|
|
|
[features]
|
|
default = [
|
|
"std",
|
|
"bevy_reflect",
|
|
"bevy_tasks",
|
|
"bevy_ecs/default",
|
|
"error_panic_hook",
|
|
]
|
|
|
|
# Functionality
|
|
|
|
## Adds runtime reflection support using `bevy_reflect`.
|
|
bevy_reflect = ["dep:bevy_reflect", "bevy_ecs/bevy_reflect"]
|
|
|
|
## Extends reflection support to functions.
|
|
reflect_functions = [
|
|
"bevy_reflect",
|
|
"bevy_reflect/functions",
|
|
"bevy_ecs/reflect_functions",
|
|
]
|
|
|
|
## Adds support for running async background tasks
|
|
bevy_tasks = ["dep:bevy_tasks"]
|
|
|
|
# Debugging Features
|
|
|
|
## Enables `tracing` integration, allowing spans and other metrics to be reported
|
|
## through that framework.
|
|
trace = ["dep:tracing"]
|
|
|
|
## Provides system stepping support, allowing them to be paused, stepped, and
|
|
## other debug operations which can help with diagnosing certain behaviors.
|
|
bevy_debug_stepping = []
|
|
|
|
## Will set the BevyError panic hook, which gives cleaner filtered backtraces when
|
|
## a BevyError is hit.
|
|
error_panic_hook = []
|
|
|
|
# Platform Compatibility
|
|
|
|
## Allows access to the `std` crate. Enabling this feature will prevent compilation
|
|
## on `no_std` targets, but provides access to certain additional features on
|
|
## supported platforms.
|
|
std = [
|
|
"bevy_reflect?/std",
|
|
"bevy_ecs/std",
|
|
"dep:ctrlc",
|
|
"downcast-rs/std",
|
|
"bevy_utils/std",
|
|
"bevy_tasks?/std",
|
|
"bevy_platform_support/std",
|
|
]
|
|
|
|
## `critical-section` provides the building blocks for synchronization primitives
|
|
## on all platforms, including `no_std`.
|
|
critical-section = [
|
|
"bevy_tasks?/critical-section",
|
|
"bevy_ecs/critical-section",
|
|
"bevy_platform_support/critical-section",
|
|
"bevy_reflect?/critical-section",
|
|
]
|
|
|
|
[dependencies]
|
|
# bevy
|
|
bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" }
|
|
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false }
|
|
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, optional = true }
|
|
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev", default-features = false, features = [
|
|
"alloc",
|
|
] }
|
|
bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev", default-features = false, optional = true }
|
|
bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false }
|
|
|
|
# other
|
|
downcast-rs = { version = "2", default-features = false }
|
|
thiserror = { version = "2", default-features = false }
|
|
variadics_please = "1.1"
|
|
tracing = { version = "0.1", default-features = false, optional = true }
|
|
log = { version = "0.4", default-features = false }
|
|
|
|
[target.'cfg(any(unix, windows))'.dependencies]
|
|
ctrlc = { version = "3.4.4", optional = true }
|
|
|
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
|
wasm-bindgen = { version = "0.2" }
|
|
web-sys = { version = "0.3", features = ["Window"] }
|
|
console_error_panic_hook = "0.1.6"
|
|
|
|
[dev-dependencies]
|
|
crossbeam-channel = "0.5.0"
|
|
|
|
[lints]
|
|
workspace = true
|
|
|
|
[package.metadata.docs.rs]
|
|
rustdoc-args = ["-Zunstable-options", "--generate-link-to-definition"]
|
|
all-features = true
|