From cca581347218854664a8ef3cfd69e230d4ae7e43 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Thu, 6 Mar 2025 17:50:07 -0800 Subject: [PATCH] BevyError: Bevy's new catch-all error type (#18144) ## Objective Fixes #18092 Bevy's current error type is a simple type alias for `Box`. 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` 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. --- crates/bevy_app/Cargo.toml | 12 +- crates/bevy_app/src/app.rs | 4 +- crates/bevy_app/src/panic_handler.rs | 27 +- crates/bevy_app/src/sub_app.rs | 4 +- crates/bevy_ecs/Cargo.toml | 5 +- crates/bevy_ecs/src/error/bevy_error.rs | 241 ++++++++++++++++++ crates/bevy_ecs/src/error/handler.rs | 75 ++++++ .../bevy_ecs/src/{result.rs => error/mod.rs} | 89 +------ crates/bevy_ecs/src/lib.rs | 4 +- crates/bevy_ecs/src/observer/runner.rs | 10 +- crates/bevy_ecs/src/query/state.rs | 2 +- crates/bevy_ecs/src/schedule/config.rs | 2 +- crates/bevy_ecs/src/schedule/executor/mod.rs | 6 +- .../src/schedule/executor/multi_threaded.rs | 6 +- .../bevy_ecs/src/schedule/executor/simple.rs | 4 +- .../src/schedule/executor/single_threaded.rs | 4 +- crates/bevy_ecs/src/schedule/schedule.rs | 10 +- .../bevy_ecs/src/system/commands/command.rs | 10 +- .../src/system/commands/entity_command.rs | 2 +- .../src/system/commands/error_handler.rs | 16 +- crates/bevy_ecs/src/system/commands/mod.rs | 10 +- crates/bevy_ecs/src/system/mod.rs | 4 +- crates/bevy_ecs/src/system/observer_system.rs | 2 +- crates/bevy_ecs/src/system/schedule_system.rs | 2 +- crates/bevy_ecs/src/system/system_registry.rs | 2 +- examples/ecs/fallible_systems.rs | 10 +- 26 files changed, 417 insertions(+), 146 deletions(-) create mode 100644 crates/bevy_ecs/src/error/bevy_error.rs create mode 100644 crates/bevy_ecs/src/error/handler.rs rename crates/bevy_ecs/src/{result.rs => error/mod.rs} (54%) diff --git a/crates/bevy_app/Cargo.toml b/crates/bevy_app/Cargo.toml index c19a3329d3..010e789fa5 100644 --- a/crates/bevy_app/Cargo.toml +++ b/crates/bevy_app/Cargo.toml @@ -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 diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 9799693aff..83aeac84bc 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -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 diff --git a/crates/bevy_app/src/panic_handler.rs b/crates/bevy_app/src/panic_handler.rs index 5a2ae097ff..e73c3d0098 100644 --- a/crates/bevy_app/src/panic_handler.rs +++ b/crates/bevy_app/src/panic_handler.rs @@ -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. + } + }); } } } diff --git a/crates/bevy_app/src/sub_app.rs b/crates/bevy_app/src/sub_app.rs index ead7c468c3..1843143c2e 100644 --- a/crates/bevy_app/src/sub_app.rs +++ b/crates/bevy_app/src/sub_app.rs @@ -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() diff --git a/crates/bevy_ecs/Cargo.toml b/crates/bevy_ecs/Cargo.toml index e42b2b62a0..f8adb9a92a 100644 --- a/crates/bevy_ecs/Cargo.toml +++ b/crates/bevy_ecs/Cargo.toml @@ -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 diff --git a/crates/bevy_ecs/src/error/bevy_error.rs b/crates/bevy_ecs/src/error/bevy_error.rs new file mode 100644 index 0000000000..73777bad77 --- /dev/null +++ b/crates/bevy_ecs/src/error/bevy_error.rs @@ -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, +} + +impl BevyError { + /// Attempts to downcast the internal error to the given type. + pub fn downcast_ref(&self) -> Option<&E> { + self.inner.error.downcast_ref::() + } +} + +/// This type exists (rather than having a `BevyError(Box, + #[cfg(feature = "backtrace")] + backtrace: std::backtrace::Backtrace, +} + +// NOTE: writing the impl this way gives us From<&str> ... nice! +impl From for BevyError +where + Box: From, +{ + #[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(">::from") { + skip_next_location_line = true; + continue; + } + if line.contains(" as core::ops::try_trait::FromResidual>>::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::() { + 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()); + } +} diff --git a/crates/bevy_ecs/src/error/handler.rs b/crates/bevy_ecs/src/error/handler.rs new file mode 100644 index 0000000000..eb0d9809af --- /dev/null +++ b/crates/bevy_ecs/src/error/handler.rs @@ -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) {} diff --git a/crates/bevy_ecs/src/result.rs b/crates/bevy_ecs/src/error/mod.rs similarity index 54% rename from crates/bevy_ecs/src/result.rs rename to crates/bevy_ecs/src/error/mod.rs index ef5eace90b..307d93158f 100644 --- a/crates/bevy_ecs/src/result.rs +++ b/crates/bevy_ecs/src/error/mod.rs @@ -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; +pub use bevy_error::*; +pub use handler::*; /// A result type for use in fallible systems. -pub type Result = core::result::Result; - -/// 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 = core::result::Result; diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 3b1229ab2a..b35882d18a 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -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, diff --git a/crates/bevy_ecs/src/observer/runner.rs b/crates/bevy_ecs/src/observer/runner.rs index fdb3b4fa80..7485af7f87 100644 --- a/crates/bevy_ecs/src/observer/runner.rs +++ b/crates/bevy_ecs/src/observer/runner.rs @@ -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, descriptor: ObserverDescriptor, hook_on_add: ComponentHook, - error_handler: Option, + error_handler: Option, } 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::(); - 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); diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 7013520f94..9f6b55fa89 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -1698,7 +1698,7 @@ impl QueryState { /// /// 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. /// diff --git a/crates/bevy_ecs/src/schedule/config.rs b/crates/bevy_ecs/src/schedule/config.rs index 898cf67424..f685bbecbd 100644 --- a/crates/bevy_ecs/src/schedule/config.rs +++ b/crates/bevy_ecs/src/schedule/config.rs @@ -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}, diff --git a/crates/bevy_ecs/src/schedule/executor/mod.rs b/crates/bevy_ecs/src/schedule/executor/mod.rs index c99d263e04..983d9629f8 100644 --- a/crates/bevy_ecs/src/schedule/executor/mod.rs +++ b/crates/bevy_ecs/src/schedule/executor/mod.rs @@ -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}, }; diff --git a/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs b/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs index e78cb666ae..7ab6f1a392 100644 --- a/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs +++ b/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs @@ -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 diff --git a/crates/bevy_ecs/src/schedule/executor/simple.rs b/crates/bevy_ecs/src/schedule/executor/simple.rs index ad01c324e9..a295919690 100644 --- a/crates/bevy_ecs/src/schedule/executor/simple.rs +++ b/crates/bevy_ecs/src/schedule/executor/simple.rs @@ -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. diff --git a/crates/bevy_ecs/src/schedule/executor/single_threaded.rs b/crates/bevy_ecs/src/schedule/executor/single_threaded.rs index b5c44085d5..277d41eb0b 100644 --- a/crates/bevy_ecs/src/schedule/executor/single_threaded.rs +++ b/crates/bevy_ecs/src/schedule/executor/single_threaded.rs @@ -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. diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index d67ca188e8..0cb2db2de5 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -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, executor_initialized: bool, - error_handler: Option, + error_handler: Option, } #[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); } diff --git a/crates/bevy_ecs/src/system/commands/command.rs b/crates/bevy_ecs/src/system/commands/command.rs index 5344534f39..7dd61946d6 100644 --- a/crates/bevy_ecs/src/system/commands/command.rs +++ b/crates/bevy_ecs/src/system/commands/command.rs @@ -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 { /// 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 { impl HandleError> for C where C: Command>, - E: Into, + E: Into, { - 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] diff --git a/crates/bevy_ecs/src/system/commands/entity_command.rs b/crates/bevy_ecs/src/system/commands/entity_command.rs index 0039963967..48019c31a2 100644 --- a/crates/bevy_ecs/src/system/commands/entity_command.rs +++ b/crates/bevy_ecs/src/system/commands/entity_command.rs @@ -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}, }; diff --git a/crates/bevy_ecs/src/system/commands/error_handler.rs b/crates/bevy_ecs/src/system/commands/error_handler.rs index 231df9ec73..e5895f91bb 100644 --- a/crates/bevy_ecs/src/system/commands/error_handler.rs +++ b/crates/bevy_ecs/src/system/commands/error_handler.rs @@ -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 = +pub static GLOBAL_ERROR_HANDLER: std::sync::OnceLock = 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 = /// [`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()) } diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index d569b7ca7b..d91154d985 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -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 + HandleError, 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 + CommandWithEntity, 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); diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index a8a3404537..9be853bf12 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -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, diff --git a/crates/bevy_ecs/src/system/observer_system.rs b/crates/bevy_ecs/src/system/observer_system.rs index 8a8f82d99e..17dd4fb017 100644 --- a/crates/bevy_ecs/src/system/observer_system.rs +++ b/crates/bevy_ecs/src/system/observer_system.rs @@ -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}, diff --git a/crates/bevy_ecs/src/system/schedule_system.rs b/crates/bevy_ecs/src/system/schedule_system.rs index e0005f06f4..749060d2b9 100644 --- a/crates/bevy_ecs/src/system/schedule_system.rs +++ b/crates/bevy_ecs/src/system/schedule_system.rs @@ -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}, }; diff --git a/crates/bevy_ecs/src/system/system_registry.rs b/crates/bevy_ecs/src/system/system_registry.rs index f1837f99dd..e65f82cb8e 100644 --- a/crates/bevy_ecs/src/system/system_registry.rs +++ b/crates/bevy_ecs/src/system/system_registry.rs @@ -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 { diff --git a/examples/ecs/fallible_systems.rs b/examples/ecs/fallible_systems.rs index 39d29fbebd..bfd94b84d5 100644 --- a/examples/ecs/fallible_systems.rs +++ b/examples/ecs/fallible_systems.rs @@ -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>` 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`, so we use `ok_or` to convert it to a `Result` on // which we can call `?` to propagate the error. .get_resource::() - // We can provide a `str` here because `Box` implements `From<&str>`. + // We can provide a `str` here because `BevyError` implements `From<&str>`. .ok_or("Resource not initialized")?; Ok(())