# Objective - Many strings in bevy_ecs are created but only used for debug: system name, component name, ... - Those strings make a significant part of the final binary and are no use in a released game ## Solution - Use [`strings`](https://linux.die.net/man/1/strings) to find ... strings in a binary - Try to find where they come from - Many are made from `type_name::<T>()` and only used in error / debug messages - Add a new structure `DebugName` that holds no value if `debug` feature is disabled - Replace `core::any::type_name::<T>()` by `DebugName::type_name::<T>()` ## Testing Measurements were taken without the new feature being enabled by default, to help with commands ### File Size I tried building the `breakout` example with `cargo run --release --example breakout` |`debug` enabled|`debug` disabled| |-|-| |81621776 B|77735728B| |77.84MB|74.13MB| ### Compilation time `hyperfine --min-runs 15 --prepare "cargo clean && sleep 5" 'RUSTC_WRAPPER="" cargo build --release --example breakout' 'RUSTC_WRAPPER="" cargo build --release --example breakout --features debug'` ``` breakout' 'RUSTC_WRAPPER="" cargo build --release --example breakout --features debug' Benchmark 1: RUSTC_WRAPPER="" cargo build --release --example breakout Time (mean ± σ): 84.856 s ± 3.565 s [User: 1093.817 s, System: 32.547 s] Range (min … max): 78.038 s … 89.214 s 15 runs Benchmark 2: RUSTC_WRAPPER="" cargo build --release --example breakout --features debug Time (mean ± σ): 92.303 s ± 2.466 s [User: 1193.443 s, System: 33.803 s] Range (min … max): 90.619 s … 99.684 s 15 runs Summary RUSTC_WRAPPER="" cargo build --release --example breakout ran 1.09 ± 0.05 times faster than RUSTC_WRAPPER="" cargo build --release --example breakout --features debug ```
252 lines
8.3 KiB
Rust
252 lines
8.3 KiB
Rust
#![expect(deprecated, reason = "Everything here is deprecated")]
|
|
|
|
use core::panic::AssertUnwindSafe;
|
|
use fixedbitset::FixedBitSet;
|
|
|
|
#[cfg(feature = "trace")]
|
|
use tracing::info_span;
|
|
|
|
#[cfg(feature = "std")]
|
|
use std::eprintln;
|
|
|
|
use crate::{
|
|
error::{ErrorContext, ErrorHandler},
|
|
schedule::{
|
|
executor::is_apply_deferred, ConditionWithAccess, ExecutorKind, SystemExecutor,
|
|
SystemSchedule,
|
|
},
|
|
world::World,
|
|
};
|
|
#[cfg(feature = "hotpatching")]
|
|
use crate::{event::Events, HotPatched};
|
|
|
|
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)]
|
|
#[deprecated(
|
|
since = "0.17.0",
|
|
note = "Use SingleThreadedExecutor instead. See https://github.com/bevyengine/bevy/issues/18453 for motivation."
|
|
)]
|
|
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>,
|
|
error_handler: ErrorHandler,
|
|
) {
|
|
// 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;
|
|
}
|
|
|
|
#[cfg(feature = "hotpatching")]
|
|
let should_update_hotpatch = !world
|
|
.get_resource::<Events<HotPatched>>()
|
|
.map(Events::is_empty)
|
|
.unwrap_or(true);
|
|
|
|
for system_index in 0..schedule.systems.len() {
|
|
#[cfg(feature = "trace")]
|
|
let name = schedule.systems[system_index].system.name();
|
|
#[cfg(feature = "trace")]
|
|
let should_run_span = info_span!("check_conditions", name = name.as_string()).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,
|
|
error_handler,
|
|
);
|
|
|
|
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,
|
|
error_handler,
|
|
);
|
|
|
|
should_run &= system_conditions_met;
|
|
|
|
let system = &mut schedule.systems[system_index].system;
|
|
if should_run {
|
|
let valid_params = match system.validate_param(world) {
|
|
Ok(()) => true,
|
|
Err(e) => {
|
|
if !e.skipped {
|
|
error_handler(
|
|
e.into(),
|
|
ErrorContext::System {
|
|
name: system.name(),
|
|
last_run: system.get_last_run(),
|
|
},
|
|
);
|
|
}
|
|
false
|
|
}
|
|
};
|
|
should_run &= valid_params;
|
|
}
|
|
|
|
#[cfg(feature = "trace")]
|
|
should_run_span.exit();
|
|
|
|
#[cfg(feature = "hotpatching")]
|
|
if should_update_hotpatch {
|
|
system.refresh_hotpatch();
|
|
}
|
|
|
|
// system has either been skipped or will run
|
|
self.completed_systems.insert(system_index);
|
|
|
|
if !should_run {
|
|
continue;
|
|
}
|
|
|
|
if is_apply_deferred(system) {
|
|
continue;
|
|
}
|
|
|
|
let f = AssertUnwindSafe(|| {
|
|
if let Err(err) = __rust_begin_short_backtrace::run(system, world) {
|
|
error_handler(
|
|
err,
|
|
ErrorContext::System {
|
|
name: system.name(),
|
|
last_run: system.get_last_run(),
|
|
},
|
|
);
|
|
}
|
|
});
|
|
|
|
#[cfg(feature = "std")]
|
|
#[expect(clippy::print_stderr, reason = "Allowed behind `std` feature gate.")]
|
|
{
|
|
if let Err(payload) = std::panic::catch_unwind(f) {
|
|
eprintln!("Encountered a panic in system `{}`!", system.name());
|
|
std::panic::resume_unwind(payload);
|
|
}
|
|
}
|
|
|
|
#[cfg(not(feature = "std"))]
|
|
{
|
|
(f)();
|
|
}
|
|
}
|
|
|
|
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).
|
|
pub const fn new() -> Self {
|
|
Self {
|
|
evaluated_sets: FixedBitSet::new(),
|
|
completed_systems: FixedBitSet::new(),
|
|
}
|
|
}
|
|
}
|
|
#[deprecated(
|
|
since = "0.17.0",
|
|
note = "Use SingleThreadedExecutor instead. See https://github.com/bevyengine/bevy/issues/18453 for motivation."
|
|
)]
|
|
fn evaluate_and_fold_conditions(
|
|
conditions: &mut [ConditionWithAccess],
|
|
world: &mut World,
|
|
error_handler: ErrorHandler,
|
|
) -> bool {
|
|
#[cfg(feature = "hotpatching")]
|
|
let should_update_hotpatch = !world
|
|
.get_resource::<Events<HotPatched>>()
|
|
.map(Events::is_empty)
|
|
.unwrap_or(true);
|
|
|
|
#[expect(
|
|
clippy::unnecessary_fold,
|
|
reason = "Short-circuiting here would prevent conditions from mutating their own state as needed."
|
|
)]
|
|
conditions
|
|
.iter_mut()
|
|
.map(|ConditionWithAccess { condition, .. }| {
|
|
match condition.validate_param(world) {
|
|
Ok(()) => (),
|
|
Err(e) => {
|
|
if !e.skipped {
|
|
error_handler(
|
|
e.into(),
|
|
ErrorContext::System {
|
|
name: condition.name(),
|
|
last_run: condition.get_last_run(),
|
|
},
|
|
);
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
#[cfg(feature = "hotpatching")]
|
|
if should_update_hotpatch {
|
|
condition.refresh_hotpatch();
|
|
}
|
|
__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 ApplyDeferred 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);
|
|
}
|