Skip to content
51 changes: 51 additions & 0 deletions crates/bevy_ecs/src/schedule/executor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,54 @@ pub(super) fn is_apply_deferred(system: &BoxedSystem) -> bool {
// 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))
}
}
13 changes: 10 additions & 3 deletions crates/bevy_ecs/src/schedule/executor/multi_threaded.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ use crate::{

use crate as bevy_ecs;

use super::__rust_begin_short_backtrace;

/// Borrowed data used by the [`MultiThreadedExecutor`].
struct Environment<'env, 'sys> {
executor: &'env MultiThreadedExecutor,
Expand Down Expand Up @@ -618,7 +620,12 @@ impl ExecutorState {
// - The caller ensures that we have permission to
// access the world data used by the system.
// - `update_archetype_component_access` has been called.
unsafe { system.run_unsafe((), context.environment.world_cell) };
unsafe {
__rust_begin_short_backtrace::run_unsafe(
&mut **system,
context.environment.world_cell,
);
};
}));
context.system_completed(system_index, res, system);
};
Expand Down Expand Up @@ -668,7 +675,7 @@ impl ExecutorState {
let res = std::panic::catch_unwind(AssertUnwindSafe(|| {
#[cfg(feature = "trace")]
let _span = system_span.enter();
system.run((), world);
__rust_begin_short_backtrace::run(&mut **system, world);
}));
context.system_completed(system_index, res, system);
};
Expand Down Expand Up @@ -780,7 +787,7 @@ unsafe fn evaluate_and_fold_conditions(
.map(|condition| {
// SAFETY: The caller ensures that `world` has permission to
// access any data required by the condition.
unsafe { condition.run_unsafe((), world) }
unsafe { __rust_begin_short_backtrace::readonly_run_unsafe(&mut **condition, world) }
})
.fold(true, |acc, res| acc && res)
}
Expand Down
6 changes: 4 additions & 2 deletions crates/bevy_ecs/src/schedule/executor/simple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ use crate::{
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)]
Expand Down Expand Up @@ -93,7 +95,7 @@ impl SystemExecutor for SimpleExecutor {
}

let res = std::panic::catch_unwind(AssertUnwindSafe(|| {
system.run((), world);
__rust_begin_short_backtrace::run(&mut **system, world);
}));
if let Err(payload) = res {
eprintln!("Encountered a panic in system `{}`!", &*system.name());
Expand Down Expand Up @@ -126,7 +128,7 @@ fn evaluate_and_fold_conditions(conditions: &mut [BoxedCondition], world: &mut W
#[allow(clippy::unnecessary_fold)]
conditions
.iter_mut()
.map(|condition| condition.run((), world))
.map(|condition| __rust_begin_short_backtrace::readonly_run(&mut **condition, world))
.fold(true, |acc, res| acc && res)
}

Expand Down
8 changes: 5 additions & 3 deletions crates/bevy_ecs/src/schedule/executor/single_threaded.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ use crate::{
world::World,
};

use super::__rust_begin_short_backtrace;

/// Runs the schedule using a single thread.
///
/// Useful if you're dealing with a single-threaded environment, saving your threads for
Expand Down Expand Up @@ -101,14 +103,14 @@ impl SystemExecutor for SingleThreadedExecutor {

let res = std::panic::catch_unwind(AssertUnwindSafe(|| {
if system.is_exclusive() {
system.run((), world);
__rust_begin_short_backtrace::run(&mut **system, world);
} else {
// Use run_unsafe to avoid immediately applying deferred buffers
let world = world.as_unsafe_world_cell();
system.update_archetype_component_access(world);
// SAFETY: We have exclusive, single-threaded access to the world and
// update_archetype_component_access is being called immediately before this.
unsafe { system.run_unsafe((), world) };
unsafe { __rust_begin_short_backtrace::run_unsafe(&mut **system, world) };
}
}));
if let Err(payload) = res {
Expand Down Expand Up @@ -158,6 +160,6 @@ fn evaluate_and_fold_conditions(conditions: &mut [BoxedCondition], world: &mut W
#[allow(clippy::unnecessary_fold)]
conditions
.iter_mut()
.map(|condition| condition.run((), world))
.map(|condition| __rust_begin_short_backtrace::readonly_run(&mut **condition, world))
.fold(true, |acc, res| acc && res)
}
25 changes: 15 additions & 10 deletions crates/bevy_render/src/pipelined_rendering.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use async_channel::{Receiver, Sender};

use bevy_app::{App, AppLabel, Main, Plugin, SubApp};
use bevy_app::{App, AppExit, AppLabel, Main, Plugin, SubApp};
use bevy_ecs::{
schedule::MainThreadExecutor,
system::Resource,
Expand Down Expand Up @@ -45,10 +45,11 @@ impl RenderAppChannels {
}

/// Receive the `render_app` from the rendering thread.
pub async fn recv(&mut self) -> SubApp {
let render_app = self.render_to_app_receiver.recv().await.unwrap();
/// Return `None` if the render thread has panicked.
pub async fn recv(&mut self) -> Option<SubApp> {
let render_app = self.render_to_app_receiver.recv().await.ok()?;
self.render_app_in_render_thread = false;
render_app
Some(render_app)
}
}

Expand Down Expand Up @@ -180,16 +181,20 @@ fn update_rendering(app_world: &mut World, _sub_app: &mut App) {
world.resource_scope(|world, mut render_channels: Mut<RenderAppChannels>| {
// we use a scope here to run any main thread tasks that the render world still needs to run
// while we wait for the render world to be received.
let mut render_app = ComputeTaskPool::get()
if let Some(mut render_app) = ComputeTaskPool::get()
.scope_with_executor(true, Some(&*main_thread_executor.0), |s| {
s.spawn(async { render_channels.recv().await });
})
.pop()
.unwrap();

render_app.extract(world);

render_channels.send_blocking(render_app);
.unwrap()
{
render_app.extract(world);

render_channels.send_blocking(render_app);
} else {
// Renderer thread panicked
world.send_event(AppExit);
}
});
});
}