From 3ab5ce09ed5380c2e77236a2a6370d3d6ce0e5a4 Mon Sep 17 00:00:00 2001 From: Joy <51241057+maniwani@users.noreply.github.com> Date: Fri, 1 Apr 2022 21:52:35 -0700 Subject: [PATCH] update bevy_app, bevy_core and a whole lotta docs --- crates/bevy_app/src/app.rs | 792 +++++------------- crates/bevy_app/src/lib.rs | 43 +- crates/bevy_app/src/plugin.rs | 17 +- crates/bevy_app/src/plugin_group.rs | 25 +- crates/bevy_app/src/schedule_runner.rs | 11 +- crates/bevy_core/src/lib.rs | 90 +- crates/bevy_core/src/time/fixed_timestep.rs | 193 +++-- crates/bevy_core/src/time/time.rs | 28 +- crates/bevy_ecs/src/lib.rs | 5 +- crates/bevy_ecs/src/schedule/condition.rs | 55 +- crates/bevy_ecs/src/schedule/descriptor.rs | 340 ++++---- crates/bevy_ecs/src/schedule/fsm.rs | 63 +- crates/bevy_ecs/src/schedule/mod.rs | 192 ++--- crates/bevy_ecs/src/schedule/runner/mod.rs | 50 +- .../src/system/commands/command_queue.rs | 10 +- crates/bevy_ecs/src/system/mod.rs | 25 +- crates/bevy_ecs/src/system/system_chaining.rs | 12 +- crates/bevy_ecs/src/system/system_param.rs | 256 +++--- 18 files changed, 924 insertions(+), 1283 deletions(-) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index c429e524ff9bf..e920e7187c316 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -1,11 +1,11 @@ -use crate::{CoreStage, Plugin, PluginGroup, PluginGroupBuilder, StartupSchedule, StartupStage}; +use crate::{Plugin, PluginGroup, PluginGroupBuilder}; pub use bevy_derive::AppLabel; use bevy_ecs::{ event::Events, prelude::FromWorld, schedule::{ - IntoSystemDescriptor, RunOnce, Schedule, Stage, StageLabel, State, StateData, SystemSet, - SystemStage, + apply_buffers, chain, run_scheduled, IntoScheduledSet, IntoScheduledSystem, Scheduled, + SystemLabel, SystemRegistry, }, system::Resource, world::World, @@ -18,13 +18,9 @@ use bevy_utils::tracing::info_span; bevy_utils::define_label!(AppLabel); #[allow(clippy::needless_doctest_main)] -/// Containers of app logic and data +/// An ECS application that wraps a [`World`], a runner function, and a [`SubApp`] collection. /// -/// Bundles together the necessary elements, like [`World`] and [`Schedule`], to create -/// an ECS-based application. It also stores a pointer to a -/// [runner function](Self::set_runner). The runner is responsible for managing the application's -/// event loop and applying the [`Schedule`] to the [`World`] to drive application logic. -/// Apps are constructed with the builder pattern. +/// [`App`] instances are constructed using a builder pattern. /// /// ## Example /// Here is a simple "Hello World" Bevy app: @@ -43,36 +39,106 @@ bevy_utils::define_label!(AppLabel); /// } /// ``` pub struct App { - /// The main ECS [`World`] of the [`App`]. - /// This stores and provides access to all the main data of the application. - /// The systems of the [`App`] will run using this [`World`]. - /// If additional separate [`World`]-[`Schedule`] pairs are needed, you can use [`sub_app`][App::add_sub_app]s. + /// Stores all data used by the main application. + /// + /// Note that each sub-app also has its own [`World`]. pub world: World, - /// The [runner function](Self::set_runner) is primarily responsible for managing - /// the application's event loop and advancing the [`Schedule`]. - /// Typically, it is not configured manually, but set by one of Bevy's built-in plugins. - /// See `bevy::winit::WinitPlugin` and [`ScheduleRunnerPlugin`](crate::schedule_runner::ScheduleRunnerPlugin). + /// The [runner function](Self::set_runner) is responsible for managing + /// the main application's event loop and running its systems. + /// + /// Usually, the runner is configured by the [`WinitPlugin`][`bevy::winit::WinitPlugin`] + /// or [`ScheduleRunnerPlugin`](crate::schedule_runner::ScheduleRunnerPlugin). pub runner: Box, - /// A container of [`Stage`]s set to be run in a linear order. - pub schedule: Schedule, sub_apps: HashMap, SubApp>, } -/// Each [`SubApp`] has its own [`Schedule`] and [`World`], enabling a separation of concerns. +/// A "nested" [`App`] with its own [`World`] and runner, enabling a separation of concerns. struct SubApp { app: App, runner: Box, } +/// System sets providing basic app functionality. +#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)] +pub enum AppSet { + /// Systems that run only once when the app starts, before the [`Startup`](AppSet::Startup) set. + PreStartup, + /// Systems that run only once when the app starts. + Startup, + /// Systems that run only once when the app starts, after the [`Startup`](AppSet::Startup) set. + PostStartup, + /// Systems that run each time the app updates. + Update, + /// Systems that swap event buffers. + UpdateEvents, +} + +/// Systems providing basic app functionality. +#[doc(hidden)] +#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)] +pub enum AppSystem { + /// Runs the systems under the [`Startup`](`AppSet::Startup`) set. + Startup, + /// Calls [`apply_buffers`] after the systems under [`PreStartup`](AppSet::PreStartup) run. + ApplyPreStartup, + /// Calls [`apply_buffers`] after the systems under [`Startup`](AppSet::Startup) run. + ApplyStartup, + /// Calls [`apply_buffers`] after the systems under [`PostStartup`](AppSet::PostStartup) run. + ApplyPostStartup, + /// Clears the world's lists of entities with removed components. + ClearTrackers, +} + +/// Internal system sets needed to bypass limitations with [`apply_buffers`]. +#[doc(hidden)] +#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)] +pub(crate) enum AppInternalSet { + /// Encompasses all startup sets and command application. + Startup, +} + +fn run_once(mut app: App) { + app.update(); +} + +/// An event that signals the app to exit. This will close the app process. +#[derive(Debug, Clone, Default)] +pub struct AppExit; + impl Default for App { fn default() -> Self { let mut app = App::empty(); + #[cfg(feature = "bevy_reflect")] app.init_resource::(); + app.add_event::(); - app.add_default_stages() - .add_event::() - .add_system_to_stage(CoreStage::Last, World::clear_trackers); + app.add_system( + (|world: &mut World| { + run_scheduled(AppInternalSet::Startup, world); + }) + .named(AppSystem::Startup), + ); + app.add_set(AppInternalSet::Startup); + app.add_many( + chain![ + AppSet::PreStartup, + apply_buffers.named(AppSystem::ApplyPreStartup), + AppSet::Startup, + apply_buffers.named(AppSystem::ApplyStartup), + AppSet::PostStartup, + apply_buffers.named(AppSystem::ApplyPostStartup), + ] + .to(AppInternalSet::Startup), + ); + app.add_set(AppSet::Update); + app.add_set(AppSet::UpdateEvents.to(AppSet::Update)); + app.add_system( + World::clear_trackers + .named(AppSystem::ClearTrackers) + .to(AppSet::Update) + .after(AppSet::UpdateEvents), + ); #[cfg(feature = "bevy_ci_testing")] { @@ -84,528 +150,69 @@ impl Default for App { } impl App { - /// Creates a new [`App`] with some default structure to enable core engine features. + /// Constructs a new, empty [`App`] with default engine features enabled. + /// /// This is the preferred constructor for most use cases. pub fn new() -> App { App::default() } - /// Creates a new empty [`App`] with minimal default configuration. + /// Constructs a new, empty [`App`]. /// - /// This constructor should be used if you wish to provide a custom schedule, exit handling, cleanup, etc. + /// This constructor should be used if you wish to provide custom scheduling, exit handling, cleanup, etc. pub fn empty() -> App { - Self { - world: Default::default(), - schedule: Default::default(), + let mut world = World::default(); + world.init_resource::(); + App { + world, runner: Box::new(run_once), sub_apps: HashMap::default(), } } - /// Advances the execution of the [`Schedule`] by one cycle. - /// - /// This method also updates sub apps. See [`add_sub_app`](Self::add_sub_app) for more details. - /// - /// See [`Schedule::run_once`] for more details. + /// Runs the systems under the [`Update`](AppSet::Update) system set and also updates every [`SubApp`]. pub fn update(&mut self) { #[cfg(feature = "trace")] - let _bevy_frame_update_span = info_span!("frame").entered(); - self.schedule.run(&mut self.world); + let bevy_frame_update_span = info_span!("frame").entered(); + run_scheduled(AppSet::Update, &mut self.world); for sub_app in self.sub_apps.values_mut() { (sub_app.runner)(&mut self.world, &mut sub_app.app); } } - /// Starts the application by calling the app's [runner function](Self::set_runner). - /// - /// Finalizes the [`App`] configuration. For general usage, see the example on the item - /// level documentation. + /// Starts the application by calling its [runner function](Self::set_runner). pub fn run(&mut self) { #[cfg(feature = "trace")] - let _bevy_app_run_span = info_span!("bevy_app").entered(); - + let bevy_app_run_span = info_span!("bevy_app").entered(); let mut app = std::mem::replace(self, App::empty()); let runner = std::mem::replace(&mut app.runner, Box::new(run_once)); (runner)(app); } - /// Adds a [`Stage`] with the given `label` to the last position of the app's - /// [`Schedule`]. - /// - /// # Example - /// - /// ``` - /// # use bevy_app::prelude::*; - /// # use bevy_ecs::prelude::*; - /// # let mut app = App::new(); - /// # - /// app.add_stage("my_stage", SystemStage::parallel()); - /// ``` - pub fn add_stage(&mut self, label: impl StageLabel, stage: S) -> &mut Self { - self.schedule.add_stage(label, stage); - self - } - - /// Adds a [`Stage`] with the given `label` to the app's [`Schedule`], located - /// immediately after the stage labeled by `target`. - /// - /// # Example - /// - /// ``` - /// # use bevy_app::prelude::*; - /// # use bevy_ecs::prelude::*; - /// # let mut app = App::new(); - /// # - /// app.add_stage_after(CoreStage::Update, "my_stage", SystemStage::parallel()); - /// ``` - pub fn add_stage_after( - &mut self, - target: impl StageLabel, - label: impl StageLabel, - stage: S, - ) -> &mut Self { - self.schedule.add_stage_after(target, label, stage); - self - } - - /// Adds a [`Stage`] with the given `label` to the app's [`Schedule`], located - /// immediately before the stage labeled by `target`. - /// - /// # Example - /// - /// ``` - /// # use bevy_app::prelude::*; - /// # use bevy_ecs::prelude::*; - /// # let mut app = App::new(); - /// # - /// app.add_stage_before(CoreStage::Update, "my_stage", SystemStage::parallel()); - /// ``` - pub fn add_stage_before( - &mut self, - target: impl StageLabel, - label: impl StageLabel, - stage: S, - ) -> &mut Self { - self.schedule.add_stage_before(target, label, stage); - self - } - - /// Adds a [`Stage`] with the given `label` to the last position of the - /// [startup schedule](Self::add_default_stages). - /// - /// # Example - /// - /// ``` - /// # use bevy_app::prelude::*; - /// # use bevy_ecs::prelude::*; - /// # let mut app = App::new(); - /// # - /// app.add_startup_stage("my_startup_stage", SystemStage::parallel()); - /// ``` - pub fn add_startup_stage(&mut self, label: impl StageLabel, stage: S) -> &mut Self { - self.schedule - .stage(StartupSchedule, |schedule: &mut Schedule| { - schedule.add_stage(label, stage) - }); - self - } - - /// Adds a [startup stage](Self::add_default_stages) with the given `label`, immediately - /// after the stage labeled by `target`. - /// - /// The `target` label must refer to a stage inside the startup schedule. - /// - /// # Example - /// - /// ``` - /// # use bevy_app::prelude::*; - /// # use bevy_ecs::prelude::*; - /// # let mut app = App::new(); - /// # - /// app.add_startup_stage_after( - /// StartupStage::Startup, - /// "my_startup_stage", - /// SystemStage::parallel() - /// ); - /// ``` - pub fn add_startup_stage_after( - &mut self, - target: impl StageLabel, - label: impl StageLabel, - stage: S, - ) -> &mut Self { - self.schedule - .stage(StartupSchedule, |schedule: &mut Schedule| { - schedule.add_stage_after(target, label, stage) - }); - self - } - - /// Adds a [startup stage](Self::add_default_stages) with the given `label`, immediately - /// before the stage labeled by `target`. - /// - /// The `target` label must refer to a stage inside the startup schedule. - /// - /// # Example - /// - /// ``` - /// # use bevy_app::prelude::*; - /// # use bevy_ecs::prelude::*; - /// # let mut app = App::new(); - /// # - /// app.add_startup_stage_before( - /// StartupStage::Startup, - /// "my_startup_stage", - /// SystemStage::parallel() - /// ); - /// ``` - pub fn add_startup_stage_before( - &mut self, - target: impl StageLabel, - label: impl StageLabel, - stage: S, - ) -> &mut Self { - self.schedule - .stage(StartupSchedule, |schedule: &mut Schedule| { - schedule.add_stage_before(target, label, stage) - }); + /// Schedules a [`System`](bevy_ecs::system::System). + pub fn add_system

(&mut self, system: impl IntoScheduledSystem

) -> &mut Self { + let mut reg = self.world.resource_mut::(); + reg.add_system(system); self } - /// Fetches the [`Stage`] of type `T` marked with `label` from the [`Schedule`], then - /// executes the provided `func` passing the fetched stage to it as an argument. - /// - /// The `func` argument should be a function or a closure that accepts a mutable reference - /// to a struct implementing `Stage` and returns the same type. That means that it should - /// also assume that the stage has already been fetched successfully. - /// - /// See [`Schedule::stage`] for more details. - /// - /// # Example - /// - /// Here the closure is used to add a system to the update stage: - /// - /// ``` - /// # use bevy_app::prelude::*; - /// # use bevy_ecs::prelude::*; - /// # - /// # let mut app = App::new(); - /// # fn my_system() {} - /// # - /// app.stage(CoreStage::Update, |stage: &mut SystemStage| { - /// stage.add_system(my_system) - /// }); - /// ``` - pub fn stage &mut T>( - &mut self, - label: impl StageLabel, - func: F, - ) -> &mut Self { - self.schedule.stage(label, func); + /// Schedules a [`System`](bevy_ecs::system::System) set. + pub fn add_set(&mut self, set: impl IntoScheduledSet) -> &mut Self { + let mut reg = self.world.resource_mut::(); + reg.add_set(set); self } - /// Adds a system to the [update stage](Self::add_default_stages) of the app's [`Schedule`]. - /// - /// Refer to the [system module documentation](bevy_ecs::system) to see how a system - /// can be defined. - /// - /// # Example - /// - /// ``` - /// # use bevy_app::prelude::*; - /// # use bevy_ecs::prelude::*; - /// # - /// # fn my_system() {} - /// # let mut app = App::new(); - /// # - /// app.add_system(my_system); - /// ``` - pub fn add_system(&mut self, system: impl IntoSystemDescriptor) -> &mut Self { - self.add_system_to_stage(CoreStage::Update, system) - } - - /// Adds a [`SystemSet`] to the [update stage](Self::add_default_stages). - /// - /// # Example - /// - /// ``` - /// # use bevy_app::prelude::*; - /// # use bevy_ecs::prelude::*; - /// # - /// # let mut app = App::new(); - /// # fn system_a() {} - /// # fn system_b() {} - /// # fn system_c() {} - /// # - /// app.add_system_set( - /// SystemSet::new() - /// .with_system(system_a) - /// .with_system(system_b) - /// .with_system(system_c), - /// ); - /// ``` - pub fn add_system_set(&mut self, system_set: SystemSet) -> &mut Self { - self.add_system_set_to_stage(CoreStage::Update, system_set) - } - - /// Adds a system to the [`Stage`] identified by `stage_label`. - /// - /// # Example - /// - /// ``` - /// # use bevy_app::prelude::*; - /// # use bevy_ecs::prelude::*; - /// # - /// # let mut app = App::new(); - /// # fn my_system() {} - /// # - /// app.add_system_to_stage(CoreStage::PostUpdate, my_system); - /// ``` - pub fn add_system_to_stage( - &mut self, - stage_label: impl StageLabel, - system: impl IntoSystemDescriptor, - ) -> &mut Self { - use std::any::TypeId; - assert!( - stage_label.type_id() != TypeId::of::(), - "add systems to a startup stage using App::add_startup_system_to_stage" - ); - self.schedule.add_system_to_stage(stage_label, system); + /// Schedules multiple systems and system sets. + pub fn add_many(&mut self, nodes: impl IntoIterator) -> &mut Self { + let mut reg = self.world.resource_mut::(); + reg.add_many(nodes); self } - /// Adds a [`SystemSet`] to the [`Stage`] identified by `stage_label`. - /// - /// # Example - /// - /// ``` - /// # use bevy_app::prelude::*; - /// # use bevy_ecs::prelude::*; - /// # - /// # let mut app = App::new(); - /// # fn system_a() {} - /// # fn system_b() {} - /// # fn system_c() {} - /// # - /// app.add_system_set_to_stage( - /// CoreStage::PostUpdate, - /// SystemSet::new() - /// .with_system(system_a) - /// .with_system(system_b) - /// .with_system(system_c), - /// ); - /// ``` - pub fn add_system_set_to_stage( - &mut self, - stage_label: impl StageLabel, - system_set: SystemSet, - ) -> &mut Self { - use std::any::TypeId; - assert!( - stage_label.type_id() != TypeId::of::(), - "add system sets to a startup stage using App::add_startup_system_set_to_stage" - ); - self.schedule - .add_system_set_to_stage(stage_label, system_set); - self - } - - /// Adds a system to the [startup stage](Self::add_default_stages) of the app's [`Schedule`]. - /// - /// * For adding a system that runs for every frame, see [`add_system`](Self::add_system). - /// * For adding a system to specific stage, see [`add_system_to_stage`](Self::add_system_to_stage). - /// - /// ## Example - /// ``` - /// # use bevy_app::prelude::*; - /// # use bevy_ecs::prelude::*; - /// # - /// fn my_startup_system(_commands: Commands) { - /// println!("My startup system"); - /// } - /// - /// App::new() - /// .add_startup_system(my_startup_system); - /// ``` - pub fn add_startup_system( - &mut self, - system: impl IntoSystemDescriptor, - ) -> &mut Self { - self.add_startup_system_to_stage(StartupStage::Startup, system) - } - - /// Adds a [`SystemSet`] to the [startup stage](Self::add_default_stages) - /// - /// # Example - /// - /// ``` - /// # use bevy_app::prelude::*; - /// # use bevy_ecs::prelude::*; - /// # - /// # let mut app = App::new(); - /// # fn startup_system_a() {} - /// # fn startup_system_b() {} - /// # fn startup_system_c() {} - /// # - /// app.add_startup_system_set( - /// SystemSet::new() - /// .with_system(startup_system_a) - /// .with_system(startup_system_b) - /// .with_system(startup_system_c), - /// ); - /// ``` - pub fn add_startup_system_set(&mut self, system_set: SystemSet) -> &mut Self { - self.add_startup_system_set_to_stage(StartupStage::Startup, system_set) - } - - /// Adds a system to the [startup schedule](Self::add_default_stages), in the stage - /// identified by `stage_label`. - /// - /// `stage_label` must refer to a stage inside the startup schedule. - /// - /// # Example - /// - /// ``` - /// # use bevy_app::prelude::*; - /// # use bevy_ecs::prelude::*; - /// # - /// # let mut app = App::new(); - /// # fn my_startup_system() {} - /// # - /// app.add_startup_system_to_stage(StartupStage::PreStartup, my_startup_system); - /// ``` - pub fn add_startup_system_to_stage( - &mut self, - stage_label: impl StageLabel, - system: impl IntoSystemDescriptor, - ) -> &mut Self { - self.schedule - .stage(StartupSchedule, |schedule: &mut Schedule| { - schedule.add_system_to_stage(stage_label, system) - }); - self - } - - /// Adds a [`SystemSet`] to the [startup schedule](Self::add_default_stages), in the stage - /// identified by `stage_label`. - /// - /// `stage_label` must refer to a stage inside the startup schedule. - /// - /// # Example - /// - /// ``` - /// # use bevy_app::prelude::*; - /// # use bevy_ecs::prelude::*; - /// # - /// # let mut app = App::new(); - /// # fn startup_system_a() {} - /// # fn startup_system_b() {} - /// # fn startup_system_c() {} - /// # - /// app.add_startup_system_set_to_stage( - /// StartupStage::PreStartup, - /// SystemSet::new() - /// .with_system(startup_system_a) - /// .with_system(startup_system_b) - /// .with_system(startup_system_c), - /// ); - /// ``` - pub fn add_startup_system_set_to_stage( - &mut self, - stage_label: impl StageLabel, - system_set: SystemSet, - ) -> &mut Self { - self.schedule - .stage(StartupSchedule, |schedule: &mut Schedule| { - schedule.add_system_set_to_stage(stage_label, system_set) - }); - self - } - - /// Adds a new [`State`] with the given `initial` value. - /// This inserts a new `State` resource and adds a new "driver" to [`CoreStage::Update`]. - /// Each stage that uses `State` for system run criteria needs a driver. If you need to use - /// your state in a different stage, consider using [`Self::add_state_to_stage`] or manually - /// adding [`State::get_driver`] to additional stages you need it in. - pub fn add_state(&mut self, initial: T) -> &mut Self - where - T: StateData, - { - self.add_state_to_stage(CoreStage::Update, initial) - } - - /// Adds a new [`State`] with the given `initial` value. - /// This inserts a new `State` resource and adds a new "driver" to the given stage. - /// Each stage that uses `State` for system run criteria needs a driver. If you need to use - /// your state in more than one stage, consider manually adding [`State::get_driver`] to the - /// stages you need it in. - pub fn add_state_to_stage(&mut self, stage: impl StageLabel, initial: T) -> &mut Self - where - T: StateData, - { - self.insert_resource(State::new(initial)) - .add_system_set_to_stage(stage, State::::get_driver()) - } - - /// Adds utility stages to the [`Schedule`], giving it a standardized structure. - /// - /// Adding those stages is necessary to make some core engine features work, like - /// adding systems without specifying a stage, or registering events. This is however - /// done by default by calling `App::default`, which is in turn called by - /// [`App::new`]. - /// - /// # The stages - /// - /// All the added stages, with the exception of the startup stage, run every time the - /// schedule is invoked. The stages are the following, in order of execution: - /// - **First:** Runs at the very start of the schedule execution cycle, even before the - /// startup stage. - /// - **Startup:** This is actually a schedule containing sub-stages. Runs only once - /// when the app starts. - /// - **Pre-startup:** Intended for systems that need to run before other startup systems. - /// - **Startup:** The main startup stage. Startup systems are added here by default. - /// - **Post-startup:** Intended for systems that need to run after other startup systems. - /// - **Pre-update:** Often used by plugins to prepare their internal state before the - /// update stage begins. - /// - **Update:** Intended for user defined logic. Systems are added here by default. - /// - **Post-update:** Often used by plugins to finalize their internal state after the - /// world changes that happened during the update stage. - /// - **Last:** Runs right before the end of the schedule execution cycle. - /// - /// The labels for those stages are defined in the [`CoreStage`] and [`StartupStage`] - /// `enum`s. - /// - /// # Example - /// - /// ``` - /// # use bevy_app::prelude::*; - /// # - /// let app = App::empty().add_default_stages(); - /// ``` - pub fn add_default_stages(&mut self) -> &mut Self { - self.add_stage(CoreStage::First, SystemStage::parallel()) - .add_stage( - StartupSchedule, - Schedule::default() - .with_run_criteria(RunOnce::default()) - .with_stage(StartupStage::PreStartup, SystemStage::parallel()) - .with_stage(StartupStage::Startup, SystemStage::parallel()) - .with_stage(StartupStage::PostStartup, SystemStage::parallel()), - ) - .add_stage(CoreStage::PreUpdate, SystemStage::parallel()) - .add_stage(CoreStage::Update, SystemStage::parallel()) - .add_stage(CoreStage::PostUpdate, SystemStage::parallel()) - .add_stage(CoreStage::Last, SystemStage::parallel()) - } - - /// Setup the application to manage events of type `T`. - /// - /// This is done by adding a `Resource` of type `Events::`, - /// and inserting a `Events::::update_system` system into `CoreStage::First`. - /// - /// See [`Events`](bevy_ecs::event::Events) for defining events. + /// Adds [`Events::`](bevy_ecs::event::Events) as a resource in the world and schedules its + /// [`update_system`](bevy_ecs::event::Events::update_system) to run under the + /// [`UpdateEvents`](AppSet::UpdateEvents) system set. /// /// # Example /// @@ -622,19 +229,15 @@ impl App { where T: Resource, { - if !self.world.contains_resource::>() { - self.init_resource::>() - .add_system_to_stage(CoreStage::First, Events::::update_system); - } - self + self.init_resource::>() + .add_system(Events::::update_system.to(AppSet::UpdateEvents)) } - /// Inserts a resource to the current [App] and overwrites any resource previously added of the same type. + /// Adds the resource to the [`World`]. /// - /// A resource in Bevy represents globally unique data. Resources must be added to Bevy Apps - /// before using them. This happens with [`insert_resource`](Self::insert_resource). + /// If the resource already exists, this will overwrite its value. /// - /// See also `init_resource` for resources that implement `Default` or [`FromWorld`]. + /// See [`init_resource`](Self::init_resource) for resources that implement [`Default`] or [`FromWorld`]. /// /// ## Example /// ``` @@ -652,10 +255,11 @@ impl App { self } - /// Inserts a non-send resource to the app + /// Adds the non-[`Send`] resource to the [`World`]. /// - /// You usually want to use `insert_resource`, - /// but there are some special cases when a resource cannot be sent across threads. + /// If the resource already exists, this will overwrite its value. + /// + /// See [`init_resource`](Self::init_resource) for resources that implement [`Default`] or [`FromWorld`]. /// /// ## Example /// ``` @@ -673,13 +277,13 @@ impl App { self } - /// Initialize a resource with standard starting values by adding it to the [`World`] + /// Adds the resource to the [`World`], initialized to its default value. /// /// If the resource already exists, nothing happens. /// /// The resource must implement the [`FromWorld`] trait. - /// If the `Default` trait is implemented, the `FromWorld` trait will use - /// the `Default::default` method to initialize the resource. + /// If the [`Default`] trait is implemented, the `FromWorld` trait will use + /// the [`Default::default`] method to initialize the resource. /// /// ## Example /// ``` @@ -705,24 +309,21 @@ impl App { self } - /// Initialize a non-send resource with standard starting values by adding it to the [`World`] + /// Adds the non-[`Send`] resource to the [`World`], initialized to its default value. + /// + /// If the resource already exists, nothing happens. /// /// The resource must implement the [`FromWorld`] trait. - /// If the `Default` trait is implemented, the `FromWorld` trait will use - /// the `Default::default` method to initialize the resource. + /// If the [`Default`] trait is implemented, the `FromWorld` trait will use + /// the [`Default::default`] method to initialize the resource. pub fn init_non_send_resource(&mut self) -> &mut Self { self.world.init_non_send_resource::(); self } - /// Sets the function that will be called when the app is run. - /// - /// The runner function (`run_fn`) is called only once by [`App::run`]. If the - /// presence of a main loop in the app is desired, it is responsibility of the runner - /// function to provide it. + /// Sets the function that will be used to run the [`App`]. Called once by [`App::run`]. /// - /// The runner function is usually not set manually, but by Bevy integrated plugins - /// (e.g. winit plugin). + /// Often set by internal plugins (e.g. [`WinitPlugin`][bevy_winit::WinitPlugin]). /// /// ## Example /// ``` @@ -730,25 +331,22 @@ impl App { /// # /// fn my_runner(mut app: App) { /// loop { - /// println!("In main loop"); + /// println!("in main loop"); /// app.update(); /// } /// } /// - /// App::new() - /// .set_runner(my_runner); + /// App::new().set_runner(my_runner); /// ``` - pub fn set_runner(&mut self, run_fn: impl Fn(App) + 'static) -> &mut Self { - self.runner = Box::new(run_fn); + pub fn set_runner(&mut self, f: F) -> &mut Self + where + F: Fn(App) + 'static, + { + self.runner = Box::new(f); self } - /// Adds a single plugin - /// - /// One of Bevy's core principles is modularity. All Bevy engine features are implemented - /// as plugins. This includes internal features like the renderer. - /// - /// Bevy also provides a few sets of default plugins. See [`add_plugins`](Self::add_plugins). + /// Imports app configuration from a [`Plugin`]. /// /// ## Example /// ``` @@ -765,12 +363,7 @@ impl App { self } - /// Adds a group of plugins - /// - /// Bevy plugins can be grouped into a set of plugins. Bevy provides - /// built-in `PluginGroups` that provide core engine functionality. - /// - /// The plugin groups available by default are `DefaultPlugins` and `MinimalPlugins`. + /// Imports app configuration from a [`PluginGroup`]. /// /// ## Example /// ``` @@ -792,12 +385,9 @@ impl App { self } - /// Adds a group of plugins with an initializer method + /// Imports app configuration from a [`PluginGroup`], with some pre-processing function. /// - /// Can be used to add a group of plugins, where the group is modified - /// before insertion into Bevy application. For example, you can add - /// extra plugins at a specific place in the plugin group, or deactivate - /// specific plugins while keeping the rest. + /// This can be used to add more plugins to the plugin group, disable plugins, etc. /// /// ## Example /// ``` @@ -821,88 +411,92 @@ impl App { /// group.add_before::(MyOwnPlugin) /// }); /// ``` - pub fn add_plugins_with(&mut self, mut group: T, func: F) -> &mut Self + pub fn add_plugins_with(&mut self, mut group: T, f: F) -> &mut Self where T: PluginGroup, F: FnOnce(&mut PluginGroupBuilder) -> &mut PluginGroupBuilder, { let mut plugin_group_builder = PluginGroupBuilder::default(); group.build(&mut plugin_group_builder); - func(&mut plugin_group_builder); + f(&mut plugin_group_builder); plugin_group_builder.finish(self); self } - /// Adds the type `T` to the type registry resource. - #[cfg(feature = "bevy_reflect")] - pub fn register_type(&mut self) -> &mut Self { - { - let registry = self.world.resource_mut::(); - registry.write().register::(); - } - self - } - - /// Adds an `App` as a child of the current one. + /// Adds an [`App`] as a [`SubApp`] to the current one. /// - /// The provided function `f` is called by the [`update`](Self::update) method. The `World` - /// parameter represents the main app world, while the `App` parameter is just a mutable - /// reference to the sub app itself. + /// The provided function, `runner`, takes mutable references to the main app's [`World`] and the + /// sub-app and is called during the main app's [`update`](Self::update). pub fn add_sub_app( &mut self, label: impl AppLabel, app: App, - sub_app_runner: impl Fn(&mut World, &mut App) + 'static, + runner: impl Fn(&mut World, &mut App) + 'static, ) -> &mut Self { self.sub_apps.insert( Box::new(label), SubApp { app, - runner: Box::new(sub_app_runner), + runner: Box::new(runner), }, ); self } - /// Retrieves a "sub app" stored inside this [App]. This will panic if the sub app does not exist. - pub fn sub_app_mut(&mut self, label: impl AppLabel) -> &mut App { - match self.get_sub_app_mut(label) { + /// Returns a reference to the sub-[`App`] with the given label. + /// + /// # Panics + /// + /// Panic if the sub-app does not exist. + pub fn sub_app(&self, label: impl AppLabel) -> &App { + match self.get_sub_app(label) { Ok(app) => app, Err(label) => panic!("Sub-App with label '{:?}' does not exist", label), } } - /// Retrieves a "sub app" inside this [App] with the given label, if it exists. Otherwise returns - /// an [Err] containing the given label. - pub fn get_sub_app_mut(&mut self, label: impl AppLabel) -> Result<&mut App, impl AppLabel> { - self.sub_apps - .get_mut((&label) as &dyn AppLabel) - .map(|sub_app| &mut sub_app.app) - .ok_or(label) - } - - /// Retrieves a "sub app" stored inside this [App]. This will panic if the sub app does not exist. - pub fn sub_app(&self, label: impl AppLabel) -> &App { - match self.get_sub_app(label) { + /// Returns a mutable reference to the sub-[`App`] with the given label. + /// + /// # Panics + /// + /// Panic if the sub-app does not exist. + pub fn sub_app_mut(&mut self, label: impl AppLabel) -> &mut App { + match self.get_sub_app_mut(label) { Ok(app) => app, Err(label) => panic!("Sub-App with label '{:?}' does not exist", label), } } - /// Retrieves a "sub app" inside this [App] with the given label, if it exists. Otherwise returns - /// an [Err] containing the given label. + /// Returns a reference to the child [`App`] with the given label. + /// + /// # Errors + /// + /// Returns [`Err`] if the sub-app does not exist. pub fn get_sub_app(&self, label: impl AppLabel) -> Result<&App, impl AppLabel> { self.sub_apps .get((&label) as &dyn AppLabel) .map(|sub_app| &sub_app.app) .ok_or(label) } -} -fn run_once(mut app: App) { - app.update(); -} + /// Returns a mutable reference to the child [`App`] with the given label. + /// + /// # Errors + /// + /// Returns [`Err`] if the sub-app does not exist. + pub fn get_sub_app_mut(&mut self, label: impl AppLabel) -> Result<&mut App, impl AppLabel> { + self.sub_apps + .get_mut((&label) as &dyn AppLabel) + .map(|sub_app| &mut sub_app.app) + .ok_or(label) + } -/// An event that indicates the app should exit. This will fully exit the app process. -#[derive(Debug, Clone, Default)] -pub struct AppExit; + /// Adds `T` to the type registry resource for runtime reflection. + #[cfg(feature = "bevy_reflect")] + pub fn register_type(&mut self) -> &mut Self { + let registry = self.world.resource_mut::(); + registry.write().register::(); + + self + } +} diff --git a/crates/bevy_app/src/lib.rs b/crates/bevy_app/src/lib.rs index 6a6d3e8b4bae1..02268c8fdf36b 100644 --- a/crates/bevy_app/src/lib.rs +++ b/crates/bevy_app/src/lib.rs @@ -12,6 +12,7 @@ mod ci_testing; pub use app::*; pub use bevy_derive::DynamicPlugin; +pub use bevy_ecs::event::*; pub use plugin::*; pub use plugin_group::*; pub use schedule_runner::*; @@ -20,45 +21,7 @@ pub use schedule_runner::*; pub mod prelude { #[doc(hidden)] pub use crate::{ - app::App, CoreStage, DynamicPlugin, Plugin, PluginGroup, StartupSchedule, StartupStage, + app::{App, AppSet}, + DynamicPlugin, Plugin, PluginGroup, }; } - -use bevy_ecs::schedule::StageLabel; - -/// The names of the default App stages -/// -/// The relative stages are added by [`App::add_default_stages`]. -#[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)] -pub enum CoreStage { - /// Name of app stage that runs before all other app stages - First, - /// Name of app stage responsible for performing setup before an update. Runs before UPDATE. - PreUpdate, - /// Name of app stage responsible for doing most app logic. Systems should be registered here - /// by default. - Update, - /// Name of app stage responsible for processing the results of UPDATE. Runs after UPDATE. - PostUpdate, - /// Name of app stage that runs after all other app stages - Last, -} - -/// The label for the Startup [`Schedule`](bevy_ecs::schedule::Schedule), -/// which runs once at the beginning of the app. -/// -/// When targeting a [`Stage`](bevy_ecs::schedule::Stage) inside this Schedule, -/// you need to use [`StartupStage`] instead. -#[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)] -pub struct StartupSchedule; - -/// The names of the default App startup stages -#[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)] -pub enum StartupStage { - /// Name of app stage that runs once before the startup stage - PreStartup, - /// Name of app stage that runs once when an app starts up - Startup, - /// Name of app stage that runs once after the startup stage - PostStartup, -} diff --git a/crates/bevy_app/src/plugin.rs b/crates/bevy_app/src/plugin.rs index c6876db0b3b60..73e4638071295 100644 --- a/crates/bevy_app/src/plugin.rs +++ b/crates/bevy_app/src/plugin.rs @@ -1,20 +1,21 @@ use crate::App; use std::any::Any; -/// A collection of Bevy App logic and configuration +/// Portable [`App`] configuration. /// -/// Plugins configure an [`App`](crate::App). When an [`App`](crate::App) registers -/// a plugin, the plugin's [`Plugin::build`] function is run. +/// Plugins make it easy to export (and re-import) systems, systems sets, and resources. +/// +/// After you import a plugin to an [`App`](crate::App) using [`add_plugin`](App::add_plugin), the app will run its [`build`](Plugin::build) function. pub trait Plugin: Any + Send + Sync { - /// Configures the [`App`] to which this plugin is added. + /// Applies the stored configuration to the given [`App`]. fn build(&self, app: &mut App); - /// Configures a name for the [`Plugin`]. Primarily for debugging. + /// Returns the name of this plugin. fn name(&self) -> &str { std::any::type_name::() } } -/// Type representing an unsafe function that returns a mutable pointer to a [`Plugin`]. -/// Used for dynamically loading plugins. See -/// `bevy_dynamic_plugin/src/loader.rs#dynamically_load_plugin` +/// An alias for an unsafe function that returns a pointer to a [`Plugin`]. +/// Used for dynamically loading plugins, +/// as shown in [this example][`bevy_dynamic_plugin/src/loader.rs#dynamically_load_plugin`]. pub type CreatePlugin = unsafe fn() -> *mut dyn Plugin; diff --git a/crates/bevy_app/src/plugin_group.rs b/crates/bevy_app/src/plugin_group.rs index aecb3dd05acda..6dcc88c9380ef 100644 --- a/crates/bevy_app/src/plugin_group.rs +++ b/crates/bevy_app/src/plugin_group.rs @@ -2,20 +2,20 @@ use crate::{App, Plugin}; use bevy_utils::{tracing::debug, HashMap}; use std::any::TypeId; -/// Combines multiple [`Plugin`]s into a single unit. +/// Combines multiple [`Plugin`] instances into one. pub trait PluginGroup { - /// Configures the [`Plugin`]s that are to be added. + /// Builds all the plugins in the group. fn build(&mut self, group: &mut PluginGroupBuilder); } +/// A [`Plugin`] that is part of a [`PluginGroup`]. struct PluginEntry { plugin: Box, enabled: bool, } /// Facilitates the creation and configuration of a [`PluginGroup`]. -/// Provides a build ordering to ensure that [`Plugin`]s which produce/require a resource -/// are built before/after dependent/depending [`Plugin`]s. +/// Defines a [`Plugin`] build order to enable plugins being dependent on other plugins. #[derive(Default)] pub struct PluginGroupBuilder { plugins: HashMap, @@ -23,7 +23,7 @@ pub struct PluginGroupBuilder { } impl PluginGroupBuilder { - /// Appends a [`Plugin`] to the [`PluginGroupBuilder`]. + /// Appends the [`Plugin`] to the end of the current set. pub fn add(&mut self, plugin: T) -> &mut Self { self.order.push(TypeId::of::()); self.plugins.insert( @@ -36,7 +36,7 @@ impl PluginGroupBuilder { self } - /// Configures a [`Plugin`] to be built before another plugin. + /// Configures the [`Plugin`] to be built before the `Target` plugin. pub fn add_before(&mut self, plugin: T) -> &mut Self { let target_index = self .order @@ -61,7 +61,7 @@ impl PluginGroupBuilder { self } - /// Configures a [`Plugin`] to be built after another plugin. + /// Configures the [`Plugin`] to be built after the `Target` plugin. pub fn add_after(&mut self, plugin: T) -> &mut Self { let target_index = self .order @@ -86,10 +86,10 @@ impl PluginGroupBuilder { self } - /// Enables a [`Plugin`] + /// Enables the [`Plugin`]. /// - /// [`Plugin`]s within a [`PluginGroup`] are enabled by default. This function is used to - /// opt back in to a [`Plugin`] after [disabling](Self::disable) it. + /// Plugins within a [`PluginGroup`] are enabled by default. This function will + /// re-activate a plugin if [`disable`](Self::disable) was called earlier. pub fn enable(&mut self) -> &mut Self { let mut plugin_entry = self .plugins @@ -99,7 +99,7 @@ impl PluginGroupBuilder { self } - /// Disables a [`Plugin`], preventing it from being added to the `App` with the rest of the [`PluginGroup`]. + /// Disables the [`Plugin`], stopping it from being built by an [`App`] with the rest of the [`PluginGroup`]. pub fn disable(&mut self) -> &mut Self { let mut plugin_entry = self .plugins @@ -109,7 +109,8 @@ impl PluginGroupBuilder { self } - /// Consumes the [`PluginGroupBuilder`] and [builds](Plugin::build) the contained [`Plugin`]s. + /// Consumes the [`PluginGroupBuilder`] and runs the [`build`](Plugin::build) function of each + /// contained [`Plugin`] in the specified order. pub fn finish(self, app: &mut App) { for ty in self.order.iter() { if let Some(entry) = self.plugins.get(ty) { diff --git a/crates/bevy_app/src/schedule_runner.rs b/crates/bevy_app/src/schedule_runner.rs index eb66c395a104f..e1063140ef062 100644 --- a/crates/bevy_app/src/schedule_runner.rs +++ b/crates/bevy_app/src/schedule_runner.rs @@ -10,16 +10,16 @@ use std::{cell::RefCell, rc::Rc}; #[cfg(target_arch = "wasm32")] use wasm_bindgen::{prelude::*, JsCast}; -/// Determines the method used to run an [App]'s [`Schedule`](bevy_ecs::schedule::Schedule). +/// Determines the method used to run an [`App`] schedule. #[derive(Copy, Clone, Debug)] pub enum RunMode { - /// Indicates that the [`App`]'s schedule should run repeatedly. + /// The schedule should be run repeatedly. Loop { - /// Minimum duration to wait after a schedule has completed before repeating. + /// Minimum duration to wait after an update has completed before running another. /// A value of [`None`] will not wait. wait: Option, }, - /// Indicates that the [`App`]'s schedule should run only once. + /// The schedule should be run a single time. Once, } @@ -54,8 +54,7 @@ impl ScheduleRunnerSettings { } } -/// Configures an `App` to run its [`Schedule`](bevy_ecs::schedule::Schedule) according to a given -/// [`RunMode`] +/// Configures an [`App`] to run its schedule according to a given [`RunMode`]. #[derive(Default)] pub struct ScheduleRunnerPlugin; diff --git a/crates/bevy_core/src/lib.rs b/crates/bevy_core/src/lib.rs index 2f83f59ce7caf..105bd4f382ea6 100644 --- a/crates/bevy_core/src/lib.rs +++ b/crates/bevy_core/src/lib.rs @@ -16,32 +16,78 @@ pub mod prelude { //! The Bevy Core Prelude. #[doc(hidden)] pub use crate::{ - DefaultTaskPoolOptions, FixedTime, FixedTimestep, FixedTimestepState, Name, Time, Timer, + CoreSet, DefaultTaskPoolOptions, FixedTime, FixedTimestepState, Name, Time, Timer, }; } -use bevy_app::prelude::*; +use bevy_app::{prelude::*, AppSystem}; use bevy_ecs::{ entity::Entity, - schedule::{IntoSystemDescriptor, SystemLabel}, + schedule::{apply_buffers, chain, IntoScheduledSet, IntoScheduledSystem, SystemLabel}, }; use bevy_utils::HashSet; + use std::ops::Range; -/// Adds core functionality to Apps. +/// [`Plugin`] that adds a standard system execution sequence. #[derive(Default)] pub struct CorePlugin; -/// A `SystemLabel` enum for ordering systems relative to core Bevy systems. +/// Systems sets comprising the standard execution sequence. +#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)] +pub enum CoreSet { + /// Systems that run before the systems in the other core sets (but after [`Time`] is updated). + First, + /// Systems that run with the fixed timestep. Intended for most gameplay systems (e.g. physics). + /// + /// **Note:** Fixed timestep does not mean fixed *interval*. This set can repeat several times in a single frame. Use [`FixedTime`] instead of [`Time`] to ensure correct behavior. + FixedUpdate, + /// Systems that run before systems in [`Update`](CoreSet::Update). + /// Intended for systems that need to perform setup for systems in [`Update`](CoreSet::Update). + PreUpdate, + /// Systems that run each frame. + /// + /// **Note:** By default, systems and sets with no parent set specified are added here. + Update, + /// Systems that run after systems in [`Update`](CoreSet::Update). + /// Intended for systems that need to process the results of systems in [`Update`](CoreSet::Update). + PostUpdate, + /// Systems that run after the systems in the other core sets. + Last, +} + +/// Systems comprising the standard execution sequence. +#[doc(hidden)] #[derive(Debug, PartialEq, Eq, Clone, Hash, SystemLabel)] pub enum CoreSystem { - /// Advances time. Any system that interacts with the [`Time`] resource should run after this. + /// Advances [`Time`]. First thing that runs in a frame. Time, + /// Advances [`FixedTime`] and runs the systems under [`FixedUpdate`](CoreSet::FixedUpdate). + FixedUpdate, + /// Calls [`apply_buffers`] after the systems under [`First`](CoreSet::First). + ApplyFirst, + /// Calls [`apply_buffers`] after the systems under [`FixedUpdate`](CoreSet::FixedUpdate). + ApplyFixedUpdate, + /// Calls [`apply_buffers`] after the systems under [`PreUpdate`](CoreSet::PreUpdate). + ApplyPreUpdate, + /// Calls [`apply_buffers`] after the systems under [`Update`](CoreSet::Update). + ApplyUpdate, + /// Calls [`apply_buffers`] after the systems under [`PostUpdate`](CoreSet::PostUpdate). + ApplyPostUpdate, + /// Calls [`apply_buffers`] after the systems under [`Last`](CoreSet::Last). + ApplyLast, +} + +/// Internal system sets needed to bypass limitations with [`apply_buffers`]. +#[doc(hidden)] +#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)] +pub(crate) enum CoreInternalSet { + /// Encompasses [`CoreSet::FixedUpdate`] and [`CoreSystem::ApplyFixedUpdate`]. + FixedUpdate, } impl Plugin for CorePlugin { fn build(&self, app: &mut App) { - // Setup the default bevy task pools app.world .get_resource::() .cloned() @@ -57,11 +103,31 @@ impl Plugin for CorePlugin { .register_type::() .register_type::>() .register_type::() - // time system is added to the "AtStart" set to ensure it runs before other systems - // in CoreStage::First - .add_system_to_stage( - CoreStage::First, - time_system.at_start().label(CoreSystem::Time), + .add_many( + chain![ + update_time.named(CoreSystem::Time), + CoreSet::First, + apply_buffers.named(CoreSystem::ApplyFirst), + fixed_update.named(CoreSystem::FixedUpdate), + CoreSet::PreUpdate, + apply_buffers.named(CoreSystem::ApplyPreUpdate), + CoreSet::Update, + apply_buffers.named(CoreSystem::ApplyUpdate), + CoreSet::PostUpdate, + apply_buffers.named(CoreSystem::ApplyPostUpdate), + CoreSet::Last, + apply_buffers.named(CoreSystem::ApplyLast), + ] + .to(AppSet::Update) + .after(AppSet::UpdateEvents) + .before(AppSystem::ClearTrackers), + ) + .add_many( + chain![ + CoreSet::FixedUpdate, + apply_buffers.named(CoreSystem::ApplyFixedUpdate), + ] + .to(CoreInternalSet::FixedUpdate), ); register_rust_types(app); diff --git a/crates/bevy_core/src/time/fixed_timestep.rs b/crates/bevy_core/src/time/fixed_timestep.rs index 10814e8641a8e..619efd4cd7847 100644 --- a/crates/bevy_core/src/time/fixed_timestep.rs +++ b/crates/bevy_core/src/time/fixed_timestep.rs @@ -1,20 +1,16 @@ use bevy_ecs::{ - schedule::ShouldRun, - system::ResMut, + change_detection::Mut, + schedule::run_scheduled, world::{FromWorld, World}, }; use bevy_utils::{Duration, Instant}; -use crate::Time; +use crate::{CoreInternalSet, Time}; -/// The default step size used by [`FixedTimestep`]. -// 60Hz is a popular tick rate, but it can't be expressed as an exact float. -// The nearby power of two, 64Hz, is more stable for numerical integration. -pub const DEFAULT_TIMESTEP: Duration = Duration::from_micros(15625); // 64Hz - -/// Tracks how much time has advanced since its previous update and since the app was started. +/// A [`Time`]-substitute that only advances in increments of a constant [`delta`](FixedTime::delta). +/// Intended for systems running under the [`FixedUpdate`](crate::CoreSet::FixedUpdate) system set. /// -/// It's just like [`Time`] (and internally coupled to it), except it advances in fixed increments. +/// [`FixedTime`] is synchronized to [`Time`]. #[derive(Debug, Clone)] pub struct FixedTime { startup: Instant, @@ -30,16 +26,14 @@ pub struct FixedTime { impl FromWorld for FixedTime { fn from_world(world: &mut World) -> Self { - let time = world - .get_resource::

(self, condition: impl IntoRunCondition

) -> SetDescriptor; + fn iff

(self, condition: impl IntoRunCondition

) -> ScheduledSet; } -impl IntoSetDescriptor for L +impl IntoScheduledSet for L where - L: SystemLabel + sealed::IntoSetDescriptor, + L: SystemLabel + sealed::IntoScheduledSet, { - fn into_descriptor(self) -> SetDescriptor { + fn schedule(self) -> ScheduledSet { new_set(Box::new(self)) } - fn to(self, label: impl SystemLabel) -> SetDescriptor { + fn to(self, label: impl SystemLabel) -> ScheduledSet { new_set(Box::new(self)).to(label) } - fn before(self, label: impl AsSystemLabel) -> SetDescriptor { + fn before(self, label: impl AsSystemLabel) -> ScheduledSet { new_set(Box::new(self)).before(label) } - fn after(self, label: impl AsSystemLabel) -> SetDescriptor { + fn after(self, label: impl AsSystemLabel) -> ScheduledSet { new_set(Box::new(self)).after(label) } - fn iff

(self, condition: impl IntoRunCondition

) -> SetDescriptor { + fn iff

(self, condition: impl IntoRunCondition

) -> ScheduledSet { new_set(Box::new(self)).iff(condition) } } -impl IntoSetDescriptor for BoxedSystemLabel { - fn into_descriptor(self) -> SetDescriptor { +impl IntoScheduledSet for BoxedSystemLabel { + fn schedule(self) -> ScheduledSet { new_set(self) } - fn to(self, label: impl SystemLabel) -> SetDescriptor { + fn to(self, label: impl SystemLabel) -> ScheduledSet { new_set(self).to(label) } - fn before(self, label: impl AsSystemLabel) -> SetDescriptor { + fn before(self, label: impl AsSystemLabel) -> ScheduledSet { new_set(self).before(label) } - fn after(self, label: impl AsSystemLabel) -> SetDescriptor { + fn after(self, label: impl AsSystemLabel) -> ScheduledSet { new_set(self).after(label) } - fn iff

(self, condition: impl IntoRunCondition

) -> SetDescriptor { + fn iff

(self, condition: impl IntoRunCondition

) -> ScheduledSet { new_set(self).iff(condition) } } -impl IntoSetDescriptor for SetDescriptor { - fn into_descriptor(self) -> SetDescriptor { +impl IntoScheduledSet for ScheduledSet { + fn schedule(self) -> ScheduledSet { self } - fn to(mut self, label: impl SystemLabel) -> SetDescriptor { - self.config.sets.insert(label.dyn_clone()); + fn to(mut self, label: impl SystemLabel) -> ScheduledSet { + self.scheduling.sets.insert(label.dyn_clone()); self } - fn before(mut self, label: impl AsSystemLabel) -> SetDescriptor { - self.config + fn before(mut self, label: impl AsSystemLabel) -> ScheduledSet { + self.scheduling .edges .push((Order::Before, label.as_system_label().dyn_clone())); self } - fn after(mut self, label: impl AsSystemLabel) -> SetDescriptor { - self.config + fn after(mut self, label: impl AsSystemLabel) -> ScheduledSet { + self.scheduling .edges .push((Order::After, label.as_system_label().dyn_clone())); self } - fn iff

(mut self, condition: impl IntoRunCondition

) -> SetDescriptor { - self.run_criteria + fn iff

(mut self, condition: impl IntoRunCondition

) -> ScheduledSet { + self.conditions .push(Box::new(IntoSystem::into_system(condition))); self } } /// Encapsulates a [`System`](crate::system::System) and information on when it should run. -pub struct SystemDescriptor { +pub struct ScheduledSystem { pub(crate) system: BoxedSystem, - pub(crate) config: GraphConfig, - pub(crate) run_criteria: Vec, + pub(crate) scheduling: Scheduling, + pub(crate) conditions: Vec, } -fn new_system(system: BoxedSystem) -> SystemDescriptor { - SystemDescriptor { +fn new_system(system: BoxedSystem) -> ScheduledSystem { + ScheduledSystem { system, - config: GraphConfig { + scheduling: Scheduling { name: None, sets: HashSet::new(), edges: Vec::new(), }, - run_criteria: Vec::new(), + conditions: Vec::new(), } } -/// Types that can be converted into a [`SystemDescriptor`]. +/// Types that can be converted into a [`ScheduledSystem`]. /// -/// Implementation is restricted to types that implement [`System`](crate::system::System) -/// and matching trait objects. -pub trait IntoSystemDescriptor: sealed::IntoSystemDescriptor { - fn into_descriptor(self) -> SystemDescriptor; +/// Implemented for types that implement [`System`](crate::system::System) +/// and boxed trait objects. +pub trait IntoScheduledSystem: sealed::IntoScheduledSystem { + fn schedule(self) -> ScheduledSystem; /// Sets `name` as the unique label for this instance of the system. - fn named(self, name: impl SystemLabel) -> SystemDescriptor; + fn named(self, name: impl SystemLabel) -> ScheduledSystem; /// Configures the system to run under the set given by `label`. - fn to(self, label: impl SystemLabel) -> SystemDescriptor; + fn to(self, label: impl SystemLabel) -> ScheduledSystem; /// Configures the system to run before `label`. - fn before(self, label: impl AsSystemLabel) -> SystemDescriptor; + fn before(self, label: impl AsSystemLabel) -> ScheduledSystem; /// Configures the system to run after `label`. - fn after(self, label: impl AsSystemLabel) -> SystemDescriptor; + fn after(self, label: impl AsSystemLabel) -> ScheduledSystem; /// Configures the system to run only if `condition` returns `true`. - fn iff

(self, condition: impl IntoRunCondition

) -> SystemDescriptor; + fn iff

(self, condition: impl IntoRunCondition

) -> ScheduledSystem; } -impl IntoSystemDescriptor for F +impl IntoScheduledSystem for F where - F: IntoSystem<(), (), Params> + sealed::IntoSystemDescriptor, + F: IntoSystem<(), (), Params> + sealed::IntoScheduledSystem, { - fn into_descriptor(self) -> SystemDescriptor { + fn schedule(self) -> ScheduledSystem { new_system(Box::new(IntoSystem::into_system(self))) } - fn named(self, name: impl SystemLabel) -> SystemDescriptor { + fn named(self, name: impl SystemLabel) -> ScheduledSystem { new_system(Box::new(IntoSystem::into_system(self))).named(name) } - fn to(self, label: impl SystemLabel) -> SystemDescriptor { + fn to(self, label: impl SystemLabel) -> ScheduledSystem { new_system(Box::new(IntoSystem::into_system(self))).to(label) } - fn before(self, label: impl AsSystemLabel) -> SystemDescriptor { + fn before(self, label: impl AsSystemLabel) -> ScheduledSystem { new_system(Box::new(IntoSystem::into_system(self))).before(label) } - fn after(self, label: impl AsSystemLabel) -> SystemDescriptor { + fn after(self, label: impl AsSystemLabel) -> ScheduledSystem { new_system(Box::new(IntoSystem::into_system(self))).after(label) } - fn iff

(self, condition: impl IntoRunCondition

) -> SystemDescriptor { + fn iff

(self, condition: impl IntoRunCondition

) -> ScheduledSystem { new_system(Box::new(IntoSystem::into_system(self))).iff(condition) } } -impl IntoSystemDescriptor<()> for BoxedSystem<(), ()> { - fn into_descriptor(self) -> SystemDescriptor { +impl IntoScheduledSystem<()> for BoxedSystem<(), ()> { + fn schedule(self) -> ScheduledSystem { new_system(self) } - fn named(self, name: impl SystemLabel) -> SystemDescriptor { + fn named(self, name: impl SystemLabel) -> ScheduledSystem { new_system(self).named(name) } - fn to(self, label: impl SystemLabel) -> SystemDescriptor { + fn to(self, label: impl SystemLabel) -> ScheduledSystem { new_system(self).to(label) } - fn before(self, label: impl AsSystemLabel) -> SystemDescriptor { + fn before(self, label: impl AsSystemLabel) -> ScheduledSystem { new_system(self).before(label) } - fn after(self, label: impl AsSystemLabel) -> SystemDescriptor { + fn after(self, label: impl AsSystemLabel) -> ScheduledSystem { new_system(self).after(label) } - fn iff

(self, condition: impl IntoRunCondition

) -> SystemDescriptor { + fn iff

(self, condition: impl IntoRunCondition

) -> ScheduledSystem { new_system(self).iff(condition) } } -impl IntoSystemDescriptor<()> for SystemDescriptor { - fn into_descriptor(self) -> SystemDescriptor { +impl IntoScheduledSystem<()> for ScheduledSystem { + fn schedule(self) -> ScheduledSystem { self } - fn named(mut self, name: impl SystemLabel) -> SystemDescriptor { - self.config.name = Some(name.dyn_clone()); + fn named(mut self, name: impl SystemLabel) -> ScheduledSystem { + self.scheduling.name = Some(name.dyn_clone()); self } - fn to(mut self, label: impl SystemLabel) -> SystemDescriptor { - self.config.sets.insert(label.dyn_clone()); + fn to(mut self, label: impl SystemLabel) -> ScheduledSystem { + self.scheduling.sets.insert(label.dyn_clone()); self } - fn before(mut self, label: impl AsSystemLabel) -> SystemDescriptor { - self.config + fn before(mut self, label: impl AsSystemLabel) -> ScheduledSystem { + self.scheduling .edges .push((Order::Before, label.as_system_label().dyn_clone())); self } - fn after(mut self, label: impl AsSystemLabel) -> SystemDescriptor { - self.config + fn after(mut self, label: impl AsSystemLabel) -> ScheduledSystem { + self.scheduling .edges .push((Order::After, label.as_system_label().dyn_clone())); self } - fn iff

(mut self, condition: impl IntoRunCondition

) -> SystemDescriptor { - self.run_criteria + fn iff

(mut self, condition: impl IntoRunCondition

) -> ScheduledSystem { + self.conditions .push(Box::new(IntoSystem::into_system(condition))); self } @@ -336,61 +336,61 @@ mod sealed { system::{BoxedSystem, IntoSystem}, }; - use super::{SetDescriptor, SystemDescriptor}; + use super::{ScheduledSet, ScheduledSystem}; // These traits are private because non-`()` systems cannot be used. // The type system doesn't allow for mixed type collections. // Maybe we could do funky transmutes on the fn pointers like we do for `CommandQueue`. - pub trait IntoSystemDescriptor {} + pub trait IntoScheduledSystem {} - impl> IntoSystemDescriptor for F {} + impl> IntoScheduledSystem for F {} - impl IntoSystemDescriptor<()> for BoxedSystem<(), ()> {} + impl IntoScheduledSystem<()> for BoxedSystem<(), ()> {} - impl IntoSystemDescriptor<()> for SystemDescriptor {} + impl IntoScheduledSystem<()> for ScheduledSystem {} - pub trait IntoSetDescriptor {} + pub trait IntoScheduledSet {} - impl IntoSetDescriptor for L {} + impl IntoScheduledSet for L {} - impl IntoSetDescriptor for BoxedSystemLabel {} + impl IntoScheduledSet for BoxedSystemLabel {} - impl IntoSetDescriptor for SetDescriptor {} + impl IntoScheduledSet for ScheduledSet {} } -/// Generalizes system and system set descriptors. -pub enum Descriptor { - System(SystemDescriptor), - Set(SetDescriptor), +/// Enum for pseudo-type elision of scheduled systems and system sets. +pub enum Scheduled { + System(ScheduledSystem), + Set(ScheduledSet), } -impl Descriptor { +impl Scheduled { fn name(&self) -> BoxedSystemLabel { match self { - Self::System(system) => system.config.name().unwrap().dyn_clone(), - Self::Set(set) => set.config.name().unwrap().dyn_clone(), + Self::System(system) => system.scheduling.name().unwrap().dyn_clone(), + Self::Set(set) => set.scheduling.name().unwrap().dyn_clone(), } } } -impl SetDescriptor { - pub fn into_descriptor_enum(self) -> Descriptor { - Descriptor::Set(self) +impl ScheduledSet { + pub fn into_enum(self) -> Scheduled { + Scheduled::Set(self) } } -impl SystemDescriptor { - pub fn into_descriptor_enum(self) -> Descriptor { - Descriptor::System(self) +impl ScheduledSystem { + pub fn into_enum(self) -> Scheduled { + Scheduled::System(self) } } #[doc(hidden)] /// Describes multiple systems and system sets. -pub struct Group(Vec); +pub struct Group(Vec); impl Group { - pub fn new(mut vec: Vec) -> Self { + pub fn new(mut vec: Vec) -> Self { Self(vec) } @@ -398,11 +398,11 @@ impl Group { pub fn to(mut self, label: impl SystemLabel) -> Self { for node in self.0.iter_mut() { match node { - Descriptor::System(system) => { - system.config.sets.insert(label.dyn_clone()); + Scheduled::System(system) => { + system.scheduling.sets.insert(label.dyn_clone()); } - Descriptor::Set(set) => { - set.config.sets.insert(label.dyn_clone()); + Scheduled::Set(set) => { + set.scheduling.sets.insert(label.dyn_clone()); } }; } @@ -414,11 +414,16 @@ impl Group { pub fn before(mut self, label: impl SystemLabel) -> Self { for node in self.0.iter_mut() { match node { - Descriptor::System(system) => { - system.config.edges.push((Order::Before, label.dyn_clone())); + Scheduled::System(system) => { + system + .scheduling + .edges + .push((Order::Before, label.dyn_clone())); } - Descriptor::Set(set) => { - set.config.edges.push((Order::Before, label.dyn_clone())); + Scheduled::Set(set) => { + set.scheduling + .edges + .push((Order::Before, label.dyn_clone())); } } } @@ -430,11 +435,14 @@ impl Group { pub fn after(mut self, label: impl SystemLabel) -> Self { for node in self.0.iter_mut() { match node { - Descriptor::System(system) => { - system.config.edges.push((Order::After, label.dyn_clone())); + Scheduled::System(system) => { + system + .scheduling + .edges + .push((Order::After, label.dyn_clone())); } - Descriptor::Set(set) => { - set.config.edges.push((Order::After, label.dyn_clone())); + Scheduled::Set(set) => { + set.scheduling.edges.push((Order::After, label.dyn_clone())); } } } @@ -444,8 +452,8 @@ impl Group { } impl IntoIterator for Group { - type Item = Descriptor; - type IntoIter = std::vec::IntoIter; + type Item = Scheduled; + type IntoIter = std::vec::IntoIter; fn into_iter(self) -> Self::IntoIter { self.0.into_iter() @@ -454,19 +462,19 @@ impl IntoIterator for Group { #[doc(hidden)] /// Describes multiple systems and system sets that are ordered in a sequence. -pub struct Chain(Vec); +pub struct Chain(Vec); impl Chain { - pub fn new(mut vec: Vec) -> Self { + pub fn new(mut vec: Vec) -> Self { let n = vec.len(); let names = vec.iter().skip(1).map(|c| c.name()).collect::>(); for (node, next_node) in vec.iter_mut().take(n - 1).zip(names.into_iter()) { match node { - Descriptor::System(system) => { - system.config.edges.push((Order::Before, next_node)); + Scheduled::System(system) => { + system.scheduling.edges.push((Order::Before, next_node)); } - Descriptor::Set(set) => { - set.config.edges.push((Order::Before, next_node)); + Scheduled::Set(set) => { + set.scheduling.edges.push((Order::Before, next_node)); } } } @@ -478,11 +486,11 @@ impl Chain { pub fn to(mut self, label: impl SystemLabel) -> Self { for node in self.0.iter_mut() { match node { - Descriptor::System(system) => { - system.config.sets.insert(label.dyn_clone()); + Scheduled::System(system) => { + system.scheduling.sets.insert(label.dyn_clone()); } - Descriptor::Set(set) => { - set.config.sets.insert(label.dyn_clone()); + Scheduled::Set(set) => { + set.scheduling.sets.insert(label.dyn_clone()); } }; } @@ -494,11 +502,16 @@ impl Chain { pub fn before(mut self, label: impl SystemLabel) -> Self { if let Some(last) = self.0.last_mut() { match last { - Descriptor::System(system) => { - system.config.edges.push((Order::Before, label.dyn_clone())); + Scheduled::System(system) => { + system + .scheduling + .edges + .push((Order::Before, label.dyn_clone())); } - Descriptor::Set(set) => { - set.config.edges.push((Order::Before, label.dyn_clone())); + Scheduled::Set(set) => { + set.scheduling + .edges + .push((Order::Before, label.dyn_clone())); } } } @@ -510,11 +523,14 @@ impl Chain { pub fn after(mut self, label: impl SystemLabel) -> Self { if let Some(first) = self.0.first_mut() { match first { - Descriptor::System(system) => { - system.config.edges.push((Order::After, label.dyn_clone())); + Scheduled::System(system) => { + system + .scheduling + .edges + .push((Order::After, label.dyn_clone())); } - Descriptor::Set(set) => { - set.config.edges.push((Order::After, label.dyn_clone())); + Scheduled::Set(set) => { + set.scheduling.edges.push((Order::After, label.dyn_clone())); } } } @@ -524,26 +540,30 @@ impl Chain { } impl IntoIterator for Chain { - type Item = Descriptor; - type IntoIter = std::vec::IntoIter; + type Item = Scheduled; + type IntoIter = std::vec::IntoIter; fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } } -#[macro_export] /// A mixed group of systems and system sets. +#[macro_export] macro_rules! group { ($($x:expr),+ $(,)?) => { - bevy_ecs::schedule::Group::new(vec![$(($x).into_descriptor().into_descriptor_enum()),+]) + bevy_ecs::schedule::Group::new(vec![$(($x).schedule().into_enum()),+]) }; } -#[macro_export] +pub use group; + /// A mixed group of systems and system sets, ordered in a sequence. +#[macro_export] macro_rules! chain { ($($x:expr),+ $(,)?) => { - bevy_ecs::schedule::Chain::new(vec![$(($x).into_descriptor().into_descriptor_enum()),+]) + bevy_ecs::schedule::Chain::new(vec![$(($x).schedule().into_enum()),+]) }; } + +pub use chain; diff --git a/crates/bevy_ecs/src/schedule/fsm.rs b/crates/bevy_ecs/src/schedule/fsm.rs index d51e1edf90b53..0bc858950f33b 100644 --- a/crates/bevy_ecs/src/schedule/fsm.rs +++ b/crates/bevy_ecs/src/schedule/fsm.rs @@ -1,7 +1,7 @@ use crate::{ self as bevy_ecs, change_detection::Mut, - schedule::{run_systems, SystemLabel}, + schedule::{run_scheduled, SystemLabel}, system::Res, world::World, }; @@ -13,15 +13,19 @@ use thiserror::Error; pub trait State: 'static + Send + Sync + Clone + PartialEq + Eq + Debug + Hash {} impl State for T where T: 'static + Send + Sync + Clone + PartialEq + Eq + Debug + Hash {} -/// A [`SystemLabel`] for a state's "on enter" transition. +/// A [`SystemLabel`] for the system set that runs during a state's "on enter" transition. #[derive(Debug, Clone, Hash, PartialEq, Eq, SystemLabel)] pub struct OnEnter(pub S); -/// A [`SystemLabel`] for a state's "on exit" transition. +/// A [`SystemLabel`] for the system set that runs during a state's "on exit" transition. #[derive(Debug, Clone, Hash, PartialEq, Eq, SystemLabel)] pub struct OnExit(pub S); -/// A simple finite-state machine. +/// A simple finite-state machine whose transitions (enter and exit) can have associated system sets +/// ([`OnEnter(s)`] and [`OnExit(s)`]). +/// +/// A state transition can be queued with [`queue_transition`](Fsm::queue_transition), and it will +/// be applied the next time an [`apply_state_transition::`] system runs. #[derive(Debug, Clone)] pub struct Fsm { current_state: S, @@ -30,6 +34,7 @@ pub struct Fsm { } impl Fsm { + /// Constructs a new `Fsm` that starts in the specified initial state. pub fn new(initial_state: S) -> Self { Self { current_state: initial_state, @@ -55,59 +60,21 @@ impl Fsm { /// Sets `state` to become the machine's next state. /// - /// # Errors - /// - /// Errors if a transition has already been queued or transitions leads to same state. - pub fn queue_transition(&mut self, state: S) -> Result<(), StateTransitionError> { - if *self.current_state() == state { - return Err(StateTransitionError::AlreadyInState); - } - - if self.next_state.is_some() { - return Err(StateTransitionError::TransitionAlreadyQueued); - } - - self.next_state = Some(state); - - Ok(()) - } -} - -/// Returns `true` if the machine exists. -pub fn state_exists() -> impl FnMut(Option>>) -> bool { - move |fsm: Option>>| fsm.is_some() -} - -/// Returns `true` if the machine is currently in `state`. -pub fn state_equals(state: S) -> impl FnMut(Res>) -> bool { - move |fsm: Res>| *fsm.current_state() == state -} - -/// Returns `true` if the machine exists and is currently in `state`. -pub fn state_exists_and_equals(state: S) -> impl FnMut(Option>>) -> bool { - move |fsm: Option>>| match fsm { - Some(fsm) => *fsm.current_state() == state, - None => false, + /// If a transition was already queued, replaces and returns the queued state. + pub fn queue_transition(&mut self, state: S) -> Option { + std::mem::replace(&mut self.next_state, Some(state)) } } /// If state transition queued, applies it, then runs the systems under [`OnExit(old_state)`] -/// and [`OnEnter(new_state)`]. +/// and then [`OnEnter(new_state)`]. pub fn apply_state_transition(world: &mut World) { world.resource_scope(|world, mut fsm: Mut>| { if let Some(new_state) = fsm.next_state.take() { let old_state = std::mem::replace(&mut fsm.current_state, new_state.clone()); fsm.prev_state = Some(old_state.clone()); - run_systems(OnExit(old_state), world); - run_systems(OnEnter(new_state), world); + run_scheduled(OnExit(old_state), world); + run_scheduled(OnEnter(new_state), world); } }); } - -#[derive(Debug, Error)] -pub enum StateTransitionError { - #[error("queued transition to same state")] - AlreadyInState, - #[error("queued transition when one was already queued")] - TransitionAlreadyQueued, -} diff --git a/crates/bevy_ecs/src/schedule/mod.rs b/crates/bevy_ecs/src/schedule/mod.rs index 4b9e7dd6d8c06..c464bdda417e9 100644 --- a/crates/bevy_ecs/src/schedule/mod.rs +++ b/crates/bevy_ecs/src/schedule/mod.rs @@ -1,12 +1,5 @@ #![allow(warnings)] //! Tools for controlling system execution. -//! -//! When using Bevy ECS, systems are usually run from a [`SystemRegistry`], not directly. -//! -//! # Running systems in order -//! -//! - Systems can be arranged through a combination of `.label()`, `.before()`, and `.after()` methods. -//! - When systems have an undefined order, their command queues may be applied in a different order each app run. mod condition; mod descriptor; @@ -82,17 +75,17 @@ enum Cycle { /// A schematic for running a `System` collection on a `World`. pub(crate) struct SetMetadata { /// A graph containing all systems and sets directly under this set. - graph: DiGraphMap, + graph: DiGraphMap, /// A graph containing all systems under this set. - flat: DiGraphMap, + flat: DiGraphMap, /// The sub-hierarchy under this set. - hier: DiGraphMap, + hier: DiGraphMap, /// A cached topological order for `graph`. - topsort: Vec, + topsort: Vec, /// A cached topological order for `flat`. - flat_topsort: Vec, + flat_topsort: Vec, /// A cached topological order for `hier`. - hier_topsort: Vec, + hier_topsort: Vec, /// The combined component access of all systems under this set. component_access: Access, /// Does this metadata needs to be rebuilt before set can run again? @@ -121,15 +114,15 @@ pub struct Builder<'a> { } impl Builder<'_> { - pub fn add_system

(&mut self, system: impl IntoSystemDescriptor

) { + pub fn add_system

(&mut self, system: impl IntoScheduledSystem

) { self.registry.add_system(system); } - pub fn add_set(&mut self, set: impl IntoSetDescriptor) { + pub fn add_set(&mut self, set: impl IntoScheduledSet) { self.registry.add_set(set); } - pub fn add_many(&mut self, nodes: impl IntoIterator) { + pub fn add_many(&mut self, nodes: impl IntoIterator) { self.registry.add_many(nodes); } @@ -146,15 +139,15 @@ impl Builder<'_> { pub struct SystemRegistry { world_id: Option, next_id: u64, - ids: HashMap, - // names: HashMap, - hier: DiGraphMap, - pub(crate) systems: HashMap>, - pub(crate) system_conditions: HashMap>>, - pub(crate) runners: HashMap>, - pub(crate) set_conditions: HashMap>>, - pub(crate) set_metadata: HashMap, - uninit_nodes: Vec, + ids: HashMap, + // names: HashMap, + hier: DiGraphMap, + pub(crate) systems: HashMap>, + pub(crate) system_conditions: HashMap>>, + pub(crate) runners: HashMap>, + pub(crate) set_conditions: HashMap>>, + pub(crate) set_metadata: HashMap, + uninit_nodes: Vec, } impl Default for SystemRegistry { @@ -181,60 +174,60 @@ impl SystemRegistry { } /// Registers a [`System`](crate::system::System) and returns its ID. - pub fn add_system

(&mut self, system: impl IntoSystemDescriptor

) -> RegistryId { - let SystemDescriptor { + pub fn add_system

(&mut self, system: impl IntoScheduledSystem

) -> RegId { + let ScheduledSystem { system, - mut config, - run_criteria, - } = system.into_descriptor(); + mut scheduling, + conditions, + } = system.schedule(); - if config.name().is_none() { - config.name = Some(system.name().dyn_clone()); + if scheduling.name().is_none() { + scheduling.name = Some(system.name().dyn_clone()); } - let name = config.name().unwrap(); + let name = scheduling.name().unwrap(); assert!(!self.ids.contains_key(name), "name already used"); - let id = RegistryId::System(self.next_id); + let id = RegId::System(self.next_id); self.next_id += 1; self.ids.insert(name.dyn_clone(), id); self.systems.insert(id, Some(system)); - self.system_conditions.insert(id, Some(run_criteria)); - self.uninit_nodes.push(config); + self.system_conditions.insert(id, Some(conditions)); + self.uninit_nodes.push(scheduling); id } /// Registers a [`System`](crate::system::System) set and returns its ID. - pub fn add_set(&mut self, set: impl IntoSetDescriptor) -> RegistryId { - let SetDescriptor { - config, - run_criteria, - } = set.into_descriptor(); + pub fn add_set(&mut self, set: impl IntoScheduledSet) -> RegId { + let ScheduledSet { + scheduling, + conditions, + } = set.schedule(); - let name = config.name().unwrap(); + let name = scheduling.name().unwrap(); assert!(!self.ids.contains_key(name), "name already used"); - let id = RegistryId::Set(self.next_id); + let id = RegId::Set(self.next_id); self.next_id += 1; self.ids.insert(name.dyn_clone(), id); self.set_metadata.insert(id, SetMetadata::default()); - self.set_conditions.insert(id, Some(run_criteria)); + self.set_conditions.insert(id, Some(conditions)); self.runners.insert(id, None); - self.uninit_nodes.push(config); + self.uninit_nodes.push(scheduling); id } - /// Registers multiple systems and system sets at once and returns their IDs. - pub fn add_many(&mut self, nodes: impl IntoIterator) -> Vec { + /// Registers multiple systems and system sets at the same time and returns their IDs. + pub fn add_many(&mut self, nodes: impl IntoIterator) -> Vec { let ids = nodes .into_iter() - .map(|desc| match desc { - Descriptor::System(system) => self.add_system(system), - Descriptor::Set(set) => self.add_set(set), + .map(|node| match node { + Scheduled::System(system) => self.add_system(system), + Scheduled::Set(set) => self.add_set(set), }) .collect(); @@ -288,9 +281,9 @@ impl SystemRegistry { fn initialize(&mut self, world: &mut World) -> Result<()> { // check for obvious errors - for config in self.uninit_nodes.iter() { - for label in config.sets().iter() { - if config.name() == Some(label) { + for scheduling in self.uninit_nodes.iter() { + for label in scheduling.sets().iter() { + if scheduling.name() == Some(label) { return Err(ScheduleBuildError::HierarchyLoop); } if self.ids.get(label).is_none() { @@ -300,8 +293,8 @@ impl SystemRegistry { return Err(ScheduleBuildError::InvalidSetLabel); } } - for (_order, label) in config.edges().iter() { - if config.name() == Some(label) { + for (_order, label) in scheduling.edges().iter() { + if scheduling.name() == Some(label) { return Err(ScheduleBuildError::DependencyLoop); } if self.ids.get(label).is_none() { @@ -311,32 +304,32 @@ impl SystemRegistry { } // convert labels to ids - let configs_indexed = self + let indexed = self .uninit_nodes .drain(..) - .map(|config| { - let name = config.name().unwrap(); + .map(|sched| { + let name = sched.name().unwrap(); let id = *self.ids.get(name).unwrap(); - let sets = config + let sets = sched .sets() .iter() .map(|label| *self.ids.get(label).unwrap()) .collect::>(); - let edges = config + let edges = sched .edges() .iter() .map(|(order, label)| (*order, *self.ids.get(label).unwrap())) .collect::>(); - (id, IndexedGraphConfig { sets, edges }) + (id, IndexedScheduling { sets, edges }) }) .collect::>(); // init the systems - for (id, config) in configs_indexed.iter() { + for (id, _scheduling) in indexed.iter() { match id { - RegistryId::System(_) => { + RegId::System(_) => { let system = self.systems.get_mut(&id).unwrap(); system.as_mut().unwrap().initialize(world); let conditions = self.system_conditions.get_mut(&id).unwrap(); @@ -345,7 +338,7 @@ impl SystemRegistry { .flatten() .for_each(|system| system.initialize(world)); } - RegistryId::Set(_) => { + RegId::Set(_) => { let conditions = self.set_conditions.get_mut(&id).unwrap(); conditions .iter_mut() @@ -356,32 +349,32 @@ impl SystemRegistry { } // add nodes to hierarchy - for (&id, config) in configs_indexed.iter() { + for (&id, scheduling) in indexed.iter() { self.hier.add_node(id); - for &set_id in config.sets().iter() { + for &set_id in scheduling.sets().iter() { self.hier.add_edge(set_id, id, ()); } // mark all sets above as modified self.walk_up(id)?; - for &set_id in config.sets().iter() { + for &set_id in scheduling.sets().iter() { let set = self.set_metadata.get_mut(&set_id).unwrap(); set.graph.add_node(id); } - for &(order, other_id) in config.edges().iter() { + for &(order, other_id) in scheduling.edges().iter() { let (before, after) = match order { Order::Before => (id, other_id), Order::After => (other_id, id), }; - let other_sets = configs_indexed.get(&other_id).unwrap().sets(); - if config.sets().is_disjoint(other_sets) { + let other_sets = indexed.get(&other_id).unwrap().sets(); + if scheduling.sets().is_disjoint(other_sets) { // TODO: consider allowing when satisfiable return Err(ScheduleBuildError::CrossDependency); } else { - for set_id in config.sets().intersection(other_sets) { + for set_id in scheduling.sets().intersection(other_sets) { let set = self.set_metadata.get_mut(&set_id).unwrap(); set.graph.add_edge(before, after, ()); } @@ -392,7 +385,7 @@ impl SystemRegistry { Ok(()) } - fn walk_up(&mut self, id: RegistryId) -> Result<()> { + fn walk_up(&mut self, id: RegId) -> Result<()> { #[cfg(feature = "trace")] let _guard = bevy_utils::tracing::info_span!("propagate change").entered(); @@ -425,12 +418,12 @@ impl SystemRegistry { Ok(()) } - fn walk_down(&mut self, id: RegistryId) -> Result<()> { + fn walk_down(&mut self, id: RegId) -> Result<()> { assert!(id.is_set(), "only sets can have nodes below them"); #[cfg(feature = "trace")] let _guard = bevy_utils::tracing::info_span!("rebuild set graph").entered(); - let mut sub_hier = DiGraphMap::::new(); + let mut sub_hier = DiGraphMap::::new(); // BFS let mut queue = VecDeque::new(); @@ -491,13 +484,13 @@ impl SystemRegistry { let children = set.graph.nodes().collect::>(); for child in children.iter() { match child { - RegistryId::System(_) => { + RegId::System(_) => { let set = self.set_metadata.get_mut(&set_id).unwrap(); let system = self.systems.get(&child).unwrap(); let access = system.as_ref().unwrap().component_access(); set.component_access.extend(&access); } - RegistryId::Set(_) => { + RegId::Set(_) => { let [set, subset] = self.set_metadata.get_many_mut([&set_id, &child]).unwrap(); set.component_access.extend(&subset.component_access); @@ -508,14 +501,14 @@ impl SystemRegistry { // flatten (to system nodes and system-system edges only) for &set_id in sets_topsort.iter().rev() { - let mut flat = DiGraphMap::::new(); + let mut flat = DiGraphMap::::new(); let set = self.set_metadata.get(&set_id).unwrap(); for child in set.graph.nodes() { match child { - RegistryId::System(_) => { + RegId::System(_) => { flat.add_node(child); } - RegistryId::Set(_) => { + RegId::Set(_) => { let subset = self.set_metadata.get(&child).unwrap(); flat.extend(subset.flat.all_edges()); } @@ -524,20 +517,20 @@ impl SystemRegistry { for (before, after, _) in set.graph.all_edges() { match (before, after) { - (RegistryId::System(_), RegistryId::System(_)) => { + (RegId::System(_), RegId::System(_)) => { flat.add_edge(before, after, ()); } - (RegistryId::Set(_), RegistryId::System(_)) => { + (RegId::Set(_), RegId::System(_)) => { for u in self.set_metadata.get(&before).unwrap().flat.nodes() { flat.add_edge(u, after, ()); } } - (RegistryId::System(_), RegistryId::Set(_)) => { + (RegId::System(_), RegId::Set(_)) => { for v in self.set_metadata.get(&after).unwrap().flat.nodes() { flat.add_edge(before, v, ()); } } - (RegistryId::Set(_), RegistryId::Set(_)) => { + (RegId::Set(_), RegId::Set(_)) => { for u in self.set_metadata.get(&before).unwrap().flat.nodes() { for v in self.set_metadata.get(&after).unwrap().flat.nodes() { flat.add_edge(u, v, ()); @@ -575,7 +568,7 @@ impl SystemRegistry { } impl SystemRegistry { - pub(crate) fn rebuild_set_executor(&mut self, set_id: RegistryId) { + pub(crate) fn rebuild_set_executor(&mut self, set_id: RegId) { assert!(set_id.is_set()); let set = self.set_metadata.get(&set_id).unwrap(); let sys_count = set.flat.node_count(); @@ -686,14 +679,14 @@ impl SystemRegistry { // helper methods impl SystemRegistry { - fn get_node_name(&self, id: RegistryId) -> Cow<'static, str> { + fn get_node_name(&self, id: RegId) -> Cow<'static, str> { match id { - RegistryId::System(_) => "mysterious system".into(), - RegistryId::Set(_) => "mysterious set".into(), + RegId::System(_) => "mysterious system".into(), + RegId::Set(_) => "mysterious set".into(), } } - fn check_hierarchy(&self, transitive_edges: &Vec<(RegistryId, RegistryId)>) -> Result<()> { + fn check_hierarchy(&self, transitive_edges: &Vec<(RegId, RegId)>) -> Result<()> { if transitive_edges.is_empty() { return Ok(()); } @@ -717,9 +710,9 @@ impl SystemRegistry { fn check_ambiguous( &self, - graph: &DiGraphMap, - intersecting_sets: &HashMap<(RegistryId, RegistryId), HashSet>, - ambiguities: &HashSet<(RegistryId, RegistryId)>, + graph: &DiGraphMap, + intersecting_sets: &HashMap<(RegId, RegId), HashSet>, + ambiguities: &HashSet<(RegId, RegId)>, ) -> Result<()> { let required_ambiguous = intersecting_sets.keys().cloned().collect(); let actual_ambiguous = ambiguities @@ -755,22 +748,22 @@ impl SystemRegistry { fn check_conflicts( &self, - intersecting_sets: &HashMap<(RegistryId, RegistryId), HashSet>, - ambiguities: &HashSet<(RegistryId, RegistryId)>, - set_id: RegistryId, + intersecting_sets: &HashMap<(RegId, RegId), HashSet>, + ambiguities: &HashSet<(RegId, RegId)>, + set_id: RegId, world: &World, ) -> Result<()> { let get_combined_access = |id| { let mut access = Access::default(); match id { - RegistryId::System(_) => { + RegId::System(_) => { let system = self.systems.get(&id).unwrap(); access.extend(system.as_ref().unwrap().component_access()); for system in self.system_conditions.get(&id).unwrap().iter().flatten() { access.extend(system.component_access()); } } - RegistryId::Set(_) => { + RegId::Set(_) => { access.extend(&self.set_metadata.get(&id).unwrap().component_access); for system in self.set_conditions.get(&id).unwrap().iter().flatten() { access.extend(system.component_access()); @@ -837,9 +830,9 @@ impl SystemRegistry { fn check_graph_cycles( &self, - set_id: RegistryId, - set_graph: &DiGraphMap, - strongly_connected_components: &Vec>, + set_id: RegId, + set_graph: &DiGraphMap, + strongly_connected_components: &Vec>, cycle: Cycle, ) -> Result<()> { if strongly_connected_components.len() == set_graph.node_count() { @@ -965,7 +958,6 @@ mod tests { schedule::*, system::{Local, Query, ResMut}, world::World, - {chain, group}, }; struct Order(pub Vec); diff --git a/crates/bevy_ecs/src/schedule/runner/mod.rs b/crates/bevy_ecs/src/schedule/runner/mod.rs index 5660743c189f3..78483d72a7947 100644 --- a/crates/bevy_ecs/src/schedule/runner/mod.rs +++ b/crates/bevy_ecs/src/schedule/runner/mod.rs @@ -2,7 +2,7 @@ mod multi_threaded; mod single_threaded; use crate::{ - schedule::{BoxedRunCondition, RegistryId, SystemLabel, SystemRegistry}, + schedule::{BoxedRunCondition, RegId, SystemLabel, SystemRegistry}, system::BoxedSystem, world::World, }; @@ -23,7 +23,11 @@ pub trait SystemRunner: Downcast + Send + Sync { impl_downcast!(SystemRunner); -/// Internal resource used to signal the runner to apply pending commands to the [`World`]. +/// Internal resource used by [`apply_buffers`] to signal the runner to apply the buffers +/// of all completed but "unflushed" systems to the [`World`]. +/// +/// **Note** that it is only systems under the schedule being run and their buffers +/// are applied in topological order. pub(super) struct RunnerApplyBuffers(pub bool); impl Default for RunnerApplyBuffers { @@ -32,23 +36,20 @@ impl Default for RunnerApplyBuffers { } } -/// Applies pending command queues to the [`World`]. +/// Signals the runner to call [`apply_buffers`](crate::System::apply_buffers) for all +/// completed but "unflushed" systems on the [`World`]. +/// +/// **Note** that it is only systems under the schedule being run and their buffers +/// are applied in topological order. pub fn apply_buffers(world: &mut World) { - // Best place to do this check. - world.check_change_ticks(); - - let mut should_apply_buffers = world - .get_resource_mut::() - .expect("RunnerApplyBuffers resource does not exist."); - - // The executor resets this to false, so if it's true, something is weird. - assert!( - !should_apply_buffers.0, - "pending commands should have been applied" - ); + let mut should_apply_buffers = world.resource_mut::(); + assert!(!should_apply_buffers.0, "pending commands not applied"); should_apply_buffers.0 = true; + world.check_change_ticks(); } +/// Temporarily takes ownership of systems and the schematic for running them in order +/// and testing their conditions. pub(crate) struct Runner { /// A list of systems, topsorted according to system dependency graph. pub(crate) systems: Vec, @@ -63,9 +64,11 @@ pub(crate) struct Runner { /// A map relating each set index to a bitset of its descendant systems. pub(crate) set_systems: HashMap, /// A list of systems (their ids), topsorted according to dependency graph. - pub(crate) systems_topsort: Vec, + /// Used to return systems and their conditions to the registry. + pub(crate) systems_topsort: Vec, /// A list of sets (their ids), topsorted according to hierarchy graph. - pub(crate) sets_topsort: Vec, + /// Used to return set conditions to the registry. + pub(crate) sets_topsort: Vec, } impl Default for Runner { @@ -94,11 +97,12 @@ impl Runner { } /// Runs the system or system set given by `schedule` on the world. -pub fn run_systems(schedule: impl SystemLabel, world: &mut World) { +pub fn run_scheduled(schedule: impl SystemLabel, world: &mut World) { let mut reg = world.resource_mut::(); let id = *reg.ids.get(&schedule.dyn_clone()).expect("unknown label"); match id { - RegistryId::System(_) => { + RegId::System(_) => { + // TODO: Need to confirm that system is available before taking ownership. let mut reg = world.resource_mut::(); let mut system = reg.systems.get_mut(&id).unwrap().take().unwrap(); let mut system_conditions = reg.system_conditions.get_mut(&id).unwrap().take().unwrap(); @@ -122,13 +126,15 @@ pub fn run_systems(schedule: impl SystemLabel, world: &mut World) { .get_mut(&id) .map(|container| container.insert(system_conditions)); } - RegistryId::Set(_) => { + RegId::Set(_) => { let mut reg = world.resource_mut::(); let mut runner = reg.runners.get_mut(&id).unwrap().take().unwrap(); assert!(runner.systems.is_empty()); assert!(runner.system_conditions.is_empty()); assert!(runner.set_conditions.is_empty()); + // TODO: Need to refresh set metadata first. + // TODO: Then, need to confirm that all its systems are available before taking ownership. for sys_id in runner.systems_topsort.iter() { runner .systems @@ -165,6 +171,10 @@ pub fn run_systems(schedule: impl SystemLabel, world: &mut World) { .iter() .zip(runner.set_conditions.drain(..)); + // If the systems and sets were removed while they were running, + // they'll just be dropped here. + // TODO: Say something if container wasn't empty. + // TODO: User might be doing something weird (removing systems then re-adding under same name). let mut reg = world.resource_mut::(); for (sys_id, (system, run_criteria)) in sys_iter { reg.systems diff --git a/crates/bevy_ecs/src/system/commands/command_queue.rs b/crates/bevy_ecs/src/system/commands/command_queue.rs index 64a3bd7914a6f..4fef08c2da83f 100644 --- a/crates/bevy_ecs/src/system/commands/command_queue.rs +++ b/crates/bevy_ecs/src/system/commands/command_queue.rs @@ -6,13 +6,11 @@ struct CommandMeta { func: unsafe fn(value: *mut u8, world: &mut World), } -/// A queue of [`Command`]s +/// A queue of [`Command`] instances. // -// NOTE: [`CommandQueue`] is implemented via a `Vec` over a `Vec>` -// as an optimization. Since commands are used frequently in systems as a way to spawn -// entities/components/resources, and it's not currently possible to parallelize these -// due to mutable [`World`] access, maximizing performance for [`CommandQueue`] is -// preferred to simplicity of implementation. +// NOTE: `CommandQueue` is implemented as a type-elided `Vec` instead of `Vec>` +// as a memory optimization. Commands can't be processed in parallel but are still used heavily, so +// maximizing cache locality was preferred over a simpler implementation. #[derive(Default)] pub struct CommandQueue { bytes: Vec, diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index 128b8c79c96c0..8525ce8144649 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -28,28 +28,7 @@ //! # System construction //! //! Any function or closure whose parameters implement the [`SystemParam`] trait can be automatically -//! converted into a system: -//! -//! - [`Query<...>`](Query) -//! - [`QuerySet`](QuerySet) -//! - [`Res`] and ([`Option>`](Res)) -//! - [`ResMut`] and ([`Option>`](ResMut)) -//! - [`NonSend`] and ([`Option>`](NonSend)) -//! - [`NonSendMut`] and ([`Option>`](NonSendMut)) -//! - [`Commands`] -//! - [`EventReader`](crate::event::EventReader) -//! - [`EventWriter`](crate::event::EventWriter) -//! - [`Local`] -//! - [`&World`](crate::world::World) -//! - [`&mut World`](crate::world::World) -//! - [`&Entities`](crate::entity::Entities) -//! - [`&Components`](crate::component::Components) -//! - [`&Bundles`](crate::bundle::Bundles) -//! - [`&Archetypes`](crate::archetype::Archetypes) -//! - [`RemovedComponents`] -//! - [`SystemChangeTick`] -//! - [`()` (unit primitive type)](https://doc.rust-lang.org/stable/std/primitive.unit.html) -//! - Tuples with up to 16 [`SystemParam`] elements +//! converted into a system. mod commands; mod function_system; @@ -67,7 +46,7 @@ pub use system_chaining::*; pub use system_param::*; pub fn assert_is_system>(sys: S) { - // Check it can be converted into a system + // check that it can be converted into a system IntoSystem::into_system(sys); } diff --git a/crates/bevy_ecs/src/system/system_chaining.rs b/crates/bevy_ecs/src/system/system_chaining.rs index aa9e8fd4b0e3d..3be95ec6a8b4d 100644 --- a/crates/bevy_ecs/src/system/system_chaining.rs +++ b/crates/bevy_ecs/src/system/system_chaining.rs @@ -8,15 +8,11 @@ use crate::{ }; use std::borrow::Cow; -/// A [`System`] that chains two systems together, creating a new system that routes the output of -/// the first system into the input of the second system, then returns the output of the second system. +/// Bundles two systems together, creating a new [`System`] that routes the output of the first system +/// into the input of the second system, then returns the output of the second system. /// -/// Given two systems, A and B, A can be chained with B as `A.chain(B)` if the output type of A -/// matches the input type of B. -/// -/// Note: A [`FunctionSystem`](crate::system::FunctionSystem)'s output is the function -/// return type and its input is the argument wrapped with [`In`](crate::system::In) -/// or `()` if there is none. +/// Given two systems, A and B, A can be chained with B using `A.chain(B)` if the A's [`Out`](System::Out) +/// matches B's [`In`](System::In) type. /// /// # Examples /// diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 7aa3cbd29f33a..766d82104544b 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -21,12 +21,12 @@ use std::{ ops::{Deref, DerefMut}, }; -/// A parameter that can be used in a [`System`](super::System). +/// A function parameter that can be used in a valid [`System`](super::System). /// /// # Derive /// -/// This trait can be derived with the [`derive@super::SystemParam`] macro. The only requirement -/// is that every struct field must also implement `SystemParam`. +/// This trait can be derived with [`#[derive(SystemParam)]`](`derive@super::SystemParam`), but note +/// that every struct field must also implement `SystemParam`. /// /// ``` /// # use bevy_ecs::prelude::*; @@ -50,16 +50,16 @@ pub trait SystemParam: Sized { type Fetch: for<'w, 's> SystemParamFetch<'w, 's>; } +/// A convenient type alias for the [`Item`](SystemParamFetch::Item) retrieved by a [`SystemParam`]. pub type SystemParamItem<'w, 's, P> = <

::Fetch as SystemParamFetch<'w, 's>>::Item; -/// The state of a [`SystemParam`]. +/// Types that can represent the internal state of a [`SystemParam`]. /// /// # Safety /// -/// It is the implementor's responsibility to ensure `system_meta` is populated with the _exact_ -/// [`World`] access used by the [`SystemParamState`] (and associated [`SystemParamFetch`]). -/// Additionally, it is the implementor's responsibility to ensure there is no -/// conflicting access across all [`SystemParam`]'s. +/// The implementor must ensure: +/// - Initialization specifies the param's *exact* [`World`] access. +/// - Initialization fails if the param's access conflicts with another, previously initialized param. pub unsafe trait SystemParamState: Send + Sync + 'static { fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self; #[inline] @@ -68,18 +68,16 @@ pub unsafe trait SystemParamState: Send + Sync + 'static { fn apply(&mut self, _world: &mut World) {} } -/// A [`SystemParamFetch`] that only reads a given [`World`]. -/// -/// # Safety -/// This must only be implemented for [`SystemParamFetch`] impls that exclusively read the World passed in to [`SystemParamFetch::get_param`] -pub unsafe trait ReadOnlySystemParamFetch {} - +/// Types that can retrieve the data represented by the [`SystemParam`] from a [`World`]. pub trait SystemParamFetch<'world, 'state>: SystemParamState { + /// The type retrieved by the param. type Item: SystemParam; + /// # Safety /// - /// This call might access any of the input parameters in an unsafe way. Make sure the data - /// access is safe in the context of the system scheduler. + /// The caller must ensure: + /// - The given world is the same world used to initialize the param. + /// - There are no active references that conflict with the param's access. Mutable access must be unique. unsafe fn get_param( state: &'state mut Self, system_meta: &SystemMeta, @@ -88,6 +86,13 @@ pub trait SystemParamFetch<'world, 'state>: SystemParamState { ) -> Self::Item; } +/// [`Fetch`](SystemParam::Fetch) types that access [`World`] data immutably (or not at all). +/// +/// # Safety +/// +/// The implementor must ensure that this is only implemented for types that fit the criteria. +pub unsafe trait ReadOnlySystemParamFetch {} + /// A non-existent component accessed by systems with params that hold a /// [`World`](crate::world::World) reference. /// @@ -185,16 +190,8 @@ where type Fetch = QueryState; } -// SAFE: QueryState is constrained to read-only fetches, so it only reads World. -unsafe impl ReadOnlySystemParamFetch for QueryState -where - Q::Fetch: ReadOnlyFetch, - F::Fetch: FilterFetch, -{ -} - -// SAFE: Relevant query ComponentId and ArchetypeComponentId access is applied to SystemMeta. If -// this QueryState conflicts with any prior access, a panic will occur. +// SAFETY: ComponentId and ArchetypeComponentId access is checked against the SystemMeta. +// This will panic if there's a conflict with any prior access. unsafe impl SystemParamState for QueryState where F::Fetch: FilterFetch, @@ -258,6 +255,14 @@ where } } +// SAFETY: QueryState is limited to read-only fetches. +unsafe impl ReadOnlySystemParamFetch for QueryState +where + Q::Fetch: ReadOnlyFetch, + F::Fetch: FilterFetch, +{ +} + fn assert_component_access_compatibility( system_name: &str, query_type: &'static str, @@ -279,30 +284,34 @@ fn assert_component_access_compatibility( query_type, filter_type, system_name, accesses); } +/// [`SystemParam`] set that can contain params with conflicting access, with the caveat that only +/// one param is accessible at a time. pub struct ParamSet<'w, 's, T: SystemParam> { param_states: &'s mut T::Fetch, world: SemiSafeCell<'w, World>, system_meta: SystemMeta, change_tick: u32, } -/// The [`SystemParamState`] of [`ParamSet`]. + +/// The [`SystemParamState`] of [`ParamSet<(...)>`](ParamSet). pub struct ParamSetState SystemParamFetch<'w, 's>>(T); impl_param_set!(); +/// Types that are singletons. A [`World`] can have most one instance of these types. pub trait Resource: Send + Sync + 'static {} impl Resource for T where T: Send + Sync + 'static {} -/// Shared borrow of a resource. +/// Shared borrow of resource. /// -/// See the [`World`] documentation to see the usage of a resource. +/// See the [`World`](crate::world::World) documentation to see the usage of a resource. /// -/// If you need a unique mutable borrow, use [`ResMut`] instead. +/// For a unique, mutable borrow, see [`ResMut`]. /// /// # Panics /// -/// Panics when used as a [`SystemParameter`](SystemParam) if the resource does not exist. +/// Panics when used as a `SystemParam` if `T` has not be inserted as a resource. /// /// Use `Option>` instead if the resource might not always exist. pub struct Res<'w, T: Resource> { @@ -312,9 +321,6 @@ pub struct Res<'w, T: Resource> { change_tick: u32, } -// SAFE: Res only reads a single World resource -unsafe impl ReadOnlySystemParamFetch for ResState {} - impl<'w, T: Resource> Debug for Res<'w, T> where T: Debug, @@ -325,12 +331,12 @@ where } impl<'w, T: Resource> Res<'w, T> { - /// Returns `true` if the resource was added after the system last ran, `false` otherwise. + /// Returns `true` if the resource was added after the system last ran. pub fn is_added(&self) -> bool { self.ticks.is_added(self.last_change_tick, self.change_tick) } - /// Returns `true` if the resource was added or mutably-dereferenced after the system last ran, `false` otherwise. + /// Returns `true` if the resource was added or mutably-dereferenced after the system last ran. pub fn is_changed(&self) -> bool { self.ticks .is_changed(self.last_change_tick, self.change_tick) @@ -367,6 +373,9 @@ impl<'w, T: Resource> From> for Res<'w, T> { } } +// SAFETY: non-mutable borrow +unsafe impl ReadOnlySystemParamFetch for ResState {} + /// The [`SystemParamState`] of [`Res`]. #[doc(hidden)] pub struct ResState { @@ -378,8 +387,8 @@ impl<'a, T: Resource> SystemParam for Res<'a, T> { type Fetch = ResState; } -// SAFE: Res ComponentId and ArchetypeComponentId access is applied to SystemMeta. If this Res -// conflicts with any prior access, a panic will occur. +// SAFETY: ComponentId and ArchetypeComponentId access is checked against the SystemMeta. +// This will panic if there's a conflict with any prior access. unsafe impl SystemParamState for ResState { fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { let component_id = world.initialize_resource::(); @@ -435,8 +444,7 @@ impl<'w, 's, T: Resource> SystemParamFetch<'w, 's> for ResState { } } -/// The [`SystemParamState`] of [`Option>`]. -/// See: [`Res`] +/// The [`SystemParamState`] of [`Option>`](`Res`). #[doc(hidden)] pub struct OptionResState(ResState); @@ -444,9 +452,7 @@ impl<'a, T: Resource> SystemParam for Option> { type Fetch = OptionResState; } -// SAFE: Only reads a single World resource -unsafe impl ReadOnlySystemParamFetch for OptionResState {} - +// SAFETY: See ResState unsafe impl SystemParamState for OptionResState { fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { Self(ResState::init(world, system_meta)) @@ -475,6 +481,9 @@ impl<'w, 's, T: Resource> SystemParamFetch<'w, 's> for OptionResState { } } +// SAFETY: non-mutable borrow +unsafe impl ReadOnlySystemParamFetch for OptionResState {} + /// The [`SystemParamState`] of [`ResMut`]. #[doc(hidden)] pub struct ResMutState { @@ -486,8 +495,8 @@ impl<'a, T: Resource> SystemParam for ResMut<'a, T> { type Fetch = ResMutState; } -// SAFE: Res ComponentId and ArchetypeComponentId access is applied to SystemMeta. If this Res -// conflicts with any prior access, a panic will occur. +// SAFETY: ComponentId and ArchetypeComponentId access is checked against the SystemMeta. +// This will panic if there's a conflict with any prior access. unsafe impl SystemParamState for ResMutState { fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { let component_id = world.initialize_resource::(); @@ -548,8 +557,7 @@ impl<'w, 's, T: Resource> SystemParamFetch<'w, 's> for ResMutState { } } -/// The [`SystemParamState`] of [`Option>`]. -/// See: [`ResMut`] +/// The [`SystemParamState`] of [`Option>`](`ResMut`). #[doc(hidden)] pub struct OptionResMutState(ResMutState); @@ -557,6 +565,7 @@ impl<'a, T: Resource> SystemParam for Option> { type Fetch = OptionResMutState; } +// SAFETY: See ResMut unsafe impl SystemParamState for OptionResMutState { fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { Self(ResMutState::init(world, system_meta)) @@ -591,10 +600,11 @@ impl<'w, 's> SystemParam for Commands<'w, 's> { type Fetch = CommandQueue; } -// SAFE: Commands only accesses internal state +// SAFETY: commands only access internal state and &Entities (which impl ReadOnlySystemParamFetch) unsafe impl ReadOnlySystemParamFetch for CommandQueue {} -// SAFE: only local state is accessed +// SAFETY: ComponentId and ArchetypeComponentId access is checked against the SystemMeta. +// This will panic if there's a conflict with any prior access. unsafe impl SystemParamState for CommandQueue { fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { add_shared_world_access(world, system_meta, false, "Commands"); @@ -628,6 +638,8 @@ impl<'w, 's> SystemParam for &'w World { type Fetch = WorldState; } +// SAFETY: ComponentId and ArchetypeComponentId access is checked against the SystemMeta. +// This will panic if there's a conflict with any prior access. unsafe impl<'w, 's> SystemParamState for WorldState { fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { add_shared_world_access(world, system_meta, true, "&World"); @@ -648,16 +660,19 @@ impl<'w, 's> SystemParamFetch<'w, 's> for WorldState { } } -/// SAFETY: &World is an immutable borrow. +/// SAFETY: &World is an non-mutable borrow. unsafe impl ReadOnlySystemParamFetch for WorldState {} /// The [`SystemParamState`] of [`&mut World`](crate::world::World). +#[doc(hidden)] pub struct WorldMutState; impl<'w, 's> SystemParam for &'w mut World { type Fetch = WorldMutState; } +// SAFETY: ComponentId and ArchetypeComponentId access is checked against the SystemMeta. +// This will panic if there's a conflict with any prior access. unsafe impl<'w, 's> SystemParamState for WorldMutState { fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { // world could contain non-send resources, run on local thread @@ -680,10 +695,10 @@ impl<'w, 's> SystemParamFetch<'w, 's> for WorldMutState { } } -/// A system local [`SystemParam`]. +/// A [`SystemParam`] that is stored on the system itself. /// -/// A local may only be accessed by the system itself and is therefore not visible to other systems. -/// If two or more systems specify the same local type each will have their own unique local. +/// A `Local` cannot be read or written to from outside its containing system. +/// If several systems have the same local type, they will all have their own unique instance. /// /// # Examples /// @@ -707,8 +722,7 @@ impl<'w, 's> SystemParamFetch<'w, 's> for WorldMutState { /// assert_eq!(read_system.run((), world), 0); /// ``` /// -/// N.B. A [`Local`]s value cannot be read or written to outside of the containing system. -/// To add configuration to a system, convert a capturing closure into the system instead: +/// **Tip:** You can convert a capturing closure into a system to configure it. /// /// ``` /// # use bevy_ecs::prelude::*; @@ -724,9 +738,6 @@ impl<'w, 's> SystemParamFetch<'w, 's> for WorldMutState { /// ``` pub struct Local<'a, T: Resource>(&'a mut T); -// SAFE: Local only accesses internal state -unsafe impl ReadOnlySystemParamFetch for LocalState {} - impl<'a, T: Resource> Debug for Local<'a, T> where T: Debug, @@ -752,6 +763,9 @@ impl<'a, T: Resource> DerefMut for Local<'a, T> { } } +// SAFETY: only reads internal system data +unsafe impl ReadOnlySystemParamFetch for LocalState {} + /// The [`SystemParamState`] of [`Local`]. #[doc(hidden)] pub struct LocalState(T); @@ -760,7 +774,7 @@ impl<'a, T: Resource + FromWorld> SystemParam for Local<'a, T> { type Fetch = LocalState; } -// SAFE: only local state is accessed +// SAFETY: only local state is accessed unsafe impl SystemParamState for LocalState { fn init(world: &mut World, _system_meta: &mut SystemMeta) -> Self { Self(T::from_world(world)) @@ -781,21 +795,15 @@ impl<'w, 's, T: Resource + FromWorld> SystemParamFetch<'w, 's> for LocalState } } -/// A [`SystemParam`] that grants access to the entities that had their `T` [`Component`] removed. -/// -/// Note that this does not allow you to see which data existed before removal. -/// If you need this, you will need to track the component data value on your own, -/// using a regularly scheduled system that requests `Query<(Entity, &T), Changed>` -/// and stores the data somewhere safe to later cross-reference. +/// A [`SystemParam`] that iterates entities whose `T` [`Component`] was removed. /// -/// If you are using `bevy_ecs` as a standalone crate, -/// note that the `RemovedComponents` list will not be automatically cleared for you, -/// and will need to be manually flushed using [`World::clear_trackers`] +/// This param does *not* return the data that was removed (that's gone), but it +/// may return entities that have already been despawned. /// -/// For users of `bevy` itself, this is automatically done in a system added by `MinimalPlugins` -/// or `DefaultPlugins` at the end of each pass of the game loop during the `CoreStage::Last` -/// stage. As such `RemovedComponents` systems should be scheduled after the stage where -/// removal occurs but before `CoreStage::Last`. +/// - If you are using `bevy_ecs` as a standalone crate, `RemovedComponents` will need to be +/// manually cleared using [`World::clear_trackers`]. +/// - If you are using `bevy`, both the `MinimalPlugins` and `DefaultPlugins` add [`World::clear_trackers`] +/// as a system to [`CoreSet::Last`](bevy_core::CoreSet::Last). /// /// # Examples /// @@ -828,9 +836,6 @@ impl<'a, T: Component> RemovedComponents<'a, T> { } } -// SAFE: Only reads World components -unsafe impl ReadOnlySystemParamFetch for RemovedComponentsState {} - /// The [`SystemParamState`] of [`RemovedComponents`]. #[doc(hidden)] pub struct RemovedComponentsState { @@ -842,8 +847,8 @@ impl<'a, T: Component> SystemParam for RemovedComponents<'a, T> { type Fetch = RemovedComponentsState; } -// SAFE: no component access. removed component entity collections can be read in parallel and are -// never mutably borrowed during system execution +// SAFETY: ComponentId and ArchetypeComponentId access is checked against the SystemMeta. +// This will panic if there's a conflict with any prior access. unsafe impl SystemParamState for RemovedComponentsState { fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { let param_name = format!("RemovedComponents<{}>", std::any::type_name::()); @@ -874,16 +879,19 @@ impl<'w, 's, T: Component> SystemParamFetch<'w, 's> for RemovedComponentsState ReadOnlySystemParamFetch for RemovedComponentsState {} + /// Shared borrow of a non-[`Send`] resource. /// -/// Only `Send` resources may be accessed with the [`Res`] [`SystemParam`]. In case that the -/// resource does not implement `Send`, this `SystemParam` wrapper can be used. This will instruct -/// the scheduler to instead run the system on the main thread so that it doesn't send the resource -/// over to another thread. +/// Only `Send` resources may be accessed with [`Res`]. If a resource does not implement `Send`, +/// this [`SystemParam`](crate::system::SystemParam) must be used to ensure the accessing system runs on the same thread. +/// +/// For a unique, mutable borrow, see [`NonSendMut`](NonSendMut). /// /// # Panics /// -/// Panics when used as a `SystemParameter` if the resource does not exist. +/// Panics when used as a `SystemParam` if `T` has not be inserted as a resource. /// /// Use `Option>` instead if the resource might not always exist. pub struct NonSend<'w, T: 'static> { @@ -893,9 +901,6 @@ pub struct NonSend<'w, T: 'static> { change_tick: u32, } -// SAFE: Only reads a single World non-send resource -unsafe impl ReadOnlySystemParamFetch for NonSendState {} - impl<'w, T> Debug for NonSend<'w, T> where T: Debug, @@ -906,12 +911,12 @@ where } impl<'w, T: 'static> NonSend<'w, T> { - /// Returns `true` if the resource was added after the system last ran, `false` otherwise. + /// Returns `true` if the resource was added after the system last ran. pub fn is_added(&self) -> bool { self.ticks.is_added(self.last_change_tick, self.change_tick) } - /// Returns `true` if the resource was added or mutably-dereferenced after the system last ran, `false` otherwise. + /// Returns `true` if the resource was added or mutably-dereferenced after the system last ran. pub fn is_changed(&self) -> bool { self.ticks .is_changed(self.last_change_tick, self.change_tick) @@ -947,8 +952,8 @@ impl<'a, T: 'static> SystemParam for NonSend<'a, T> { type Fetch = NonSendState; } -// SAFE: NonSendComponentId and ArchetypeComponentId access is applied to SystemMeta. If this -// NonSend conflicts with any prior access, a panic will occur. +// SAFETY: ComponentId and ArchetypeComponentId access is checked against the SystemMeta. +// This will panic if there's a conflict with any prior access. unsafe impl SystemParamState for NonSendState { fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { system_meta.set_non_send(); @@ -1008,8 +1013,10 @@ impl<'w, 's, T: 'static> SystemParamFetch<'w, 's> for NonSendState { } } -/// The [`SystemParamState`] of [`Option>`]. -/// See: [`NonSend`] +// SAFETY: non-mutable borrow (also can only be accessed on one thread) +unsafe impl ReadOnlySystemParamFetch for NonSendState {} + +/// The [`SystemParamState`] of [`Option>`](`NonSend`). #[doc(hidden)] pub struct OptionNonSendState(NonSendState); @@ -1017,9 +1024,7 @@ impl<'w, T: 'static> SystemParam for Option> { type Fetch = OptionNonSendState; } -// SAFE: Only reads a single non-send resource -unsafe impl ReadOnlySystemParamFetch for OptionNonSendState {} - +// SAFETY: See NonSendState unsafe impl SystemParamState for OptionNonSendState { fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { Self(NonSendState::init(world, system_meta)) @@ -1049,6 +1054,9 @@ impl<'w, 's, T: 'static> SystemParamFetch<'w, 's> for OptionNonSendState { } } +// SAFETY: non-mutable borrow (also can only be accessed on one thread) +unsafe impl ReadOnlySystemParamFetch for OptionNonSendState {} + /// The [`SystemParamState`] of [`NonSendMut`]. #[doc(hidden)] pub struct NonSendMutState { @@ -1060,8 +1068,8 @@ impl<'a, T: 'static> SystemParam for NonSendMut<'a, T> { type Fetch = NonSendMutState; } -// SAFE: NonSendMut ComponentId and ArchetypeComponentId access is applied to SystemMeta. If this -// NonSendMut conflicts with any prior access, a panic will occur. +// SAFETY: ComponentId and ArchetypeComponentId access is checked against the SystemMeta. +// This will panic if there's a conflict with any prior access. unsafe impl SystemParamState for NonSendMutState { fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { system_meta.set_non_send(); @@ -1125,8 +1133,7 @@ impl<'w, 's, T: 'static> SystemParamFetch<'w, 's> for NonSendMutState { } } -/// The [`SystemParamState`] of [`Option>`]. -/// See: [`NonSendMut`] +/// The [`SystemParamState`] of [`Option>`](`NonSendMut`). #[doc(hidden)] pub struct OptionNonSendMutState(NonSendMutState); @@ -1134,6 +1141,7 @@ impl<'a, T: 'static> SystemParam for Option> { type Fetch = OptionNonSendMutState; } +// SAFETY: See NonSendMut unsafe impl SystemParamState for OptionNonSendMutState { fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { Self(NonSendMutState::init(world, system_meta)) @@ -1169,14 +1177,12 @@ impl<'a> SystemParam for &'a Archetypes { type Fetch = ArchetypesState; } -// SAFE: Only reads World archetypes -unsafe impl ReadOnlySystemParamFetch for ArchetypesState {} - -/// The [`SystemParamState`] of [`Archetypes`]. +/// The [`SystemParamState`] of [`&Archetypes`](Archetypes). #[doc(hidden)] pub struct ArchetypesState; -// SAFE: no component value access +// SAFETY: ComponentId and ArchetypeComponentId access is checked against the SystemMeta. +// This will panic if there's a conflict with any prior access. unsafe impl SystemParamState for ArchetypesState { fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { add_shared_world_access(world, system_meta, false, "&Archetypes"); @@ -1199,18 +1205,19 @@ impl<'w, 's> SystemParamFetch<'w, 's> for ArchetypesState { } } +// SAFETY: &Archetypes is an non-mutable borrow +unsafe impl ReadOnlySystemParamFetch for ArchetypesState {} + impl<'a> SystemParam for &'a Components { type Fetch = ComponentsState; } -// SAFE: Only reads World components -unsafe impl ReadOnlySystemParamFetch for ComponentsState {} - -/// The [`SystemParamState`] of [`Components`]. +/// The [`SystemParamState`] of [`&Components`](Components). #[doc(hidden)] pub struct ComponentsState; -// SAFE: no component value access +// SAFETY: ComponentId and ArchetypeComponentId access is checked against the SystemMeta. +// This will panic if there's a conflict with any prior access. unsafe impl SystemParamState for ComponentsState { fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { add_shared_world_access(world, system_meta, false, "&Components"); @@ -1237,14 +1244,15 @@ impl<'a> SystemParam for &'a Entities { type Fetch = EntitiesState; } -// SAFE: Only reads World entities -unsafe impl ReadOnlySystemParamFetch for EntitiesState {} +// SAFETY: &Components is an non-mutable borrow +unsafe impl ReadOnlySystemParamFetch for ComponentsState {} -/// The [`SystemParamState`] of [`Entities`]. +/// The [`SystemParamState`] of [`&Entities`](Entities). #[doc(hidden)] pub struct EntitiesState; -// SAFE: no component value access +// SAFETY: ComponentId and ArchetypeComponentId access is checked against the SystemMeta. +// This will panic if there's a conflict with any prior access. unsafe impl SystemParamState for EntitiesState { fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { add_shared_world_access(world, system_meta, false, "&Entities"); @@ -1271,14 +1279,15 @@ impl<'a> SystemParam for &'a Bundles { type Fetch = BundlesState; } -// SAFE: Only reads World bundles -unsafe impl ReadOnlySystemParamFetch for BundlesState {} +// SAFETY: &Entities is a thread-safe shared borrow +unsafe impl ReadOnlySystemParamFetch for EntitiesState {} -/// The [`SystemParamState`] of [`Bundles`]. +/// The [`SystemParamState`] of [`&Bundles`](Bundles). #[doc(hidden)] pub struct BundlesState; -// SAFE: no component value access +// SAFETY: ComponentId and ArchetypeComponentId access is checked against the SystemMeta. +// This will panic if there's a conflict with any prior access. unsafe impl SystemParamState for BundlesState { fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { add_shared_world_access(world, system_meta, false, "&Bundles"); @@ -1301,16 +1310,15 @@ impl<'w, 's> SystemParamFetch<'w, 's> for BundlesState { } } -/// The [`SystemParamState`] of [`SystemChangeTick`]. +// SAFETY: &Bundles is an non-mutable borrow +unsafe impl ReadOnlySystemParamFetch for BundlesState {} + #[derive(Debug)] pub struct SystemChangeTick { pub last_change_tick: u32, pub change_tick: u32, } -// SAFE: Only reads internal system state -unsafe impl ReadOnlySystemParamFetch for SystemChangeTickState {} - impl SystemParam for SystemChangeTick { type Fetch = SystemChangeTickState; } @@ -1319,6 +1327,7 @@ impl SystemParam for SystemChangeTick { #[doc(hidden)] pub struct SystemChangeTickState {} +// SAFETY: only reads internal system data unsafe impl SystemParamState for SystemChangeTickState { fn init(_world: &mut World, _system_meta: &mut SystemMeta) -> Self { Self {} @@ -1341,13 +1350,16 @@ impl<'w, 's> SystemParamFetch<'w, 's> for SystemChangeTickState { } } +// SAFETY: only reads internal system data +unsafe impl ReadOnlySystemParamFetch for SystemChangeTickState {} + macro_rules! impl_system_param_tuple { ($($param: ident),*) => { impl<$($param: SystemParam),*> SystemParam for ($($param,)*) { type Fetch = ($($param::Fetch,)*); } - // SAFE: tuple consists only of ReadOnlySystemParamFetches + // SAFETY: tuple consists only of types that impl ReadOnlySystemParamFetch unsafe impl<$($param: ReadOnlySystemParamFetch),*> ReadOnlySystemParamFetch for ($($param,)*) {} #[allow(unused_variables)] @@ -1369,7 +1381,7 @@ macro_rules! impl_system_param_tuple { } } - /// SAFE: implementors of each `SystemParamState` in the tuple have validated their impls + // SAFETY: implementors assure their `SystemParamState` impls follow the rules #[allow(non_snake_case)] unsafe impl<$($param: SystemParamState),*> SystemParamState for ($($param,)*) { #[inline]