diff --git a/Cargo.toml b/Cargo.toml index 0d9a46df254c0..a7cd0e9c911fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -820,6 +820,16 @@ description = "Shows how to iterate over combinations of query results" category = "ECS (Entity Component System)" wasm = true +[[example]] +name = "one_shot_systems" +path = "examples/ecs/one_shot_systems.rs" + +[package.metadata.example.one_shot_systems] +name = "One Shot Systems" +description = "Shows how to flexibly run systems without scheduling them" +category = "ECS (Entity Component System)" +wasm = false + [[example]] name = "parallel_query" path = "examples/ecs/parallel_query.rs" diff --git a/crates/bevy_app/src/lib.rs b/crates/bevy_app/src/lib.rs index c0a9c68f79c3f..ec40a289ad33c 100644 --- a/crates/bevy_app/src/lib.rs +++ b/crates/bevy_app/src/lib.rs @@ -6,6 +6,7 @@ mod app; mod plugin; mod plugin_group; mod schedule_runner; +mod system_registry; #[cfg(feature = "bevy_ci_testing")] mod ci_testing; diff --git a/crates/bevy_app/src/system_registry.rs b/crates/bevy_app/src/system_registry.rs new file mode 100644 index 0000000000000..ecccb0a67bbd3 --- /dev/null +++ b/crates/bevy_app/src/system_registry.rs @@ -0,0 +1,53 @@ +use crate::App; +use bevy_ecs::prelude::*; +use bevy_ecs::system::{Callback, SystemRegistryError}; + +impl App { + /// Register a system with any number of [`SystemLabel`]s. + /// + /// Calls [`SystemRegistry::register_system`](bevy_ecs::system::SystemRegistry::register_system). + pub fn register_system< + Params, + S: IntoSystem<(), (), Params> + 'static, + LI: IntoIterator, + L: SystemLabel, + >( + &mut self, + system: S, + labels: LI, + ) -> &mut Self { + self.world.register_system(system, labels); + self + } + + /// Runs the supplied system on the [`World`] a single time. + /// + /// Calls [`SystemRegistry::run_system`](bevy_ecs::system::SystemRegistry::run_system). + #[inline] + pub fn run_system + 'static>( + &mut self, + system: S, + ) -> &mut Self { + self.world.run_system(system); + self + } + + /// Runs the systems corresponding to the supplied [`SystemLabel`] on the [`World`] a single time. + /// + /// Calls [`SystemRegistry::run_systems_by_label`](bevy_ecs::system::SystemRegistry::run_systems_by_label). + #[inline] + pub fn run_systems_by_label( + &mut self, + label: L, + ) -> Result<(), SystemRegistryError> { + self.world.run_systems_by_label(label) + } + + /// Run the systems corresponding to the label stored in the provided [`Callback`] + /// + /// Calls [`SystemRegistry::run_callback`](bevy_ecs::system::SystemRegistry::run_callback). + #[inline] + pub fn run_callback(&mut self, callback: Callback) -> Result<(), SystemRegistryError> { + self.world.run_callback(callback) + } +} diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 1521eebe529da..72eb2e080054c 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -5,6 +5,8 @@ use crate::{ bundle::Bundle, component::Component, entity::{Entities, Entity}, + schedule::SystemLabel, + system::{Callback, IntoSystem, RunSystemCommand, RunSystemsByLabelCommand}, world::{FromWorld, World}, }; use bevy_utils::tracing::{error, info, warn}; @@ -401,6 +403,37 @@ impl<'w, 's> Commands<'w, 's> { }); } + /// Runs the supplied system on the [`World`] a single time. + /// + /// Calls [`SystemRegistry::run_system`](crate::SystemRegistry::run_system). + pub fn run_system< + Params: Send + Sync + 'static, + S: IntoSystem<(), (), Params> + Send + Sync + 'static, + >( + &mut self, + system: S, + ) { + self.queue.push(RunSystemCommand::new(system)); + } + + /// Runs the systems corresponding to the supplied [`SystemLabel`] on the [`World`] a single time. + /// + /// Calls [`SystemRegistry::run_systems_by_label`](crate::SystemRegistry::run_systems_by_label). + pub fn run_systems_by_label(&mut self, label: impl SystemLabel) { + self.queue.push(RunSystemsByLabelCommand { + callback: Callback { + label: Box::new(label), + }, + }); + } + + /// Run the systems corresponding to the label stored in the provided [`Callback`] + /// + /// Calls [`SystemRegistry::run_callback`](crate::SystemRegistry::run_callback). + pub fn run_callback(&mut self, callback: Callback) { + self.queue.push(RunSystemsByLabelCommand { callback }); + } + /// Adds a command directly to the command queue. /// /// `command` can be a built-in command, custom struct that implements [`Command`] or a closure diff --git a/crates/bevy_ecs/src/system/function_system.rs b/crates/bevy_ecs/src/system/function_system.rs index a1e209e2426d8..8fff658769ef6 100644 --- a/crates/bevy_ecs/src/system/function_system.rs +++ b/crates/bevy_ecs/src/system/function_system.rs @@ -56,8 +56,10 @@ impl SystemMeta { // (to avoid the need for unwrapping to retrieve SystemMeta) /// Holds on to persistent state required to drive [`SystemParam`] for a [`System`]. /// -/// This is a very powerful and convenient tool for working with exclusive world access, +/// This is a powerful and convenient tool for working with exclusive world access, /// allowing you to fetch data from the [`World`] as if you were running a [`System`]. +/// However, simply calling `world::run_system(my_system)` using a [`SystemRegistry`](crate::system::SystemRegistry) +/// can be significantly simpler and ensures that change detection and command flushing work as expected. /// /// Borrow-checking is handled for you, allowing you to mutably access multiple compatible system parameters at once, /// and arbitrary system parameters (like [`EventWriter`](crate::event::EventWriter)) can be conveniently fetched. @@ -73,6 +75,8 @@ impl SystemMeta { /// - [`Local`](crate::system::Local) variables that hold state /// - [`EventReader`](crate::event::EventReader) system parameters, which rely on a [`Local`](crate::system::Local) to track which events have been seen /// +/// Note that this is automatically handled for you when using a [`SystemRegistry`](crate::system::SystemRegistry). +/// /// # Example /// /// Basic usage: @@ -444,14 +448,26 @@ where self.system_meta.name.as_ref(), ); } + fn default_labels(&self) -> Vec { vec![self.func.as_system_label().as_label()] } } /// A [`SystemLabel`] that was automatically generated for a system on the basis of its `TypeId`. +#[derive(Default)] pub struct SystemTypeIdLabel(PhantomData T>); +impl SystemTypeIdLabel { + /// Constructs a new copy of the [`SystemTypeIdLabel`] for the type `T` + /// + /// You can also construct this by using [`.as_system_label()`](crate::system::AsSystemLabel) on a concrete instance + /// of your function type. + pub fn new() -> Self { + Self(PhantomData::default()) + } +} + impl SystemLabel for SystemTypeIdLabel { #[inline] fn as_str(&self) -> &'static str { diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index 7b368b62ef471..f89245bf601cb 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -74,6 +74,7 @@ mod query; mod system; mod system_chaining; mod system_param; +mod system_registry; pub use commands::*; pub use exclusive_system::*; @@ -82,6 +83,7 @@ pub use query::*; pub use system::*; pub use system_chaining::*; pub use system_param::*; +pub use system_registry::*; /// Ensure that a given function is a system /// diff --git a/crates/bevy_ecs/src/system/system_registry.rs b/crates/bevy_ecs/src/system/system_registry.rs new file mode 100644 index 0000000000000..377275cbcb9c7 --- /dev/null +++ b/crates/bevy_ecs/src/system/system_registry.rs @@ -0,0 +1,589 @@ +use bevy_utils::tracing::warn; +use bevy_utils::HashMap; +use std::hash::Hash; +use std::marker::PhantomData; + +use crate::schedule::{IntoSystemDescriptor, SystemLabel}; +use crate::system::{Command, IntoSystem, System, SystemTypeIdLabel}; +use crate::world::{Mut, World}; +// Needed for derive(Component) macro +use crate as bevy_ecs; +use bevy_ecs_macros::Component; + +/// Stores initialized [`System`]s, so they can be reused and run in an ad-hoc fashion. +/// +/// Systems are keyed by their [`SystemLabel`]: +/// - all systems with a given label will be run (in linear registration order) when a given label is run +/// - repeated calls with the same function type will reuse cached state, including for change detection +/// +/// Any [`Commands`](crate::system::Commands) generated by these systems (but not other systems), will immediately be applied. +/// +/// This type is stored as a [`Resource`](crate::system::Resource) on each [`World`], initialized by default. +/// However, it will likely be easier to use the corresponding methods on [`World`], +/// to avoid having to worry about split mutable borrows yourself. +/// +/// # Limitations +/// +/// - stored systems cannot be chained: they can neither have an [`In`](crate::system::In) nor return any values +/// - stored systems cannot recurse: they cannot run other systems via the [`SystemRegistry`] methods on `World` or `Commands` +/// - exclusive systems cannot be used +/// +/// # Examples +/// +/// You can run a single system directly on the World, +/// applying its effect and caching its state for the next time +/// you call this method (internally, this is based on [`SystemTypeIdLabel`]). +/// +/// ```rust +/// use bevy_ecs::prelude::*; +/// +/// let mut world = World::new(); +/// +/// #[derive(Default, PartialEq, Debug)] +/// struct Counter(u8); +/// +/// fn count_up(mut counter: ResMut){ +/// counter.0 += 1; +/// } +/// +/// world.init_resource::(); +/// world.run_system(count_up); +/// +/// assert_eq!(Counter(1), *world.resource()); +/// ``` +/// +/// These systems immediately apply commands and cache state, +/// ensuring that change detection and [`Local`](crate::system::Local) variables work correctly. +/// +/// ```rust +/// use bevy_ecs::prelude::*; +/// +/// let mut world = World::new(); +/// +/// #[derive(Component)] +/// struct Marker; +/// +/// fn spawn_7_entities(mut commands: Commands) { +/// for _ in 0..7 { +/// commands.spawn().insert(Marker); +/// } +/// } +/// +/// fn assert_7_spawned(query: Query<(), Added>){ +/// let n_spawned = query.iter().count(); +/// assert_eq!(n_spawned, 7); +/// } +/// +/// world.run_system(spawn_7_entities); +/// world.run_system(assert_7_spawned); +/// ``` +#[derive(Default)] +pub struct SystemRegistry { + systems: Vec, + // Stores the index of all systems that match the key's label + labels: HashMap, Vec>, +} + +struct StoredSystem { + system: Box>, +} + +impl SystemRegistry { + /// Registers a system in the [`SystemRegistry`], so then it can be later run. + /// + /// This allows the system to be run by their [`SystemTypeIdLabel`] using the `run_systems_by_label` method. + /// Repeatedly registering a system will have no effect. + /// + /// When [`run_systems_by_label`](SystemRegistry::run_systems_by_label) is called, + /// all registered systems that match that label will be evaluated (in insertion order). + /// + /// To provide explicit label(s), use [`register_system_with_labels`](SystemRegistry::register_system_with_labels). + #[inline] + fn register_system_with_type_label + 'static>( + &mut self, + world: &mut World, + system: S, + ) { + let automatic_system_label: SystemTypeIdLabel = SystemTypeIdLabel::new(); + + // This avoids nasty surprising behavior in case systems are registered twice + if !self.is_label_registered(automatic_system_label) { + let boxed_system: Box> = + Box::new(IntoSystem::into_system(system)); + self.register_boxed_system(world, boxed_system, vec![Box::new(automatic_system_label)]); + } else { + let type_name = std::any::type_name::(); + warn!("A system of type {type_name} was registered more than once!"); + }; + } + + /// Register a system so that it may be run by any of their [`SystemLabel`]s. + /// + /// This is only needed if you want to run a system by a particular label; + /// the `run_system` label will automatically register systems under their [`SystemTypeIdLabel`] when first used. + /// + /// # Warning + /// + /// Duplicate systems may be added if this method is called repeatedly. + /// Each copy will be called seperately if they share a label. + pub fn register_system< + Params, + S: IntoSystem<(), (), Params> + 'static, + LI: IntoIterator, + L: SystemLabel, + >( + &mut self, + world: &mut World, + system: S, + labels: LI, + ) { + let boxed_system: Box> = + Box::new(IntoSystem::into_system(system)); + + let collected_labels = labels + .into_iter() + .map(|label| { + let boxed_label: Box = Box::new(label); + boxed_label + }) + .collect(); + + self.register_boxed_system(world, boxed_system, collected_labels); + } + + /// A less generic version of [`register_system`](Self::register_system_with_labels). + /// + /// Returns the index in the vector of systems that this new system is stored at. + /// This is only useful for debugging as an external user of this method. + /// + /// This can be useful when you have a boxed system or boxed labels, + /// as the corresponding traits are not implemented for boxed trait objects + /// to avoid indefinite nesting. + pub fn register_boxed_system( + &mut self, + world: &mut World, + mut boxed_system: Box>, + labels: Vec>, + ) -> usize { + // Intialize the system's state + boxed_system.initialize(world); + + let stored_system = StoredSystem { + system: boxed_system, + }; + + // Add the system to the end of the vec + let system_index = self.systems.len(); + self.systems.push(stored_system); + + // For each label that the system has + for label in labels { + let maybe_label_indexes = self.labels.get_mut(&label); + + // Add the index of the system in the vec to the lookup hashmap + // under the corresponding label key + if let Some(label_indexes) = maybe_label_indexes { + label_indexes.push(system_index); + } else { + self.labels.insert(label, vec![system_index]); + }; + } + + system_index + } + + /// Runs the system at the supplied `index` a single time. + #[inline] + fn run_system_at_index(&mut self, world: &mut World, index: usize) { + let stored_system = &mut self.systems[index]; + + // Run the system + stored_system.system.run((), world); + // Apply any generated commands + stored_system.system.apply_buffers(world); + } + + /// Is at least one system in the [`SystemRegistry`] associated with the provided [`SystemLabel`]? + #[inline] + pub fn is_label_registered(&self, label: L) -> bool { + let boxed_label: Box = Box::new(label); + self.labels.get(&boxed_label).is_some() + } + + /// Returns the first matching index for systems with this label if any. + #[inline] + fn first_registered_index(&self, label: L) -> Option { + let boxed_label: Box = Box::new(label); + let vec_of_indexes = self.labels.get(&boxed_label)?; + vec_of_indexes.iter().next().copied() + } + + /// Runs the set of systems corresponding to the provided [`SystemLabel`] on the [`World`] a single time. + /// + /// Systems will be run sequentially in registration order if more than one registered system matches the provided label. + pub fn run_systems_by_label( + &mut self, + world: &mut World, + label: L, + ) -> Result<(), SystemRegistryError> { + self.run_callback( + world, + Callback { + label: label.dyn_clone(), + }, + ) + } + + /// Run the systems corresponding to the label stored in the provided [`Callback`] + /// + /// Systems must be registered before they can be run by their label, + /// including via this method. + /// + /// Systems will be run sequentially in registration order if more than one registered system matches the provided label. + #[inline] + pub fn run_callback( + &mut self, + world: &mut World, + callback: Callback, + ) -> Result<(), SystemRegistryError> { + let boxed_label = callback.label; + + match self.labels.get(&boxed_label) { + Some(matching_indexes) => { + // Loop over the system in registration order + for index in matching_indexes.clone() { + self.run_system_at_index(world, index); + } + + Ok(()) + } + None => Err(SystemRegistryError::LabelNotFound(boxed_label)), + } + } + + /// Runs the supplied system on the [`World`] a single time. + /// + /// You do not need to register systems before they are run in this way. + /// Instead, systems will be automatically registered according to their [`SystemTypeIdLabel`] the first time this method is called on them. + /// + /// System state will be reused between runs, ensuring that [`Local`](crate::system::Local) variables and change detection works correctly. + /// + /// If, via manual system registration, you have somehow managed to insert more than one system with the same [`SystemTypeIdLabel`], + /// only the first will be run. + pub fn run_system + 'static>( + &mut self, + world: &mut World, + system: S, + ) { + let automatic_system_label: SystemTypeIdLabel = SystemTypeIdLabel::new(); + let index = if self.is_label_registered(automatic_system_label) { + self.first_registered_index(automatic_system_label).unwrap() + } else { + let boxed_system: Box> = + Box::new(IntoSystem::into_system(system)); + let labels = boxed_system.default_labels(); + self.register_boxed_system(world, boxed_system, labels) + }; + + self.run_system_at_index(world, index); + } +} + +impl World { + /// Register system a system with any number of [`SystemLabel`]s. + /// + /// Calls [`SystemRegistry::register_system`]. + pub fn register_system< + Params, + S: IntoSystem<(), (), Params> + 'static, + LI: IntoIterator, + L: SystemLabel, + >( + &mut self, + system: S, + labels: LI, + ) { + self.resource_scope(|world, mut registry: Mut| { + registry.register_system(world, system, labels); + }); + } + + /// Runs the supplied system on the [`World`] a single time. + /// + /// Calls [`SystemRegistry::run_system`]. + #[inline] + pub fn run_system + 'static>(&mut self, system: S) { + self.resource_scope(|world, mut registry: Mut| { + registry.run_system(world, system); + }); + } + + /// Runs the systems corresponding to the supplied [`SystemLabel`] on the [`World`] a single time. + /// + /// Calls [`SystemRegistry::run_systems_by_label`]. + #[inline] + pub fn run_systems_by_label( + &mut self, + label: L, + ) -> Result<(), SystemRegistryError> { + self.resource_scope(|world, mut registry: Mut| { + registry.run_systems_by_label(world, label) + }) + } + + /// Run the systems corresponding to the label stored in the provided [`Callback`] + /// + /// Calls [`SystemRegistry::run_callback`]. + #[inline] + pub fn run_callback(&mut self, callback: Callback) -> Result<(), SystemRegistryError> { + self.resource_scope(|world, mut registry: Mut| { + registry.run_callback(world, callback) + }) + } +} + +/// The [`Command`] type for [`SystemRegistry::run_system`] +#[derive(Debug, Clone)] +pub struct RunSystemCommand< + Params: Send + Sync + 'static, + S: IntoSystem<(), (), Params> + Send + Sync + 'static, +> { + _phantom_params: PhantomData, + system: S, +} + +impl + Send + Sync + 'static> + RunSystemCommand +{ + /// Creates a new [`Command`] struct, which can be added to [`Commands`](crate::system::Commands) + #[inline] + #[must_use] + pub fn new(system: S) -> Self { + Self { + _phantom_params: PhantomData::default(), + system, + } + } +} + +impl + Send + Sync + 'static> Command + for RunSystemCommand +{ + #[inline] + fn write(self, world: &mut World) { + world.run_system(self.system); + } +} + +/// The [`Command`] type for [`SystemRegistry::run_systems_by_label`] +#[derive(Debug, Clone)] +pub struct RunSystemsByLabelCommand { + pub callback: Callback, +} + +impl Command for RunSystemsByLabelCommand { + #[inline] + fn write(self, world: &mut World) { + world.resource_scope(|world, mut registry: Mut| { + registry + .run_callback(world, self.callback) + // Ideally this error should be handled more gracefully, + // but that's blocked on a full error handling solution for commands + .unwrap(); + }); + } +} + +/// A struct that stores a boxed [`SystemLabel`], used to cause a [`SystemRegistry`] to run systems. +/// +/// This might be stored as a component, used as an event, or arranged in a queue stored in a resource. +/// Unless you need to inspect the list of events or add additional information, +/// prefer the simpler `commands.run_system` over storing callbacks as events, +/// +/// Systems must be registered via the `register_system` methods on [`SystemRegistry`], [`World`] or `App` +/// before they can be run by their label using a callback. +#[derive(Debug, Component, Clone, Eq)] +pub struct Callback { + /// The label of the system(s) to be run. + /// + /// By default, this is set to the [`SystemTypeIdLabel`] + /// of the system passed in via [`Callback::new()`]. + pub label: Box, +} + +impl Callback { + /// Creates a new callback from a function that can be used as a system. + /// + /// Remember that you must register your systems with the `App` / [`World`] before they can be run as callbacks! + pub fn new + 'static, Params>(_system: S) -> Self { + Callback { + label: Box::new(SystemTypeIdLabel::::new()), + } + } +} + +impl PartialEq for Callback { + fn eq(&self, other: &Self) -> bool { + self.label.dyn_eq(other.label.as_dyn_eq()) + } +} + +impl Hash for Callback { + fn hash(&self, state: &mut H) { + self.label.dyn_hash(state); + } +} + +/// An operation on a [`SystemRegistry`] failed +#[derive(Debug)] +pub enum SystemRegistryError { + /// A system was run by label, but no system with that label was found. + /// + /// Did you forget to register it? + LabelNotFound(Box), +} + +mod tests { + use crate::prelude::*; + + #[derive(Default, PartialEq, Debug)] + struct Counter(u8); + + #[allow(dead_code)] + fn count_up(mut counter: ResMut) { + counter.0 += 1; + } + + #[test] + fn run_system() { + let mut world = World::new(); + world.init_resource::(); + assert_eq!(*world.resource::(), Counter(0)); + world.run_system(count_up); + assert_eq!(*world.resource::(), Counter(1)); + } + + #[test] + /// We need to ensure that the system registry is accessible + /// even after being used once. + fn run_two_systems() { + let mut world = World::new(); + world.init_resource::(); + assert_eq!(*world.resource::(), Counter(0)); + world.run_system(count_up); + assert_eq!(*world.resource::(), Counter(1)); + world.run_system(count_up); + assert_eq!(*world.resource::(), Counter(2)); + } + + #[test] + fn run_system_by_label() { + let mut world = World::new(); + world.init_resource::(); + assert_eq!(*world.resource::(), Counter(0)); + world.register_system(count_up, ["count"]); + world.register_system(count_up, ["count"]); + world.run_systems_by_label("count").unwrap(); + // All systems matching the label will be run. + assert_eq!(*world.resource::(), Counter(2)); + } + + #[allow(dead_code)] + fn spawn_entity(mut commands: Commands) { + commands.spawn(); + } + + #[test] + fn command_processing() { + let mut world = World::new(); + world.init_resource::(); + assert_eq!(world.entities.len(), 0); + world.run_system(spawn_entity); + assert_eq!(world.entities.len(), 1); + } + + #[test] + fn non_send_resources() { + fn non_send_count_down(mut ns: NonSendMut) { + ns.0 -= 1; + } + + let mut world = World::new(); + world.insert_non_send_resource(Counter(10)); + assert_eq!(*world.non_send_resource::(), Counter(10)); + world.run_system(non_send_count_down); + assert_eq!(*world.non_send_resource::(), Counter(9)); + } + + #[test] + fn change_detection() { + #[derive(Default)] + struct ChangeDetector; + + #[allow(dead_code)] + fn count_up_iff_changed( + mut counter: ResMut, + change_detector: ResMut, + ) { + if change_detector.is_changed() { + counter.0 += 1; + } + } + + let mut world = World::new(); + world.init_resource::(); + world.init_resource::(); + assert_eq!(*world.resource::(), Counter(0)); + // Resources are changed when they are first added. + world.run_system(count_up_iff_changed); + assert_eq!(*world.resource::(), Counter(1)); + // Nothing changed + world.run_system(count_up_iff_changed); + assert_eq!(*world.resource::(), Counter(1)); + // Making a change + world.resource_mut::().set_changed(); + world.run_system(count_up_iff_changed); + assert_eq!(*world.resource::(), Counter(2)); + } + + #[test] + fn local_variables() { + // The `Local` begins at the default value of 0 + fn doubling(mut last_counter: Local, mut counter: ResMut) { + counter.0 += last_counter.0; + last_counter.0 = counter.0; + } + + let mut world = World::new(); + world.insert_resource(Counter(1)); + assert_eq!(*world.resource::(), Counter(1)); + world.run_system(doubling); + assert_eq!(*world.resource::(), Counter(1)); + world.run_system(doubling); + assert_eq!(*world.resource::(), Counter(2)); + world.run_system(doubling); + assert_eq!(*world.resource::(), Counter(4)); + world.run_system(doubling); + assert_eq!(*world.resource::(), Counter(8)); + } + + #[test] + // This is a known limitation; + // if this test passes the docs must be updated + // to reflect the ability to chain run_system commands + #[should_panic] + fn system_recursion() { + fn count_to_ten(mut counter: ResMut, mut commands: Commands) { + counter.0 += 1; + if counter.0 < 10 { + commands.run_system(count_to_ten); + } + } + + let mut world = World::new(); + world.init_resource::(); + assert_eq!(*world.resource::(), Counter(0)); + world.run_system(count_to_ten); + assert_eq!(*world.resource::(), Counter(10)); + } +} diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index d849d4a39914a..196dac32c44f5 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -18,7 +18,7 @@ use crate::{ entity::{AllocAtWithoutReplacement, Entities, Entity}, query::{QueryState, WorldQuery}, storage::{Column, SparseSet, Storages}, - system::Resource, + system::{Resource, SystemRegistry}, }; use bevy_ptr::{OwningPtr, Ptr, UnsafeCellDeref}; use bevy_utils::tracing::debug; @@ -95,7 +95,7 @@ pub struct World { impl Default for World { fn default() -> Self { - Self { + let mut world = Self { id: WorldId::new().expect("More `bevy` `World`s have been created than is supported"), entities: Default::default(), components: Default::default(), @@ -109,7 +109,10 @@ impl Default for World { // are detected on first system runs and for direct world queries. change_tick: AtomicU32::new(1), last_change_tick: 0, - } + }; + // This resource is required by bevy_ecs itself, so cannot be included in a plugin + world.init_resource::(); + world } } diff --git a/crates/bevy_utils/src/label.rs b/crates/bevy_utils/src/label.rs index 835656569c1fe..7edbef23f2e87 100644 --- a/crates/bevy_utils/src/label.rs +++ b/crates/bevy_utils/src/label.rs @@ -80,7 +80,7 @@ macro_rules! define_label { } $(#[$label_attr])* - pub trait $label_name: 'static { + pub trait $label_name: Send + Sync + 'static { /// Converts this type into an opaque, strongly-typed label. fn as_label(&self) -> $id_name { let id = self.type_id(); diff --git a/examples/README.md b/examples/README.md index 97c7acaa0a19e..0dbb8b99d8a09 100644 --- a/examples/README.md +++ b/examples/README.md @@ -194,6 +194,7 @@ Example | Description [Generic System](../examples/ecs/generic_system.rs) | Shows how to create systems that can be reused with different types [Hierarchy](../examples/ecs/hierarchy.rs) | Creates a hierarchy of parents and children entities [Iter Combinations](../examples/ecs/iter_combinations.rs) | Shows how to iterate over combinations of query results +[One Shot Systems](../examples/ecs/one_shot_systems.rs) | Shows how to flexibly run systems without scheduling them [Parallel Query](../examples/ecs/parallel_query.rs) | Illustrates parallel queries with `ParallelIterator` [Removal Detection](../examples/ecs/removal_detection.rs) | Query for entities that had a specific component removed in a previous stage during the current frame [Startup System](../examples/ecs/startup_system.rs) | Demonstrates a startup system (one that runs once when the app starts up) diff --git a/examples/ecs/one_shot_systems.rs b/examples/ecs/one_shot_systems.rs new file mode 100644 index 0000000000000..abed2a5b88cb8 --- /dev/null +++ b/examples/ecs/one_shot_systems.rs @@ -0,0 +1,73 @@ +//! Demonstrates the use of "one-shot systems", which run once when triggered. +//! +//! These can be useful to help structure your logic in a push-based fashion, +//! reducing the overhead of running extremely rarely run systems +//! and improving schedule flexibility. +//! +//! See the [`SystemRegistry`](bevy::ecs::SystemRegistry) docs for more details. + +use bevy::ecs::system::Callback; +use bevy::prelude::*; + +fn main() { + App::new() + .add_startup_system(count_entities) + .add_startup_system(setup) + // One shot systems are interchangeable with ordinarily scheduled systems. + // Change detection, Local and NonSend all work as expected. + .add_system_to_stage(CoreStage::PostUpdate, count_entities) + // Registered systems can be called dynamically by their label. + // These must be registered in advance, or commands.run_system will panic + // as no matching system was found. + .register_system(button_pressed) + .register_system(slider_toggled) + // One-shot systems can be used to build complex abstractions + // to match the needs of your design. + // Here, we model a very simple component-linked callback architecture. + .add_system(evaluate_callbacks) + .run(); +} + +// Any ordinary system can be run via commands.run_system or world.run_system. +// +// Chained systems, exclusive systems and systems which themselves run systems cannot be called in this way. +fn count_entities(all_entities: Query<()>) { + dbg!(all_entities.iter().count()); +} + +#[derive(Component)] +struct Triggered; + +fn setup(mut commands: Commands) { + commands + .spawn() + // The Callback component is defined in bevy_ecs, + // but wrapping this (or making your own customized variant) is easy. + // Just stored a boxed SystemLabel! + .insert(Callback::new(button_pressed)) + .insert(Triggered); + // This entity does not have a Triggered component, so its callback won't run. + commands.spawn().insert(Callback::new(slider_toggled)); + commands.run_system(count_entities); +} + +fn button_pressed() { + println!("A button was pressed!"); +} + +fn slider_toggled() { + println!("A slider was toggled!"); +} + +/// Runs the systems associated with each `Callback` component if the entity also has a Triggered component. +/// +/// This could be done in an exclusive system rather than using `Commands` if preferred. +fn evaluate_callbacks(query: Query<&Callback, With>, mut commands: Commands) { + for callback in query.iter() { + // Because we don't have access to the type information of the callbacks + // we have to use the layer of indirection provided by system labels. + // Note that if we had registered multiple systems with the same label, + // they would all be evaluated here. + commands.run_callback(callback.clone()); + } +}