Unify and simplify command and system error handling (#18351)

# Objective

- ECS error handling is a lovely flagship feature for Bevy 0.16, all in
the name of reducing panics and encouraging better error handling
(#14275).
- Currently though, command and system error handling are completely
disjoint and use different mechanisms.
- Additionally, there's a number of distinct ways to set the
default/fallback/global error handler that have limited value. As far as
I can tell, this will be cfg flagged to toggle between dev and
production builds in 99.9% of cases, with no real value in more granular
settings or helpers.
- Fixes #17272

## Solution

- Standardize error handling on the OnceLock global error mechanisms
ironed out in https://github.com/bevyengine/bevy/pull/17215
- As discussed there, there are serious performance concerns there,
especially for commands
- I also think this is a better fit for the use cases, as it's truly
global
- Move from `SystemErrorContext` to a more general purpose
`ErrorContext`, which can handle observers and commands more clearly
- Cut the superfluous setter methods on `App` and `SubApp`
- Rename the limited (and unhelpful) `fallible_systems` example to
`error_handling`, and add an example of command error handling

## Testing

Ran the `error_handling` example.

## Notes for reviewers

- Do you see a clear way to allow commands to retain &mut World access
in the per-command custom error handlers? IMO that's a key feature here
(allowing the ad-hoc creation of custom commands), but I'm not sure how
to get there without exploding complexity.
- I've removed the feature gate on the default_error_handler: contrary
to @cart's opinion in #17215 I think that virtually all apps will want
to use this. Can you think of a category of app that a) is extremely
performance sensitive b) is fine with shipping to production with the
panic error handler? If so, I can try to gather performance numbers
and/or reintroduce the feature flag. UPDATE: see benches at the end of
this message.
- ~~`OnceLock` is in `std`: @bushrat011899 what should we do here?~~
- Do you have ideas for more automated tests for this collection of
features?

## Benchmarks

I checked the impact of the feature flag introduced: benchmarks might
show regressions. This bears more investigation. I'm still skeptical
that there are users who are well-served by a fast always panicking
approach, but I'm going to re-add the feature flag here to avoid
stalling this out.


![image](https://github.com/user-attachments/assets/237f644a-b36d-4332-9b45-76fd5cbff4d0)

---------

Co-authored-by: Zachary Harrold <zac@harrold.com.au>
This commit is contained in:
Alice Cecile 2025-03-18 15:27:50 -04:00 committed by François Mockers
parent f04406ccce
commit 27d02de375
22 changed files with 386 additions and 366 deletions

View File

@ -280,6 +280,9 @@ bevy_log = ["bevy_internal/bevy_log"]
# Enable input focus subsystem # Enable input focus subsystem
bevy_input_focus = ["bevy_internal/bevy_input_focus"] 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) # 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"] spirv_shader_passthrough = ["bevy_internal/spirv_shader_passthrough"]
@ -2208,12 +2211,12 @@ category = "ECS (Entity Component System)"
wasm = false wasm = false
[[example]] [[example]]
name = "fallible_systems" name = "error_handling"
path = "examples/ecs/fallible_systems.rs" path = "examples/ecs/error_handling.rs"
doc-scrape-examples = true 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" name = "Fallible Systems"
description = "Systems that return results to handle errors" description = "Systems that return results to handle errors"
category = "ECS (Entity Component System)" category = "ECS (Entity Component System)"

View File

@ -10,7 +10,6 @@ use alloc::{
pub use bevy_derive::AppLabel; pub use bevy_derive::AppLabel;
use bevy_ecs::{ use bevy_ecs::{
component::RequiredComponentsError, component::RequiredComponentsError,
error::{BevyError, SystemErrorContext},
event::{event_update_system, EventCursor}, event::{event_update_system, EventCursor},
intern::Interned, intern::Interned,
prelude::*, prelude::*,
@ -1274,18 +1273,6 @@ impl App {
self 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. /// Attempts to determine if an [`AppExit`] was raised since the last update.
/// ///
/// Will attempt to return the first [`Error`](AppExit::Error) it encounters. /// Will attempt to return the first [`Error`](AppExit::Error) it encounters.

View File

@ -1,7 +1,6 @@
use crate::{App, AppLabel, InternedAppLabel, Plugin, Plugins, PluginsState}; use crate::{App, AppLabel, InternedAppLabel, Plugin, Plugins, PluginsState};
use alloc::{boxed::Box, string::String, vec::Vec}; use alloc::{boxed::Box, string::String, vec::Vec};
use bevy_ecs::{ use bevy_ecs::{
error::{DefaultSystemErrorHandler, SystemErrorContext},
event::EventRegistry, event::EventRegistry,
prelude::*, prelude::*,
schedule::{InternedScheduleLabel, InternedSystemSet, ScheduleBuildSettings, ScheduleLabel}, schedule::{InternedScheduleLabel, InternedSystemSet, ScheduleBuildSettings, ScheduleLabel},
@ -336,22 +335,6 @@ impl SubApp {
self 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::<DefaultSystemErrorHandler>();
default_handler.0 = error_handler;
self
}
/// See [`App::add_event`]. /// See [`App::add_event`].
pub fn add_event<T>(&mut self) -> &mut Self pub fn add_event<T>(&mut self) -> &mut Self
where where

View File

@ -33,7 +33,11 @@ bevy_reflect = ["dep:bevy_reflect"]
## Extends reflection support to functions. ## Extends reflection support to functions.
reflect_functions = ["bevy_reflect", "bevy_reflect/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 = [] configurable_error_handler = []
## Enables automatic backtrace capturing in BevyError ## Enables automatic backtrace capturing in BevyError

View File

@ -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<Out = ()> {
/// Takes a [`Command`] that returns a Result and uses a given error handler function to convert it into
/// a [`Command`] that internally handles an error if it occurs and returns `()`.
fn handle_error_with(self, error_handler: fn(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<C, T, E> HandleError<Result<T, E>> for C
where
C: Command<Result<T, E>>,
E: Into<BevyError>,
{
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::<C>().into(),
},
),
}
}
}
impl<C> 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<Out> {
/// 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<Out> + HandleError<Out>;
}
impl<C> CommandWithEntity<Result<(), EntityMutableFetchError>> for C
where
C: EntityCommand,
{
fn with_entity(
self,
entity: Entity,
) -> impl Command<Result<(), EntityMutableFetchError>>
+ HandleError<Result<(), EntityMutableFetchError>> {
move |world: &mut World| -> Result<(), EntityMutableFetchError> {
let entity = world.get_entity_mut(entity)?;
self.apply(entity);
Ok(())
}
}
}
impl<C, T, Err> CommandWithEntity<Result<T, EntityCommandError<Err>>> for C
where
C: EntityCommand<Result<T, Err>>,
Err: fmt::Debug + fmt::Display + Send + Sync + 'static,
{
fn with_entity(
self,
entity: Entity,
) -> impl Command<Result<T, EntityCommandError<Err>>> + HandleError<Result<T, EntityCommandError<Err>>>
{
move |world: &mut World| {
let entity = world.get_entity_mut(entity)?;
self.apply(entity)
.map_err(EntityCommandError::CommandFailed)
}
}
}

View File

@ -1,75 +1,171 @@
use crate::{component::Tick, error::BevyError, resource::Resource}; #[cfg(feature = "configurable_error_handler")]
use bevy_platform_support::sync::OnceLock;
use core::fmt::Display;
use crate::{component::Tick, error::BevyError};
use alloc::borrow::Cow; use alloc::borrow::Cow;
/// Additional context for a failed system run. /// Context for a [`BevyError`] to aid in debugging.
pub struct SystemErrorContext { #[derive(Debug, PartialEq, Eq, Clone)]
/// The name of the system that failed. pub enum ErrorContext {
pub name: Cow<'static, str>, /// The error occurred in a system.
System {
/// The last tick that the system was run. /// The name of the system that failed.
pub last_run: Tick, 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). impl Display for ErrorContext {
pub struct DefaultSystemErrorHandler(pub fn(BevyError, SystemErrorContext)); fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
impl Resource for DefaultSystemErrorHandler {} Self::System { name, .. } => {
write!(f, "System `{}` failed", name)
impl Default for DefaultSystemErrorHandler { }
fn default() -> Self { Self::Command { name } => write!(f, "Command `{}` failed", name),
Self(panic) Self::Observer { name, .. } => {
write!(f, "Observer `{}` failed", name)
}
}
} }
} }
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,
}
}
/// 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<fn(BevyError, ErrorContext)> = 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 { macro_rules! inner {
($call:path, $e:ident, $c:ident) => { ($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. /// Error handler that panics with the system error.
#[track_caller] #[track_caller]
#[inline] #[inline]
pub fn panic(error: BevyError, ctx: SystemErrorContext) { pub fn panic(error: BevyError, ctx: ErrorContext) {
inner!(panic, error, ctx); inner!(panic, error, ctx);
} }
/// Error handler that logs the system error at the `error` level. /// Error handler that logs the system error at the `error` level.
#[track_caller] #[track_caller]
#[inline] #[inline]
pub fn error(error: BevyError, ctx: SystemErrorContext) { pub fn error(error: BevyError, ctx: ErrorContext) {
inner!(log::error, error, ctx); inner!(log::error, error, ctx);
} }
/// Error handler that logs the system error at the `warn` level. /// Error handler that logs the system error at the `warn` level.
#[track_caller] #[track_caller]
#[inline] #[inline]
pub fn warn(error: BevyError, ctx: SystemErrorContext) { pub fn warn(error: BevyError, ctx: ErrorContext) {
inner!(log::warn, error, ctx); inner!(log::warn, error, ctx);
} }
/// Error handler that logs the system error at the `info` level. /// Error handler that logs the system error at the `info` level.
#[track_caller] #[track_caller]
#[inline] #[inline]
pub fn info(error: BevyError, ctx: SystemErrorContext) { pub fn info(error: BevyError, ctx: ErrorContext) {
inner!(log::info, error, ctx); inner!(log::info, error, ctx);
} }
/// Error handler that logs the system error at the `debug` level. /// Error handler that logs the system error at the `debug` level.
#[track_caller] #[track_caller]
#[inline] #[inline]
pub fn debug(error: BevyError, ctx: SystemErrorContext) { pub fn debug(error: BevyError, ctx: ErrorContext) {
inner!(log::debug, error, ctx); inner!(log::debug, error, ctx);
} }
/// Error handler that logs the system error at the `trace` level. /// Error handler that logs the system error at the `trace` level.
#[track_caller] #[track_caller]
#[inline] #[inline]
pub fn trace(error: BevyError, ctx: SystemErrorContext) { pub fn trace(error: BevyError, ctx: ErrorContext) {
inner!(log::trace, error, ctx); inner!(log::trace, error, ctx);
} }
/// Error handler that ignores the system error. /// Error handler that ignores the system error.
#[track_caller] #[track_caller]
#[inline] #[inline]
pub fn ignore(_: BevyError, _: SystemErrorContext) {} pub fn ignore(_: BevyError, _: ErrorContext) {}

View File

@ -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 //! 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`] //! considers those systems to be "fallible", and the ECS scheduler will special-case the [`Err`]
//! variant of the returned `Result`. //! 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. //! [`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 //! You can change the default behavior by registering a custom error handler.
//! per [`Schedule`]: //! 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,
//! - `App::set_system_error_handler` (via `bevy_app`) sets the global error handler for all systems of the //! and quietly logging or ignoring them in production to avoid crashing the app.
//! current [`World`] by modifying the [`DefaultSystemErrorHandler`].
//! - [`Schedule::set_error_handler`] sets the error handler for all systems of that schedule.
//! //!
//! Bevy provides a number of pre-built error-handlers for you to use: //! Bevy provides a number of pre-built error-handlers for you to use:
//! //!
@ -29,51 +27,52 @@
//! signature: //! signature:
//! //!
//! ```rust,ignore //! ```rust,ignore
//! fn(BevyError, SystemErrorContext) //! fn(BevyError, ErrorContext)
//! ``` //! ```
//! //!
//! The [`SystemErrorContext`] allows you to access additional details relevant to providing //! The [`ErrorContext`] allows you to access additional details relevant to providing
//! context surrounding the system error such as the system's [`name`] in your error messages. //! context surrounding the error such as the system's [`name`] in your error messages.
//! //!
//! For example: //! Remember to turn on the `configurable_error_handler` feature to set a global error handler!
//! //!
//! ```rust //! ```rust, ignore
//! # use bevy_ecs::prelude::*; //! use bevy_ecs::error::{GLOBAL_ERROR_HANDLER, BevyError, ErrorContext};
//! # use bevy_ecs::schedule::ScheduleLabel; //! use log::trace;
//! # 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;
//! }
//! //!
//! bevy_ecs::error::error(error, ctx); //! 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);
//! }
//!
//! 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 //! 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 //! [`Schedule`]: crate::schedule::Schedule
//! [`panic`]: panic() //! [`panic`]: panic()
//! [`World`]: crate::world::World //! [`World`]: crate::world::World
//! [`Schedule::set_error_handler`]: crate::schedule::Schedule::set_error_handler
//! [`System`]: crate::system::System //! [`System`]: crate::system::System
//! [`name`]: crate::system::System::name //! [`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 //! [`system piping feature`]: crate::system::In
mod bevy_error; mod bevy_error;
mod command_handling;
mod handler; mod handler;
pub use bevy_error::*; pub use bevy_error::*;
pub use command_handling::*;
pub use handler::*; pub use handler::*;
/// A result type for use in fallible systems, commands and observers. /// A result type for use in fallible systems, commands and observers.

View File

@ -3,7 +3,7 @@ use core::any::Any;
use crate::{ use crate::{
component::{ComponentHook, ComponentId, HookContext, Mutable, StorageType}, component::{ComponentHook, ComponentId, HookContext, Mutable, StorageType},
error::{DefaultSystemErrorHandler, SystemErrorContext}, error::{default_error_handler, ErrorContext},
observer::{ObserverDescriptor, ObserverTrigger}, observer::{ObserverDescriptor, ObserverTrigger},
prelude::*, prelude::*,
query::DebugCheckedUnwrap, query::DebugCheckedUnwrap,
@ -273,7 +273,7 @@ pub struct Observer {
system: Box<dyn Any + Send + Sync + 'static>, system: Box<dyn Any + Send + Sync + 'static>,
descriptor: ObserverDescriptor, descriptor: ObserverDescriptor,
hook_on_add: ComponentHook, hook_on_add: ComponentHook,
error_handler: Option<fn(BevyError, SystemErrorContext)>, error_handler: Option<fn(BevyError, ErrorContext)>,
} }
impl Observer { impl Observer {
@ -322,7 +322,7 @@ impl Observer {
/// Set the error handler to use for this observer. /// Set the error handler to use for this observer.
/// ///
/// See the [`error` module-level documentation](crate::error) for more information. /// 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.error_handler = Some(error_handler);
self self
} }
@ -409,7 +409,7 @@ fn observer_system_runner<E: Event, B: Bundle, S: ObserverSystem<E, B>>(
if let Err(err) = (*system).run_unsafe(trigger, world) { if let Err(err) = (*system).run_unsafe(trigger, world) {
error_handler( error_handler(
err, err,
SystemErrorContext { ErrorContext::Observer {
name: (*system).name(), name: (*system).name(),
last_run: (*system).get_last_run(), last_run: (*system).get_last_run(),
}, },
@ -444,7 +444,7 @@ fn hook_on_add<E: Event, B: Bundle, S: ObserverSystem<E, B>>(
..Default::default() ..Default::default()
}; };
let error_handler = world.get_resource_or_init::<DefaultSystemErrorHandler>().0; let error_handler = default_error_handler();
// Initialize System // Initialize System
let system: *mut dyn ObserverSystem<E, B> = let system: *mut dyn ObserverSystem<E, B> =

View File

@ -13,10 +13,10 @@ pub use relationship_source_collection::*;
use crate::{ use crate::{
component::{Component, HookContext, Mutable}, component::{Component, HookContext, Mutable},
entity::{ComponentCloneCtx, Entity, SourceComponent}, entity::{ComponentCloneCtx, Entity, SourceComponent},
error::{ignore, CommandWithEntity, HandleError},
system::{ system::{
command::HandleError, entity_command::{self},
entity_command::{self, CommandWithEntity}, Commands,
error_handler, Commands,
}, },
world::{DeferredWorld, EntityWorldMut}, world::{DeferredWorld, EntityWorldMut},
}; };
@ -230,7 +230,7 @@ pub trait RelationshipTarget: Component<Mutability = Mutable> + Sized {
commands.queue( commands.queue(
entity_command::remove::<Self::Relationship>() entity_command::remove::<Self::Relationship>()
.with_entity(source_entity) .with_entity(source_entity)
.handle_error_with(error_handler::silent()), .handle_error_with(ignore),
); );
} else { } else {
warn!( warn!(
@ -255,7 +255,7 @@ pub trait RelationshipTarget: Component<Mutability = Mutable> + Sized {
commands.queue( commands.queue(
entity_command::despawn() entity_command::despawn()
.with_entity(source_entity) .with_entity(source_entity)
.handle_error_with(error_handler::silent()), .handle_error_with(ignore),
); );
} else { } else {
warn!( warn!(

View File

@ -16,7 +16,7 @@ use fixedbitset::FixedBitSet;
use crate::{ use crate::{
archetype::ArchetypeComponentId, archetype::ArchetypeComponentId,
component::{ComponentId, Tick}, component::{ComponentId, Tick},
error::{BevyError, Result, SystemErrorContext}, error::{BevyError, ErrorContext, Result},
prelude::{IntoSystemSet, SystemSet}, prelude::{IntoSystemSet, SystemSet},
query::Access, query::Access,
schedule::{BoxedCondition, InternedSystemSet, NodeId, SystemTypeSet}, schedule::{BoxedCondition, InternedSystemSet, NodeId, SystemTypeSet},
@ -33,7 +33,7 @@ pub(super) trait SystemExecutor: Send + Sync {
schedule: &mut SystemSchedule, schedule: &mut SystemSchedule,
world: &mut World, world: &mut World,
skip_systems: Option<&FixedBitSet>, skip_systems: Option<&FixedBitSet>,
error_handler: fn(BevyError, SystemErrorContext), error_handler: fn(BevyError, ErrorContext),
); );
fn set_apply_final_deferred(&mut self, value: bool); fn set_apply_final_deferred(&mut self, value: bool);
} }

View File

@ -14,7 +14,7 @@ use tracing::{info_span, Span};
use crate::{ use crate::{
archetype::ArchetypeComponentId, archetype::ArchetypeComponentId,
error::{BevyError, Result, SystemErrorContext}, error::{BevyError, ErrorContext, Result},
prelude::Resource, prelude::Resource,
query::Access, query::Access,
schedule::{is_apply_deferred, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule}, schedule::{is_apply_deferred, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule},
@ -131,7 +131,7 @@ pub struct ExecutorState {
struct Context<'scope, 'env, 'sys> { struct Context<'scope, 'env, 'sys> {
environment: &'env Environment<'env, 'sys>, environment: &'env Environment<'env, 'sys>,
scope: &'scope Scope<'scope, 'env, ()>, scope: &'scope Scope<'scope, 'env, ()>,
error_handler: fn(BevyError, SystemErrorContext), error_handler: fn(BevyError, ErrorContext),
} }
impl Default for MultiThreadedExecutor { impl Default for MultiThreadedExecutor {
@ -182,7 +182,7 @@ impl SystemExecutor for MultiThreadedExecutor {
schedule: &mut SystemSchedule, schedule: &mut SystemSchedule,
world: &mut World, world: &mut World,
_skip_systems: Option<&FixedBitSet>, _skip_systems: Option<&FixedBitSet>,
error_handler: fn(BevyError, SystemErrorContext), error_handler: fn(BevyError, ErrorContext),
) { ) {
let state = self.state.get_mut().unwrap(); let state = self.state.get_mut().unwrap();
// reset counts // reset counts
@ -617,7 +617,7 @@ impl ExecutorState {
) { ) {
(context.error_handler)( (context.error_handler)(
err, err,
SystemErrorContext { ErrorContext::System {
name: system.name(), name: system.name(),
last_run: system.get_last_run(), last_run: system.get_last_run(),
}, },
@ -669,7 +669,7 @@ impl ExecutorState {
if let Err(err) = __rust_begin_short_backtrace::run(system, world) { if let Err(err) = __rust_begin_short_backtrace::run(system, world) {
(context.error_handler)( (context.error_handler)(
err, err,
SystemErrorContext { ErrorContext::System {
name: system.name(), name: system.name(),
last_run: system.get_last_run(), last_run: system.get_last_run(),
}, },

View File

@ -8,7 +8,7 @@ use tracing::info_span;
use std::eprintln; use std::eprintln;
use crate::{ use crate::{
error::{BevyError, SystemErrorContext}, error::{BevyError, ErrorContext},
schedule::{ schedule::{
executor::is_apply_deferred, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule, executor::is_apply_deferred, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule,
}, },
@ -44,7 +44,7 @@ impl SystemExecutor for SimpleExecutor {
schedule: &mut SystemSchedule, schedule: &mut SystemSchedule,
world: &mut World, world: &mut World,
_skip_systems: Option<&FixedBitSet>, _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 // If stepping is enabled, make sure we skip those systems that should
// not be run. // not be run.
@ -109,7 +109,7 @@ impl SystemExecutor for SimpleExecutor {
if let Err(err) = __rust_begin_short_backtrace::run(system, world) { if let Err(err) = __rust_begin_short_backtrace::run(system, world) {
error_handler( error_handler(
err, err,
SystemErrorContext { ErrorContext::System {
name: system.name(), name: system.name(),
last_run: system.get_last_run(), last_run: system.get_last_run(),
}, },

View File

@ -8,7 +8,7 @@ use tracing::info_span;
use std::eprintln; use std::eprintln;
use crate::{ use crate::{
error::{BevyError, SystemErrorContext}, error::{BevyError, ErrorContext},
schedule::{is_apply_deferred, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule}, schedule::{is_apply_deferred, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule},
world::World, world::World,
}; };
@ -50,7 +50,7 @@ impl SystemExecutor for SingleThreadedExecutor {
schedule: &mut SystemSchedule, schedule: &mut SystemSchedule,
world: &mut World, world: &mut World,
_skip_systems: Option<&FixedBitSet>, _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 // If stepping is enabled, make sure we skip those systems that should
// not be run. // not be run.
@ -117,7 +117,7 @@ impl SystemExecutor for SingleThreadedExecutor {
if let Err(err) = __rust_begin_short_backtrace::run(system, world) { if let Err(err) = __rust_begin_short_backtrace::run(system, world) {
error_handler( error_handler(
err, err,
SystemErrorContext { ErrorContext::System {
name: system.name(), name: system.name(),
last_run: system.get_last_run(), 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) { if let Err(err) = __rust_begin_short_backtrace::run_unsafe(system, world) {
error_handler( error_handler(
err, err,
SystemErrorContext { ErrorContext::System {
name: system.name(), name: system.name(),
last_run: system.get_last_run(), last_run: system.get_last_run(),
}, },

View File

@ -26,7 +26,7 @@ use tracing::info_span;
use crate::{ use crate::{
component::{ComponentId, Components, Tick}, component::{ComponentId, Components, Tick},
error::{BevyError, DefaultSystemErrorHandler, SystemErrorContext}, error::default_error_handler,
prelude::Component, prelude::Component,
resource::Resource, resource::Resource,
schedule::*, schedule::*,
@ -296,7 +296,6 @@ pub struct Schedule {
executable: SystemSchedule, executable: SystemSchedule,
executor: Box<dyn SystemExecutor>, executor: Box<dyn SystemExecutor>,
executor_initialized: bool, executor_initialized: bool,
error_handler: Option<fn(BevyError, SystemErrorContext)>,
} }
#[derive(ScheduleLabel, Hash, PartialEq, Eq, Debug, Clone)] #[derive(ScheduleLabel, Hash, PartialEq, Eq, Debug, Clone)]
@ -321,7 +320,6 @@ impl Schedule {
executable: SystemSchedule::new(), executable: SystemSchedule::new(),
executor: make_executor(ExecutorKind::default()), executor: make_executor(ExecutorKind::default()),
executor_initialized: false, executor_initialized: false,
error_handler: None,
}; };
// Call `set_build_settings` to add any default build passes // Call `set_build_settings` to add any default build passes
this.set_build_settings(Default::default()); this.set_build_settings(Default::default());
@ -405,13 +403,6 @@ impl Schedule {
self 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`. /// Returns the schedule's current `ScheduleBuildSettings`.
pub fn get_build_settings(&self) -> ScheduleBuildSettings { pub fn get_build_settings(&self) -> ScheduleBuildSettings {
self.graph.settings.clone() self.graph.settings.clone()
@ -449,7 +440,7 @@ impl Schedule {
self.initialize(world) self.initialize(world)
.unwrap_or_else(|e| panic!("Error when initializing schedule {:?}: {e}", self.label)); .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"))] #[cfg(not(feature = "bevy_debug_stepping"))]
self.executor self.executor
@ -492,10 +483,6 @@ impl Schedule {
self.executor_initialized = false; self.executor_initialized = false;
} }
if self.error_handler.is_none() {
self.error_handler = Some(world.get_resource_or_init::<DefaultSystemErrorHandler>().0);
}
if !self.executor_initialized { if !self.executor_initialized {
self.executor.init(&self.executable); self.executor.init(&self.executable);
self.executor_initialized = true; self.executor_initialized = true;

View File

@ -8,12 +8,12 @@ use crate::{
bundle::{Bundle, InsertMode, NoBundleEffect}, bundle::{Bundle, InsertMode, NoBundleEffect},
change_detection::MaybeLocation, change_detection::MaybeLocation,
entity::Entity, entity::Entity,
error::{BevyError, Result}, error::Result,
event::{Event, Events}, event::{Event, Events},
observer::TriggerTargets, observer::TriggerTargets,
resource::Resource, resource::Resource,
schedule::ScheduleLabel, schedule::ScheduleLabel,
system::{error_handler, IntoSystem, SystemId, SystemInput}, system::{IntoSystem, SystemId, SystemInput},
world::{FromWorld, SpawnBatchIter, World}, 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<Out = ()> {
/// Takes a [`Command`] that returns a Result and uses a given error handler function to convert it into
/// a [`Command`] that internally handles an error if it occurs and returns `()`.
fn handle_error_with(self, error_handler: fn(&mut World, 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<C, T, E> HandleError<Result<T, E>> for C
where
C: Command<Result<T, E>>,
E: Into<BevyError>,
{
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<C> 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. /// A [`Command`] that consumes an iterator of [`Bundles`](Bundle) to spawn a series of entities.
/// ///
/// This is more efficient than spawning the entities individually. /// This is more efficient than spawning the entities individually.

View File

@ -5,7 +5,6 @@
//! [`EntityCommands`](crate::system::EntityCommands). //! [`EntityCommands`](crate::system::EntityCommands).
use alloc::vec::Vec; use alloc::vec::Vec;
use core::fmt;
use log::info; use log::info;
use crate::{ use crate::{
@ -13,11 +12,10 @@ use crate::{
change_detection::MaybeLocation, change_detection::MaybeLocation,
component::{Component, ComponentId, ComponentInfo}, component::{Component, ComponentId, ComponentInfo},
entity::{Entity, EntityClonerBuilder}, entity::{Entity, EntityClonerBuilder},
error::Result,
event::Event, event::Event,
relationship::RelationshipHookMode, relationship::RelationshipHookMode,
system::{command::HandleError, Command, IntoObserverSystem}, system::IntoObserverSystem,
world::{error::EntityMutableFetchError, EntityWorldMut, FromWorld, World}, world::{error::EntityMutableFetchError, EntityWorldMut, FromWorld},
}; };
use bevy_ptr::OwningPtr; use bevy_ptr::OwningPtr;
@ -84,53 +82,6 @@ pub trait EntityCommand<Out = ()>: Send + 'static {
/// Executes this command for the given [`Entity`]. /// Executes this command for the given [`Entity`].
fn apply(self, entity: EntityWorldMut) -> Out; 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<Out> {
/// 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<Out> + HandleError<Out>;
}
impl<C> CommandWithEntity<Result<(), EntityMutableFetchError>> for C
where
C: EntityCommand,
{
fn with_entity(
self,
entity: Entity,
) -> impl Command<Result<(), EntityMutableFetchError>>
+ HandleError<Result<(), EntityMutableFetchError>> {
move |world: &mut World| -> Result<(), EntityMutableFetchError> {
let entity = world.get_entity_mut(entity)?;
self.apply(entity);
Ok(())
}
}
}
impl<C, T, Err> CommandWithEntity<Result<T, EntityCommandError<Err>>> for C
where
C: EntityCommand<Result<T, Err>>,
Err: fmt::Debug + fmt::Display + Send + Sync + 'static,
{
fn with_entity(
self,
entity: Entity,
) -> impl Command<Result<T, EntityCommandError<Err>>> + HandleError<Result<T, EntityCommandError<Err>>>
{
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. /// An error that occurs when running an [`EntityCommand`] on a specific entity.
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]

View File

@ -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<fn(&mut World, BevyError)> =
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())
}

View File

@ -1,6 +1,5 @@
pub mod command; pub mod command;
pub mod entity_command; pub mod entity_command;
pub mod error_handler;
#[cfg(feature = "std")] #[cfg(feature = "std")]
mod parallel_scope; mod parallel_scope;
@ -21,15 +20,12 @@ use crate::{
change_detection::{MaybeLocation, Mut}, change_detection::{MaybeLocation, Mut},
component::{Component, ComponentId, Mutable}, component::{Component, ComponentId, Mutable},
entity::{Entities, Entity, EntityClonerBuilder, EntityDoesNotExistError}, entity::{Entities, Entity, EntityClonerBuilder, EntityDoesNotExistError},
error::BevyError, error::{ignore, warn, BevyError, CommandWithEntity, ErrorContext, HandleError},
event::Event, event::Event,
observer::{Observer, TriggerTargets}, observer::{Observer, TriggerTargets},
resource::Resource, resource::Resource,
schedule::ScheduleLabel, schedule::ScheduleLabel,
system::{ system::{Deferred, IntoObserverSystem, IntoSystem, RegisteredSystem, SystemId, SystemInput},
command::HandleError, entity_command::CommandWithEntity, input::SystemInput, Deferred,
IntoObserverSystem, IntoSystem, RegisteredSystem, SystemId,
},
world::{ world::{
command_queue::RawCommandQueue, unsafe_world_cell::UnsafeWorldCell, CommandQueue, command_queue::RawCommandQueue, unsafe_world_cell::UnsafeWorldCell, CommandQueue,
EntityWorldMut, FromWorld, World, EntityWorldMut, FromWorld, World,
@ -95,14 +91,13 @@ use crate::{
/// They are granted exclusive access to the [`World`], which enables them to /// They are granted exclusive access to the [`World`], which enables them to
/// respond to the error in whatever way is necessary. /// respond to the error in whatever way is necessary.
/// ///
/// The [default error handler](error_handler::default) panics. /// The [default error handler](crate::error::default_error_handler) panics.
/// It can be configured by enabling the `configurable_error_handler` cargo feature, /// It can be configured by setting the `GLOBAL_ERROR_HANDLER`.
/// then setting the `GLOBAL_ERROR_HANDLER`.
/// ///
/// Alternatively, you can customize the error handler for a specific command /// Alternatively, you can customize the error handler for a specific command
/// by calling [`Commands::queue_handled`]. /// 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 /// [`ApplyDeferred`]: crate::schedule::ApplyDeferred
pub struct Commands<'w, 's> { pub struct Commands<'w, 's> {
@ -547,7 +542,7 @@ impl<'w, 's> Commands<'w, 's> {
/// Pushes a generic [`Command`] to the command queue. /// Pushes a generic [`Command`] to the command queue.
/// ///
/// If the [`Command`] returns a [`Result`], /// 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`]. /// 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::prelude::*;
/// # use bevy_ecs::system::error_handler; /// use bevy_ecs::error::warn;
///
/// #[derive(Resource, Default)] /// #[derive(Resource, Default)]
/// struct Counter(u64); /// struct Counter(u64);
/// ///
@ -625,7 +621,7 @@ impl<'w, 's> Commands<'w, 's> {
/// } /// }
/// ///
/// fn add_three_to_counter_system(mut commands: Commands) { /// 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) { /// fn add_twenty_five_to_counter_system(mut commands: Commands) {
/// commands.queue(|world: &mut World| { /// commands.queue(|world: &mut World| {
@ -639,7 +635,7 @@ impl<'w, 's> Commands<'w, 's> {
pub fn queue_handled<C: Command<T> + HandleError<T>, T>( pub fn queue_handled<C: Command<T> + HandleError<T>, T>(
&mut self, &mut self,
command: C, command: C,
error_handler: fn(&mut World, BevyError), error_handler: fn(BevyError, ErrorContext),
) { ) {
self.queue_internal(command.handle_error_with(error_handler)); self.queue_internal(command.handle_error_with(error_handler));
} }
@ -785,10 +781,7 @@ impl<'w, 's> Commands<'w, 's> {
I: IntoIterator<Item = (Entity, B)> + Send + Sync + 'static, I: IntoIterator<Item = (Entity, B)> + Send + Sync + 'static,
B: Bundle<Effect: NoBundleEffect>, B: Bundle<Effect: NoBundleEffect>,
{ {
self.queue( self.queue(command::insert_batch(batch, InsertMode::Replace).handle_error_with(warn));
command::insert_batch(batch, InsertMode::Replace)
.handle_error_with(error_handler::warn()),
);
} }
/// Pushes a [`Command`] to the queue for adding a [`Bundle`] type to a batch of [`Entities`](Entity). /// 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<Item = (Entity, B)> + Send + Sync + 'static, I: IntoIterator<Item = (Entity, B)> + Send + Sync + 'static,
B: Bundle<Effect: NoBundleEffect>, B: Bundle<Effect: NoBundleEffect>,
{ {
self.queue( self.queue(command::insert_batch(batch, InsertMode::Keep).handle_error_with(warn));
command::insert_batch(batch, InsertMode::Keep).handle_error_with(error_handler::warn()),
);
} }
/// Pushes a [`Command`] to the queue for inserting a [`Resource`] in the [`World`] with an inferred value. /// 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 /// 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. /// [`World::run_system`] or [`World::run_system_with`] instead of running the system as a command.
pub fn run_system(&mut self, id: SystemId) { 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`]. /// Runs the system corresponding to the given [`SystemId`].
@ -927,7 +918,7 @@ impl<'w, 's> Commands<'w, 's> {
where where
I: SystemInput<Inner<'static>: Send> + 'static, I: SystemInput<Inner<'static>: 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`]. /// 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, I: SystemInput + Send + 'static,
O: 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`]. /// Removes a system previously registered with [`World::register_system_cached`].
@ -1016,9 +1007,7 @@ impl<'w, 's> Commands<'w, 's> {
&mut self, &mut self,
system: S, system: S,
) { ) {
self.queue( self.queue(command::unregister_system_cached(system).handle_error_with(warn));
command::unregister_system_cached(system).handle_error_with(error_handler::warn()),
);
} }
/// Similar to [`Self::run_system`], but caching the [`SystemId`] in a /// Similar to [`Self::run_system`], but caching the [`SystemId`] in a
@ -1029,7 +1018,7 @@ impl<'w, 's> Commands<'w, 's> {
&mut self, &mut self,
system: S, 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 /// Similar to [`Self::run_system_with`], but caching the [`SystemId`] in a
@ -1042,9 +1031,7 @@ impl<'w, 's> Commands<'w, 's> {
M: 'static, M: 'static,
S: IntoSystem<I, (), M> + Send + 'static, S: IntoSystem<I, (), M> + Send + 'static,
{ {
self.queue( self.queue(command::run_system_cached_with(system, input).handle_error_with(warn));
command::run_system_cached_with(system, input).handle_error_with(error_handler::warn()),
);
} }
/// Sends a "global" [`Trigger`] without any targets. This will run any [`Observer`] of the `event` that /// 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::<Counter>().0, 1); /// # assert_eq!(world.resource::<Counter>().0, 1);
/// ``` /// ```
pub fn run_schedule(&mut self, label: impl ScheduleLabel) { 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 /// They are granted exclusive access to the [`World`], which enables them to
/// respond to the error in whatever way is necessary. /// respond to the error in whatever way is necessary.
/// ///
/// The [default error handler](error_handler::default) panics. /// The [default error handler](crate::error::default_error_handler) panics.
/// It can be configured by enabling the `configurable_error_handler` cargo feature, /// It can be configured by setting the `GLOBAL_ERROR_HANDLER`.
/// then setting the `GLOBAL_ERROR_HANDLER`.
/// ///
/// Alternatively, you can customize the error handler for a specific command /// Alternatively, you can customize the error handler for a specific command
/// by calling [`EntityCommands::queue_handled`]. /// 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 struct EntityCommands<'a> {
pub(crate) entity: Entity, pub(crate) entity: Entity,
pub(crate) commands: Commands<'a, 'a>, pub(crate) commands: Commands<'a, 'a>,
@ -1430,7 +1416,7 @@ impl<'a> EntityCommands<'a> {
// - `ComponentId` safety is ensured by the caller. // - `ComponentId` safety is ensured by the caller.
// - `T` safety is ensured by the caller. // - `T` safety is ensured by the caller.
unsafe { entity_command::insert_by_id(component_id, value, InsertMode::Replace) }, 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] #[track_caller]
pub fn try_insert(&mut self, bundle: impl Bundle) -> &mut Self { pub fn try_insert(&mut self, bundle: impl Bundle) -> &mut Self {
self.queue_handled( self.queue_handled(entity_command::insert(bundle, InsertMode::Replace), ignore)
entity_command::insert(bundle, InsertMode::Replace),
error_handler::silent(),
)
} }
/// Similar to [`Self::try_insert`] but will only try to insert if the predicate returns true. /// 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. /// Unlike [`Self::insert_if_new`], this will not panic if the associated entity does not exist.
#[track_caller] #[track_caller]
pub fn try_insert_if_new(&mut self, bundle: impl Bundle) -> &mut Self { pub fn try_insert_if_new(&mut self, bundle: impl Bundle) -> &mut Self {
self.queue_handled( self.queue_handled(entity_command::insert(bundle, InsertMode::Keep), ignore)
entity_command::insert(bundle, InsertMode::Keep),
error_handler::silent(),
)
} }
/// Removes a [`Bundle`] of components from the entity. /// Removes a [`Bundle`] of components from the entity.
@ -1632,7 +1612,7 @@ impl<'a> EntityCommands<'a> {
where where
T: Bundle, T: Bundle,
{ {
self.queue_handled(entity_command::remove::<T>(), error_handler::warn()) self.queue_handled(entity_command::remove::<T>(), warn)
} }
/// Removes a [`Bundle`] of components from the entity. /// Removes a [`Bundle`] of components from the entity.
@ -1678,7 +1658,7 @@ impl<'a> EntityCommands<'a> {
where where
T: Bundle, T: Bundle,
{ {
self.queue_handled(entity_command::remove::<T>(), error_handler::silent()) self.queue_handled(entity_command::remove::<T>(), ignore)
} }
/// Removes all components in the [`Bundle`] components and remove all required components for each component in the [`Bundle`] from entity. /// 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] #[track_caller]
pub fn despawn(&mut self) { 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. /// Despawns the provided entity and its descendants.
#[deprecated( #[deprecated(
@ -1777,13 +1757,13 @@ impl<'a> EntityCommands<'a> {
/// This will also despawn the entities in any [`RelationshipTarget`](crate::relationship::RelationshipTarget) that are configured /// 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). /// to despawn descendants. For example, this will recursively despawn [`Children`](crate::hierarchy::Children).
pub fn try_despawn(&mut self) { 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`]. /// Pushes an [`EntityCommand`] to the queue, which will get executed for the current [`Entity`].
/// ///
/// If the [`EntityCommand`] returns a [`Result`], /// 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`]. /// 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::prelude::*;
/// # use bevy_ecs::system::error_handler;
/// # fn my_system(mut commands: Commands) { /// # fn my_system(mut commands: Commands) {
/// use bevy_ecs::error::warn;
///
/// commands /// commands
/// .spawn_empty() /// .spawn_empty()
/// // Closures with this signature implement `EntityCommand`. /// // Closures with this signature implement `EntityCommand`.
@ -1845,7 +1826,7 @@ impl<'a> EntityCommands<'a> {
/// println!("Successfully parsed the value {} for entity {}", value, entity.id()); /// println!("Successfully parsed the value {} for entity {}", value, entity.id());
/// Ok(()) /// Ok(())
/// }, /// },
/// error_handler::warn() /// warn
/// ); /// );
/// # } /// # }
/// # bevy_ecs::system::assert_is_system(my_system); /// # bevy_ecs::system::assert_is_system(my_system);
@ -1853,7 +1834,7 @@ impl<'a> EntityCommands<'a> {
pub fn queue_handled<C: EntityCommand<T> + CommandWithEntity<M>, T, M>( pub fn queue_handled<C: EntityCommand<T> + CommandWithEntity<M>, T, M>(
&mut self, &mut self,
command: C, command: C,
error_handler: fn(&mut World, BevyError), error_handler: fn(BevyError, ErrorContext),
) -> &mut Self { ) -> &mut Self {
self.commands self.commands
.queue_handled(command.with_entity(self.entity), error_handler); .queue_handled(command.with_entity(self.entity), error_handler);

View File

@ -280,6 +280,9 @@ custom_cursor = ["bevy_winit/custom_cursor"]
# Experimental support for nodes that are ignored for UI layouting # Experimental support for nodes that are ignored for UI layouting
ghost_nodes = ["bevy_ui/ghost_nodes"] 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 # Allows access to the `std` crate. Enabling this feature will prevent compilation
# on `no_std` targets, but provides access to certain additional features on # on `no_std` targets, but provides access to certain additional features on
# supported platforms. # supported platforms.

View File

@ -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_remote|Enable the Bevy Remote Protocol|
|bevy_ui_debug|Provides a debug overlay for bevy UI| |bevy_ui_debug|Provides a debug overlay for bevy UI|
|bmp|BMP image format support| |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`.| |critical-section|`critical-section` provides the building blocks for synchronization primitives on all platforms, including `no_std`.|
|dds|DDS compressed texture support| |dds|DDS compressed texture support|
|debug_glam_assert|Enable assertions in debug builds to check the validity of parameters passed to glam| |debug_glam_assert|Enable assertions in debug builds to check the validity of parameters passed to glam|

View File

@ -313,7 +313,7 @@ Example | Description
[Entity disabling](../examples/ecs/entity_disabling.rs) | Demonstrates how to hide entities from the ECS without deleting them [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 [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 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 [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 [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 [Hierarchy](../examples/ecs/hierarchy.rs) | Creates a hierarchy of parents and children entities

View File

@ -1,7 +1,13 @@
//! Showcases how fallible systems and observers can make use of Rust's powerful result handling //! Showcases how fallible systems and observers can make use of Rust's powerful result handling
//! syntax. //! syntax.
//!
//! 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::world::DeferredWorld; use bevy::ecs::{
error::{warn, GLOBAL_ERROR_HANDLER},
world::DeferredWorld,
};
use bevy::math::sampling::UniformMeshSampler; use bevy::math::sampling::UniformMeshSampler;
use bevy::prelude::*; use bevy::prelude::*;
@ -10,6 +16,16 @@ use rand::SeedableRng;
use rand_chacha::ChaCha8Rng; use rand_chacha::ChaCha8Rng;
fn main() { 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(); let mut app = App::new();
app.add_plugins(DefaultPlugins); app.add_plugins(DefaultPlugins);
@ -22,26 +38,9 @@ fn main() {
// types of systems the same way, except for the error handling. // types of systems the same way, except for the error handling.
app.add_systems(Startup, setup); app.add_systems(Startup, setup);
// By default, fallible systems that return an error will panic. // Commands can also return `Result`s, which are automatically handled by the global error handler
// // if not explicitly handled by the user.
// We can change this by setting a custom error handler. This can be done globally for all app.add_systems(Startup, failing_commands);
// 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));
// Individual systems can also be handled by piping the output result: // Individual systems can also be handled by piping the output result:
app.add_systems( app.add_systems(
@ -166,3 +165,28 @@ fn failing_system(world: &mut World) -> Result {
Ok(()) 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::<UninitializedResource>()
.ok_or("Resource not initialized when accessed in a command")?;
Ok(())
},
|error, context| {
error!("{error}, {context}");
},
);
}