Skip to content

Commit ba6d4b5

Browse files
move default error handler to World
1 parent ec9df9f commit ba6d4b5

File tree

13 files changed

+110
-129
lines changed

13 files changed

+110
-129
lines changed

crates/bevy_app/src/app.rs

Lines changed: 35 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::{panic, 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: 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: panic,
146149
}
147150
}
148151

@@ -1115,7 +1118,10 @@ 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+
sub_app
1123+
.world_mut()
1124+
.set_default_error_handler(self.default_error_handler);
11191125
self.sub_apps.sub_apps.insert(label.intern(), sub_app);
11201126
}
11211127

@@ -1334,6 +1340,34 @@ impl App {
13341340
self.world_mut().add_observer(observer);
13351341
self
13361342
}
1343+
1344+
/// Gets the error handler to set for new supapps.
1345+
///
1346+
/// Note that the error handler of existing subapps may differ.
1347+
pub fn get_error_handler(&self) -> ErrorHandler {
1348+
self.default_error_handler
1349+
}
1350+
1351+
/// Override the error handler for the all subapps (including the main one).
1352+
///
1353+
/// This handler will be called when an error is produced and not otherwise handled.
1354+
///
1355+
/// # Example
1356+
/// ```
1357+
/// # use bevy_app::*;
1358+
/// # use bevy_ecs::error::warn;
1359+
/// App::new()
1360+
/// .set_error_handler(warn)
1361+
/// .add_plugins(DefaultPlugins)
1362+
/// .run()
1363+
/// ```
1364+
pub fn set_error_handler(&mut self, handler: ErrorHandler) -> &mut Self {
1365+
self.default_error_handler = handler;
1366+
for sub_app in self.sub_apps.iter_mut() {
1367+
sub_app.world_mut().set_default_error_handler(handler);
1368+
}
1369+
self
1370+
}
13371371
}
13381372

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

crates/bevy_ecs/src/error/command_handling.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,24 @@ use crate::{
77
world::{error::EntityMutableFetchError, World},
88
};
99

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

1212
/// Takes a [`Command`] that 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 `()`.
2020
fn handle_error(self) -> impl Command
2121
where
2222
Self: Sized,
2323
{
24-
self.handle_error_with(default_error_handler())
24+
move |world: &mut World| {
25+
self.handle_error_with(world.default_error_handler)
26+
.apply(world);
27+
}
2528
}
2629
}
2730

@@ -30,7 +33,7 @@ where
3033
C: Command<Result<T, E>>,
3134
E: Into<BevyError>,
3235
{
33-
fn handle_error_with(self, error_handler: fn(BevyError, ErrorContext)) -> impl Command {
36+
fn handle_error_with(self, error_handler: ErrorHandler) -> impl Command {
3437
move |world: &mut World| match self.apply(world) {
3538
Ok(_) => {}
3639
Err(err) => (error_handler)(

crates/bevy_ecs/src/error/handler.rs

Lines changed: 3 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -75,83 +75,6 @@ impl ErrorContext {
7575
}
7676
}
7777

78-
mod global_error_handler {
79-
use super::{panic, BevyError, ErrorContext};
80-
use bevy_platform_support::sync::atomic::{
81-
AtomicBool, AtomicPtr,
82-
Ordering::{AcqRel, Acquire, Relaxed},
83-
};
84-
85-
/// The default global error handler, cast to a data pointer as Rust doesn't
86-
/// currently have a way to express atomic function pointers.
87-
/// Should we add support for a platform on which function pointers and data pointers
88-
/// have different sizes, the transmutation back will fail to compile. In that case,
89-
/// we can replace the atomic pointer with a regular pointer protected by a `OnceLock`
90-
/// on only those platforms.
91-
/// SAFETY: Only accessible from within this module.
92-
static HANDLER: AtomicPtr<()> = AtomicPtr::new(panic as *mut ());
93-
94-
/// Set the global error handler.
95-
///
96-
/// If used, this should be called [before] any uses of [`default_error_handler`],
97-
/// generally inside your `main` function before initializing the app.
98-
///
99-
/// # Example
100-
///
101-
/// ```
102-
/// # use bevy_ecs::error::{set_global_default_error_handler, warn};
103-
/// set_global_default_error_handler(warn);
104-
/// // initialize Bevy App here
105-
/// ```
106-
///
107-
/// To use this error handler in your app for custom error handling logic:
108-
///
109-
/// ```rust
110-
/// use bevy_ecs::error::{default_error_handler, BevyError, ErrorContext};
111-
///
112-
/// fn handle_errors(error: BevyError, ctx: ErrorContext) {
113-
/// let error_handler = default_error_handler();
114-
/// error_handler(error, ctx);
115-
/// }
116-
/// ```
117-
///
118-
/// # Warning
119-
///
120-
/// As this can *never* be overwritten, library code should never set this value.
121-
///
122-
/// [before]: https://doc.rust-lang.org/nightly/core/sync/atomic/index.html#memory-model-for-atomic-accesses
123-
/// [`default_error_handler`]: super::default_error_handler
124-
pub fn set_global_default_error_handler(handler: fn(BevyError, ErrorContext)) {
125-
// Prevent the handler from being set multiple times.
126-
// We use a separate atomic instead of trying `compare_exchange` on `HANDLER_ADDRESS`
127-
// because Rust doesn't guarantee that function addresses are unique.
128-
static INITIALIZED: AtomicBool = AtomicBool::new(false);
129-
if INITIALIZED
130-
.compare_exchange(false, true, AcqRel, Acquire)
131-
.is_err()
132-
{
133-
panic!("Global error handler set multiple times");
134-
}
135-
HANDLER.store(handler as *mut (), Relaxed);
136-
}
137-
138-
/// The default error handler. This defaults to [`panic`],
139-
/// but you can override this behavior via [`set_global_default_error_handler`].
140-
///
141-
/// [`panic`]: super::panic
142-
#[inline]
143-
pub fn default_error_handler() -> fn(BevyError, ErrorContext) {
144-
// The error handler must have been already set from the perspective of this thread,
145-
// otherwise we will panic. It will never be updated after this point.
146-
// We therefore only need a relaxed load.
147-
let ptr = HANDLER.load(Relaxed);
148-
// SAFETY: We only ever store valid handler functions.
149-
unsafe { core::mem::transmute(ptr) }
150-
}
151-
}
152-
153-
pub use global_error_handler::{default_error_handler, set_global_default_error_handler};
154-
15578
macro_rules! inner {
15679
($call:path, $e:ident, $c:ident) => {
15780
$call!(
@@ -163,6 +86,9 @@ macro_rules! inner {
16386
};
16487
}
16588

89+
/// Defines how Bevy reacts to errors.
90+
pub type ErrorHandler = fn(BevyError, ErrorContext);
91+
16692
/// Error handler that panics with the system error.
16793
#[track_caller]
16894
#[inline]

crates/bevy_ecs/src/error/mod.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
//! [`panic`] error handler function is used, resulting in a panic with the error message attached.
99
//!
1010
//! You can change the default behavior by registering a custom error handler:
11-
//! Use [`set_global_default_error_handler`]
12-
//! to set a custom error handler function for your entire app.
11+
//! Use [`World::set_default_error_handler`] to set a custom error handler function for a world,
12+
//! or `App::set_error_handler` for a whole app.
1313
//! In practice, this is generally feature-flagged: panicking or loudly logging errors in development,
1414
//! and quietly logging or ignoring them in production to avoid crashing the app.
1515
//!
@@ -35,7 +35,7 @@
3535
//! context surrounding the error – such as the system's [`name`] – in your error messages.
3636
//!
3737
//! ```rust, ignore
38-
//! use bevy_ecs::error::{set_global_default_error_handler, BevyError, ErrorContext};
38+
//! use bevy_ecs::error::{BevyError, ErrorContext};
3939
//! use log::trace;
4040
//!
4141
//! fn my_error_handler(error: BevyError, ctx: ErrorContext) {
@@ -47,9 +47,9 @@
4747
//! }
4848
//!
4949
//! fn main() {
50-
//! set_global_default_error_handler(my_error_handler);
51-
//!
52-
//! // Initialize your Bevy App here
50+
//! let mut world = World::new();
51+
//! world.set_default_error_handler(my_error_handler);
52+
//!
5353
//! }
5454
//! ```
5555
//!

crates/bevy_ecs/src/observer/runner.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use core::any::Any;
33

44
use crate::{
55
component::{ComponentHook, ComponentId, HookContext, Mutable, StorageType},
6-
error::{default_error_handler, ErrorContext},
6+
error::ErrorContext,
77
observer::{ObserverDescriptor, ObserverTrigger},
88
prelude::*,
99
query::DebugCheckedUnwrap,
@@ -232,14 +232,15 @@ impl Observer {
232232
system: Box::new(|| {}),
233233
descriptor: Default::default(),
234234
hook_on_add: |mut world, hook_context| {
235+
let default_error_handler = world.default_error_handler;
235236
world.commands().queue(move |world: &mut World| {
236237
let entity = hook_context.entity;
237238
if let Some(mut observe) = world.get_mut::<Observer>(entity) {
238239
if observe.descriptor.events.is_empty() {
239240
return;
240241
}
241242
if observe.error_handler.is_none() {
242-
observe.error_handler = Some(default_error_handler());
243+
observe.error_handler = Some(default_error_handler);
243244
}
244245
world.register_observer(entity);
245246
}
@@ -420,12 +421,13 @@ fn hook_on_add<E: Event, B: Bundle, S: ObserverSystem<E, B>>(
420421
B::component_ids(&mut world.components_registrator(), &mut |id| {
421422
components.push(id);
422423
});
424+
let default_error_handler = world.default_error_handler;
423425
if let Some(mut observe) = world.get_mut::<Observer>(entity) {
424426
observe.descriptor.events.push(event_id);
425427
observe.descriptor.components.extend(components);
426428

427429
if observe.error_handler.is_none() {
428-
observe.error_handler = Some(default_error_handler());
430+
observe.error_handler = Some(default_error_handler);
429431
}
430432
let system: *mut dyn ObserverSystem<E, B> = observe.system.downcast_mut::<S>().unwrap();
431433
// SAFETY: World reference is exclusive and initialize does not touch system, so references do not alias

crates/bevy_ecs/src/schedule/executor/multi_threaded.rs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use std::sync::{Mutex, MutexGuard};
1313
use tracing::{info_span, Span};
1414

1515
use crate::{
16-
error::{default_error_handler, BevyError, ErrorContext, Result},
16+
error::{ErrorContext, ErrorHandler, Result},
1717
prelude::Resource,
1818
schedule::{is_apply_deferred, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule},
1919
system::ScheduleSystem,
@@ -134,7 +134,7 @@ pub struct ExecutorState {
134134
struct Context<'scope, 'env, 'sys> {
135135
environment: &'env Environment<'env, 'sys>,
136136
scope: &'scope Scope<'scope, 'env, ()>,
137-
error_handler: fn(BevyError, ErrorContext),
137+
error_handler: ErrorHandler,
138138
}
139139

140140
impl Default for MultiThreadedExecutor {
@@ -240,7 +240,7 @@ impl SystemExecutor for MultiThreadedExecutor {
240240
schedule: &mut SystemSchedule,
241241
world: &mut World,
242242
_skip_systems: Option<&FixedBitSet>,
243-
error_handler: fn(BevyError, ErrorContext),
243+
error_handler: ErrorHandler,
244244
) {
245245
let state = self.state.get_mut().unwrap();
246246
// reset counts
@@ -586,7 +586,6 @@ impl ExecutorState {
586586
world: UnsafeWorldCell,
587587
) -> bool {
588588
let mut should_run = !self.skipped_systems.contains(system_index);
589-
let error_handler = default_error_handler();
590589

591590
for set_idx in conditions.sets_with_conditions_of_systems[system_index].ones() {
592591
if self.evaluated_sets.contains(set_idx) {
@@ -635,7 +634,7 @@ impl ExecutorState {
635634
Ok(()) => true,
636635
Err(e) => {
637636
if !e.skipped {
638-
error_handler(
637+
world.default_error_handler()(
639638
e.into(),
640639
ErrorContext::System {
641640
name: system.name(),
@@ -823,8 +822,6 @@ unsafe fn evaluate_and_fold_conditions(
823822
conditions: &mut [BoxedCondition],
824823
world: UnsafeWorldCell,
825824
) -> bool {
826-
let error_handler = default_error_handler();
827-
828825
#[expect(
829826
clippy::unnecessary_fold,
830827
reason = "Short-circuiting here would prevent conditions from mutating their own state as needed."
@@ -840,7 +837,7 @@ unsafe fn evaluate_and_fold_conditions(
840837
Ok(()) => (),
841838
Err(e) => {
842839
if !e.skipped {
843-
error_handler(
840+
world.default_error_handler()(
844841
e.into(),
845842
ErrorContext::System {
846843
name: condition.name(),

crates/bevy_ecs/src/schedule/executor/simple.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use tracing::info_span;
1010
use std::eprintln;
1111

1212
use crate::{
13-
error::{default_error_handler, BevyError, ErrorContext},
13+
error::{ErrorContext, ErrorHandler},
1414
schedule::{
1515
executor::is_apply_deferred, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule,
1616
},
@@ -50,7 +50,7 @@ impl SystemExecutor for SimpleExecutor {
5050
schedule: &mut SystemSchedule,
5151
world: &mut World,
5252
_skip_systems: Option<&FixedBitSet>,
53-
error_handler: fn(BevyError, ErrorContext),
53+
error_handler: ErrorHandler,
5454
) {
5555
// If stepping is enabled, make sure we skip those systems that should
5656
// not be run.
@@ -176,8 +176,6 @@ impl SimpleExecutor {
176176
note = "Use SingleThreadedExecutor instead. See https://github.com/bevyengine/bevy/issues/18453 for motivation."
177177
)]
178178
fn evaluate_and_fold_conditions(conditions: &mut [BoxedCondition], world: &mut World) -> bool {
179-
let error_handler = default_error_handler();
180-
181179
#[expect(
182180
clippy::unnecessary_fold,
183181
reason = "Short-circuiting here would prevent conditions from mutating their own state as needed."
@@ -189,7 +187,7 @@ fn evaluate_and_fold_conditions(conditions: &mut [BoxedCondition], world: &mut W
189187
Ok(()) => (),
190188
Err(e) => {
191189
if !e.skipped {
192-
error_handler(
190+
world.default_error_handler()(
193191
e.into(),
194192
ErrorContext::System {
195193
name: condition.name(),

0 commit comments

Comments
 (0)