diff --git a/Cargo.toml b/Cargo.toml index 898ab0bbc3b8f..6fc3b0babf071 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,8 @@ trace_chrome = ["bevy_internal/trace_chrome"] trace = ["bevy_internal/trace"] wgpu_trace = ["bevy_internal/wgpu_trace"] +bevy_command_panic_origin = ["bevy_internal/bevy_command_panic_origin"] + # Image format support for texture loading (PNG and HDR are enabled by default) hdr = ["bevy_internal/hdr"] png = ["bevy_internal/png"] diff --git a/crates/bevy_ecs/Cargo.toml b/crates/bevy_ecs/Cargo.toml index ff86bb109c1b4..75b323e838622 100644 --- a/crates/bevy_ecs/Cargo.toml +++ b/crates/bevy_ecs/Cargo.toml @@ -15,6 +15,7 @@ categories = ["game-engines", "data-structures"] [features] trace = [] +command_panic_origin = [] default = ["bevy_reflect"] [dependencies] diff --git a/crates/bevy_ecs/src/system/commands.rs b/crates/bevy_ecs/src/system/commands.rs index 90adfb8dc510c..79ed066cfd7e3 100644 --- a/crates/bevy_ecs/src/system/commands.rs +++ b/crates/bevy_ecs/src/system/commands.rs @@ -5,7 +5,14 @@ use crate::{ world::World, }; use bevy_utils::tracing::debug; +#[cfg(feature = "command_panic_origin")] +use bevy_utils::tracing::error; use std::marker::PhantomData; +#[cfg(feature = "command_panic_origin")] +use std::{ + borrow::Cow, + panic::{self, AssertUnwindSafe}, +}; /// A [`World`] mutation. pub trait Command: Send + Sync + 'static { @@ -15,7 +22,9 @@ pub trait Command: Send + Sync + 'static { /// A queue of [`Command`]s. #[derive(Default)] pub struct CommandQueue { - commands: Vec>, + pub(crate) commands: Vec>, + #[cfg(feature = "command_panic_origin")] + pub(crate) system_name: Option>, } impl CommandQueue { @@ -24,7 +33,22 @@ impl CommandQueue { pub fn apply(&mut self, world: &mut World) { world.flush(); for command in self.commands.drain(..) { + // TODO: replace feature by proper error handling from commands + // https://github.com/bevyengine/bevy/issues/2004 + #[cfg(not(feature = "command_panic_origin"))] command.write(world); + #[cfg(feature = "command_panic_origin")] + { + let may_panic = panic::catch_unwind(AssertUnwindSafe(|| { + command.write(world); + })); + if let Err(err) = may_panic { + if let Some(system_name) = &self.system_name { + error!("panic while applying a command from {}", system_name); + } + std::panic::resume_unwind(err); + } + } } } diff --git a/crates/bevy_ecs/src/system/into_system.rs b/crates/bevy_ecs/src/system/into_system.rs index 97f3e0f62f0f2..63fd295774729 100644 --- a/crates/bevy_ecs/src/system/into_system.rs +++ b/crates/bevy_ecs/src/system/into_system.rs @@ -47,6 +47,11 @@ impl SystemState { pub fn set_non_send(&mut self) { self.is_send = false; } + + #[cfg(feature = "command_panic_origin")] + pub fn name(&self) -> Cow<'static, str> { + self.name.clone() + } } /// Conversion trait to turn something into a [`System`]. diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index bd93e844eb266..0040ea5a97352 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -529,8 +529,13 @@ impl<'a> SystemParam for Commands<'a> { unsafe impl SystemParamState for CommandQueue { type Config = (); - fn init(_world: &mut World, _system_state: &mut SystemState, _config: Self::Config) -> Self { - Default::default() + #[allow(unused)] + fn init(_world: &mut World, system_state: &mut SystemState, _config: Self::Config) -> Self { + CommandQueue { + #[cfg(feature = "command_panic_origin")] + system_name: Some(system_state.name()), + ..Default::default() + } } fn apply(&mut self, world: &mut World) { diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index 9d557aff5d167..e149f033c8ea5 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -41,6 +41,9 @@ x11 = ["bevy_winit/x11"] # enable rendering of font glyphs using subpixel accuracy subpixel_glyph_atlas = ["bevy_text/subpixel_glyph_atlas"] +# enable tracing system origin of commands in case of command failure +bevy_command_panic_origin = ["bevy_ecs/command_panic_origin"] + # enable systems that allow for automated testing on CI bevy_ci_testing = ["bevy_app/bevy_ci_testing"]