
# Objective Remove Bevy internals from backtraces ## Solution Executors insert `__rust_begin_short_backtrace` into the callstack before running a system. <details> <summary>Example current output</summary> ``` thread 'Compute Task Pool (3)' panicked at src/main.rs:7:33: Foo stack backtrace: 0: rust_begin_unwind at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/std/src/panicking.rs:647:5 1: core::panicking::panic_fmt at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/core/src/panicking.rs:72:14 2: foo::main::{{closure}} at ./src/main.rs:7:33 3: core::ops::function::impls::<impl core::ops::function::FnMut<A> for &mut F>::call_mut at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/core/src/ops/function.rs:294:13 4: <Func as bevy_ecs::system::function_system::SystemParamFunction<fn() .> Out>>::run::call_inner at /home/vj/workspace/rust/bevy/crates/bevy_ecs/src/system/function_system.rs:661:21 5: <Func as bevy_ecs::system::function_system::SystemParamFunction<fn() .> Out>>::run at /home/vj/workspace/rust/bevy/crates/bevy_ecs/src/system/function_system.rs:664:17 6: <bevy_ecs::system::function_system::FunctionSystem<Marker,F> as bevy_ecs::system::system::System>::run_unsafe at /home/vj/workspace/rust/bevy/crates/bevy_ecs/src/system/function_system.rs:504:19 7: bevy_ecs::schedule::executor::multi_threaded::ExecutorState::spawn_system_task::{{closure}}::{{closure}} at /home/vj/workspace/rust/bevy/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs:621:26 8: core::ops::function::FnOnce::call_once at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/core/src/ops/function.rs:250:5 9: <core::panic::unwind_safe::AssertUnwindSafe<F> as core::ops::function::FnOnce<()>>::call_once at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/core/src/panic/unwind_safe.rs:272:9 10: std::panicking::try::do_call at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/std/src/panicking.rs:554:40 11: __rust_try 12: std::panicking::try at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/std/src/panicking.rs:518:19 13: std::panic::catch_unwind at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/std/src/panic.rs:142:14 14: bevy_ecs::schedule::executor::multi_threaded::ExecutorState::spawn_system_task::{{closure}} at /home/vj/workspace/rust/bevy/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs:614:23 15: <core::panic::unwind_safe::AssertUnwindSafe<F> as core::future::future::Future>::poll at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/core/src/panic/unwind_safe.rs:297:9 16: <futures_lite::future::CatchUnwind<F> as core::future::future::Future>::poll::{{closure}} at /home/vj/.cargo/registry/src/index.crates.io-6f17d22bba15001f/futures-lite-2.2.0/src/future.rs:588:42 17: <core::panic::unwind_safe::AssertUnwindSafe<F> as core::ops::function::FnOnce<()>>::call_once at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/core/src/panic/unwind_safe.rs:272:9 18: std::panicking::try::do_call at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/std/src/panicking.rs:554:40 19: __rust_try 20: std::panicking::try at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/std/src/panicking.rs:518:19 21: std::panic::catch_unwind at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/std/src/panic.rs:142:14 22: <futures_lite::future::CatchUnwind<F> as core::future::future::Future>::poll at /home/vj/.cargo/registry/src/index.crates.io-6f17d22bba15001f/futures-lite-2.2.0/src/future.rs:588:9 23: async_executor::Executor::spawn::{{closure}} at /home/vj/.cargo/registry/src/index.crates.io-6f17d22bba15001f/async-executor-1.8.0/src/lib.rs:158:20 24: async_task::raw::RawTask<F,T,S,M>::run::{{closure}} at /home/vj/.cargo/registry/src/index.crates.io-6f17d22bba15001f/async-task-4.7.0/src/raw.rs:550:21 25: core::ops::function::FnOnce::call_once at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/core/src/ops/function.rs:250:5 26: <core::panic::unwind_safe::AssertUnwindSafe<F> as core::ops::function::FnOnce<()>>::call_once at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/core/src/panic/unwind_safe.rs:272:9 27: std::panicking::try::do_call at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/std/src/panicking.rs:554:40 28: __rust_try 29: std::panicking::try at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/std/src/panicking.rs:518:19 30: std::panic::catch_unwind at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/std/src/panic.rs:142:14 31: async_task::raw::RawTask<F,T,S,M>::run at /home/vj/.cargo/registry/src/index.crates.io-6f17d22bba15001f/async-task-4.7.0/src/raw.rs:549:23 32: async_task::runnable::Runnable<M>::run at /home/vj/.cargo/registry/src/index.crates.io-6f17d22bba15001f/async-task-4.7.0/src/runnable.rs:781:18 33: async_executor::Executor::run::{{closure}}::{{closure}} at /home/vj/.cargo/registry/src/index.crates.io-6f17d22bba15001f/async-executor-1.8.0/src/lib.rs:254:21 34: <futures_lite::future::Or<F1,F2> as core::future::future::Future>::poll at /home/vj/.cargo/registry/src/index.crates.io-6f17d22bba15001f/futures-lite-2.2.0/src/future.rs:449:33 35: async_executor::Executor::run::{{closure}} at /home/vj/.cargo/registry/src/index.crates.io-6f17d22bba15001f/async-executor-1.8.0/src/lib.rs:261:32 36: futures_lite::future::block_on::{{closure}} at /home/vj/.cargo/registry/src/index.crates.io-6f17d22bba15001f/futures-lite-2.2.0/src/future.rs:99:19 37: std:🧵:local::LocalKey<T>::try_with at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/std/src/thread/local.rs:286:16 38: std:🧵:local::LocalKey<T>::with at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/std/src/thread/local.rs:262:9 39: futures_lite::future::block_on at /home/vj/.cargo/registry/src/index.crates.io-6f17d22bba15001f/futures-lite-2.2.0/src/future.rs:78:5 40: bevy_tasks::task_pool::TaskPool::new_internal::{{closure}}::{{closure}}::{{closure}}::{{closure}} at /home/vj/workspace/rust/bevy/crates/bevy_tasks/src/task_pool.rs:180:37 41: std::panicking::try::do_call at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/std/src/panicking.rs:554:40 42: __rust_try 43: std::panicking::try at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/std/src/panicking.rs:518:19 44: std::panic::catch_unwind at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/std/src/panic.rs:142:14 45: bevy_tasks::task_pool::TaskPool::new_internal::{{closure}}::{{closure}}::{{closure}} at /home/vj/workspace/rust/bevy/crates/bevy_tasks/src/task_pool.rs:174:43 46: std:🧵:local::LocalKey<T>::try_with at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/std/src/thread/local.rs:286:16 47: std:🧵:local::LocalKey<T>::with at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/std/src/thread/local.rs:262:9 48: bevy_tasks::task_pool::TaskPool::new_internal::{{closure}}::{{closure}} at /home/vj/workspace/rust/bevy/crates/bevy_tasks/src/task_pool.rs:167:25 note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace. Encountered a panic in system `foo::main::{{closure}}`! Encountered a panic in system `bevy_app::main_schedule::Main::run_main`! get on your knees and beg mommy for forgiveness you pervert~ 💖 ``` </details> <details> <summary>Example output with this PR</summary> ``` Panic at src/main.rs:7:33: Foo stack backtrace: 0: rust_begin_unwind at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/std/src/panicking.rs:647:5 1: core::panicking::panic_fmt at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/core/src/panicking.rs:72:14 2: foo::main::{{closure}} at ./src/main.rs:7:59 3: core::ops::function::impls::<impl core::ops::function::FnMut<A> for &mut F>::call_mut at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/core/src/ops/function.rs:294:13 4: <Func as bevy_ecs::system::function_system::SystemParamFunction<fn() .> Out>>::run::call_inner at /home/vj/workspace/rust/bevy/crates/bevy_ecs/src/system/function_system.rs:661:21 5: <Func as bevy_ecs::system::function_system::SystemParamFunction<fn() .> Out>>::run at /home/vj/workspace/rust/bevy/crates/bevy_ecs/src/system/function_system.rs:664:17 6: <bevy_ecs::system::function_system::FunctionSystem<Marker,F> as bevy_ecs::system::system::System>::run_unsafe at /home/vj/workspace/rust/bevy/crates/bevy_ecs/src/system/function_system.rs:504:19 note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace. Encountered a panic in system `foo::main::{{closure}}`! Encountered a panic in system `bevy_app::main_schedule::Main::run_main`! ``` </details> Full backtraces (`RUST_BACKTRACE=full`) are unchanged. ## Alternative solutions Write a custom panic hook. This could potentially let use exclude a few more callstack frames but requires a dependency on `backtrace` and is incompatible with user-provided panic hooks. --- ## Changelog - Backtraces now exclude many Bevy internals (unless `RUST_BACKTRACE=full` is used) --------- Co-authored-by: James Liu <contact@jamessliu.com>
173 lines
6.5 KiB
Rust
173 lines
6.5 KiB
Rust
mod multi_threaded;
|
|
mod simple;
|
|
mod single_threaded;
|
|
|
|
pub use self::multi_threaded::{MainThreadExecutor, MultiThreadedExecutor};
|
|
pub use self::simple::SimpleExecutor;
|
|
pub use self::single_threaded::SingleThreadedExecutor;
|
|
|
|
use fixedbitset::FixedBitSet;
|
|
|
|
use crate::{
|
|
schedule::{BoxedCondition, NodeId},
|
|
system::BoxedSystem,
|
|
world::World,
|
|
};
|
|
|
|
/// Types that can run a [`SystemSchedule`] on a [`World`].
|
|
pub(super) trait SystemExecutor: Send + Sync {
|
|
fn kind(&self) -> ExecutorKind;
|
|
fn init(&mut self, schedule: &SystemSchedule);
|
|
fn run(
|
|
&mut self,
|
|
schedule: &mut SystemSchedule,
|
|
world: &mut World,
|
|
skip_systems: Option<&FixedBitSet>,
|
|
);
|
|
fn set_apply_final_deferred(&mut self, value: bool);
|
|
}
|
|
|
|
/// Specifies how a [`Schedule`](super::Schedule) will be run.
|
|
///
|
|
/// The default depends on the target platform:
|
|
/// - [`SingleThreaded`](ExecutorKind::SingleThreaded) on WASM.
|
|
/// - [`MultiThreaded`](ExecutorKind::MultiThreaded) everywhere else.
|
|
#[derive(PartialEq, Eq, Default, Debug, Copy, Clone)]
|
|
pub enum ExecutorKind {
|
|
/// Runs the schedule using a single thread.
|
|
///
|
|
/// Useful if you're dealing with a single-threaded environment, saving your threads for
|
|
/// other things, or just trying minimize overhead.
|
|
#[cfg_attr(any(target_arch = "wasm32", not(feature = "multi-threaded")), default)]
|
|
SingleThreaded,
|
|
/// Like [`SingleThreaded`](ExecutorKind::SingleThreaded) but calls [`apply_deferred`](crate::system::System::apply_deferred)
|
|
/// immediately after running each system.
|
|
Simple,
|
|
/// Runs the schedule using a thread pool. Non-conflicting systems can run in parallel.
|
|
#[cfg_attr(all(not(target_arch = "wasm32"), feature = "multi-threaded"), default)]
|
|
MultiThreaded,
|
|
}
|
|
|
|
/// Holds systems and conditions of a [`Schedule`](super::Schedule) sorted in topological order
|
|
/// (along with dependency information for multi-threaded execution).
|
|
///
|
|
/// Since the arrays are sorted in the same order, elements are referenced by their index.
|
|
/// [`FixedBitSet`] is used as a smaller, more efficient substitute of `HashSet<usize>`.
|
|
#[derive(Default)]
|
|
pub struct SystemSchedule {
|
|
/// List of system node ids.
|
|
pub(super) system_ids: Vec<NodeId>,
|
|
/// Indexed by system node id.
|
|
pub(super) systems: Vec<BoxedSystem>,
|
|
/// Indexed by system node id.
|
|
pub(super) system_conditions: Vec<Vec<BoxedCondition>>,
|
|
/// Indexed by system node id.
|
|
pub(super) system_dependencies: Vec<usize>,
|
|
/// Indexed by system node id.
|
|
pub(super) system_dependents: Vec<Vec<usize>>,
|
|
/// Indexed by system node id.
|
|
pub(super) sets_with_conditions_of_systems: Vec<FixedBitSet>,
|
|
/// List of system set node ids.
|
|
pub(super) set_ids: Vec<NodeId>,
|
|
/// Indexed by system set node id.
|
|
pub(super) set_conditions: Vec<Vec<BoxedCondition>>,
|
|
/// Indexed by system set node id.
|
|
pub(super) systems_in_sets_with_conditions: Vec<FixedBitSet>,
|
|
}
|
|
|
|
impl SystemSchedule {
|
|
/// Creates an empty [`SystemSchedule`].
|
|
pub const fn new() -> Self {
|
|
Self {
|
|
systems: Vec::new(),
|
|
system_conditions: Vec::new(),
|
|
set_conditions: Vec::new(),
|
|
system_ids: Vec::new(),
|
|
set_ids: Vec::new(),
|
|
system_dependencies: Vec::new(),
|
|
system_dependents: Vec::new(),
|
|
sets_with_conditions_of_systems: Vec::new(),
|
|
systems_in_sets_with_conditions: Vec::new(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Instructs the executor to call [`System::apply_deferred`](crate::system::System::apply_deferred)
|
|
/// on the systems that have run but not applied their [`Deferred`](crate::system::Deferred) system parameters
|
|
/// (like [`Commands`](crate::prelude::Commands)) or other system buffers.
|
|
///
|
|
/// ## Scheduling
|
|
///
|
|
/// `apply_deferred` systems are scheduled *by default*
|
|
/// - later in the same schedule run (for example, if a system with `Commands` param
|
|
/// is scheduled in `Update`, all the changes will be visible in `PostUpdate`)
|
|
/// - between systems with dependencies if the dependency
|
|
/// [has deferred buffers](crate::system::System::has_deferred)
|
|
/// (if system `bar` directly or indirectly depends on `foo`, and `foo` uses `Commands` param,
|
|
/// changes to the world in `foo` will be visible in `bar`)
|
|
///
|
|
/// ## Notes
|
|
/// - This function (currently) does nothing if it's called manually or wrapped inside a [`PipeSystem`](crate::system::PipeSystem).
|
|
/// - Modifying a [`Schedule`](super::Schedule) may change the order buffers are applied.
|
|
#[doc(alias = "apply_system_buffers")]
|
|
#[allow(unused_variables)]
|
|
pub fn apply_deferred(world: &mut World) {}
|
|
|
|
/// Returns `true` if the [`System`](crate::system::System) is an instance of [`apply_deferred`].
|
|
pub(super) fn is_apply_deferred(system: &BoxedSystem) -> bool {
|
|
use crate::system::IntoSystem;
|
|
// deref to use `System::type_id` instead of `Any::type_id`
|
|
system.as_ref().type_id() == apply_deferred.system_type_id()
|
|
}
|
|
|
|
/// These functions hide the bottom of the callstack from `RUST_BACKTRACE=1` (assuming the default panic handler is used).
|
|
/// The full callstack will still be visible with `RUST_BACKTRACE=full`.
|
|
/// They are specialized for `System::run` & co instead of being generic over closures because this avoids an
|
|
/// extra frame in the backtrace.
|
|
///
|
|
/// This is reliant on undocumented behavior in Rust's default panic handler, which checks the call stack for symbols
|
|
/// containing the string `__rust_begin_short_backtrace` in their mangled name.
|
|
mod __rust_begin_short_backtrace {
|
|
use std::hint::black_box;
|
|
|
|
use crate::{
|
|
system::{ReadOnlySystem, System},
|
|
world::{unsafe_world_cell::UnsafeWorldCell, World},
|
|
};
|
|
|
|
/// # Safety
|
|
/// See `System::run_unsafe`.
|
|
#[inline(never)]
|
|
pub(super) unsafe fn run_unsafe(
|
|
system: &mut dyn System<In = (), Out = ()>,
|
|
world: UnsafeWorldCell,
|
|
) {
|
|
system.run_unsafe((), world);
|
|
black_box(());
|
|
}
|
|
|
|
/// # Safety
|
|
/// See `ReadOnlySystem::run_unsafe`.
|
|
#[inline(never)]
|
|
pub(super) unsafe fn readonly_run_unsafe<O: 'static>(
|
|
system: &mut dyn ReadOnlySystem<In = (), Out = O>,
|
|
world: UnsafeWorldCell,
|
|
) -> O {
|
|
black_box(system.run_unsafe((), world))
|
|
}
|
|
|
|
#[inline(never)]
|
|
pub(super) fn run(system: &mut dyn System<In = (), Out = ()>, world: &mut World) {
|
|
system.run((), world);
|
|
black_box(());
|
|
}
|
|
|
|
#[inline(never)]
|
|
pub(super) fn readonly_run<O: 'static>(
|
|
system: &mut dyn ReadOnlySystem<In = (), Out = O>,
|
|
world: &mut World,
|
|
) -> O {
|
|
black_box(system.run((), world))
|
|
}
|
|
}
|