diff --git a/benches/benches/bevy_ecs/scheduling/schedule.rs b/benches/benches/bevy_ecs/scheduling/schedule.rs index 9844461d399ce..d7e78dd4a6379 100644 --- a/benches/benches/bevy_ecs/scheduling/schedule.rs +++ b/benches/benches/bevy_ecs/scheduling/schedule.rs @@ -136,10 +136,5 @@ pub fn empty_schedule_run(criterion: &mut Criterion) { bencher.iter(|| schedule.run(app.world_mut())); }); - let mut schedule = Schedule::default(); - schedule.set_executor_kind(bevy_ecs::schedule::ExecutorKind::Simple); - group.bench_function("Simple", |bencher| { - bencher.iter(|| schedule.run(app.world_mut())); - }); group.finish(); } diff --git a/crates/bevy_ecs/src/schedule/executor/mod.rs b/crates/bevy_ecs/src/schedule/executor/mod.rs index e384680cf4e7a..e5f1afa92e451 100644 --- a/crates/bevy_ecs/src/schedule/executor/mod.rs +++ b/crates/bevy_ecs/src/schedule/executor/mod.rs @@ -1,12 +1,11 @@ #[cfg(feature = "std")] mod multi_threaded; -mod simple; mod single_threaded; use alloc::{borrow::Cow, vec, vec::Vec}; use core::any::TypeId; -pub use self::{simple::SimpleExecutor, single_threaded::SingleThreadedExecutor}; +pub use self::single_threaded::SingleThreadedExecutor; #[cfg(feature = "std")] pub use self::multi_threaded::{MainThreadExecutor, MultiThreadedExecutor}; @@ -51,9 +50,6 @@ pub enum ExecutorKind { /// 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(feature = "std")] #[cfg_attr(all(not(target_arch = "wasm32"), feature = "multi_threaded"), default)] @@ -324,11 +320,8 @@ mod tests { #[derive(Component)] struct TestComponent; - const EXECUTORS: [ExecutorKind; 3] = [ - ExecutorKind::Simple, - ExecutorKind::SingleThreaded, - ExecutorKind::MultiThreaded, - ]; + const EXECUTORS: [ExecutorKind; 2] = + [ExecutorKind::SingleThreaded, ExecutorKind::MultiThreaded]; #[derive(Resource, Default)] struct TestState { @@ -376,17 +369,6 @@ mod tests { fn look_for_missing_resource(_res: Res) {} - #[test] - #[should_panic] - fn missing_resource_panics_simple() { - let mut world = World::new(); - let mut schedule = Schedule::default(); - - schedule.set_executor_kind(ExecutorKind::Simple); - schedule.add_systems(look_for_missing_resource); - schedule.run(&mut world); - } - #[test] #[should_panic] fn missing_resource_panics_single_threaded() { diff --git a/crates/bevy_ecs/src/schedule/executor/simple.rs b/crates/bevy_ecs/src/schedule/executor/simple.rs deleted file mode 100644 index a237a356de688..0000000000000 --- a/crates/bevy_ecs/src/schedule/executor/simple.rs +++ /dev/null @@ -1,210 +0,0 @@ -use core::panic::AssertUnwindSafe; -use fixedbitset::FixedBitSet; - -#[cfg(feature = "trace")] -use tracing::info_span; - -#[cfg(feature = "std")] -use std::eprintln; - -use crate::{ - error::{default_error_handler, BevyError, ErrorContext}, - 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>, - error_handler: fn(BevyError, ErrorContext), - ) { - // 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; - - let system = &mut schedule.systems[system_index]; - 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(); - - // 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(), - } - } -} - -fn evaluate_and_fold_conditions(conditions: &mut [BoxedCondition], world: &mut World) -> bool { - let error_handler = default_error_handler(); - - #[expect( - clippy::unnecessary_fold, - reason = "Short-circuiting here would prevent conditions from mutating their own state as needed." - )] - conditions - .iter_mut() - .map(|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; - } - } - __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); -} diff --git a/crates/bevy_ecs/src/schedule/mod.rs b/crates/bevy_ecs/src/schedule/mod.rs index aeaf8e3929ce6..58ef87c7d156c 100644 --- a/crates/bevy_ecs/src/schedule/mod.rs +++ b/crates/bevy_ecs/src/schedule/mod.rs @@ -1235,12 +1235,6 @@ mod tests { }; } - /// verify the [`SimpleExecutor`] supports stepping - #[test] - fn simple_executor() { - assert_executor_supports_stepping!(ExecutorKind::Simple); - } - /// verify the [`SingleThreadedExecutor`] supports stepping #[test] fn single_threaded_executor() { diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 11d42020f3eb0..e0f12f1928300 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -218,7 +218,6 @@ impl Schedules { fn make_executor(kind: ExecutorKind) -> Box { match kind { - ExecutorKind::Simple => Box::new(SimpleExecutor::new()), ExecutorKind::SingleThreaded => Box::new(SingleThreadedExecutor::new()), #[cfg(feature = "std")] ExecutorKind::MultiThreaded => Box::new(MultiThreadedExecutor::new()), diff --git a/release-content/migration-guides/removed_simple_executor.md b/release-content/migration-guides/removed_simple_executor.md new file mode 100644 index 0000000000000..7aaea4d3c3533 --- /dev/null +++ b/release-content/migration-guides/removed_simple_executor.md @@ -0,0 +1,15 @@ +--- +title: Removed Simple Executor +pull_requests: [18741] +--- + +Bevy has removed `SimpleExecutor`, one of the `SystemExecutor`s in Bevy alongside `SingleThreadedExecutor` and `MultiThreadedExecutor` (which aren't going anywhere any time soon). +The `MultiThreadedExecutor` is great at large schedules and async heavy work, and the `SingleThreadedExecutor` is good at smaller schedules or schedules that have fewer parallelizable systems. +So what was `SimpleExecutor` good at? Not much. That's why it was removed. Removing it reduced some maintenance and consistency burdons on maintainers, allowing them to focus on more exciting features! + +If you were using `SimpleExecutor`, consider upgrading to `SingleThreadedExecutor` instead, or try `MultiThreadedExecutor` if if fits the schedule. +It's worth mentioning that `SimpleExecutor` ran deferred commands inbetween *each* system, regardless of it it was needed. +The other executors are more efficient about this, but that means they need extra information about when to run those commands. +In most schedules, that information comes from the contents and ordering of systems, via `before`, `after`, `chain`, etc. +If a schedule that was previously using `SimpleExecutor` still needs commands from one system to be applied before another system runs, +make sure that ordering is enforced explicitly by these methods, rather than implicitly by the order of `add_systems`.