BevyError: Bevy's new catch-all error type (#18144)

## 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:


![image](https://github.com/user-attachments/assets/7a5f5c9b-ea70-4176-af3b-d231da31c967)

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.
This commit is contained in:
Carter Anderson 2025-03-06 17:50:07 -08:00 committed by GitHub
parent 8f85d4e598
commit cca5813472
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 417 additions and 146 deletions

View File

@ -9,7 +9,13 @@ license = "MIT OR Apache-2.0"
keywords = ["bevy"]
[features]
default = ["std", "bevy_reflect", "bevy_tasks", "bevy_ecs/default"]
default = [
"std",
"bevy_reflect",
"bevy_tasks",
"bevy_ecs/default",
"error_panic_hook",
]
# Functionality
@ -36,6 +42,10 @@ trace = ["dep:tracing"]
## 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

View File

@ -10,10 +10,10 @@ use alloc::{
pub use bevy_derive::AppLabel;
use bevy_ecs::{
component::RequiredComponentsError,
error::{BevyError, SystemErrorContext},
event::{event_update_system, EventCursor},
intern::Interned,
prelude::*,
result::{Error, SystemErrorContext},
schedule::{ScheduleBuildSettings, ScheduleLabel},
system::{IntoObserverSystem, SystemId, SystemInput},
};
@ -1280,7 +1280,7 @@ impl App {
/// for more information.
pub fn set_system_error_handler(
&mut self,
error_handler: fn(Error, SystemErrorContext),
error_handler: fn(BevyError, SystemErrorContext),
) -> &mut Self {
self.main_mut().set_system_error_handler(error_handler);
self

View File

@ -39,13 +39,28 @@ pub struct PanicHandlerPlugin;
impl Plugin for PanicHandlerPlugin {
fn build(&self, _app: &mut App) {
#[cfg(target_arch = "wasm32")]
#[cfg(feature = "std")]
{
console_error_panic_hook::set_once();
}
#[cfg(not(target_arch = "wasm32"))]
{
// Use the default target panic hook - Do nothing.
static SET_HOOK: std::sync::Once = std::sync::Once::new();
SET_HOOK.call_once(|| {
#[cfg(target_arch = "wasm32")]
{
// This provides better panic handling in JS engines (displays the panic message and improves the backtrace).
std::panic::set_hook(alloc::boxed::Box::new(console_error_panic_hook::hook));
}
#[cfg(not(target_arch = "wasm32"))]
{
#[cfg(feature = "error_panic_hook")]
{
let current_hook = std::panic::take_hook();
std::panic::set_hook(alloc::boxed::Box::new(
bevy_ecs::error::bevy_error_panic_hook(current_hook),
));
}
// Otherwise use the default target panic hook - Do nothing.
}
});
}
}
}

View File

@ -1,9 +1,9 @@
use crate::{App, AppLabel, InternedAppLabel, Plugin, Plugins, PluginsState};
use alloc::{boxed::Box, string::String, vec::Vec};
use bevy_ecs::{
error::{DefaultSystemErrorHandler, SystemErrorContext},
event::EventRegistry,
prelude::*,
result::{DefaultSystemErrorHandler, SystemErrorContext},
schedule::{InternedScheduleLabel, ScheduleBuildSettings, ScheduleLabel},
system::{SystemId, SystemInput},
};
@ -342,7 +342,7 @@ impl SubApp {
/// for more information.
pub fn set_system_error_handler(
&mut self,
error_handler: fn(Error, SystemErrorContext),
error_handler: fn(BevyError, SystemErrorContext),
) -> &mut Self {
let mut default_handler = self
.world_mut()

View File

@ -11,7 +11,7 @@ categories = ["game-engines", "data-structures"]
rust-version = "1.85.0"
[features]
default = ["std", "bevy_reflect", "async_executor"]
default = ["std", "bevy_reflect", "async_executor", "backtrace"]
# Functionality
@ -36,6 +36,9 @@ reflect_functions = ["bevy_reflect", "bevy_reflect/functions"]
## Use the configurable global error handler as the default error handler
configurable_error_handler = []
## Enables automatic backtrace capturing in BevyError
backtrace = []
# Debugging Features
## Enables `tracing` integration, allowing spans and other metrics to be reported

View File

@ -0,0 +1,241 @@
use alloc::boxed::Box;
use core::{
error::Error,
fmt::{Debug, Display},
};
/// The built in "universal" Bevy error type. This has a blanket [`From`] impl for any type that implements Rust's [`Error`],
/// meaning it can be used as a "catch all" error.
///
/// # Backtraces
///
/// When used with the `backtrace` Cargo feature, it will capture a backtrace when the error is constructed (generally in the [`From`] impl]).
/// When printed, the backtrace will be displayed. By default, the backtrace will be trimmed down to filter out noise. To see the full backtrace,
/// set the `BEVY_BACKTRACE=full` environment variable.
///
/// # Usage
///
/// ```
/// # use bevy_ecs::prelude::*;
///
/// fn fallible_system() -> Result<(), BevyError> {
/// // This will result in Rust's built-in ParseIntError, which will automatically
/// // be converted into a BevyError.
/// let parsed: usize = "I am not a number".parse()?;
/// Ok(())
/// }
/// ```
pub struct BevyError {
inner: Box<InnerBevyError>,
}
impl BevyError {
/// Attempts to downcast the internal error to the given type.
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)?;
#[cfg(feature = "backtrace")]
{
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");
let backtrace_str = alloc::string::ToString::to_string(backtrace);
let mut skip_next_location_line = false;
for line in backtrace_str.split('\n') {
if !full_backtrace {
if skip_next_location_line {
if line.starts_with(" at") {
continue;
}
skip_next_location_line = false;
}
if line.contains("std::backtrace_rs::backtrace::") {
skip_next_location_line = true;
continue;
}
if line.contains("std::backtrace::Backtrace::") {
skip_next_location_line = true;
continue;
}
if line.contains("<bevy_ecs::error::bevy_error::BevyError as core::convert::From<E>>::from") {
skip_next_location_line = true;
continue;
}
if line.contains("<core::result::Result<T,F> as core::ops::try_trait::FromResidual<core::result::Result<core::convert::Infallible,E>>>::from_residual") {
skip_next_location_line = true;
continue;
}
if line.contains("__rust_begin_short_backtrace") {
break;
}
if line.contains("bevy_ecs::observer::Observers::invoke::{{closure}}") {
break;
}
}
writeln!(f, "{}", line)?;
}
if !full_backtrace {
if std::thread::panicking() {
SKIP_NORMAL_BACKTRACE.store(1, core::sync::atomic::Ordering::Relaxed);
}
writeln!(f, "{FILTER_MESSAGE}")?;
}
}
}
Ok(())
}
}
#[cfg(feature = "backtrace")]
const FILTER_MESSAGE: &str = "note: Some \"noisy\" backtrace lines have been filtered out. Run with `BEVY_BACKTRACE=full` for a verbose backtrace.";
#[cfg(feature = "backtrace")]
static SKIP_NORMAL_BACKTRACE: core::sync::atomic::AtomicUsize =
core::sync::atomic::AtomicUsize::new(0);
/// When called, this will skip the currently configured panic hook when a [`BevyError`] backtrace has already been printed.
#[cfg(feature = "std")]
pub fn bevy_error_panic_hook(
current_hook: impl Fn(&std::panic::PanicHookInfo),
) -> impl Fn(&std::panic::PanicHookInfo) {
move |info| {
if SKIP_NORMAL_BACKTRACE.load(core::sync::atomic::Ordering::Relaxed) > 0 {
if let Some(payload) = info.payload().downcast_ref::<&str>() {
std::println!("{payload}");
} else if let Some(payload) = info.payload().downcast_ref::<alloc::string::String>() {
std::println!("{payload}");
}
SKIP_NORMAL_BACKTRACE.store(0, core::sync::atomic::Ordering::Relaxed);
return;
}
current_hook(info);
}
}
#[cfg(test)]
mod tests {
#[test]
#[cfg(not(miri))] // miri backtraces are weird
#[cfg(not(windows))] // the windows backtrace in this context is ... unhelpful and not worth testing
fn filtered_backtrace_test() {
fn i_fail() -> crate::error::Result {
let _: usize = "I am not a number".parse()?;
Ok(())
}
// SAFETY: this is not safe ... this test could run in parallel with another test
// that writes the environment variable. We either accept that so we can write this test,
// or we don't.
unsafe { std::env::set_var("RUST_BACKTRACE", "1") };
let error = i_fail().err().unwrap();
let debug_message = alloc::format!("{error:?}");
let mut lines = debug_message.lines().peekable();
assert_eq!(
"ParseIntError { kind: InvalidDigit }",
lines.next().unwrap()
);
// On mac backtraces can start with Backtrace::create
let mut skip = false;
if let Some(line) = lines.peek() {
if &line[6..] == "std::backtrace::Backtrace::create" {
skip = true;
}
}
if skip {
lines.next().unwrap();
}
let expected_lines = alloc::vec![
"bevy_ecs::error::bevy_error::tests::filtered_backtrace_test::i_fail",
"bevy_ecs::error::bevy_error::tests::filtered_backtrace_test",
"bevy_ecs::error::bevy_error::tests::filtered_backtrace_test::{{closure}}",
"core::ops::function::FnOnce::call_once",
];
for expected in expected_lines {
let line = lines.next().unwrap();
assert_eq!(&line[6..], expected);
let mut skip = false;
if let Some(line) = lines.peek() {
if line.starts_with(" at") {
skip = true;
}
}
if skip {
lines.next().unwrap();
}
}
// on linux there is a second call_once
let mut skip = false;
if let Some(line) = lines.peek() {
if &line[6..] == "core::ops::function::FnOnce::call_once" {
skip = true;
}
}
if skip {
lines.next().unwrap();
}
let mut skip = false;
if let Some(line) = lines.peek() {
if line.starts_with(" at") {
skip = true;
}
}
if skip {
lines.next().unwrap();
}
assert_eq!(super::FILTER_MESSAGE, lines.next().unwrap());
assert!(lines.next().is_none());
}
}

View File

@ -0,0 +1,75 @@
use crate::{component::Tick, error::BevyError, resource::Resource};
use alloc::borrow::Cow;
/// Additional context for a failed system run.
pub struct SystemErrorContext {
/// The name of the system that failed.
pub name: Cow<'static, str>,
/// The last tick that the system was run.
pub last_run: Tick,
}
/// The default systems error handler stored as a resource in the [`World`](crate::world::World).
pub struct DefaultSystemErrorHandler(pub fn(BevyError, SystemErrorContext));
impl Resource for DefaultSystemErrorHandler {}
impl Default for DefaultSystemErrorHandler {
fn default() -> Self {
Self(panic)
}
}
macro_rules! inner {
($call:path, $e:ident, $c:ident) => {
$call!("Encountered an error in system `{}`: {:?}", $c.name, $e);
};
}
/// Error handler that panics with the system error.
#[track_caller]
#[inline]
pub fn panic(error: BevyError, ctx: SystemErrorContext) {
inner!(panic, error, ctx);
}
/// Error handler that logs the system error at the `error` level.
#[track_caller]
#[inline]
pub fn error(error: BevyError, ctx: SystemErrorContext) {
inner!(log::error, error, ctx);
}
/// Error handler that logs the system error at the `warn` level.
#[track_caller]
#[inline]
pub fn warn(error: BevyError, ctx: SystemErrorContext) {
inner!(log::warn, error, ctx);
}
/// Error handler that logs the system error at the `info` level.
#[track_caller]
#[inline]
pub fn info(error: BevyError, ctx: SystemErrorContext) {
inner!(log::info, error, ctx);
}
/// Error handler that logs the system error at the `debug` level.
#[track_caller]
#[inline]
pub fn debug(error: BevyError, ctx: SystemErrorContext) {
inner!(log::debug, error, ctx);
}
/// Error handler that logs the system error at the `trace` level.
#[track_caller]
#[inline]
pub fn trace(error: BevyError, ctx: SystemErrorContext) {
inner!(log::trace, error, ctx);
}
/// Error handler that ignores the system error.
#[track_caller]
#[inline]
pub fn ignore(_: BevyError, _: SystemErrorContext) {}

View File

@ -4,7 +4,7 @@
//! considers those systems to be "fallible", and the ECS scheduler will special-case the [`Err`]
//! variant of the returned `Result`.
//!
//! All [`Error`]s returned by a system are handled by an "error handler". By default, the
//! All [`BevyError`]s returned by a system are handled by an "error handler". By default, the
//! [`panic`] error handler function is used, resulting in a panic with the error message attached.
//!
//! You can change the default behavior by registering a custom error handler, either globally or
@ -29,7 +29,7 @@
//! signature:
//!
//! ```rust,ignore
//! fn(Error, SystemErrorContext)
//! fn(BevyError, SystemErrorContext)
//! ```
//!
//! The [`SystemErrorContext`] allows you to access additional details relevant to providing
@ -53,7 +53,7 @@
//! return;
//! }
//!
//! bevy_ecs::result::error(error, ctx);
//! bevy_ecs::error::error(error, ctx);
//! });
//! # }
//! ```
@ -70,84 +70,11 @@
//! [`App::set_system_error_handler`]: ../../bevy_app/struct.App.html#method.set_system_error_handler
//! [`system piping feature`]: crate::system::In
use crate::{component::Tick, resource::Resource};
use alloc::{borrow::Cow, boxed::Box};
mod bevy_error;
mod handler;
/// A dynamic error type for use in fallible systems.
pub type Error = Box<dyn core::error::Error + Send + Sync + 'static>;
pub use bevy_error::*;
pub use handler::*;
/// A result type for use in fallible systems.
pub type Result<T = (), E = Error> = core::result::Result<T, E>;
/// Additional context for a failed system run.
pub struct SystemErrorContext {
/// The name of the system that failed.
pub name: Cow<'static, str>,
/// The last tick that the system was run.
pub last_run: Tick,
}
/// The default systems error handler stored as a resource in the [`World`](crate::world::World).
pub struct DefaultSystemErrorHandler(pub fn(Error, SystemErrorContext));
impl Resource for DefaultSystemErrorHandler {}
impl Default for DefaultSystemErrorHandler {
fn default() -> Self {
Self(panic)
}
}
macro_rules! inner {
($call:path, $e:ident, $c:ident) => {
$call!("Encountered an error in system `{}`: {:?}", $c.name, $e);
};
}
/// Error handler that panics with the system error.
#[track_caller]
#[inline]
pub fn panic(error: Error, ctx: SystemErrorContext) {
inner!(panic, error, ctx);
}
/// Error handler that logs the system error at the `error` level.
#[track_caller]
#[inline]
pub fn error(error: Error, ctx: SystemErrorContext) {
inner!(log::error, error, ctx);
}
/// Error handler that logs the system error at the `warn` level.
#[track_caller]
#[inline]
pub fn warn(error: Error, ctx: SystemErrorContext) {
inner!(log::warn, error, ctx);
}
/// Error handler that logs the system error at the `info` level.
#[track_caller]
#[inline]
pub fn info(error: Error, ctx: SystemErrorContext) {
inner!(log::info, error, ctx);
}
/// Error handler that logs the system error at the `debug` level.
#[track_caller]
#[inline]
pub fn debug(error: Error, ctx: SystemErrorContext) {
inner!(log::debug, error, ctx);
}
/// Error handler that logs the system error at the `trace` level.
#[track_caller]
#[inline]
pub fn trace(error: Error, ctx: SystemErrorContext) {
inner!(log::trace, error, ctx);
}
/// Error handler that ignores the system error.
#[track_caller]
#[inline]
pub fn ignore(_: Error, _: SystemErrorContext) {}
pub type Result<T = (), E = BevyError> = core::result::Result<T, E>;

View File

@ -36,6 +36,7 @@ pub mod change_detection;
pub mod component;
pub mod entity;
pub mod entity_disabling;
pub mod error;
pub mod event;
pub mod hierarchy;
pub mod identifier;
@ -49,7 +50,6 @@ pub mod reflect;
pub mod relationship;
pub mod removal_detection;
pub mod resource;
pub mod result;
pub mod schedule;
pub mod spawn;
pub mod storage;
@ -74,6 +74,7 @@ pub mod prelude {
children,
component::Component,
entity::{Entity, EntityBorrow, EntityMapper},
error::{BevyError, Result},
event::{Event, EventMutator, EventReader, EventWriter, Events},
hierarchy::{ChildOf, ChildSpawner, ChildSpawnerCommands, Children},
name::{Name, NameOrEntity},
@ -83,7 +84,6 @@ pub mod prelude {
relationship::RelationshipTarget,
removal_detection::RemovedComponents,
resource::Resource,
result::{Error, Result},
schedule::{
apply_deferred, common_conditions::*, ApplyDeferred, Condition, IntoSystemConfigs,
IntoSystemSet, IntoSystemSetConfigs, Schedule, Schedules, SystemSet,

View File

@ -3,10 +3,10 @@ use core::any::Any;
use crate::{
component::{ComponentHook, ComponentId, HookContext, Mutable, StorageType},
error::{DefaultSystemErrorHandler, SystemErrorContext},
observer::{ObserverDescriptor, ObserverTrigger},
prelude::*,
query::DebugCheckedUnwrap,
result::{DefaultSystemErrorHandler, SystemErrorContext},
system::{IntoObserverSystem, ObserverSystem},
world::DeferredWorld,
};
@ -273,7 +273,7 @@ pub struct Observer {
system: Box<dyn Any + Send + Sync + 'static>,
descriptor: ObserverDescriptor,
hook_on_add: ComponentHook,
error_handler: Option<fn(Error, SystemErrorContext)>,
error_handler: Option<fn(BevyError, SystemErrorContext)>,
}
impl Observer {
@ -321,8 +321,8 @@ impl Observer {
/// Set the error handler to use for this observer.
///
/// See the [`result` module-level documentation](crate::result) for more information.
pub fn with_error_handler(mut self, error_handler: fn(Error, SystemErrorContext)) -> Self {
/// See the [`error` module-level documentation](crate::error) for more information.
pub fn with_error_handler(mut self, error_handler: fn(BevyError, SystemErrorContext)) -> Self {
self.error_handler = Some(error_handler);
self
}
@ -509,7 +509,7 @@ mod tests {
let mut world = World::default();
world.init_resource::<Ran>();
let observer = Observer::new(system).with_error_handler(crate::result::ignore);
let observer = Observer::new(system).with_error_handler(crate::error::ignore);
world.spawn(observer);
Schedule::default().run(&mut world);
world.trigger(TriggerEvent);

View File

@ -1698,7 +1698,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
///
/// This allows you to globally control how errors are handled in your application,
/// by setting up a custom error handler.
/// See the [`bevy_ecs::result`] module docs for more information!
/// See the [`bevy_ecs::error`] module docs for more information!
/// Commonly, you might want to panic on an error during development, but log the error and continue
/// execution in production.
///

View File

@ -2,7 +2,7 @@ use alloc::{boxed::Box, vec, vec::Vec};
use variadics_please::all_tuples;
use crate::{
result::Result,
error::Result,
schedule::{
auto_insert_apply_deferred::IgnoreDeferred,
condition::{BoxedCondition, Condition},

View File

@ -16,9 +16,9 @@ use fixedbitset::FixedBitSet;
use crate::{
archetype::ArchetypeComponentId,
component::{ComponentId, Tick},
error::{BevyError, Result, SystemErrorContext},
prelude::{IntoSystemSet, SystemSet},
query::Access,
result::{Error, Result, SystemErrorContext},
schedule::{BoxedCondition, InternedSystemSet, NodeId, SystemTypeSet},
system::{ScheduleSystem, System, SystemIn},
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World},
@ -33,7 +33,7 @@ pub(super) trait SystemExecutor: Send + Sync {
schedule: &mut SystemSchedule,
world: &mut World,
skip_systems: Option<&FixedBitSet>,
error_handler: fn(Error, SystemErrorContext),
error_handler: fn(BevyError, SystemErrorContext),
);
fn set_apply_final_deferred(&mut self, value: bool);
}
@ -265,7 +265,7 @@ mod __rust_begin_short_backtrace {
use core::hint::black_box;
use crate::{
result::Result,
error::Result,
system::{ReadOnlySystem, ScheduleSystem},
world::{unsafe_world_cell::UnsafeWorldCell, World},
};

View File

@ -15,9 +15,9 @@ use tracing::{info_span, Span};
use crate::{
archetype::ArchetypeComponentId,
error::{BevyError, Result, SystemErrorContext},
prelude::Resource,
query::Access,
result::{Error, Result, SystemErrorContext},
schedule::{is_apply_deferred, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule},
system::ScheduleSystem,
world::{unsafe_world_cell::UnsafeWorldCell, World},
@ -132,7 +132,7 @@ pub struct ExecutorState {
struct Context<'scope, 'env, 'sys> {
environment: &'env Environment<'env, 'sys>,
scope: &'scope Scope<'scope, 'env, ()>,
error_handler: fn(Error, SystemErrorContext),
error_handler: fn(BevyError, SystemErrorContext),
}
impl Default for MultiThreadedExecutor {
@ -183,7 +183,7 @@ impl SystemExecutor for MultiThreadedExecutor {
schedule: &mut SystemSchedule,
world: &mut World,
_skip_systems: Option<&FixedBitSet>,
error_handler: fn(Error, SystemErrorContext),
error_handler: fn(BevyError, SystemErrorContext),
) {
let state = self.state.get_mut().unwrap();
// reset counts

View File

@ -8,7 +8,7 @@ use tracing::info_span;
use std::eprintln;
use crate::{
result::{Error, SystemErrorContext},
error::{BevyError, SystemErrorContext},
schedule::{
executor::is_apply_deferred, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule,
},
@ -44,7 +44,7 @@ impl SystemExecutor for SimpleExecutor {
schedule: &mut SystemSchedule,
world: &mut World,
_skip_systems: Option<&FixedBitSet>,
error_handler: fn(Error, SystemErrorContext),
error_handler: fn(BevyError, SystemErrorContext),
) {
// If stepping is enabled, make sure we skip those systems that should
// not be run.

View File

@ -8,7 +8,7 @@ use tracing::info_span;
use std::eprintln;
use crate::{
result::{Error, SystemErrorContext},
error::{BevyError, SystemErrorContext},
schedule::{is_apply_deferred, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule},
world::World,
};
@ -50,7 +50,7 @@ impl SystemExecutor for SingleThreadedExecutor {
schedule: &mut SystemSchedule,
world: &mut World,
_skip_systems: Option<&FixedBitSet>,
error_handler: fn(Error, SystemErrorContext),
error_handler: fn(BevyError, SystemErrorContext),
) {
// If stepping is enabled, make sure we skip those systems that should
// not be run.

View File

@ -26,9 +26,9 @@ use tracing::info_span;
use crate::{
component::{ComponentId, Components, Tick},
error::{BevyError, DefaultSystemErrorHandler, SystemErrorContext},
prelude::Component,
resource::Resource,
result::{DefaultSystemErrorHandler, Error, SystemErrorContext},
schedule::*,
system::ScheduleSystem,
world::World,
@ -296,7 +296,7 @@ pub struct Schedule {
executable: SystemSchedule,
executor: Box<dyn SystemExecutor>,
executor_initialized: bool,
error_handler: Option<fn(Error, SystemErrorContext)>,
error_handler: Option<fn(BevyError, SystemErrorContext)>,
}
#[derive(ScheduleLabel, Hash, PartialEq, Eq, Debug, Clone)]
@ -399,10 +399,10 @@ impl Schedule {
self
}
/// Set the error handler to use for systems that return a [`Result`](crate::result::Result).
/// Set the error handler to use for systems that return a [`Result`](crate::error::Result).
///
/// See the [`result` module-level documentation](crate::result) for more information.
pub fn set_error_handler(&mut self, error_handler: fn(Error, SystemErrorContext)) {
/// See the [`error` module-level documentation](crate::error) for more information.
pub fn set_error_handler(&mut self, error_handler: fn(BevyError, SystemErrorContext)) {
self.error_handler = Some(error_handler);
}

View File

@ -8,10 +8,10 @@ use crate::{
bundle::{Bundle, InsertMode, NoBundleEffect},
change_detection::MaybeLocation,
entity::Entity,
error::{BevyError, Result},
event::{Event, Events},
observer::TriggerTargets,
resource::Resource,
result::{Error, Result},
schedule::ScheduleLabel,
system::{error_handler, IntoSystem, SystemId, SystemInput},
world::{FromWorld, SpawnBatchIter, World},
@ -68,7 +68,7 @@ where
pub trait HandleError<Out = ()> {
/// Takes a [`Command`] that returns a Result and uses a given error handler function to convert it into
/// a [`Command`] that internally handles an error if it occurs and returns `()`.
fn handle_error_with(self, error_handler: fn(&mut World, Error)) -> impl Command;
fn handle_error_with(self, error_handler: fn(&mut World, BevyError)) -> impl Command;
/// Takes a [`Command`] that returns a Result and uses the default error handler function to convert it into
/// a [`Command`] that internally handles an error if it occurs and returns `()`.
fn handle_error(self) -> impl Command
@ -82,9 +82,9 @@ pub trait HandleError<Out = ()> {
impl<C, T, E> HandleError<Result<T, E>> for C
where
C: Command<Result<T, E>>,
E: Into<Error>,
E: Into<BevyError>,
{
fn handle_error_with(self, error_handler: fn(&mut World, Error)) -> impl Command {
fn handle_error_with(self, error_handler: fn(&mut World, BevyError)) -> impl Command {
move |world: &mut World| match self.apply(world) {
Ok(_) => {}
Err(err) => (error_handler)(world, err.into()),
@ -97,7 +97,7 @@ where
C: Command,
{
#[inline]
fn handle_error_with(self, _error_handler: fn(&mut World, Error)) -> impl Command {
fn handle_error_with(self, _error_handler: fn(&mut World, BevyError)) -> impl Command {
self
}
#[inline]

View File

@ -13,9 +13,9 @@ use crate::{
change_detection::MaybeLocation,
component::{Component, ComponentId, ComponentInfo},
entity::{Entity, EntityClonerBuilder},
error::Result,
event::Event,
relationship::RelationshipInsertHookMode,
result::Result,
system::{command::HandleError, Command, IntoObserverSystem},
world::{error::EntityMutableFetchError, EntityWorldMut, FromWorld, World},
};

View File

@ -1,27 +1,27 @@
//! This module contains convenience functions that return simple error handlers
//! for use with [`Commands::queue_handled`](super::Commands::queue_handled) and [`EntityCommands::queue_handled`](super::EntityCommands::queue_handled).
use crate::{result::Error, world::World};
use crate::{error::BevyError, world::World};
use log::{error, warn};
/// An error handler that does nothing.
pub fn silent() -> fn(&mut World, Error) {
pub fn silent() -> fn(&mut World, BevyError) {
|_, _| {}
}
/// An error handler that accepts an error and logs it with [`warn!`].
pub fn warn() -> fn(&mut World, Error) {
pub fn warn() -> fn(&mut World, BevyError) {
|_, error| warn!("{error}")
}
/// An error handler that accepts an error and logs it with [`error!`].
pub fn error() -> fn(&mut World, Error) {
pub fn error() -> fn(&mut World, BevyError) {
|_, error| error!("{error}")
}
/// An error handler that accepts an error and panics with the error in
/// the panic message.
pub fn panic() -> fn(&mut World, Error) {
pub fn panic() -> fn(&mut World, BevyError) {
|_, error| panic!("{error}")
}
@ -30,7 +30,7 @@ pub fn panic() -> fn(&mut World, Error) {
/// `GLOBAL_ERROR_HANDLER` will be used instead, enabling error handler customization.
#[cfg(not(feature = "configurable_error_handler"))]
#[inline]
pub fn default() -> fn(&mut World, Error) {
pub fn default() -> fn(&mut World, BevyError) {
panic()
}
@ -48,7 +48,7 @@ pub fn default() -> fn(&mut World, Error) {
/// // initialize Bevy App here
/// ```
#[cfg(feature = "configurable_error_handler")]
pub static GLOBAL_ERROR_HANDLER: std::sync::OnceLock<fn(&mut World, Error)> =
pub static GLOBAL_ERROR_HANDLER: std::sync::OnceLock<fn(&mut World, BevyError)> =
std::sync::OnceLock::new();
/// The default error handler. This defaults to [`panic()`]. If the
@ -56,6 +56,6 @@ pub static GLOBAL_ERROR_HANDLER: std::sync::OnceLock<fn(&mut World, Error)> =
/// [`GLOBAL_ERROR_HANDLER`] will be used instead, enabling error handler customization.
#[cfg(feature = "configurable_error_handler")]
#[inline]
pub fn default() -> fn(&mut World, Error) {
pub fn default() -> fn(&mut World, BevyError) {
*GLOBAL_ERROR_HANDLER.get_or_init(|| panic())
}

View File

@ -21,10 +21,10 @@ use crate::{
change_detection::{MaybeLocation, Mut},
component::{Component, ComponentId, Mutable},
entity::{Entities, Entity, EntityClonerBuilder, EntityDoesNotExistError},
error::BevyError,
event::Event,
observer::{Observer, TriggerTargets},
resource::Resource,
result::Error,
schedule::ScheduleLabel,
system::{
command::HandleError, entity_command::CommandWithEntity, input::SystemInput, Deferred,
@ -88,7 +88,7 @@ use crate::{
///
/// # Error handling
///
/// A [`Command`] can return a [`Result`](crate::result::Result),
/// A [`Command`] can return a [`Result`](crate::error::Result),
/// which will be passed to an error handler if the `Result` is an error.
///
/// Error handlers are functions/closures of the form `fn(&mut World, Error)`.
@ -639,7 +639,7 @@ impl<'w, 's> Commands<'w, 's> {
pub fn queue_handled<C: Command<T> + HandleError<T>, T>(
&mut self,
command: C,
error_handler: fn(&mut World, Error),
error_handler: fn(&mut World, BevyError),
) {
self.queue_internal(command.handle_error_with(error_handler));
}
@ -1160,7 +1160,7 @@ impl<'w, 's> Commands<'w, 's> {
///
/// # Error handling
///
/// An [`EntityCommand`] can return a [`Result`](crate::result::Result),
/// An [`EntityCommand`] can return a [`Result`](crate::error::Result),
/// which will be passed to an error handler if the `Result` is an error.
///
/// Error handlers are functions/closures of the form `fn(&mut World, Error)`.
@ -1853,7 +1853,7 @@ impl<'a> EntityCommands<'a> {
pub fn queue_handled<C: EntityCommand<T> + CommandWithEntity<M>, T, M>(
&mut self,
command: C,
error_handler: fn(&mut World, Error),
error_handler: fn(&mut World, BevyError),
) -> &mut Self {
self.commands
.queue_handled(command.with_entity(self.entity), error_handler);

View File

@ -82,7 +82,7 @@
//! # System return type
//!
//! Systems added to a schedule through [`add_systems`](crate::schedule::Schedule) may either return
//! empty `()` or a [`Result`](crate::result::Result). Other contexts (like one shot systems) allow
//! empty `()` or a [`Result`](crate::error::Result). Other contexts (like one shot systems) allow
//! systems to return arbitrary values.
//!
//! # System parameter list
@ -335,11 +335,11 @@ mod tests {
change_detection::DetectChanges,
component::{Component, Components},
entity::{Entities, Entity},
error::Result,
prelude::{AnyOf, EntityRef},
query::{Added, Changed, Or, With, Without},
removal_detection::RemovedComponents,
resource::Resource,
result::Result,
schedule::{
common_conditions::resource_exists, ApplyDeferred, Condition, IntoSystemConfigs,
Schedule,

View File

@ -4,9 +4,9 @@ use core::marker::PhantomData;
use crate::{
archetype::ArchetypeComponentId,
component::{ComponentId, Tick},
error::Result,
prelude::{Bundle, Trigger},
query::Access,
result::Result,
schedule::{Fallible, Infallible},
system::{input::SystemIn, System},
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World},

View File

@ -3,8 +3,8 @@ use alloc::{borrow::Cow, vec::Vec};
use crate::{
archetype::ArchetypeComponentId,
component::{ComponentId, Tick},
error::Result,
query::Access,
result::Result,
system::{input::SystemIn, BoxedSystem, System},
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World},
};

View File

@ -770,7 +770,7 @@ mod tests {
#[test]
fn cached_system_into_same_system_type() {
use crate::result::Result;
use crate::error::Result;
struct Foo;
impl IntoSystem<(), Result<()>, ()> for Foo {

View File

@ -18,9 +18,9 @@ fn main() {
app.add_plugins(MeshPickingPlugin);
// Fallible systems can be used the same way as regular systems. The only difference is they
// return a `Result<(), Box<dyn Error>>` instead of a `()` (unit) type. Bevy will handle both
// return a `Result<(), BevyError>` instead of a `()` (unit) type. Bevy will handle both
// types of systems the same way, except for the error handling.
app.add_systems(Startup, (setup, failing_system));
app.add_systems(Startup, setup);
// By default, fallible systems that return an error will panic.
//
@ -28,7 +28,7 @@ fn main() {
// systems in a given `App`. Here we set the global error handler using one of the built-in
// error handlers. Bevy provides built-in handlers for `panic`, `error`, `warn`, `info`,
// `debug`, `trace` and `ignore`.
app.set_system_error_handler(bevy::ecs::result::warn);
app.set_system_error_handler(bevy::ecs::error::warn);
// Additionally, you can set a custom error handler per `Schedule`. This will take precedence
// over the global error handler.
@ -36,7 +36,7 @@ fn main() {
// In this instance we provide our own non-capturing closure that coerces to the expected error
// handler function pointer:
//
// fn(bevy_ecs::result::Error, bevy_ecs::result::SystemErrorContext)
// fn(bevy_ecs::error::BevyError, bevy_ecs::error::SystemErrorContext)
//
app.add_systems(PostStartup, failing_system)
.get_schedule_mut(PostStartup)
@ -161,7 +161,7 @@ fn failing_system(world: &mut World) -> Result {
// `get_resource` returns an `Option<T>`, so we use `ok_or` to convert it to a `Result` on
// which we can call `?` to propagate the error.
.get_resource::<UninitializedResource>()
// We can provide a `str` here because `Box<dyn Error>` implements `From<&str>`.
// We can provide a `str` here because `BevyError` implements `From<&str>`.
.ok_or("Resource not initialized")?;
Ok(())