Path: blob/main/crates/bevy_ecs/src/schedule/executor/mod.rs
6849 views
#[cfg(feature = "std")]1mod multi_threaded;2mod single_threaded;34use alloc::{vec, vec::Vec};5use bevy_utils::prelude::DebugName;6use core::any::TypeId;78pub use self::single_threaded::SingleThreadedExecutor;910#[cfg(feature = "std")]11pub use self::multi_threaded::{MainThreadExecutor, MultiThreadedExecutor};1213use fixedbitset::FixedBitSet;1415use crate::{16component::{CheckChangeTicks, Tick},17error::{BevyError, ErrorContext, Result},18prelude::{IntoSystemSet, SystemSet},19query::FilteredAccessSet,20schedule::{21ConditionWithAccess, InternedSystemSet, SystemKey, SystemSetKey, SystemTypeSet,22SystemWithAccess,23},24system::{RunSystemError, System, SystemIn, SystemParamValidationError, SystemStateFlags},25world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World},26};2728/// Types that can run a [`SystemSchedule`] on a [`World`].29pub(super) trait SystemExecutor: Send + Sync {30fn kind(&self) -> ExecutorKind;31fn init(&mut self, schedule: &SystemSchedule);32fn run(33&mut self,34schedule: &mut SystemSchedule,35world: &mut World,36skip_systems: Option<&FixedBitSet>,37error_handler: fn(BevyError, ErrorContext),38);39fn set_apply_final_deferred(&mut self, value: bool);40}4142/// Specifies how a [`Schedule`](super::Schedule) will be run.43///44/// The default depends on the target platform:45/// - [`SingleThreaded`](ExecutorKind::SingleThreaded) on Wasm.46/// - [`MultiThreaded`](ExecutorKind::MultiThreaded) everywhere else.47#[derive(PartialEq, Eq, Default, Debug, Copy, Clone)]48pub enum ExecutorKind {49/// Runs the schedule using a single thread.50///51/// Useful if you're dealing with a single-threaded environment, saving your threads for52/// other things, or just trying minimize overhead.53#[cfg_attr(54any(55target_arch = "wasm32",56not(feature = "std"),57not(feature = "multi_threaded")58),59default60)]61SingleThreaded,62/// Runs the schedule using a thread pool. Non-conflicting systems can run in parallel.63#[cfg(feature = "std")]64#[cfg_attr(all(not(target_arch = "wasm32"), feature = "multi_threaded"), default)]65MultiThreaded,66}6768/// Holds systems and conditions of a [`Schedule`](super::Schedule) sorted in topological order69/// (along with dependency information for `multi_threaded` execution).70///71/// Since the arrays are sorted in the same order, elements are referenced by their index.72/// [`FixedBitSet`] is used as a smaller, more efficient substitute of `HashSet<usize>`.73#[derive(Default)]74pub struct SystemSchedule {75/// List of system node ids.76pub(super) system_ids: Vec<SystemKey>,77/// Indexed by system node id.78pub(super) systems: Vec<SystemWithAccess>,79/// Indexed by system node id.80pub(super) system_conditions: Vec<Vec<ConditionWithAccess>>,81/// Indexed by system node id.82/// Number of systems that the system immediately depends on.83#[cfg_attr(84not(feature = "std"),85expect(dead_code, reason = "currently only used with the std feature")86)]87pub(super) system_dependencies: Vec<usize>,88/// Indexed by system node id.89/// List of systems that immediately depend on the system.90#[cfg_attr(91not(feature = "std"),92expect(dead_code, reason = "currently only used with the std feature")93)]94pub(super) system_dependents: Vec<Vec<usize>>,95/// Indexed by system node id.96/// List of sets containing the system that have conditions97pub(super) sets_with_conditions_of_systems: Vec<FixedBitSet>,98/// List of system set node ids.99pub(super) set_ids: Vec<SystemSetKey>,100/// Indexed by system set node id.101pub(super) set_conditions: Vec<Vec<ConditionWithAccess>>,102/// Indexed by system set node id.103/// List of systems that are in sets that have conditions.104///105/// If a set doesn't run because of its conditions, this is used to skip all systems in it.106pub(super) systems_in_sets_with_conditions: Vec<FixedBitSet>,107}108109impl SystemSchedule {110/// Creates an empty [`SystemSchedule`].111pub const fn new() -> Self {112Self {113systems: Vec::new(),114system_conditions: Vec::new(),115set_conditions: Vec::new(),116system_ids: Vec::new(),117set_ids: Vec::new(),118system_dependencies: Vec::new(),119system_dependents: Vec::new(),120sets_with_conditions_of_systems: Vec::new(),121systems_in_sets_with_conditions: Vec::new(),122}123}124}125126/// A special [`System`] that instructs the executor to call127/// [`System::apply_deferred`] on the systems that have run but not applied128/// their [`Deferred`] system parameters (like [`Commands`]) or other system buffers.129///130/// ## Scheduling131///132/// `ApplyDeferred` systems are scheduled *by default*133/// - later in the same schedule run (for example, if a system with `Commands` param134/// is scheduled in `Update`, all the changes will be visible in `PostUpdate`)135/// - between systems with dependencies if the dependency [has deferred buffers]136/// (if system `bar` directly or indirectly depends on `foo`, and `foo` uses137/// `Commands` param, changes to the world in `foo` will be visible in `bar`)138///139/// ## Notes140/// - This system (currently) does nothing if it's called manually or wrapped141/// inside a [`PipeSystem`].142/// - Modifying a [`Schedule`] may change the order buffers are applied.143///144/// [`System::apply_deferred`]: crate::system::System::apply_deferred145/// [`Deferred`]: crate::system::Deferred146/// [`Commands`]: crate::prelude::Commands147/// [has deferred buffers]: crate::system::System::has_deferred148/// [`PipeSystem`]: crate::system::PipeSystem149/// [`Schedule`]: super::Schedule150#[doc(alias = "apply_system_buffers")]151pub struct ApplyDeferred;152153/// Returns `true` if the [`System`] is an instance of [`ApplyDeferred`].154pub(super) fn is_apply_deferred(system: &dyn System<In = (), Out = ()>) -> bool {155system.type_id() == TypeId::of::<ApplyDeferred>()156}157158impl System for ApplyDeferred {159type In = ();160type Out = ();161162fn name(&self) -> DebugName {163DebugName::borrowed("bevy_ecs::apply_deferred")164}165166fn flags(&self) -> SystemStateFlags {167// non-send , exclusive , no deferred168SystemStateFlags::NON_SEND | SystemStateFlags::EXCLUSIVE169}170171unsafe fn run_unsafe(172&mut self,173_input: SystemIn<'_, Self>,174_world: UnsafeWorldCell,175) -> Result<Self::Out, RunSystemError> {176// This system does nothing on its own. The executor will apply deferred177// commands from other systems instead of running this system.178Ok(())179}180181#[cfg(feature = "hotpatching")]182#[inline]183fn refresh_hotpatch(&mut self) {}184185fn run(186&mut self,187_input: SystemIn<'_, Self>,188_world: &mut World,189) -> Result<Self::Out, RunSystemError> {190// This system does nothing on its own. The executor will apply deferred191// commands from other systems instead of running this system.192Ok(())193}194195fn apply_deferred(&mut self, _world: &mut World) {}196197fn queue_deferred(&mut self, _world: DeferredWorld) {}198199unsafe fn validate_param_unsafe(200&mut self,201_world: UnsafeWorldCell,202) -> Result<(), SystemParamValidationError> {203// This system is always valid to run because it doesn't do anything,204// and only used as a marker for the executor.205Ok(())206}207208fn initialize(&mut self, _world: &mut World) -> FilteredAccessSet {209FilteredAccessSet::new()210}211212fn check_change_tick(&mut self, _check: CheckChangeTicks) {}213214fn default_system_sets(&self) -> Vec<InternedSystemSet> {215vec![SystemTypeSet::<Self>::new().intern()]216}217218fn get_last_run(&self) -> Tick {219// This system is never run, so it has no last run tick.220Tick::MAX221}222223fn set_last_run(&mut self, _last_run: Tick) {}224}225226impl IntoSystemSet<()> for ApplyDeferred {227type Set = SystemTypeSet<Self>;228229fn into_system_set(self) -> Self::Set {230SystemTypeSet::<Self>::new()231}232}233234/// These functions hide the bottom of the callstack from `RUST_BACKTRACE=1` (assuming the default panic handler is used).235///236/// The full callstack will still be visible with `RUST_BACKTRACE=full`.237/// They are specialized for `System::run` & co instead of being generic over closures because this avoids an238/// extra frame in the backtrace.239///240/// This is reliant on undocumented behavior in Rust's default panic handler, which checks the call stack for symbols241/// containing the string `__rust_begin_short_backtrace` in their mangled name.242mod __rust_begin_short_backtrace {243use core::hint::black_box;244245#[cfg(feature = "std")]246use crate::world::unsafe_world_cell::UnsafeWorldCell;247use crate::{248error::Result,249system::{ReadOnlySystem, RunSystemError, ScheduleSystem},250world::World,251};252253/// # Safety254/// See `System::run_unsafe`.255// This is only used by `MultiThreadedExecutor`, and would be dead code without `std`.256#[cfg(feature = "std")]257#[inline(never)]258pub(super) unsafe fn run_unsafe(259system: &mut ScheduleSystem,260world: UnsafeWorldCell,261) -> Result<(), RunSystemError> {262let result = system.run_unsafe((), world);263// Call `black_box` to prevent this frame from being tail-call optimized away264black_box(());265result266}267268/// # Safety269/// See `ReadOnlySystem::run_unsafe`.270// This is only used by `MultiThreadedExecutor`, and would be dead code without `std`.271#[cfg(feature = "std")]272#[inline(never)]273pub(super) unsafe fn readonly_run_unsafe<O: 'static>(274system: &mut dyn ReadOnlySystem<In = (), Out = O>,275world: UnsafeWorldCell,276) -> Result<O, RunSystemError> {277// Call `black_box` to prevent this frame from being tail-call optimized away278black_box(system.run_unsafe((), world))279}280281#[cfg(feature = "std")]282#[inline(never)]283pub(super) fn run(284system: &mut ScheduleSystem,285world: &mut World,286) -> Result<(), RunSystemError> {287let result = system.run((), world);288// Call `black_box` to prevent this frame from being tail-call optimized away289black_box(());290result291}292293#[inline(never)]294pub(super) fn run_without_applying_deferred(295system: &mut ScheduleSystem,296world: &mut World,297) -> Result<(), RunSystemError> {298let result = system.run_without_applying_deferred((), world);299// Call `black_box` to prevent this frame from being tail-call optimized away300black_box(());301result302}303304#[inline(never)]305pub(super) fn readonly_run<O: 'static>(306system: &mut dyn ReadOnlySystem<In = (), Out = O>,307world: &mut World,308) -> Result<O, RunSystemError> {309// Call `black_box` to prevent this frame from being tail-call optimized away310black_box(system.run((), world))311}312}313314#[cfg(test)]315mod tests {316use crate::{317prelude::{Component, In, IntoSystem, Resource, Schedule},318schedule::ExecutorKind,319system::{Populated, Res, ResMut, Single},320world::World,321};322323#[derive(Component)]324struct TestComponent;325326const EXECUTORS: [ExecutorKind; 2] =327[ExecutorKind::SingleThreaded, ExecutorKind::MultiThreaded];328329#[derive(Resource, Default)]330struct TestState {331populated_ran: bool,332single_ran: bool,333}334335#[derive(Resource, Default)]336struct Counter(u8);337338fn set_single_state(mut _single: Single<&TestComponent>, mut state: ResMut<TestState>) {339state.single_ran = true;340}341342fn set_populated_state(343mut _populated: Populated<&TestComponent>,344mut state: ResMut<TestState>,345) {346state.populated_ran = true;347}348349#[test]350#[expect(clippy::print_stdout, reason = "std and println are allowed in tests")]351fn single_and_populated_skipped_and_run() {352for executor in EXECUTORS {353std::println!("Testing executor: {executor:?}");354355let mut world = World::new();356world.init_resource::<TestState>();357358let mut schedule = Schedule::default();359schedule.set_executor_kind(executor);360schedule.add_systems((set_single_state, set_populated_state));361schedule.run(&mut world);362363let state = world.get_resource::<TestState>().unwrap();364assert!(!state.single_ran);365assert!(!state.populated_ran);366367world.spawn(TestComponent);368369schedule.run(&mut world);370let state = world.get_resource::<TestState>().unwrap();371assert!(state.single_ran);372assert!(state.populated_ran);373}374}375376fn look_for_missing_resource(_res: Res<TestState>) {}377378#[test]379#[should_panic]380fn missing_resource_panics_single_threaded() {381let mut world = World::new();382let mut schedule = Schedule::default();383384schedule.set_executor_kind(ExecutorKind::SingleThreaded);385schedule.add_systems(look_for_missing_resource);386schedule.run(&mut world);387}388389#[test]390#[should_panic]391fn missing_resource_panics_multi_threaded() {392let mut world = World::new();393let mut schedule = Schedule::default();394395schedule.set_executor_kind(ExecutorKind::MultiThreaded);396schedule.add_systems(look_for_missing_resource);397schedule.run(&mut world);398}399400#[test]401fn piped_systems_first_system_skipped() {402// This system should be skipped when run due to no matching entity403fn pipe_out(_single: Single<&TestComponent>) -> u8 {40442405}406407fn pipe_in(_input: In<u8>, mut counter: ResMut<Counter>) {408counter.0 += 1;409}410411let mut world = World::new();412world.init_resource::<Counter>();413let mut schedule = Schedule::default();414415schedule.add_systems(pipe_out.pipe(pipe_in));416schedule.run(&mut world);417418let counter = world.resource::<Counter>();419assert_eq!(counter.0, 0);420}421422#[test]423fn piped_system_second_system_skipped() {424// This system will be run before the second system is validated425fn pipe_out(mut counter: ResMut<Counter>) -> u8 {426counter.0 += 1;42742428}429430// This system should be skipped when run due to no matching entity431fn pipe_in(_input: In<u8>, _single: Single<&TestComponent>, mut counter: ResMut<Counter>) {432counter.0 += 1;433}434435let mut world = World::new();436world.init_resource::<Counter>();437let mut schedule = Schedule::default();438439schedule.add_systems(pipe_out.pipe(pipe_in));440schedule.run(&mut world);441let counter = world.resource::<Counter>();442assert_eq!(counter.0, 1);443}444445#[test]446#[should_panic]447fn piped_system_first_system_panics() {448// This system should panic when run because the resource is missing449fn pipe_out(_res: Res<TestState>) -> u8 {45042451}452453fn pipe_in(_input: In<u8>) {}454455let mut world = World::new();456let mut schedule = Schedule::default();457458schedule.add_systems(pipe_out.pipe(pipe_in));459schedule.run(&mut world);460}461462#[test]463#[should_panic]464fn piped_system_second_system_panics() {465fn pipe_out() -> u8 {46642467}468469// This system should panic when run because the resource is missing470fn pipe_in(_input: In<u8>, _res: Res<TestState>) {}471472let mut world = World::new();473let mut schedule = Schedule::default();474475schedule.add_systems(pipe_out.pipe(pipe_in));476schedule.run(&mut world);477}478479// This test runs without panicking because we've480// decided to use early-out behavior for piped systems481#[test]482fn piped_system_skip_and_panic() {483// This system should be skipped when run due to no matching entity484fn pipe_out(_single: Single<&TestComponent>) -> u8 {48542486}487488// This system should panic when run because the resource is missing489fn pipe_in(_input: In<u8>, _res: Res<TestState>) {}490491let mut world = World::new();492let mut schedule = Schedule::default();493494schedule.add_systems(pipe_out.pipe(pipe_in));495schedule.run(&mut world);496}497498#[test]499#[should_panic]500fn piped_system_panic_and_skip() {501// This system should panic when run because the resource is missing502503fn pipe_out(_res: Res<TestState>) -> u8 {50442505}506507// This system should be skipped when run due to no matching entity508fn pipe_in(_input: In<u8>, _single: Single<&TestComponent>) {}509510let mut world = World::new();511let mut schedule = Schedule::default();512513schedule.add_systems(pipe_out.pipe(pipe_in));514schedule.run(&mut world);515}516517#[test]518#[should_panic]519fn piped_system_panic_and_panic() {520// This system should panic when run because the resource is missing521522fn pipe_out(_res: Res<TestState>) -> u8 {52342524}525526// This system should panic when run because the resource is missing527fn pipe_in(_input: In<u8>, _res: Res<TestState>) {}528529let mut world = World::new();530let mut schedule = Schedule::default();531532schedule.add_systems(pipe_out.pipe(pipe_in));533schedule.run(&mut world);534}535536#[test]537fn piped_system_skip_and_skip() {538// This system should be skipped when run due to no matching entity539540fn pipe_out(_single: Single<&TestComponent>, mut counter: ResMut<Counter>) -> u8 {541counter.0 += 1;54242543}544545// This system should be skipped when run due to no matching entity546fn pipe_in(_input: In<u8>, _single: Single<&TestComponent>, mut counter: ResMut<Counter>) {547counter.0 += 1;548}549550let mut world = World::new();551world.init_resource::<Counter>();552let mut schedule = Schedule::default();553554schedule.add_systems(pipe_out.pipe(pipe_in));555schedule.run(&mut world);556557let counter = world.resource::<Counter>();558assert_eq!(counter.0, 0);559}560}561562563