diff --git a/Cargo.toml b/Cargo.toml index 04db26208b3dd..edcdbc185f58b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -288,9 +288,6 @@ bevy_remote = ["bevy_internal/bevy_remote"] # Enable input focus subsystem bevy_input_focus = ["bevy_internal/bevy_input_focus"] -# Use the configurable global error handler as the default error handler. -configurable_error_handler = ["bevy_internal/configurable_error_handler"] - # Enable passthrough loading for SPIR-V shaders (Only supported on Vulkan, shader capabilities and extensions must agree with the platform implementation) spirv_shader_passthrough = ["bevy_internal/spirv_shader_passthrough"] @@ -2215,7 +2212,6 @@ wasm = false name = "fallible_params" path = "examples/ecs/fallible_params.rs" doc-scrape-examples = true -required-features = ["configurable_error_handler"] [package.metadata.example.fallible_params] name = "Fallible System Parameters" @@ -2227,7 +2223,7 @@ wasm = false name = "error_handling" path = "examples/ecs/error_handling.rs" doc-scrape-examples = true -required-features = ["bevy_mesh_picking_backend", "configurable_error_handler"] +required-features = ["bevy_mesh_picking_backend"] [package.metadata.example.error_handling] name = "Error handling" diff --git a/crates/bevy_ecs/Cargo.toml b/crates/bevy_ecs/Cargo.toml index 97cdcee0824b7..28987f1413449 100644 --- a/crates/bevy_ecs/Cargo.toml +++ b/crates/bevy_ecs/Cargo.toml @@ -33,13 +33,6 @@ bevy_reflect = ["dep:bevy_reflect"] ## Extends reflection support to functions. reflect_functions = ["bevy_reflect", "bevy_reflect/functions"] -## Use the configurable global error handler as the default error handler. -## -## This is typically used to turn panics from the ECS into loggable errors. -## This may be useful for production builds, -## but can result in a measurable performance impact, especially for commands. -configurable_error_handler = [] - ## Enables automatic backtrace capturing in BevyError backtrace = ["std"] diff --git a/crates/bevy_ecs/src/error/handler.rs b/crates/bevy_ecs/src/error/handler.rs index 688b599473ab9..a684ec0ebcb49 100644 --- a/crates/bevy_ecs/src/error/handler.rs +++ b/crates/bevy_ecs/src/error/handler.rs @@ -1,5 +1,3 @@ -#[cfg(feature = "configurable_error_handler")] -use bevy_platform::sync::OnceLock; use core::fmt::Display; use crate::{component::Tick, error::BevyError}; @@ -77,53 +75,83 @@ impl ErrorContext { } } -/// 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::{default_error_handler, GLOBAL_ERROR_HANDLER, BevyError, ErrorContext, panic}; -/// -/// fn handle_errors(error: BevyError, ctx: ErrorContext) { -/// let error_handler = default_error_handler(); -/// error_handler(error, ctx); -/// } -/// ``` -/// -/// # Warning -/// -/// As this can *never* be overwritten, library code should never set this value. -#[cfg(feature = "configurable_error_handler")] -pub static GLOBAL_ERROR_HANDLER: OnceLock = OnceLock::new(); - -/// The default error handler. This defaults to [`panic()`], -/// but if set, the [`GLOBAL_ERROR_HANDLER`] will be used instead, enabling error handler customization. -/// The `configurable_error_handler` feature must be enabled to change this from the panicking default behavior, -/// as there may be runtime overhead. -#[inline] -pub fn default_error_handler() -> fn(BevyError, ErrorContext) { - #[cfg(not(feature = "configurable_error_handler"))] - return panic; +mod global_error_handler { + use super::{panic, BevyError, ErrorContext}; + use bevy_platform::sync::atomic::{ + AtomicBool, AtomicPtr, + Ordering::{AcqRel, Acquire, Relaxed}, + }; + + /// The default global error handler, cast to a data pointer as Rust doesn't + /// currently have a way to express atomic function pointers. + /// Should we add support for a platform on which function pointers and data pointers + /// have different sizes, the transmutation back will fail to compile. In that case, + /// we can replace the atomic pointer with a regular pointer protected by a `OnceLock` + /// on only those platforms. + /// SAFETY: Only accessible from within this module. + static HANDLER: AtomicPtr<()> = AtomicPtr::new(panic as *mut ()); + + /// Set the global error handler. + /// + /// If used, this should be called [before] any uses of [`default_error_handler`], + /// generally inside your `main` function before initializing the app. + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::error::{set_global_default_error_handler, warn}; + /// set_global_default_error_handler(warn); + /// // initialize Bevy App here + /// ``` + /// + /// To use this error handler in your app for custom error handling logic: + /// + /// ```rust + /// use bevy_ecs::error::{default_error_handler, BevyError, ErrorContext}; + /// + /// 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. + /// + /// [before]: https://doc.rust-lang.org/nightly/core/sync/atomic/index.html#memory-model-for-atomic-accesses + /// [`default_error_handler`]: super::default_error_handler + pub fn set_global_default_error_handler(handler: fn(BevyError, ErrorContext)) { + // Prevent the handler from being set multiple times. + // We use a separate atomic instead of trying `compare_exchange` on `HANDLER_ADDRESS` + // because Rust doesn't guarantee that function addresses are unique. + static INITIALIZED: AtomicBool = AtomicBool::new(false); + if INITIALIZED + .compare_exchange(false, true, AcqRel, Acquire) + .is_err() + { + panic!("Global error handler set multiple times"); + } + HANDLER.store(handler as *mut (), Relaxed); + } - #[cfg(feature = "configurable_error_handler")] - return *GLOBAL_ERROR_HANDLER.get_or_init(|| panic); + /// The default error handler. This defaults to [`panic`], + /// but you can override this behavior via [`set_global_default_error_handler`]. + /// + /// [`panic`]: super::panic + #[inline] + pub fn default_error_handler() -> fn(BevyError, ErrorContext) { + // The error handler must have been already set from the perspective of this thread, + // otherwise we will panic. It will never be updated after this point. + // We therefore only need a relaxed load. + let ptr = HANDLER.load(Relaxed); + // SAFETY: We only ever store valid handler functions. + unsafe { core::mem::transmute(ptr) } + } } +pub use global_error_handler::{default_error_handler, set_global_default_error_handler}; + macro_rules! inner { ($call:path, $e:ident, $c:ident) => { $call!( diff --git a/crates/bevy_ecs/src/error/mod.rs b/crates/bevy_ecs/src/error/mod.rs index 950deee3ecf97..bb3016d5d513a 100644 --- a/crates/bevy_ecs/src/error/mod.rs +++ b/crates/bevy_ecs/src/error/mod.rs @@ -7,8 +7,9 @@ //! All [`BevyError`]s returned by a system, observer or command are handled by an "error handler". By default, the //! [`panic`] error handler function is used, resulting in a panic with the error message attached. //! -//! You can change the default behavior by registering a custom error handler. -//! Modify the [`GLOBAL_ERROR_HANDLER`] value to set a custom error handler function for your entire app. +//! You can change the default behavior by registering a custom error handler: +//! Use [`set_global_default_error_handler`] +//! to set a custom error handler function for your entire app. //! In practice, this is generally feature-flagged: panicking or loudly logging errors in development, //! and quietly logging or ignoring them in production to avoid crashing the app. //! @@ -33,10 +34,8 @@ //! The [`ErrorContext`] allows you to access additional details relevant to providing //! context surrounding the error – such as the system's [`name`] – in your error messages. //! -//! Remember to turn on the `configurable_error_handler` feature to set a global error handler! -//! //! ```rust, ignore -//! use bevy_ecs::error::{GLOBAL_ERROR_HANDLER, BevyError, ErrorContext}; +//! use bevy_ecs::error::{set_global_default_error_handler, BevyError, ErrorContext}; //! use log::trace; //! //! fn my_error_handler(error: BevyError, ctx: ErrorContext) { @@ -48,8 +47,7 @@ //! } //! //! 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."); +//! set_global_default_error_handler(my_error_handler); //! //! // Initialize your Bevy App here //! } diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 4cb6d61bc0e9a..087a134db8ba8 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -90,7 +90,7 @@ use crate::{ /// which will be passed to an [error handler](crate::error) if the `Result` is an error. /// /// The [default error handler](crate::error::default_error_handler) panics. -/// It can be configured by setting the `GLOBAL_ERROR_HANDLER`. +/// It can be configured via [`set_global_default_error_handler`](crate::error::set_global_default_error_handler). /// /// Alternatively, you can customize the error handler for a specific command /// by calling [`Commands::queue_handled`]. @@ -1224,7 +1224,7 @@ impl<'w, 's> Commands<'w, 's> { /// which will be passed to an [error handler](crate::error) if the `Result` is an error. /// /// The [default error handler](crate::error::default_error_handler) panics. -/// It can be configured by setting the `GLOBAL_ERROR_HANDLER`. +/// It can be configured via [`set_global_default_error_handler`](crate::error::set_global_default_error_handler). /// /// Alternatively, you can customize the error handler for a specific command /// by calling [`EntityCommands::queue_handled`]. diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index f4c2f2f03100d..98a292ec8a44d 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -289,9 +289,6 @@ custom_cursor = ["bevy_winit/custom_cursor"] # Experimental support for nodes that are ignored for UI layouting ghost_nodes = ["bevy_ui/ghost_nodes"] -# Use the configurable global error handler as the default error handler. -configurable_error_handler = ["bevy_ecs/configurable_error_handler"] - # Allows access to the `std` crate. Enabling this feature will prevent compilation # on `no_std` targets, but provides access to certain additional features on # supported platforms. diff --git a/docs/cargo_features.md b/docs/cargo_features.md index 774dafe952889..72b3d6e170b49 100644 --- a/docs/cargo_features.md +++ b/docs/cargo_features.md @@ -70,7 +70,6 @@ The default feature set enables most of the expected features of a game engine, |bevy_remote|Enable the Bevy Remote Protocol| |bevy_ui_debug|Provides a debug overlay for bevy UI| |bmp|BMP image format support| -|configurable_error_handler|Use the configurable global error handler as the default error handler.| |critical-section|`critical-section` provides the building blocks for synchronization primitives on all platforms, including `no_std`.| |dds|DDS compressed texture support| |debug_glam_assert|Enable assertions in debug builds to check the validity of parameters passed to glam| diff --git a/examples/ecs/error_handling.rs b/examples/ecs/error_handling.rs index b13a018530fc0..23da8186e96da 100644 --- a/examples/ecs/error_handling.rs +++ b/examples/ecs/error_handling.rs @@ -1,11 +1,8 @@ //! Showcases how fallible systems and observers can make use of Rust's powerful result handling //! 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::{ - error::{warn, GLOBAL_ERROR_HANDLER}, + error::{set_global_default_error_handler, warn}, world::DeferredWorld, }; use bevy::math::sampling::UniformMeshSampler; @@ -22,9 +19,7 @@ fn main() { // 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."); + set_global_default_error_handler(warn); let mut app = App::new(); diff --git a/examples/ecs/fallible_params.rs b/examples/ecs/fallible_params.rs index 99eaedf10d071..27fe25ff986df 100644 --- a/examples/ecs/fallible_params.rs +++ b/examples/ecs/fallible_params.rs @@ -2,7 +2,7 @@ //! from running if their acquiry conditions aren't met. //! //! Fallible system parameters include: -//! - [`Res`], [`ResMut`] - Resource has to exist, and the [`GLOBAL_ERROR_HANDLER`] will be called if it doesn't. +//! - [`Res`], [`ResMut`] - Resource has to exist, and the [`default_error_handler`] will be called if it doesn't. //! - [`Single`] - There must be exactly one matching entity, but the system will be silently skipped otherwise. //! - [`Option>`] - There must be zero or one matching entity. The system will be silently skipped if there are more. //! - [`Populated`] - There must be at least one matching entity, but the system will be silently skipped otherwise. @@ -18,18 +18,17 @@ //! //! [`SystemParamValidationError`]: bevy::ecs::system::SystemParamValidationError //! [`SystemParam::validate_param`]: bevy::ecs::system::SystemParam::validate_param +//! [`default_error_handler`]: bevy::ecs::error::default_error_handler -use bevy::ecs::error::{warn, GLOBAL_ERROR_HANDLER}; +use bevy::ecs::error::{set_global_default_error_handler, warn}; use bevy::prelude::*; use rand::Rng; fn main() { // By default, if a parameter fail to be fetched, - // the `GLOBAL_ERROR_HANDLER` will be used to handle the error, + // the `default_error_handler` will be used to handle the error, // which by default is set to panic. - GLOBAL_ERROR_HANDLER - .set(warn) - .expect("The error handler can only be set once, globally."); + set_global_default_error_handler(warn); println!(); println!("Press 'A' to add enemy ships and 'R' to remove them.");