Skip to content

Commit e7e9973

Browse files
Per world error handler (#18810)
# Objective [see original comment](#18801 (comment)) > Alternately, could we store it on the World instead of a global? I think we have a World nearby whenever we call default_error_handler(). That would avoid the need for atomics or locks, since we could do ordinary reads and writes to the World. Global error handlers don't actually need to be global – per world is enough. This allows using different handlers for different worlds and also removes the restrictions on changing the handler only once. ## Solution Each `World` can now store its own error handler in a resource. For convenience, you can also set the default error handler for an `App`, which applies it to the worlds of all `SubApp`s. The old behavior of only being able to set the error handler once is kept for apps. We also don't need the `configurable_error_handler` feature anymore now. ## Testing New/adjusted tests for failing schedule systems & observers. --- ## Showcase ```rust App::new() .set_error_handler(info) … ```
1 parent 45ba5b9 commit e7e9973

File tree

19 files changed

+273
-166
lines changed

19 files changed

+273
-166
lines changed

Cargo.toml

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -291,9 +291,6 @@ bevy_log = ["bevy_internal/bevy_log"]
291291
# Enable input focus subsystem
292292
bevy_input_focus = ["bevy_internal/bevy_input_focus"]
293293

294-
# Use the configurable global error handler as the default error handler.
295-
configurable_error_handler = ["bevy_internal/configurable_error_handler"]
296-
297294
# Enable passthrough loading for SPIR-V shaders (Only supported on Vulkan, shader capabilities and extensions must agree with the platform implementation)
298295
spirv_shader_passthrough = ["bevy_internal/spirv_shader_passthrough"]
299296

@@ -2215,7 +2212,6 @@ wasm = false
22152212
name = "fallible_params"
22162213
path = "examples/ecs/fallible_params.rs"
22172214
doc-scrape-examples = true
2218-
required-features = ["configurable_error_handler"]
22192215

22202216
[package.metadata.example.fallible_params]
22212217
name = "Fallible System Parameters"
@@ -2227,7 +2223,7 @@ wasm = false
22272223
name = "error_handling"
22282224
path = "examples/ecs/error_handling.rs"
22292225
doc-scrape-examples = true
2230-
required-features = ["bevy_mesh_picking_backend", "configurable_error_handler"]
2226+
required-features = ["bevy_mesh_picking_backend"]
22312227

22322228
[package.metadata.example.error_handling]
22332229
name = "Error handling"

crates/bevy_app/src/app.rs

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use alloc::{
1010
pub use bevy_derive::AppLabel;
1111
use bevy_ecs::{
1212
component::RequiredComponentsError,
13+
error::{DefaultErrorHandler, ErrorHandler},
1314
event::{event_update_system, EventCursor},
1415
intern::Interned,
1516
prelude::*,
@@ -85,6 +86,7 @@ pub struct App {
8586
/// [`WinitPlugin`]: https://docs.rs/bevy/latest/bevy/winit/struct.WinitPlugin.html
8687
/// [`ScheduleRunnerPlugin`]: https://docs.rs/bevy/latest/bevy/app/struct.ScheduleRunnerPlugin.html
8788
pub(crate) runner: RunnerFn,
89+
default_error_handler: Option<ErrorHandler>,
8890
}
8991

9092
impl Debug for App {
@@ -143,6 +145,7 @@ impl App {
143145
sub_apps: HashMap::default(),
144146
},
145147
runner: Box::new(run_once),
148+
default_error_handler: None,
146149
}
147150
}
148151

@@ -1115,7 +1118,12 @@ impl App {
11151118
}
11161119

11171120
/// Inserts a [`SubApp`] with the given label.
1118-
pub fn insert_sub_app(&mut self, label: impl AppLabel, sub_app: SubApp) {
1121+
pub fn insert_sub_app(&mut self, label: impl AppLabel, mut sub_app: SubApp) {
1122+
if let Some(handler) = self.default_error_handler {
1123+
sub_app
1124+
.world_mut()
1125+
.get_resource_or_insert_with(|| DefaultErrorHandler(handler));
1126+
}
11191127
self.sub_apps.sub_apps.insert(label.intern(), sub_app);
11201128
}
11211129

@@ -1334,6 +1342,49 @@ impl App {
13341342
self.world_mut().add_observer(observer);
13351343
self
13361344
}
1345+
1346+
/// Gets the error handler to set for new supapps.
1347+
///
1348+
/// Note that the error handler of existing subapps may differ.
1349+
pub fn get_error_handler(&self) -> Option<ErrorHandler> {
1350+
self.default_error_handler
1351+
}
1352+
1353+
/// Set the [default error handler] for the all subapps (including the main one and future ones)
1354+
/// that do not have one.
1355+
///
1356+
/// May only be called once and should be set by the application, not by libraries.
1357+
///
1358+
/// The handler will be called when an error is produced and not otherwise handled.
1359+
///
1360+
/// # Panics
1361+
/// Panics if called multiple times.
1362+
///
1363+
/// # Example
1364+
/// ```
1365+
/// # use bevy_app::*;
1366+
/// # use bevy_ecs::error::warn;
1367+
/// # fn MyPlugins(_: &mut App) {}
1368+
/// App::new()
1369+
/// .set_error_handler(warn)
1370+
/// .add_plugins(MyPlugins)
1371+
/// .run();
1372+
/// ```
1373+
///
1374+
/// [default error handler]: bevy_ecs::error::DefaultErrorHandler
1375+
pub fn set_error_handler(&mut self, handler: ErrorHandler) -> &mut Self {
1376+
assert!(
1377+
self.default_error_handler.is_none(),
1378+
"`set_error_handler` called multiple times on same `App`"
1379+
);
1380+
self.default_error_handler = Some(handler);
1381+
for sub_app in self.sub_apps.iter_mut() {
1382+
sub_app
1383+
.world_mut()
1384+
.get_resource_or_insert_with(|| DefaultErrorHandler(handler));
1385+
}
1386+
self
1387+
}
13371388
}
13381389

13391390
type RunnerFn = Box<dyn FnOnce(App) -> AppExit>;

crates/bevy_ecs/Cargo.toml

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,6 @@ bevy_reflect = ["dep:bevy_reflect"]
3333
## Extends reflection support to functions.
3434
reflect_functions = ["bevy_reflect", "bevy_reflect/functions"]
3535

36-
## Use the configurable global error handler as the default error handler.
37-
##
38-
## This is typically used to turn panics from the ECS into loggable errors.
39-
## This may be useful for production builds,
40-
## but can result in a measurable performance impact, especially for commands.
41-
configurable_error_handler = []
42-
4336
## Enables automatic backtrace capturing in BevyError
4437
backtrace = ["std"]
4538

crates/bevy_ecs/src/error/command_handling.rs

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,30 +7,25 @@ use crate::{
77
world::{error::EntityMutableFetchError, World},
88
};
99

10-
use super::{default_error_handler, BevyError, ErrorContext};
10+
use super::{BevyError, ErrorContext, ErrorHandler};
1111

12-
/// Takes a [`Command`] that returns a Result and uses a given error handler function to convert it into
12+
/// Takes a [`Command`] that potentially returns a Result and uses a given error handler function to convert it into
1313
/// a [`Command`] that internally handles an error if it occurs and returns `()`.
14-
pub trait HandleError<Out = ()> {
14+
pub trait HandleError<Out = ()>: Send + 'static {
1515
/// Takes a [`Command`] that returns a Result and uses a given error handler function to convert it into
1616
/// a [`Command`] that internally handles an error if it occurs and returns `()`.
17-
fn handle_error_with(self, error_handler: fn(BevyError, ErrorContext)) -> impl Command;
17+
fn handle_error_with(self, error_handler: ErrorHandler) -> impl Command;
1818
/// Takes a [`Command`] that returns a Result and uses the default error handler function to convert it into
1919
/// a [`Command`] that internally handles an error if it occurs and returns `()`.
20-
fn handle_error(self) -> impl Command
21-
where
22-
Self: Sized,
23-
{
24-
self.handle_error_with(default_error_handler())
25-
}
20+
fn handle_error(self) -> impl Command;
2621
}
2722

2823
impl<C, T, E> HandleError<Result<T, E>> for C
2924
where
3025
C: Command<Result<T, E>>,
3126
E: Into<BevyError>,
3227
{
33-
fn handle_error_with(self, error_handler: fn(BevyError, ErrorContext)) -> impl Command {
28+
fn handle_error_with(self, error_handler: ErrorHandler) -> impl Command {
3429
move |world: &mut World| match self.apply(world) {
3530
Ok(_) => {}
3631
Err(err) => (error_handler)(
@@ -41,6 +36,18 @@ where
4136
),
4237
}
4338
}
39+
40+
fn handle_error(self) -> impl Command {
41+
move |world: &mut World| match self.apply(world) {
42+
Ok(_) => {}
43+
Err(err) => world.default_error_handler()(
44+
err.into(),
45+
ErrorContext::Command {
46+
name: type_name::<C>().into(),
47+
},
48+
),
49+
}
50+
}
4451
}
4552

4653
impl<C> HandleError<Never> for C
@@ -52,6 +59,13 @@ where
5259
self.apply(world);
5360
}
5461
}
62+
63+
#[inline]
64+
fn handle_error(self) -> impl Command {
65+
move |world: &mut World| {
66+
self.apply(world);
67+
}
68+
}
5569
}
5670

5771
impl<C> HandleError for C
@@ -63,10 +77,7 @@ where
6377
self
6478
}
6579
#[inline]
66-
fn handle_error(self) -> impl Command
67-
where
68-
Self: Sized,
69-
{
80+
fn handle_error(self) -> impl Command {
7081
self
7182
}
7283
}

crates/bevy_ecs/src/error/handler.rs

Lines changed: 21 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
#[cfg(feature = "configurable_error_handler")]
2-
use bevy_platform::sync::OnceLock;
31
use core::fmt::Display;
42

5-
use crate::{component::Tick, error::BevyError};
3+
use crate::{component::Tick, error::BevyError, prelude::Resource};
64
use alloc::borrow::Cow;
5+
use derive_more::derive::{Deref, DerefMut};
76

87
/// Context for a [`BevyError`] to aid in debugging.
98
#[derive(Debug, PartialEq, Eq, Clone)]
@@ -77,53 +76,6 @@ impl ErrorContext {
7776
}
7877
}
7978

80-
/// A global error handler. This can be set at startup, as long as it is set before
81-
/// any uses. This should generally be configured _before_ initializing the app.
82-
///
83-
/// This should be set inside of your `main` function, before initializing the Bevy app.
84-
/// The value of this error handler can be accessed using the [`default_error_handler`] function,
85-
/// which calls [`OnceLock::get_or_init`] to get the value.
86-
///
87-
/// **Note:** this is only available when the `configurable_error_handler` feature of `bevy_ecs` (or `bevy`) is enabled!
88-
///
89-
/// # Example
90-
///
91-
/// ```
92-
/// # use bevy_ecs::error::{GLOBAL_ERROR_HANDLER, warn};
93-
/// GLOBAL_ERROR_HANDLER.set(warn).expect("The error handler can only be set once, globally.");
94-
/// // initialize Bevy App here
95-
/// ```
96-
///
97-
/// To use this error handler in your app for custom error handling logic:
98-
///
99-
/// ```rust
100-
/// use bevy_ecs::error::{default_error_handler, GLOBAL_ERROR_HANDLER, BevyError, ErrorContext, panic};
101-
///
102-
/// fn handle_errors(error: BevyError, ctx: ErrorContext) {
103-
/// let error_handler = default_error_handler();
104-
/// error_handler(error, ctx);
105-
/// }
106-
/// ```
107-
///
108-
/// # Warning
109-
///
110-
/// As this can *never* be overwritten, library code should never set this value.
111-
#[cfg(feature = "configurable_error_handler")]
112-
pub static GLOBAL_ERROR_HANDLER: OnceLock<fn(BevyError, ErrorContext)> = OnceLock::new();
113-
114-
/// The default error handler. This defaults to [`panic()`],
115-
/// but if set, the [`GLOBAL_ERROR_HANDLER`] will be used instead, enabling error handler customization.
116-
/// The `configurable_error_handler` feature must be enabled to change this from the panicking default behavior,
117-
/// as there may be runtime overhead.
118-
#[inline]
119-
pub fn default_error_handler() -> fn(BevyError, ErrorContext) {
120-
#[cfg(not(feature = "configurable_error_handler"))]
121-
return panic;
122-
123-
#[cfg(feature = "configurable_error_handler")]
124-
return *GLOBAL_ERROR_HANDLER.get_or_init(|| panic);
125-
}
126-
12779
macro_rules! inner {
12880
($call:path, $e:ident, $c:ident) => {
12981
$call!(
@@ -135,6 +87,25 @@ macro_rules! inner {
13587
};
13688
}
13789

90+
/// Defines how Bevy reacts to errors.
91+
pub type ErrorHandler = fn(BevyError, ErrorContext);
92+
93+
/// Error handler to call when an error is not handled otherwise.
94+
/// Defaults to [`panic()`].
95+
///
96+
/// When updated while a [`Schedule`] is running, it doesn't take effect for
97+
/// that schedule until it's completed.
98+
///
99+
/// [`Schedule`]: crate::schedule::Schedule
100+
#[derive(Resource, Deref, DerefMut, Copy, Clone)]
101+
pub struct DefaultErrorHandler(pub ErrorHandler);
102+
103+
impl Default for DefaultErrorHandler {
104+
fn default() -> Self {
105+
Self(panic)
106+
}
107+
}
108+
138109
/// Error handler that panics with the system error.
139110
#[track_caller]
140111
#[inline]

crates/bevy_ecs/src/error/mod.rs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@
77
//! All [`BevyError`]s returned by a system, observer or command are handled by an "error handler". By default, the
88
//! [`panic`] error handler function is used, resulting in a panic with the error message attached.
99
//!
10-
//! You can change the default behavior by registering a custom error handler.
11-
//! Modify the [`GLOBAL_ERROR_HANDLER`] value to set a custom error handler function for your entire app.
10+
//! You can change the default behavior by registering a custom error handler:
11+
//! Use [`DefaultErrorHandler`] to set a custom error handler function for a world,
12+
//! or `App::set_error_handler` for a whole app.
1213
//! In practice, this is generally feature-flagged: panicking or loudly logging errors in development,
1314
//! and quietly logging or ignoring them in production to avoid crashing the app.
1415
//!
@@ -33,10 +34,8 @@
3334
//! The [`ErrorContext`] allows you to access additional details relevant to providing
3435
//! context surrounding the error – such as the system's [`name`] – in your error messages.
3536
//!
36-
//! Remember to turn on the `configurable_error_handler` feature to set a global error handler!
37-
//!
3837
//! ```rust, ignore
39-
//! use bevy_ecs::error::{GLOBAL_ERROR_HANDLER, BevyError, ErrorContext};
38+
//! use bevy_ecs::error::{BevyError, ErrorContext, DefaultErrorHandler};
4039
//! use log::trace;
4140
//!
4241
//! fn my_error_handler(error: BevyError, ctx: ErrorContext) {
@@ -48,10 +47,9 @@
4847
//! }
4948
//!
5049
//! fn main() {
51-
//! // This requires the "configurable_error_handler" feature to be enabled to be in scope.
52-
//! GLOBAL_ERROR_HANDLER.set(my_error_handler).expect("The error handler can only be set once.");
53-
//!
54-
//! // Initialize your Bevy App here
50+
//! let mut world = World::new();
51+
//! world.insert_resource(DefaultErrorHandler(my_error_handler));
52+
//! // Use your world here
5553
//! }
5654
//! ```
5755
//!

0 commit comments

Comments
 (0)