diff --git a/Cargo.toml b/Cargo.toml index 4efd0f5d87d44..5872daf97017b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -280,6 +280,9 @@ bevy_log = ["bevy_internal/bevy_log"] # Enable input focus subsystem bevy_input_focus = ["bevy_internal/bevy_input_focus"] +# Use the configurable global error handler as the default error handler. +configurable_error_handler = ["bevy_internal/configurable_error_handler"] + # Enable passthrough loading for SPIR-V shaders (Only supported on Vulkan, shader capabilities and extensions must agree with the platform implementation) spirv_shader_passthrough = ["bevy_internal/spirv_shader_passthrough"] @@ -2208,12 +2211,12 @@ category = "ECS (Entity Component System)" wasm = false [[example]] -name = "fallible_systems" -path = "examples/ecs/fallible_systems.rs" +name = "error_handling" +path = "examples/ecs/error_handling.rs" doc-scrape-examples = true -required-features = ["bevy_mesh_picking_backend"] +required-features = ["bevy_mesh_picking_backend", "configurable_error_handler"] -[package.metadata.example.fallible_systems] +[package.metadata.example.error_handling] name = "Fallible Systems" description = "Systems that return results to handle errors" category = "ECS (Entity Component System)" diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 9ce162d21774b..dacd9483ab5d2 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -10,7 +10,6 @@ use alloc::{ pub use bevy_derive::AppLabel; use bevy_ecs::{ component::RequiredComponentsError, - error::{BevyError, SystemErrorContext}, event::{event_update_system, EventCursor}, intern::Interned, prelude::*, @@ -1274,18 +1273,6 @@ impl App { self } - /// Set the global system error handler to use for systems that return a [`Result`]. - /// - /// See the [`bevy_ecs::error` module-level documentation](bevy_ecs::error) - /// for more information. - pub fn set_system_error_handler( - &mut self, - error_handler: fn(BevyError, SystemErrorContext), - ) -> &mut Self { - self.main_mut().set_system_error_handler(error_handler); - self - } - /// Attempts to determine if an [`AppExit`] was raised since the last update. /// /// Will attempt to return the first [`Error`](AppExit::Error) it encounters. diff --git a/crates/bevy_app/src/sub_app.rs b/crates/bevy_app/src/sub_app.rs index 7d0dfd3106766..ea51931b3507c 100644 --- a/crates/bevy_app/src/sub_app.rs +++ b/crates/bevy_app/src/sub_app.rs @@ -1,7 +1,6 @@ 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::*, schedule::{InternedScheduleLabel, InternedSystemSet, ScheduleBuildSettings, ScheduleLabel}, @@ -336,22 +335,6 @@ impl SubApp { self } - /// Set the global error handler to use for systems that return a [`Result`]. - /// - /// See the [`bevy_ecs::error` module-level documentation](bevy_ecs::error) - /// for more information. - pub fn set_system_error_handler( - &mut self, - error_handler: fn(BevyError, SystemErrorContext), - ) -> &mut Self { - let mut default_handler = self - .world_mut() - .get_resource_or_init::(); - - default_handler.0 = error_handler; - self - } - /// See [`App::add_event`]. pub fn add_event(&mut self) -> &mut Self where diff --git a/crates/bevy_ecs/Cargo.toml b/crates/bevy_ecs/Cargo.toml index 2b8d2f0d542e9..c5350dd91ad17 100644 --- a/crates/bevy_ecs/Cargo.toml +++ b/crates/bevy_ecs/Cargo.toml @@ -33,7 +33,11 @@ bevy_reflect = ["dep:bevy_reflect"] ## Extends reflection support to functions. reflect_functions = ["bevy_reflect", "bevy_reflect/functions"] -## Use the configurable global error handler as the default error handler +## Use the configurable global error handler as the default error handler. +## +## This is typically used to turn panics from the ECS into loggable errors. +## This may be useful for production builds, +## but can result in a measurable performance impact, especially for commands. configurable_error_handler = [] ## Enables automatic backtrace capturing in BevyError diff --git a/crates/bevy_ecs/src/error/command_handling.rs b/crates/bevy_ecs/src/error/command_handling.rs new file mode 100644 index 0000000000000..0e21412832cac --- /dev/null +++ b/crates/bevy_ecs/src/error/command_handling.rs @@ -0,0 +1,108 @@ +use core::{any::type_name, fmt}; + +use crate::{ + entity::Entity, + system::{entity_command::EntityCommandError, Command, EntityCommand}, + world::{error::EntityMutableFetchError, World}, +}; + +use super::{default_error_handler, BevyError, ErrorContext}; + +/// 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 `()`. +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(BevyError, ErrorContext)) -> 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 + where + Self: Sized, + { + self.handle_error_with(default_error_handler()) + } +} + +impl HandleError> for C +where + C: Command>, + E: Into, +{ + fn handle_error_with(self, error_handler: fn(BevyError, ErrorContext)) -> impl Command { + move |world: &mut World| match self.apply(world) { + Ok(_) => {} + Err(err) => (error_handler)( + err.into(), + ErrorContext::Command { + name: type_name::().into(), + }, + ), + } + } +} + +impl HandleError for C +where + C: Command, +{ + #[inline] + fn handle_error_with(self, _error_handler: fn(BevyError, ErrorContext)) -> impl Command { + self + } + #[inline] + fn handle_error(self) -> impl Command + where + Self: Sized, + { + self + } +} + +/// Passes in a specific entity to an [`EntityCommand`], resulting in a [`Command`] that +/// internally runs the [`EntityCommand`] on that entity. +/// +// NOTE: This is a separate trait from `EntityCommand` because "result-returning entity commands" and +// "non-result returning entity commands" require different implementations, so they cannot be automatically +// implemented. And this isn't the type of implementation that we want to thrust on people implementing +// EntityCommand. +pub trait CommandWithEntity { + /// Passes in a specific entity to an [`EntityCommand`], resulting in a [`Command`] that + /// internally runs the [`EntityCommand`] on that entity. + fn with_entity(self, entity: Entity) -> impl Command + HandleError; +} + +impl CommandWithEntity> for C +where + C: EntityCommand, +{ + fn with_entity( + self, + entity: Entity, + ) -> impl Command> + + HandleError> { + move |world: &mut World| -> Result<(), EntityMutableFetchError> { + let entity = world.get_entity_mut(entity)?; + self.apply(entity); + Ok(()) + } + } +} + +impl CommandWithEntity>> for C +where + C: EntityCommand>, + Err: fmt::Debug + fmt::Display + Send + Sync + 'static, +{ + fn with_entity( + self, + entity: Entity, + ) -> impl Command>> + HandleError>> + { + move |world: &mut World| { + let entity = world.get_entity_mut(entity)?; + self.apply(entity) + .map_err(EntityCommandError::CommandFailed) + } + } +} diff --git a/crates/bevy_ecs/src/error/handler.rs b/crates/bevy_ecs/src/error/handler.rs index eb0d9809af789..c957784dc8b58 100644 --- a/crates/bevy_ecs/src/error/handler.rs +++ b/crates/bevy_ecs/src/error/handler.rs @@ -1,75 +1,171 @@ -use crate::{component::Tick, error::BevyError, resource::Resource}; -use alloc::borrow::Cow; +#[cfg(feature = "configurable_error_handler")] +use bevy_platform_support::sync::OnceLock; +use core::fmt::Display; -/// Additional context for a failed system run. -pub struct SystemErrorContext { - /// The name of the system that failed. - pub name: Cow<'static, str>, +use crate::{component::Tick, error::BevyError}; +use alloc::borrow::Cow; - /// The last tick that the system was run. - pub last_run: Tick, +/// Context for a [`BevyError`] to aid in debugging. +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum ErrorContext { + /// The error occurred in a system. + System { + /// The name of the system that failed. + name: Cow<'static, str>, + /// The last tick that the system was run. + last_run: Tick, + }, + /// The error occurred in a command. + Command { + /// The name of the command that failed. + name: Cow<'static, str>, + }, + /// The error occurred in an observer. + Observer { + /// The name of the observer that failed. + name: Cow<'static, str>, + /// The last tick that the observer was run. + 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 Display for ErrorContext { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::System { name, .. } => { + write!(f, "System `{}` failed", name) + } + Self::Command { name } => write!(f, "Command `{}` failed", name), + Self::Observer { name, .. } => { + write!(f, "Observer `{}` failed", name) + } + } + } +} -impl Resource for DefaultSystemErrorHandler {} +impl ErrorContext { + /// The name of the ECS construct that failed. + pub fn name(&self) -> &str { + match self { + Self::System { name, .. } + | Self::Command { name, .. } + | Self::Observer { name, .. } => name, + } + } -impl Default for DefaultSystemErrorHandler { - fn default() -> Self { - Self(panic) + /// A string representation of the kind of ECS construct that failed. + /// + /// This is a simpler helper used for logging. + pub fn kind(&self) -> &str { + match self { + Self::System { .. } => "system", + Self::Command { .. } => "command", + Self::Observer { .. } => "observer", + } } } +/// A global error handler. This can be set at startup, as long as it is set before +/// any uses. This should generally be configured _before_ initializing the app. +/// +/// This should be set inside of your `main` function, before initializing the Bevy app. +/// The value of this error handler can be accessed using the [`default_error_handler`] function, +/// which calls [`OnceLock::get_or_init`] to get the value. +/// +/// **Note:** this is only available when the `configurable_error_handler` feature of `bevy_ecs` (or `bevy`) is enabled! +/// +/// # Example +/// +/// ``` +/// # use bevy_ecs::error::{GLOBAL_ERROR_HANDLER, warn}; +/// GLOBAL_ERROR_HANDLER.set(warn).expect("The error handler can only be set once, globally."); +/// // initialize Bevy App here +/// ``` +/// +/// To use this error handler in your app for custom error handling logic: +/// +/// ```rust +/// use bevy_ecs::error::{GLOBAL_ERROR_HANDLER, BevyError, ErrorContext, panic}; +/// +/// fn handle_errors(error: BevyError, ctx: ErrorContext) { +/// let error_handler = default_error_handler(); +/// error_handler(error, ctx); +/// } +/// ``` +/// +/// # Warning +/// +/// As this can *never* be overwritten, library code should never set this value. +#[cfg(feature = "configurable_error_handler")] +pub static GLOBAL_ERROR_HANDLER: OnceLock = OnceLock::new(); + +/// The default error handler. This defaults to [`panic()`], +/// but if set, the [`GLOBAL_ERROR_HANDLER`] will be used instead, enabling error handler customization. +/// The `configurable_error_handler` feature must be enabled to change this from the panicking default behavior, +/// as there may be runtime overhead. +#[inline] +pub fn default_error_handler() -> fn(BevyError, ErrorContext) { + #[cfg(not(feature = "configurable_error_handler"))] + return panic; + + #[cfg(feature = "configurable_error_handler")] + return *GLOBAL_ERROR_HANDLER.get_or_init(|| panic); +} + macro_rules! inner { ($call:path, $e:ident, $c:ident) => { - $call!("Encountered an error in system `{}`: {:?}", $c.name, $e); + $call!( + "Encountered an error in {} `{}`: {:?}", + $c.kind(), + $c.name(), + $e + ); }; } /// Error handler that panics with the system error. #[track_caller] #[inline] -pub fn panic(error: BevyError, ctx: SystemErrorContext) { +pub fn panic(error: BevyError, ctx: ErrorContext) { 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) { +pub fn error(error: BevyError, ctx: ErrorContext) { 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) { +pub fn warn(error: BevyError, ctx: ErrorContext) { 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) { +pub fn info(error: BevyError, ctx: ErrorContext) { 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) { +pub fn debug(error: BevyError, ctx: ErrorContext) { 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) { +pub fn trace(error: BevyError, ctx: ErrorContext) { inner!(log::trace, error, ctx); } /// Error handler that ignores the system error. #[track_caller] #[inline] -pub fn ignore(_: BevyError, _: SystemErrorContext) {} +pub fn ignore(_: BevyError, _: ErrorContext) {} diff --git a/crates/bevy_ecs/src/error/mod.rs b/crates/bevy_ecs/src/error/mod.rs index 4c8ad10d8ec9c..950deee3ecf97 100644 --- a/crates/bevy_ecs/src/error/mod.rs +++ b/crates/bevy_ecs/src/error/mod.rs @@ -1,18 +1,16 @@ -//! Error handling for "fallible" systems. +//! Error handling for Bevy systems, commands, and observers. //! //! When a system is added to a [`Schedule`], and its return type is that of [`Result`], then Bevy //! considers those systems to be "fallible", and the ECS scheduler will special-case the [`Err`] //! variant of the returned `Result`. //! -//! All [`BevyError`]s returned by a system are handled by an "error handler". By default, the +//! All [`BevyError`]s returned by a system, observer or command 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 -//! per [`Schedule`]: -//! -//! - `App::set_system_error_handler` (via `bevy_app`) sets the global error handler for all systems of the -//! current [`World`] by modifying the [`DefaultSystemErrorHandler`]. -//! - [`Schedule::set_error_handler`] sets the error handler for all systems of that schedule. +//! You can change the default behavior by registering a custom error handler. +//! Modify the [`GLOBAL_ERROR_HANDLER`] value to set a custom error handler function for your entire app. +//! In practice, this is generally feature-flagged: panicking or loudly logging errors in development, +//! and quietly logging or ignoring them in production to avoid crashing the app. //! //! Bevy provides a number of pre-built error-handlers for you to use: //! @@ -29,51 +27,52 @@ //! signature: //! //! ```rust,ignore -//! fn(BevyError, SystemErrorContext) +//! fn(BevyError, ErrorContext) //! ``` //! -//! The [`SystemErrorContext`] allows you to access additional details relevant to providing -//! context surrounding the system error – such as the system's [`name`] – in your error messages. +//! The [`ErrorContext`] allows you to access additional details relevant to providing +//! context surrounding the error – such as the system's [`name`] – in your error messages. +//! +//! Remember to turn on the `configurable_error_handler` feature to set a global error handler! //! -//! For example: +//! ```rust, ignore +//! use bevy_ecs::error::{GLOBAL_ERROR_HANDLER, BevyError, ErrorContext}; +//! use log::trace; //! -//! ```rust -//! # use bevy_ecs::prelude::*; -//! # use bevy_ecs::schedule::ScheduleLabel; -//! # use log::trace; -//! # fn update() -> Result { Ok(()) } -//! # #[derive(ScheduleLabel, Hash, Debug, PartialEq, Eq, Clone, Copy)] -//! # struct MySchedule; -//! # fn main() { -//! let mut schedule = Schedule::new(MySchedule); -//! schedule.add_systems(update); -//! schedule.set_error_handler(|error, ctx| { -//! if ctx.name.ends_with("update") { -//! trace!("Nothing to see here, move along."); -//! return; -//! } +//! fn my_error_handler(error: BevyError, ctx: ErrorContext) { +//! if ctx.name().ends_with("plz_ignore") { +//! trace!("Nothing to see here, move along."); +//! return; +//! } +//! bevy_ecs::error::error(error, ctx); +//! } //! -//! bevy_ecs::error::error(error, ctx); -//! }); -//! # } +//! fn main() { +//! // This requires the "configurable_error_handler" feature to be enabled to be in scope. +//! GLOBAL_ERROR_HANDLER.set(my_error_handler).expect("The error handler can only be set once."); +//! +//! // Initialize your Bevy App here +//! } //! ``` //! //! If you need special handling of individual fallible systems, you can use Bevy's [`system piping -//! feature`] to capture the `Result` output of the system and handle it accordingly. +//! feature`] to capture the [`Result`] output of the system and handle it accordingly. +//! +//! When working with commands, you can handle the result of each command separately using the [`HandleError::handle_error_with`] method. //! //! [`Schedule`]: crate::schedule::Schedule //! [`panic`]: panic() //! [`World`]: crate::world::World -//! [`Schedule::set_error_handler`]: crate::schedule::Schedule::set_error_handler //! [`System`]: crate::system::System //! [`name`]: crate::system::System::name -//! [`App::set_system_error_handler`]: ../../bevy_app/struct.App.html#method.set_system_error_handler //! [`system piping feature`]: crate::system::In mod bevy_error; +mod command_handling; mod handler; pub use bevy_error::*; +pub use command_handling::*; pub use handler::*; /// A result type for use in fallible systems, commands and observers. diff --git a/crates/bevy_ecs/src/observer/runner.rs b/crates/bevy_ecs/src/observer/runner.rs index 309f228b9b4ef..95fda3b2f168a 100644 --- a/crates/bevy_ecs/src/observer/runner.rs +++ b/crates/bevy_ecs/src/observer/runner.rs @@ -3,7 +3,7 @@ use core::any::Any; use crate::{ component::{ComponentHook, ComponentId, HookContext, Mutable, StorageType}, - error::{DefaultSystemErrorHandler, SystemErrorContext}, + error::{default_error_handler, ErrorContext}, observer::{ObserverDescriptor, ObserverTrigger}, prelude::*, query::DebugCheckedUnwrap, @@ -273,7 +273,7 @@ pub struct Observer { system: Box, descriptor: ObserverDescriptor, hook_on_add: ComponentHook, - error_handler: Option, + error_handler: Option, } impl Observer { @@ -322,7 +322,7 @@ impl Observer { /// Set the error handler to use for this observer. /// /// See the [`error` module-level documentation](crate::error) for more information. - pub fn with_error_handler(mut self, error_handler: fn(BevyError, SystemErrorContext)) -> Self { + pub fn with_error_handler(mut self, error_handler: fn(BevyError, ErrorContext)) -> Self { self.error_handler = Some(error_handler); self } @@ -409,7 +409,7 @@ fn observer_system_runner>( if let Err(err) = (*system).run_unsafe(trigger, world) { error_handler( err, - SystemErrorContext { + ErrorContext::Observer { name: (*system).name(), last_run: (*system).get_last_run(), }, @@ -444,7 +444,7 @@ fn hook_on_add>( ..Default::default() }; - let error_handler = world.get_resource_or_init::().0; + let error_handler = default_error_handler(); // Initialize System let system: *mut dyn ObserverSystem = diff --git a/crates/bevy_ecs/src/relationship/mod.rs b/crates/bevy_ecs/src/relationship/mod.rs index 6afb4624762b8..9230d77b3f6ef 100644 --- a/crates/bevy_ecs/src/relationship/mod.rs +++ b/crates/bevy_ecs/src/relationship/mod.rs @@ -13,10 +13,10 @@ pub use relationship_source_collection::*; use crate::{ component::{Component, HookContext, Mutable}, entity::{ComponentCloneCtx, Entity, SourceComponent}, + error::{ignore, CommandWithEntity, HandleError}, system::{ - command::HandleError, - entity_command::{self, CommandWithEntity}, - error_handler, Commands, + entity_command::{self}, + Commands, }, world::{DeferredWorld, EntityWorldMut}, }; @@ -230,7 +230,7 @@ pub trait RelationshipTarget: Component + Sized { commands.queue( entity_command::remove::() .with_entity(source_entity) - .handle_error_with(error_handler::silent()), + .handle_error_with(ignore), ); } else { warn!( @@ -255,7 +255,7 @@ pub trait RelationshipTarget: Component + Sized { commands.queue( entity_command::despawn() .with_entity(source_entity) - .handle_error_with(error_handler::silent()), + .handle_error_with(ignore), ); } else { warn!( diff --git a/crates/bevy_ecs/src/schedule/executor/mod.rs b/crates/bevy_ecs/src/schedule/executor/mod.rs index 767ae1cd99de1..c892db86c4d28 100644 --- a/crates/bevy_ecs/src/schedule/executor/mod.rs +++ b/crates/bevy_ecs/src/schedule/executor/mod.rs @@ -16,7 +16,7 @@ use fixedbitset::FixedBitSet; use crate::{ archetype::ArchetypeComponentId, component::{ComponentId, Tick}, - error::{BevyError, Result, SystemErrorContext}, + error::{BevyError, ErrorContext, Result}, prelude::{IntoSystemSet, SystemSet}, query::Access, schedule::{BoxedCondition, InternedSystemSet, NodeId, SystemTypeSet}, @@ -33,7 +33,7 @@ pub(super) trait SystemExecutor: Send + Sync { schedule: &mut SystemSchedule, world: &mut World, skip_systems: Option<&FixedBitSet>, - error_handler: fn(BevyError, SystemErrorContext), + error_handler: fn(BevyError, ErrorContext), ); fn set_apply_final_deferred(&mut self, value: bool); } diff --git a/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs b/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs index 5589a31e6138a..bf63de8dc57c0 100644 --- a/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs +++ b/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs @@ -14,7 +14,7 @@ use tracing::{info_span, Span}; use crate::{ archetype::ArchetypeComponentId, - error::{BevyError, Result, SystemErrorContext}, + error::{BevyError, ErrorContext, Result}, prelude::Resource, query::Access, schedule::{is_apply_deferred, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule}, @@ -131,7 +131,7 @@ pub struct ExecutorState { struct Context<'scope, 'env, 'sys> { environment: &'env Environment<'env, 'sys>, scope: &'scope Scope<'scope, 'env, ()>, - error_handler: fn(BevyError, SystemErrorContext), + error_handler: fn(BevyError, ErrorContext), } impl Default for MultiThreadedExecutor { @@ -182,7 +182,7 @@ impl SystemExecutor for MultiThreadedExecutor { schedule: &mut SystemSchedule, world: &mut World, _skip_systems: Option<&FixedBitSet>, - error_handler: fn(BevyError, SystemErrorContext), + error_handler: fn(BevyError, ErrorContext), ) { let state = self.state.get_mut().unwrap(); // reset counts @@ -617,7 +617,7 @@ impl ExecutorState { ) { (context.error_handler)( err, - SystemErrorContext { + ErrorContext::System { name: system.name(), last_run: system.get_last_run(), }, @@ -669,7 +669,7 @@ impl ExecutorState { if let Err(err) = __rust_begin_short_backtrace::run(system, world) { (context.error_handler)( err, - SystemErrorContext { + ErrorContext::System { name: system.name(), last_run: system.get_last_run(), }, diff --git a/crates/bevy_ecs/src/schedule/executor/simple.rs b/crates/bevy_ecs/src/schedule/executor/simple.rs index 9570e98d09c7a..9088cadc10623 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::{ - error::{BevyError, SystemErrorContext}, + error::{BevyError, ErrorContext}, 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(BevyError, SystemErrorContext), + error_handler: fn(BevyError, ErrorContext), ) { // If stepping is enabled, make sure we skip those systems that should // not be run. @@ -109,7 +109,7 @@ impl SystemExecutor for SimpleExecutor { if let Err(err) = __rust_begin_short_backtrace::run(system, world) { error_handler( err, - SystemErrorContext { + ErrorContext::System { name: system.name(), last_run: system.get_last_run(), }, diff --git a/crates/bevy_ecs/src/schedule/executor/single_threaded.rs b/crates/bevy_ecs/src/schedule/executor/single_threaded.rs index 3f3d579cc2b46..0db5f7522efb5 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::{ - error::{BevyError, SystemErrorContext}, + error::{BevyError, ErrorContext}, 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(BevyError, SystemErrorContext), + error_handler: fn(BevyError, ErrorContext), ) { // If stepping is enabled, make sure we skip those systems that should // not be run. @@ -117,7 +117,7 @@ impl SystemExecutor for SingleThreadedExecutor { if let Err(err) = __rust_begin_short_backtrace::run(system, world) { error_handler( err, - SystemErrorContext { + ErrorContext::System { name: system.name(), last_run: system.get_last_run(), }, @@ -133,7 +133,7 @@ impl SystemExecutor for SingleThreadedExecutor { if let Err(err) = __rust_begin_short_backtrace::run_unsafe(system, world) { error_handler( err, - SystemErrorContext { + ErrorContext::System { name: system.name(), last_run: system.get_last_run(), }, diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index e82c4132eca3b..217bf9a0e642f 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -26,7 +26,7 @@ use tracing::info_span; use crate::{ component::{ComponentId, Components, Tick}, - error::{BevyError, DefaultSystemErrorHandler, SystemErrorContext}, + error::default_error_handler, prelude::Component, resource::Resource, schedule::*, @@ -296,7 +296,6 @@ pub struct Schedule { executable: SystemSchedule, executor: Box, executor_initialized: bool, - error_handler: Option, } #[derive(ScheduleLabel, Hash, PartialEq, Eq, Debug, Clone)] @@ -321,7 +320,6 @@ impl Schedule { executable: SystemSchedule::new(), executor: make_executor(ExecutorKind::default()), executor_initialized: false, - error_handler: None, }; // Call `set_build_settings` to add any default build passes this.set_build_settings(Default::default()); @@ -405,13 +403,6 @@ impl Schedule { self } - /// Set the error handler to use for systems that return a [`Result`](crate::error::Result). - /// - /// 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); - } - /// Returns the schedule's current `ScheduleBuildSettings`. pub fn get_build_settings(&self) -> ScheduleBuildSettings { self.graph.settings.clone() @@ -449,7 +440,7 @@ impl Schedule { self.initialize(world) .unwrap_or_else(|e| panic!("Error when initializing schedule {:?}: {e}", self.label)); - let error_handler = self.error_handler.expect("schedule initialized"); + let error_handler = default_error_handler(); #[cfg(not(feature = "bevy_debug_stepping"))] self.executor @@ -492,10 +483,6 @@ impl Schedule { self.executor_initialized = false; } - if self.error_handler.is_none() { - self.error_handler = Some(world.get_resource_or_init::().0); - } - if !self.executor_initialized { self.executor.init(&self.executable); self.executor_initialized = true; diff --git a/crates/bevy_ecs/src/system/commands/command.rs b/crates/bevy_ecs/src/system/commands/command.rs index 7dd61946d6462..8aa46f7d8e6dd 100644 --- a/crates/bevy_ecs/src/system/commands/command.rs +++ b/crates/bevy_ecs/src/system/commands/command.rs @@ -8,12 +8,12 @@ use crate::{ bundle::{Bundle, InsertMode, NoBundleEffect}, change_detection::MaybeLocation, entity::Entity, - error::{BevyError, Result}, + error::Result, event::{Event, Events}, observer::TriggerTargets, resource::Resource, schedule::ScheduleLabel, - system::{error_handler, IntoSystem, SystemId, SystemInput}, + system::{IntoSystem, SystemId, SystemInput}, world::{FromWorld, SpawnBatchIter, World}, }; @@ -63,52 +63,6 @@ where } } -/// 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 `()`. -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, 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 - where - Self: Sized, - { - self.handle_error_with(error_handler::default()) - } -} - -impl HandleError> for C -where - C: Command>, - E: Into, -{ - 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()), - } - } -} - -impl HandleError for C -where - C: Command, -{ - #[inline] - fn handle_error_with(self, _error_handler: fn(&mut World, BevyError)) -> impl Command { - self - } - #[inline] - fn handle_error(self) -> impl Command - where - Self: Sized, - { - self - } -} - /// A [`Command`] that consumes an iterator of [`Bundles`](Bundle) to spawn a series of entities. /// /// This is more efficient than spawning the entities individually. diff --git a/crates/bevy_ecs/src/system/commands/entity_command.rs b/crates/bevy_ecs/src/system/commands/entity_command.rs index 12161ba33dc31..87584383f794a 100644 --- a/crates/bevy_ecs/src/system/commands/entity_command.rs +++ b/crates/bevy_ecs/src/system/commands/entity_command.rs @@ -5,7 +5,6 @@ //! [`EntityCommands`](crate::system::EntityCommands). use alloc::vec::Vec; -use core::fmt; use log::info; use crate::{ @@ -13,11 +12,10 @@ use crate::{ change_detection::MaybeLocation, component::{Component, ComponentId, ComponentInfo}, entity::{Entity, EntityClonerBuilder}, - error::Result, event::Event, relationship::RelationshipHookMode, - system::{command::HandleError, Command, IntoObserverSystem}, - world::{error::EntityMutableFetchError, EntityWorldMut, FromWorld, World}, + system::IntoObserverSystem, + world::{error::EntityMutableFetchError, EntityWorldMut, FromWorld}, }; use bevy_ptr::OwningPtr; @@ -84,53 +82,6 @@ pub trait EntityCommand: Send + 'static { /// Executes this command for the given [`Entity`]. fn apply(self, entity: EntityWorldMut) -> Out; } -/// Passes in a specific entity to an [`EntityCommand`], resulting in a [`Command`] that -/// internally runs the [`EntityCommand`] on that entity. -/// -// NOTE: This is a separate trait from `EntityCommand` because "result-returning entity commands" and -// "non-result returning entity commands" require different implementations, so they cannot be automatically -// implemented. And this isn't the type of implementation that we want to thrust on people implementing -// EntityCommand. -pub trait CommandWithEntity { - /// Passes in a specific entity to an [`EntityCommand`], resulting in a [`Command`] that - /// internally runs the [`EntityCommand`] on that entity. - fn with_entity(self, entity: Entity) -> impl Command + HandleError; -} - -impl CommandWithEntity> for C -where - C: EntityCommand, -{ - fn with_entity( - self, - entity: Entity, - ) -> impl Command> - + HandleError> { - move |world: &mut World| -> Result<(), EntityMutableFetchError> { - let entity = world.get_entity_mut(entity)?; - self.apply(entity); - Ok(()) - } - } -} - -impl CommandWithEntity>> for C -where - C: EntityCommand>, - Err: fmt::Debug + fmt::Display + Send + Sync + 'static, -{ - fn with_entity( - self, - entity: Entity, - ) -> impl Command>> + HandleError>> - { - move |world: &mut World| { - let entity = world.get_entity_mut(entity)?; - self.apply(entity) - .map_err(EntityCommandError::CommandFailed) - } - } -} /// An error that occurs when running an [`EntityCommand`] on a specific entity. #[derive(thiserror::Error, Debug)] diff --git a/crates/bevy_ecs/src/system/commands/error_handler.rs b/crates/bevy_ecs/src/system/commands/error_handler.rs deleted file mode 100644 index e5895f91bb955..0000000000000 --- a/crates/bevy_ecs/src/system/commands/error_handler.rs +++ /dev/null @@ -1,61 +0,0 @@ -//! 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::{error::BevyError, world::World}; -use log::{error, warn}; - -/// An error handler that does nothing. -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, BevyError) { - |_, error| warn!("{error}") -} - -/// An error handler that accepts an error and logs it with [`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, BevyError) { - |_, error| panic!("{error}") -} - -/// The default error handler. This defaults to [`panic()`]. If the -/// `configurable_error_handler` cargo feature is enabled, then -/// `GLOBAL_ERROR_HANDLER` will be used instead, enabling error handler customization. -#[cfg(not(feature = "configurable_error_handler"))] -#[inline] -pub fn default() -> fn(&mut World, BevyError) { - panic() -} - -/// A global error handler. This can be set at startup, as long as it is set before -/// any uses. This should generally be configured _before_ initializing the app. -/// -/// If the `configurable_error_handler` cargo feature is enabled, this will be used -/// by default. -/// -/// This should be set in the following way: -/// -/// ``` -/// # use bevy_ecs::system::error_handler::{GLOBAL_ERROR_HANDLER, warn}; -/// GLOBAL_ERROR_HANDLER.set(warn()); -/// // initialize Bevy App here -/// ``` -#[cfg(feature = "configurable_error_handler")] -pub static GLOBAL_ERROR_HANDLER: std::sync::OnceLock = - std::sync::OnceLock::new(); - -/// The default error handler. This defaults to [`panic()`]. If the -/// `configurable_error_handler` cargo feature is enabled, then -/// [`GLOBAL_ERROR_HANDLER`] will be used instead, enabling error handler customization. -#[cfg(feature = "configurable_error_handler")] -#[inline] -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 d91154d985115..722bf5f9dcf62 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -1,6 +1,5 @@ pub mod command; pub mod entity_command; -pub mod error_handler; #[cfg(feature = "std")] mod parallel_scope; @@ -21,15 +20,12 @@ use crate::{ change_detection::{MaybeLocation, Mut}, component::{Component, ComponentId, Mutable}, entity::{Entities, Entity, EntityClonerBuilder, EntityDoesNotExistError}, - error::BevyError, + error::{ignore, warn, BevyError, CommandWithEntity, ErrorContext, HandleError}, event::Event, observer::{Observer, TriggerTargets}, resource::Resource, schedule::ScheduleLabel, - system::{ - command::HandleError, entity_command::CommandWithEntity, input::SystemInput, Deferred, - IntoObserverSystem, IntoSystem, RegisteredSystem, SystemId, - }, + system::{Deferred, IntoObserverSystem, IntoSystem, RegisteredSystem, SystemId, SystemInput}, world::{ command_queue::RawCommandQueue, unsafe_world_cell::UnsafeWorldCell, CommandQueue, EntityWorldMut, FromWorld, World, @@ -95,14 +91,13 @@ use crate::{ /// They are granted exclusive access to the [`World`], which enables them to /// respond to the error in whatever way is necessary. /// -/// The [default error handler](error_handler::default) panics. -/// It can be configured by enabling the `configurable_error_handler` cargo feature, -/// then setting the `GLOBAL_ERROR_HANDLER`. +/// The [default error handler](crate::error::default_error_handler) panics. +/// It can be configured by setting the `GLOBAL_ERROR_HANDLER`. /// /// Alternatively, you can customize the error handler for a specific command /// by calling [`Commands::queue_handled`]. /// -/// The [`error_handler`] module provides some simple error handlers for convenience. +/// The [`error`](crate::error) module provides some simple error handlers for convenience. /// /// [`ApplyDeferred`]: crate::schedule::ApplyDeferred pub struct Commands<'w, 's> { @@ -547,7 +542,7 @@ impl<'w, 's> Commands<'w, 's> { /// Pushes a generic [`Command`] to the command queue. /// /// If the [`Command`] returns a [`Result`], - /// it will be handled using the [default error handler](error_handler::default). + /// it will be handled using The [default error handler](crate::error::default_error_handler). /// /// To use a custom error handler, see [`Commands::queue_handled`]. /// @@ -609,7 +604,8 @@ impl<'w, 's> Commands<'w, 's> { /// /// ``` /// # use bevy_ecs::prelude::*; - /// # use bevy_ecs::system::error_handler; + /// use bevy_ecs::error::warn; + /// /// #[derive(Resource, Default)] /// struct Counter(u64); /// @@ -625,7 +621,7 @@ impl<'w, 's> Commands<'w, 's> { /// } /// /// fn add_three_to_counter_system(mut commands: Commands) { - /// commands.queue_handled(AddToCounter("3".to_string()), error_handler::warn()); + /// commands.queue_handled(AddToCounter("3".to_string()), warn); /// } /// fn add_twenty_five_to_counter_system(mut commands: Commands) { /// commands.queue(|world: &mut World| { @@ -639,7 +635,7 @@ impl<'w, 's> Commands<'w, 's> { pub fn queue_handled + HandleError, T>( &mut self, command: C, - error_handler: fn(&mut World, BevyError), + error_handler: fn(BevyError, ErrorContext), ) { self.queue_internal(command.handle_error_with(error_handler)); } @@ -785,10 +781,7 @@ impl<'w, 's> Commands<'w, 's> { I: IntoIterator + Send + Sync + 'static, B: Bundle, { - self.queue( - command::insert_batch(batch, InsertMode::Replace) - .handle_error_with(error_handler::warn()), - ); + self.queue(command::insert_batch(batch, InsertMode::Replace).handle_error_with(warn)); } /// Pushes a [`Command`] to the queue for adding a [`Bundle`] type to a batch of [`Entities`](Entity). @@ -813,9 +806,7 @@ impl<'w, 's> Commands<'w, 's> { I: IntoIterator + Send + Sync + 'static, B: Bundle, { - self.queue( - command::insert_batch(batch, InsertMode::Keep).handle_error_with(error_handler::warn()), - ); + self.queue(command::insert_batch(batch, InsertMode::Keep).handle_error_with(warn)); } /// Pushes a [`Command`] to the queue for inserting a [`Resource`] in the [`World`] with an inferred value. @@ -911,7 +902,7 @@ impl<'w, 's> Commands<'w, 's> { /// execution of the system happens later. To get the output of a system, use /// [`World::run_system`] or [`World::run_system_with`] instead of running the system as a command. pub fn run_system(&mut self, id: SystemId) { - self.queue(command::run_system(id).handle_error_with(error_handler::warn())); + self.queue(command::run_system(id).handle_error_with(warn)); } /// Runs the system corresponding to the given [`SystemId`]. @@ -927,7 +918,7 @@ impl<'w, 's> Commands<'w, 's> { where I: SystemInput: Send> + 'static, { - self.queue(command::run_system_with(id, input).handle_error_with(error_handler::warn())); + self.queue(command::run_system_with(id, input).handle_error_with(warn)); } /// Registers a system and returns a [`SystemId`] so it can later be called by [`World::run_system`]. @@ -1001,7 +992,7 @@ impl<'w, 's> Commands<'w, 's> { I: SystemInput + Send + 'static, O: Send + 'static, { - self.queue(command::unregister_system(system_id).handle_error_with(error_handler::warn())); + self.queue(command::unregister_system(system_id).handle_error_with(warn)); } /// Removes a system previously registered with [`World::register_system_cached`]. @@ -1016,9 +1007,7 @@ impl<'w, 's> Commands<'w, 's> { &mut self, system: S, ) { - self.queue( - command::unregister_system_cached(system).handle_error_with(error_handler::warn()), - ); + self.queue(command::unregister_system_cached(system).handle_error_with(warn)); } /// Similar to [`Self::run_system`], but caching the [`SystemId`] in a @@ -1029,7 +1018,7 @@ impl<'w, 's> Commands<'w, 's> { &mut self, system: S, ) { - self.queue(command::run_system_cached(system).handle_error_with(error_handler::warn())); + self.queue(command::run_system_cached(system).handle_error_with(warn)); } /// Similar to [`Self::run_system_with`], but caching the [`SystemId`] in a @@ -1042,9 +1031,7 @@ impl<'w, 's> Commands<'w, 's> { M: 'static, S: IntoSystem + Send + 'static, { - self.queue( - command::run_system_cached_with(system, input).handle_error_with(error_handler::warn()), - ); + self.queue(command::run_system_cached_with(system, input).handle_error_with(warn)); } /// Sends a "global" [`Trigger`] without any targets. This will run any [`Observer`] of the `event` that @@ -1138,7 +1125,7 @@ impl<'w, 's> Commands<'w, 's> { /// # assert_eq!(world.resource::().0, 1); /// ``` pub fn run_schedule(&mut self, label: impl ScheduleLabel) { - self.queue(command::run_schedule(label).handle_error_with(error_handler::warn())); + self.queue(command::run_schedule(label).handle_error_with(warn)); } } @@ -1167,14 +1154,13 @@ impl<'w, 's> Commands<'w, 's> { /// They are granted exclusive access to the [`World`], which enables them to /// respond to the error in whatever way is necessary. /// -/// The [default error handler](error_handler::default) panics. -/// It can be configured by enabling the `configurable_error_handler` cargo feature, -/// then setting the `GLOBAL_ERROR_HANDLER`. +/// The [default error handler](crate::error::default_error_handler) panics. +/// It can be configured by setting the `GLOBAL_ERROR_HANDLER`. /// /// Alternatively, you can customize the error handler for a specific command /// by calling [`EntityCommands::queue_handled`]. /// -/// The [`error_handler`] module provides some simple error handlers for convenience. +/// The [`error`](crate::error) module provides some simple error handlers for convenience. pub struct EntityCommands<'a> { pub(crate) entity: Entity, pub(crate) commands: Commands<'a, 'a>, @@ -1430,7 +1416,7 @@ impl<'a> EntityCommands<'a> { // - `ComponentId` safety is ensured by the caller. // - `T` safety is ensured by the caller. unsafe { entity_command::insert_by_id(component_id, value, InsertMode::Replace) }, - error_handler::silent(), + ignore, ) } @@ -1484,10 +1470,7 @@ impl<'a> EntityCommands<'a> { /// ``` #[track_caller] pub fn try_insert(&mut self, bundle: impl Bundle) -> &mut Self { - self.queue_handled( - entity_command::insert(bundle, InsertMode::Replace), - error_handler::silent(), - ) + self.queue_handled(entity_command::insert(bundle, InsertMode::Replace), ignore) } /// Similar to [`Self::try_insert`] but will only try to insert if the predicate returns true. @@ -1586,10 +1569,7 @@ impl<'a> EntityCommands<'a> { /// Unlike [`Self::insert_if_new`], this will not panic if the associated entity does not exist. #[track_caller] pub fn try_insert_if_new(&mut self, bundle: impl Bundle) -> &mut Self { - self.queue_handled( - entity_command::insert(bundle, InsertMode::Keep), - error_handler::silent(), - ) + self.queue_handled(entity_command::insert(bundle, InsertMode::Keep), ignore) } /// Removes a [`Bundle`] of components from the entity. @@ -1632,7 +1612,7 @@ impl<'a> EntityCommands<'a> { where T: Bundle, { - self.queue_handled(entity_command::remove::(), error_handler::warn()) + self.queue_handled(entity_command::remove::(), warn) } /// Removes a [`Bundle`] of components from the entity. @@ -1678,7 +1658,7 @@ impl<'a> EntityCommands<'a> { where T: Bundle, { - self.queue_handled(entity_command::remove::(), error_handler::silent()) + self.queue_handled(entity_command::remove::(), ignore) } /// Removes all components in the [`Bundle`] components and remove all required components for each component in the [`Bundle`] from entity. @@ -1756,7 +1736,7 @@ impl<'a> EntityCommands<'a> { /// ``` #[track_caller] pub fn despawn(&mut self) { - self.queue_handled(entity_command::despawn(), error_handler::warn()); + self.queue_handled(entity_command::despawn(), warn); } /// Despawns the provided entity and its descendants. #[deprecated( @@ -1777,13 +1757,13 @@ impl<'a> EntityCommands<'a> { /// This will also despawn the entities in any [`RelationshipTarget`](crate::relationship::RelationshipTarget) that are configured /// to despawn descendants. For example, this will recursively despawn [`Children`](crate::hierarchy::Children). pub fn try_despawn(&mut self) { - self.queue_handled(entity_command::despawn(), error_handler::silent()); + self.queue_handled(entity_command::despawn(), ignore); } /// Pushes an [`EntityCommand`] to the queue, which will get executed for the current [`Entity`]. /// /// If the [`EntityCommand`] returns a [`Result`], - /// it will be handled using the [default error handler](error_handler::default). + /// it will be handled using The [default error handler](crate::error::default_error_handler). /// /// To use a custom error handler, see [`EntityCommands::queue_handled`]. /// @@ -1834,8 +1814,9 @@ impl<'a> EntityCommands<'a> { /// /// ``` /// # use bevy_ecs::prelude::*; - /// # use bevy_ecs::system::error_handler; /// # fn my_system(mut commands: Commands) { + /// use bevy_ecs::error::warn; + /// /// commands /// .spawn_empty() /// // Closures with this signature implement `EntityCommand`. @@ -1845,7 +1826,7 @@ impl<'a> EntityCommands<'a> { /// println!("Successfully parsed the value {} for entity {}", value, entity.id()); /// Ok(()) /// }, - /// error_handler::warn() + /// warn /// ); /// # } /// # bevy_ecs::system::assert_is_system(my_system); @@ -1853,7 +1834,7 @@ impl<'a> EntityCommands<'a> { pub fn queue_handled + CommandWithEntity, T, M>( &mut self, command: C, - error_handler: fn(&mut World, BevyError), + error_handler: fn(BevyError, ErrorContext), ) -> &mut Self { self.commands .queue_handled(command.with_entity(self.entity), error_handler); diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index b0be2f00c82b5..2aae8138d771d 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -280,6 +280,9 @@ custom_cursor = ["bevy_winit/custom_cursor"] # Experimental support for nodes that are ignored for UI layouting ghost_nodes = ["bevy_ui/ghost_nodes"] +# Use the configurable global error handler as the default error handler. +configurable_error_handler = ["bevy_ecs/configurable_error_handler"] + # Allows access to the `std` crate. Enabling this feature will prevent compilation # on `no_std` targets, but provides access to certain additional features on # supported platforms. diff --git a/docs/cargo_features.md b/docs/cargo_features.md index a96c2697f5ddd..7f335dfd562d2 100644 --- a/docs/cargo_features.md +++ b/docs/cargo_features.md @@ -69,6 +69,7 @@ The default feature set enables most of the expected features of a game engine, |bevy_remote|Enable the Bevy Remote Protocol| |bevy_ui_debug|Provides a debug overlay for bevy UI| |bmp|BMP image format support| +|configurable_error_handler|Use the configurable global error handler as the default error handler.| |critical-section|`critical-section` provides the building blocks for synchronization primitives on all platforms, including `no_std`.| |dds|DDS compressed texture support| |debug_glam_assert|Enable assertions in debug builds to check the validity of parameters passed to glam| diff --git a/examples/README.md b/examples/README.md index 35fac7c16c790..fbcec2ad81a5a 100644 --- a/examples/README.md +++ b/examples/README.md @@ -313,7 +313,7 @@ Example | Description [Entity disabling](../examples/ecs/entity_disabling.rs) | Demonstrates how to hide entities from the ECS without deleting them [Event](../examples/ecs/event.rs) | Illustrates event creation, activation, and reception [Fallible System Parameters](../examples/ecs/fallible_params.rs) | Systems are skipped if their parameters cannot be acquired -[Fallible Systems](../examples/ecs/fallible_systems.rs) | Systems that return results to handle errors +[Fallible Systems](../examples/ecs/error_handling.rs) | Systems that return results to handle errors [Fixed Timestep](../examples/ecs/fixed_timestep.rs) | Shows how to create systems that run every fixed timestep, rather than every tick [Generic System](../examples/ecs/generic_system.rs) | Shows how to create systems that can be reused with different types [Hierarchy](../examples/ecs/hierarchy.rs) | Creates a hierarchy of parents and children entities diff --git a/examples/ecs/fallible_systems.rs b/examples/ecs/error_handling.rs similarity index 75% rename from examples/ecs/fallible_systems.rs rename to examples/ecs/error_handling.rs index bfd94b84d5d2c..b4726254d6b3b 100644 --- a/examples/ecs/fallible_systems.rs +++ b/examples/ecs/error_handling.rs @@ -1,7 +1,13 @@ //! Showcases how fallible systems and observers can make use of Rust's powerful result handling //! syntax. - -use bevy::ecs::world::DeferredWorld; +//! +//! Important note: to set the global error handler, the `configurable_error_handler` feature must be +//! enabled. This feature is disabled by default, as it may introduce runtime overhead, especially for commands. + +use bevy::ecs::{ + error::{warn, GLOBAL_ERROR_HANDLER}, + world::DeferredWorld, +}; use bevy::math::sampling::UniformMeshSampler; use bevy::prelude::*; @@ -10,6 +16,16 @@ use rand::SeedableRng; use rand_chacha::ChaCha8Rng; fn main() { + // By default, fallible systems that return an error will panic. + // + // We can change this by setting a custom error handler, which applies globally. + // 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`. + GLOBAL_ERROR_HANDLER + .set(warn) + .expect("The error handler can only be set once, globally."); + let mut app = App::new(); app.add_plugins(DefaultPlugins); @@ -22,26 +38,9 @@ fn main() { // types of systems the same way, except for the error handling. app.add_systems(Startup, setup); - // By default, fallible systems that return an error will panic. - // - // We can change this by setting a custom error handler. This can be done globally for all - // 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::error::warn); - - // Additionally, you can set a custom error handler per `Schedule`. This will take precedence - // over the global error handler. - // - // In this instance we provide our own non-capturing closure that coerces to the expected error - // handler function pointer: - // - // fn(bevy_ecs::error::BevyError, bevy_ecs::error::SystemErrorContext) - // - app.add_systems(PostStartup, failing_system) - .get_schedule_mut(PostStartup) - .unwrap() - .set_error_handler(|err, ctx| error!("{} failed: {err}", ctx.name)); + // Commands can also return `Result`s, which are automatically handled by the global error handler + // if not explicitly handled by the user. + app.add_systems(Startup, failing_commands); // Individual systems can also be handled by piping the output result: app.add_systems( @@ -166,3 +165,28 @@ fn failing_system(world: &mut World) -> Result { Ok(()) } + +fn failing_commands(mut commands: Commands) { + commands + // This entity doesn't exist! + .entity(Entity::from_raw(12345678)) + // Normally, this failed command would panic, + // but since we've set the global error handler to `warn` + // it will log a warning instead. + .insert(Transform::default()); + + // The error handlers for commands can be set individually as well, + // by using the queue_handled method. + commands.queue_handled( + |world: &mut World| -> Result { + world + .get_resource::() + .ok_or("Resource not initialized when accessed in a command")?; + + Ok(()) + }, + |error, context| { + error!("{error}, {context}"); + }, + ); +}