# 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>
147 lines
5.0 KiB
Rust
147 lines
5.0 KiB
Rust
#[cfg(feature = "trace")]
|
|
use bevy_utils::tracing::info_span;
|
|
use fixedbitset::FixedBitSet;
|
|
use std::panic::AssertUnwindSafe;
|
|
|
|
use crate::{
|
|
schedule::{
|
|
executor::is_apply_deferred, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule,
|
|
},
|
|
world::World,
|
|
};
|
|
|
|
use super::__rust_begin_short_backtrace;
|
|
|
|
/// A variant of [`SingleThreadedExecutor`](crate::schedule::SingleThreadedExecutor) that calls
|
|
/// [`apply_deferred`](crate::system::System::apply_deferred) immediately after running each system.
|
|
#[derive(Default)]
|
|
pub struct SimpleExecutor {
|
|
/// Systems sets whose conditions have been evaluated.
|
|
evaluated_sets: FixedBitSet,
|
|
/// Systems that have run or been skipped.
|
|
completed_systems: FixedBitSet,
|
|
}
|
|
|
|
impl SystemExecutor for SimpleExecutor {
|
|
fn kind(&self) -> ExecutorKind {
|
|
ExecutorKind::Simple
|
|
}
|
|
|
|
fn init(&mut self, schedule: &SystemSchedule) {
|
|
let sys_count = schedule.system_ids.len();
|
|
let set_count = schedule.set_ids.len();
|
|
self.evaluated_sets = FixedBitSet::with_capacity(set_count);
|
|
self.completed_systems = FixedBitSet::with_capacity(sys_count);
|
|
}
|
|
|
|
fn run(
|
|
&mut self,
|
|
schedule: &mut SystemSchedule,
|
|
world: &mut World,
|
|
_skip_systems: Option<&FixedBitSet>,
|
|
) {
|
|
// If stepping is enabled, make sure we skip those systems that should
|
|
// not be run.
|
|
#[cfg(feature = "bevy_debug_stepping")]
|
|
if let Some(skipped_systems) = _skip_systems {
|
|
// mark skipped systems as completed
|
|
self.completed_systems |= skipped_systems;
|
|
}
|
|
|
|
for system_index in 0..schedule.systems.len() {
|
|
#[cfg(feature = "trace")]
|
|
let name = schedule.systems[system_index].name();
|
|
#[cfg(feature = "trace")]
|
|
let should_run_span = info_span!("check_conditions", name = &*name).entered();
|
|
|
|
let mut should_run = !self.completed_systems.contains(system_index);
|
|
for set_idx in schedule.sets_with_conditions_of_systems[system_index].ones() {
|
|
if self.evaluated_sets.contains(set_idx) {
|
|
continue;
|
|
}
|
|
|
|
// evaluate system set's conditions
|
|
let set_conditions_met =
|
|
evaluate_and_fold_conditions(&mut schedule.set_conditions[set_idx], world);
|
|
|
|
if !set_conditions_met {
|
|
self.completed_systems
|
|
.union_with(&schedule.systems_in_sets_with_conditions[set_idx]);
|
|
}
|
|
|
|
should_run &= set_conditions_met;
|
|
self.evaluated_sets.insert(set_idx);
|
|
}
|
|
|
|
// evaluate system's conditions
|
|
let system_conditions_met =
|
|
evaluate_and_fold_conditions(&mut schedule.system_conditions[system_index], world);
|
|
|
|
should_run &= system_conditions_met;
|
|
|
|
#[cfg(feature = "trace")]
|
|
should_run_span.exit();
|
|
|
|
// system has either been skipped or will run
|
|
self.completed_systems.insert(system_index);
|
|
|
|
if !should_run {
|
|
continue;
|
|
}
|
|
|
|
let system = &mut schedule.systems[system_index];
|
|
if is_apply_deferred(system) {
|
|
continue;
|
|
}
|
|
|
|
let res = std::panic::catch_unwind(AssertUnwindSafe(|| {
|
|
__rust_begin_short_backtrace::run(&mut **system, world);
|
|
}));
|
|
if let Err(payload) = res {
|
|
eprintln!("Encountered a panic in system `{}`!", &*system.name());
|
|
std::panic::resume_unwind(payload);
|
|
}
|
|
}
|
|
|
|
self.evaluated_sets.clear();
|
|
self.completed_systems.clear();
|
|
}
|
|
|
|
fn set_apply_final_deferred(&mut self, _: bool) {
|
|
// do nothing. simple executor does not do a final sync
|
|
}
|
|
}
|
|
|
|
impl SimpleExecutor {
|
|
/// Creates a new simple executor for use in a [`Schedule`](crate::schedule::Schedule).
|
|
/// This calls each system in order and immediately calls [`System::apply_deferred`](crate::system::System::apply_deferred).
|
|
pub const fn new() -> Self {
|
|
Self {
|
|
evaluated_sets: FixedBitSet::new(),
|
|
completed_systems: FixedBitSet::new(),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn evaluate_and_fold_conditions(conditions: &mut [BoxedCondition], world: &mut World) -> bool {
|
|
// not short-circuiting is intentional
|
|
#[allow(clippy::unnecessary_fold)]
|
|
conditions
|
|
.iter_mut()
|
|
.map(|condition| __rust_begin_short_backtrace::readonly_run(&mut **condition, world))
|
|
.fold(true, |acc, res| acc && res)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
#[test]
|
|
fn skip_automatic_sync_points() {
|
|
// Schedules automatically insert apply_deferred systems, but these should
|
|
// not be executed as they only serve as markers and are not initialized
|
|
use crate::prelude::*;
|
|
let mut sched = Schedule::default();
|
|
sched.set_executor_kind(ExecutorKind::Simple);
|
|
sched.add_systems((|_: Commands| (), || ()).chain());
|
|
let mut world = World::new();
|
|
sched.run(&mut world);
|
|
}
|