diff --git a/crates/bevy_app/Cargo.toml b/crates/bevy_app/Cargo.toml index c19a3329d3434..010e789fa5f92 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 9799693aff19f..83aeac84bc777 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 5a2ae097ff053..e73c3d009845d 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 ead7c468c37a9..1843143c2ed46 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 e42b2b62a0650..f8adb9a92aae6 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 0000000000000..73777bad77faf --- /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 0000000000000..eb0d9809af789 --- /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 ef5eace90b07e..307d93158f3e8 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 bae3dbfed2438..d7988bcc996f8 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 fdb3b4fa800ba..7485af7f878b1 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 7013520f949a4..9f6b55fa891b2 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 898cf674245d2..f685bbecbdf51 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 c99d263e046df..983d9629f8690 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 e78cb666aedd4..7ab6f1a392c1d 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 ad01c324e9ea5..a295919690f94 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 b5c44085d5db3..277d41eb0bdc3 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 d67ca188e8172..0cb2db2de56cb 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 5344534f39568..7dd61946d6462 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 00399639677bb..48019c31a2704 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 231df9ec7387e..e5895f91bb955 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 12c5427225b5d..321ef47bc1324 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)); } @@ -1152,7 +1152,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)`. @@ -1845,7 +1845,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 a8a3404537671..9be853bf12bd1 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 8a8f82d99e361..17dd4fb01784a 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 e0005f06f46d9..749060d2b968a 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 f1837f99dd6f5..e65f82cb8e0f2 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 39d29fbebdc05..bfd94b84d5d2c 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(())