Path: blob/main/crates/bevy_ecs/src/schedule/executor/mod.rs
9350 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::{16change_detection::{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> {262// SAFETY: Upheld by caller263let result = unsafe { system.run_unsafe((), world) };264// Call `black_box` to prevent this frame from being tail-call optimized away265black_box(());266result267}268269/// # Safety270/// See `ReadOnlySystem::run_unsafe`.271// This is only used by `MultiThreadedExecutor`, and would be dead code without `std`.272#[cfg(feature = "std")]273#[inline(never)]274pub(super) unsafe fn readonly_run_unsafe<O: 'static>(275system: &mut dyn ReadOnlySystem<In = (), Out = O>,276world: UnsafeWorldCell,277) -> Result<O, RunSystemError> {278// Call `black_box` to prevent this frame from being tail-call optimized away279// SAFETY: Upheld by caller280black_box(unsafe { system.run_unsafe((), world) })281}282283#[cfg(feature = "std")]284#[inline(never)]285pub(super) fn run(286system: &mut ScheduleSystem,287world: &mut World,288) -> Result<(), RunSystemError> {289let result = system.run((), world);290// Call `black_box` to prevent this frame from being tail-call optimized away291black_box(());292result293}294295#[inline(never)]296pub(super) fn run_without_applying_deferred(297system: &mut ScheduleSystem,298world: &mut World,299) -> Result<(), RunSystemError> {300let result = system.run_without_applying_deferred((), world);301// Call `black_box` to prevent this frame from being tail-call optimized away302black_box(());303result304}305306#[inline(never)]307pub(super) fn readonly_run<O: 'static>(308system: &mut dyn ReadOnlySystem<In = (), Out = O>,309world: &mut World,310) -> Result<O, RunSystemError> {311// Call `black_box` to prevent this frame from being tail-call optimized away312black_box(system.run((), world))313}314}315316#[cfg(test)]317mod tests {318use crate::{319prelude::{Component, In, IntoSystem, Resource, Schedule},320schedule::ExecutorKind,321system::{Populated, Res, ResMut, Single},322world::World,323};324325#[derive(Component)]326struct TestComponent;327328const EXECUTORS: [ExecutorKind; 2] =329[ExecutorKind::SingleThreaded, ExecutorKind::MultiThreaded];330331#[derive(Resource, Default)]332struct TestState {333populated_ran: bool,334single_ran: bool,335}336337#[derive(Resource, Default)]338struct Counter(u8);339340fn set_single_state(mut _single: Single<&TestComponent>, mut state: ResMut<TestState>) {341state.single_ran = true;342}343344fn set_populated_state(345mut _populated: Populated<&TestComponent>,346mut state: ResMut<TestState>,347) {348state.populated_ran = true;349}350351#[test]352#[expect(clippy::print_stdout, reason = "std and println are allowed in tests")]353fn single_and_populated_skipped_and_run() {354for executor in EXECUTORS {355std::println!("Testing executor: {executor:?}");356357let mut world = World::new();358world.init_resource::<TestState>();359360let mut schedule = Schedule::default();361schedule.set_executor_kind(executor);362schedule.add_systems((set_single_state, set_populated_state));363schedule.run(&mut world);364365let state = world.get_resource::<TestState>().unwrap();366assert!(!state.single_ran);367assert!(!state.populated_ran);368369world.spawn(TestComponent);370371schedule.run(&mut world);372let state = world.get_resource::<TestState>().unwrap();373assert!(state.single_ran);374assert!(state.populated_ran);375}376}377378fn look_for_missing_resource(_res: Res<TestState>) {}379380#[test]381#[should_panic]382fn missing_resource_panics_single_threaded() {383let mut world = World::new();384let mut schedule = Schedule::default();385386schedule.set_executor_kind(ExecutorKind::SingleThreaded);387schedule.add_systems(look_for_missing_resource);388schedule.run(&mut world);389}390391#[test]392#[should_panic]393fn missing_resource_panics_multi_threaded() {394let mut world = World::new();395let mut schedule = Schedule::default();396397schedule.set_executor_kind(ExecutorKind::MultiThreaded);398schedule.add_systems(look_for_missing_resource);399schedule.run(&mut world);400}401402#[test]403fn piped_systems_first_system_skipped() {404// This system should be skipped when run due to no matching entity405fn pipe_out(_single: Single<&TestComponent>) -> u8 {40642407}408409fn pipe_in(_input: In<u8>, mut counter: ResMut<Counter>) {410counter.0 += 1;411}412413let mut world = World::new();414world.init_resource::<Counter>();415let mut schedule = Schedule::default();416417schedule.add_systems(pipe_out.pipe(pipe_in));418schedule.run(&mut world);419420let counter = world.resource::<Counter>();421assert_eq!(counter.0, 0);422}423424#[test]425fn piped_system_second_system_skipped() {426// This system will be run before the second system is validated427fn pipe_out(mut counter: ResMut<Counter>) -> u8 {428counter.0 += 1;42942430}431432// This system should be skipped when run due to no matching entity433fn pipe_in(_input: In<u8>, _single: Single<&TestComponent>, mut counter: ResMut<Counter>) {434counter.0 += 1;435}436437let mut world = World::new();438world.init_resource::<Counter>();439let mut schedule = Schedule::default();440441schedule.add_systems(pipe_out.pipe(pipe_in));442schedule.run(&mut world);443let counter = world.resource::<Counter>();444assert_eq!(counter.0, 1);445}446447#[test]448#[should_panic]449fn piped_system_first_system_panics() {450// This system should panic when run because the resource is missing451fn pipe_out(_res: Res<TestState>) -> u8 {45242453}454455fn pipe_in(_input: In<u8>) {}456457let mut world = World::new();458let mut schedule = Schedule::default();459460schedule.add_systems(pipe_out.pipe(pipe_in));461schedule.run(&mut world);462}463464#[test]465#[should_panic]466fn piped_system_second_system_panics() {467fn pipe_out() -> u8 {46842469}470471// This system should panic when run because the resource is missing472fn pipe_in(_input: In<u8>, _res: Res<TestState>) {}473474let mut world = World::new();475let mut schedule = Schedule::default();476477schedule.add_systems(pipe_out.pipe(pipe_in));478schedule.run(&mut world);479}480481// This test runs without panicking because we've482// decided to use early-out behavior for piped systems483#[test]484fn piped_system_skip_and_panic() {485// This system should be skipped when run due to no matching entity486fn pipe_out(_single: Single<&TestComponent>) -> u8 {48742488}489490// This system should panic when run because the resource is missing491fn pipe_in(_input: In<u8>, _res: Res<TestState>) {}492493let mut world = World::new();494let mut schedule = Schedule::default();495496schedule.add_systems(pipe_out.pipe(pipe_in));497schedule.run(&mut world);498}499500#[test]501#[should_panic]502fn piped_system_panic_and_skip() {503// This system should panic when run because the resource is missing504505fn pipe_out(_res: Res<TestState>) -> u8 {50642507}508509// This system should be skipped when run due to no matching entity510fn pipe_in(_input: In<u8>, _single: Single<&TestComponent>) {}511512let mut world = World::new();513let mut schedule = Schedule::default();514515schedule.add_systems(pipe_out.pipe(pipe_in));516schedule.run(&mut world);517}518519#[test]520#[should_panic]521fn piped_system_panic_and_panic() {522// This system should panic when run because the resource is missing523524fn pipe_out(_res: Res<TestState>) -> u8 {52542526}527528// This system should panic when run because the resource is missing529fn pipe_in(_input: In<u8>, _res: Res<TestState>) {}530531let mut world = World::new();532let mut schedule = Schedule::default();533534schedule.add_systems(pipe_out.pipe(pipe_in));535schedule.run(&mut world);536}537538#[test]539fn piped_system_skip_and_skip() {540// This system should be skipped when run due to no matching entity541542fn pipe_out(_single: Single<&TestComponent>, mut counter: ResMut<Counter>) -> u8 {543counter.0 += 1;54442545}546547// This system should be skipped when run due to no matching entity548fn pipe_in(_input: In<u8>, _single: Single<&TestComponent>, mut counter: ResMut<Counter>) {549counter.0 += 1;550}551552let mut world = World::new();553world.init_resource::<Counter>();554let mut schedule = Schedule::default();555556schedule.add_systems(pipe_out.pipe(pipe_in));557schedule.run(&mut world);558559let counter = world.resource::<Counter>();560assert_eq!(counter.0, 0);561}562}563564565