mirror of
https://github.com/bevyengine/bevy
synced 2024-11-22 12:43:34 +00:00
One Shot Systems (#8963)
I'm adopting this ~~child~~ PR. # Objective - Working with exclusive world access is not always easy: in many cases, a standard system or three is more ergonomic to write, and more modularly maintainable. - For small, one-off tasks (commonly handled with scripting), running an event-reader system incurs a small but flat overhead cost and muddies the schedule. - Certain forms of logic (e.g. turn-based games) want very fine-grained linear and/or branching control over logic. - SystemState is not automatically cached, and so performance can suffer and change detection breaks. - Fixes https://github.com/bevyengine/bevy/issues/2192. - Partial workaround for https://github.com/bevyengine/bevy/issues/279. ## Solution - Adds a SystemRegistry resource to the World, which stores initialized systems keyed by their SystemSet. - Allows users to call world.run_system(my_system) and commands.run_system(my_system), without re-initializing or losing state (essential for change detection). - Add a Callback type to enable convenient use of dynamic one shot systems and reduce the mental overhead of working with Box<dyn SystemSet>. - Allow users to run systems based on their SystemSet, enabling more complex user-made abstractions. ## Future work - Parameterized one-shot systems would improve reusability and bring them closer to events and commands. The API could be something like run_system_with_input(my_system, my_input) and use the In SystemParam. - We should evaluate the unification of commands and one-shot systems since they are two different ways to run logic on demand over a World. ### Prior attempts - https://github.com/bevyengine/bevy/pull/2234 - https://github.com/bevyengine/bevy/pull/2417 - https://github.com/bevyengine/bevy/pull/4090 - https://github.com/bevyengine/bevy/pull/7999 This PR continues the work done in https://github.com/bevyengine/bevy/pull/7999. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: Federico Rinaldi <gisquerin@gmail.com> Co-authored-by: MinerSebas <66798382+MinerSebas@users.noreply.github.com> Co-authored-by: Aevyrie <aevyrie@gmail.com> Co-authored-by: Alejandro Pascual Pozo <alejandro.pascual.pozo@gmail.com> Co-authored-by: Rob Parrett <robparrett@gmail.com> Co-authored-by: François <mockersf@gmail.com> Co-authored-by: Dmytro Banin <banind@cs.washington.edu> Co-authored-by: James Liu <contact@jamessliu.com>
This commit is contained in:
parent
b995827013
commit
e4b368721d
10 changed files with 474 additions and 17 deletions
10
Cargo.toml
10
Cargo.toml
|
@ -1284,6 +1284,16 @@ description = "Shows how to iterate over combinations of query results"
|
||||||
category = "ECS (Entity Component System)"
|
category = "ECS (Entity Component System)"
|
||||||
wasm = true
|
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]]
|
[[example]]
|
||||||
name = "parallel_query"
|
name = "parallel_query"
|
||||||
path = "examples/ecs/parallel_query.rs"
|
path = "examples/ecs/parallel_query.rs"
|
||||||
|
|
|
@ -81,6 +81,7 @@ impl<T> Hash for SystemTypeSet<T> {
|
||||||
// all systems of a given type are the same
|
// all systems of a given type are the same
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Clone for SystemTypeSet<T> {
|
impl<T> Clone for SystemTypeSet<T> {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
*self
|
*self
|
||||||
|
|
|
@ -5,6 +5,7 @@ use crate::{
|
||||||
self as bevy_ecs,
|
self as bevy_ecs,
|
||||||
bundle::Bundle,
|
bundle::Bundle,
|
||||||
entity::{Entities, Entity},
|
entity::{Entities, Entity},
|
||||||
|
system::{RunSystem, SystemId},
|
||||||
world::{EntityWorldMut, FromWorld, World},
|
world::{EntityWorldMut, FromWorld, World},
|
||||||
};
|
};
|
||||||
use bevy_ecs_macros::SystemParam;
|
use bevy_ecs_macros::SystemParam;
|
||||||
|
@ -517,11 +518,19 @@ impl<'w, 's> Commands<'w, 's> {
|
||||||
self.queue.push(RemoveResource::<R>::new());
|
self.queue.push(RemoveResource::<R>::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Runs the system corresponding to the given [`SystemId`].
|
||||||
|
/// Systems are ran in an exclusive and single threaded way.
|
||||||
|
/// Running slow systems can become a bottleneck.
|
||||||
|
///
|
||||||
|
/// Calls [`World::run_system`](crate::system::World::run_system).
|
||||||
|
pub fn run_system(&mut self, id: SystemId) {
|
||||||
|
self.queue.push(RunSystem::new(id));
|
||||||
|
}
|
||||||
|
|
||||||
/// Pushes a generic [`Command`] to the command queue.
|
/// Pushes a generic [`Command`] to the command queue.
|
||||||
///
|
///
|
||||||
/// `command` can be a built-in command, custom struct that implements [`Command`] or a closure
|
/// `command` can be a built-in command, custom struct that implements [`Command`] or a closure
|
||||||
/// that takes [`&mut World`](World) as an argument.
|
/// that takes [`&mut World`](World) as an argument.
|
||||||
///
|
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
|
|
@ -72,8 +72,10 @@ impl SystemMeta {
|
||||||
// (to avoid the need for unwrapping to retrieve SystemMeta)
|
// (to avoid the need for unwrapping to retrieve SystemMeta)
|
||||||
/// Holds on to persistent state required to drive [`SystemParam`] for a [`System`].
|
/// 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`].
|
/// 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 [`World::run_system`](crate::system::World::run_system)
|
||||||
|
/// 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,
|
/// 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.
|
/// and arbitrary system parameters (like [`EventWriter`](crate::event::EventWriter)) can be conveniently fetched.
|
||||||
|
@ -89,6 +91,8 @@ impl SystemMeta {
|
||||||
/// - [`Local`](crate::system::Local) variables that hold state
|
/// - [`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
|
/// - [`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 [`World::run_system`](crate::system::World::run_system).
|
||||||
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// Basic usage:
|
/// Basic usage:
|
||||||
|
|
|
@ -112,6 +112,7 @@ mod query;
|
||||||
#[allow(clippy::module_inception)]
|
#[allow(clippy::module_inception)]
|
||||||
mod system;
|
mod system;
|
||||||
mod system_param;
|
mod system_param;
|
||||||
|
mod system_registry;
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
@ -124,6 +125,7 @@ pub use function_system::*;
|
||||||
pub use query::*;
|
pub use query::*;
|
||||||
pub use system::*;
|
pub use system::*;
|
||||||
pub use system_param::*;
|
pub use system_param::*;
|
||||||
|
pub use system_registry::*;
|
||||||
|
|
||||||
use crate::world::World;
|
use crate::world::World;
|
||||||
|
|
||||||
|
|
|
@ -165,6 +165,30 @@ impl<In: 'static, Out: 'static> Debug for dyn System<In = In, Out = Out> {
|
||||||
/// This function is not an efficient method of running systems and its meant to be used as a utility
|
/// This function is not an efficient method of running systems and its meant to be used as a utility
|
||||||
/// for testing and/or diagnostics.
|
/// for testing and/or diagnostics.
|
||||||
///
|
///
|
||||||
|
/// Systems called through [`run_system_once`](crate::system::RunSystemOnce::run_system_once) do not hold onto any state,
|
||||||
|
/// as they are created and destroyed every time [`run_system_once`](crate::system::RunSystemOnce::run_system_once) is called.
|
||||||
|
/// Practically, this means that [`Local`](crate::system::Local) variables are
|
||||||
|
/// reset on every run and change detection does not work.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use bevy_ecs::prelude::*;
|
||||||
|
/// # use bevy_ecs::system::RunSystemOnce;
|
||||||
|
/// #[derive(Resource, Default)]
|
||||||
|
/// struct Counter(u8);
|
||||||
|
///
|
||||||
|
/// fn increment(mut counter: Local<Counter>) {
|
||||||
|
/// counter.0 += 1;
|
||||||
|
/// println!("{}", counter.0);
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// let mut world = World::default();
|
||||||
|
/// world.run_system_once(increment); // prints 1
|
||||||
|
/// world.run_system_once(increment); // still prints 1
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// If you do need systems to hold onto state between runs, use the [`World::run_system`](crate::system::World::run_system)
|
||||||
|
/// and run the system by their [`SystemId`](crate::system::SystemId).
|
||||||
|
///
|
||||||
/// # Usage
|
/// # Usage
|
||||||
/// Typically, to test a system, or to extract specific diagnostics information from a world,
|
/// Typically, to test a system, or to extract specific diagnostics information from a world,
|
||||||
/// you'd need a [`Schedule`](crate::schedule::Schedule) to run the system. This can create redundant boilerplate code
|
/// you'd need a [`Schedule`](crate::schedule::Schedule) to run the system. This can create redundant boilerplate code
|
||||||
|
@ -180,9 +204,9 @@ impl<In: 'static, Out: 'static> Debug for dyn System<In = In, Out = Out> {
|
||||||
/// This usage is helpful when trying to test systems or functions that operate on [`Commands`](crate::system::Commands):
|
/// This usage is helpful when trying to test systems or functions that operate on [`Commands`](crate::system::Commands):
|
||||||
/// ```
|
/// ```
|
||||||
/// # use bevy_ecs::prelude::*;
|
/// # use bevy_ecs::prelude::*;
|
||||||
/// # use bevy_ecs::system::RunSystem;
|
/// # use bevy_ecs::system::RunSystemOnce;
|
||||||
/// let mut world = World::default();
|
/// let mut world = World::default();
|
||||||
/// let entity = world.run_system(|mut commands: Commands| {
|
/// let entity = world.run_system_once(|mut commands: Commands| {
|
||||||
/// commands.spawn_empty().id()
|
/// commands.spawn_empty().id()
|
||||||
/// });
|
/// });
|
||||||
/// # assert!(world.get_entity(entity).is_some());
|
/// # assert!(world.get_entity(entity).is_some());
|
||||||
|
@ -193,7 +217,7 @@ impl<In: 'static, Out: 'static> Debug for dyn System<In = In, Out = Out> {
|
||||||
/// This usage is helpful when trying to run an arbitrary query on a world for testing or debugging purposes:
|
/// This usage is helpful when trying to run an arbitrary query on a world for testing or debugging purposes:
|
||||||
/// ```
|
/// ```
|
||||||
/// # use bevy_ecs::prelude::*;
|
/// # use bevy_ecs::prelude::*;
|
||||||
/// # use bevy_ecs::system::RunSystem;
|
/// # use bevy_ecs::system::RunSystemOnce;
|
||||||
///
|
///
|
||||||
/// #[derive(Component)]
|
/// #[derive(Component)]
|
||||||
/// struct T(usize);
|
/// struct T(usize);
|
||||||
|
@ -202,7 +226,7 @@ impl<In: 'static, Out: 'static> Debug for dyn System<In = In, Out = Out> {
|
||||||
/// world.spawn(T(0));
|
/// world.spawn(T(0));
|
||||||
/// world.spawn(T(1));
|
/// world.spawn(T(1));
|
||||||
/// world.spawn(T(1));
|
/// world.spawn(T(1));
|
||||||
/// let count = world.run_system(|query: Query<&T>| {
|
/// let count = world.run_system_once(|query: Query<&T>| {
|
||||||
/// query.iter().filter(|t| t.0 == 1).count()
|
/// query.iter().filter(|t| t.0 == 1).count()
|
||||||
/// });
|
/// });
|
||||||
///
|
///
|
||||||
|
@ -213,7 +237,7 @@ impl<In: 'static, Out: 'static> Debug for dyn System<In = In, Out = Out> {
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use bevy_ecs::prelude::*;
|
/// # use bevy_ecs::prelude::*;
|
||||||
/// # use bevy_ecs::system::RunSystem;
|
/// # use bevy_ecs::system::RunSystemOnce;
|
||||||
///
|
///
|
||||||
/// #[derive(Component)]
|
/// #[derive(Component)]
|
||||||
/// struct T(usize);
|
/// struct T(usize);
|
||||||
|
@ -226,26 +250,26 @@ impl<In: 'static, Out: 'static> Debug for dyn System<In = In, Out = Out> {
|
||||||
/// world.spawn(T(0));
|
/// world.spawn(T(0));
|
||||||
/// world.spawn(T(1));
|
/// world.spawn(T(1));
|
||||||
/// world.spawn(T(1));
|
/// world.spawn(T(1));
|
||||||
/// let count = world.run_system(count);
|
/// let count = world.run_system_once(count);
|
||||||
///
|
///
|
||||||
/// # assert_eq!(count, 2);
|
/// # assert_eq!(count, 2);
|
||||||
/// ```
|
/// ```
|
||||||
pub trait RunSystem: Sized {
|
pub trait RunSystemOnce: Sized {
|
||||||
/// Runs a system and applies its deferred parameters.
|
/// Runs a system and applies its deferred parameters.
|
||||||
fn run_system<T: IntoSystem<(), Out, Marker>, Out, Marker>(self, system: T) -> Out {
|
fn run_system_once<T: IntoSystem<(), Out, Marker>, Out, Marker>(self, system: T) -> Out {
|
||||||
self.run_system_with((), system)
|
self.run_system_once_with((), system)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Runs a system with given input and applies its deferred parameters.
|
/// Runs a system with given input and applies its deferred parameters.
|
||||||
fn run_system_with<T: IntoSystem<In, Out, Marker>, In, Out, Marker>(
|
fn run_system_once_with<T: IntoSystem<In, Out, Marker>, In, Out, Marker>(
|
||||||
self,
|
self,
|
||||||
input: In,
|
input: In,
|
||||||
system: T,
|
system: T,
|
||||||
) -> Out;
|
) -> Out;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RunSystem for &mut World {
|
impl RunSystemOnce for &mut World {
|
||||||
fn run_system_with<T: IntoSystem<In, Out, Marker>, In, Out, Marker>(
|
fn run_system_once_with<T: IntoSystem<In, Out, Marker>, In, Out, Marker>(
|
||||||
self,
|
self,
|
||||||
input: In,
|
input: In,
|
||||||
system: T,
|
system: T,
|
||||||
|
@ -261,10 +285,11 @@ impl RunSystem for &mut World {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate as bevy_ecs;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn run_system() {
|
fn run_system_once() {
|
||||||
struct T(usize);
|
struct T(usize);
|
||||||
|
|
||||||
impl Resource for T {}
|
impl Resource for T {}
|
||||||
|
@ -275,8 +300,53 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut world = World::default();
|
let mut world = World::default();
|
||||||
let n = world.run_system_with(1, system);
|
let n = world.run_system_once_with(1, system);
|
||||||
assert_eq!(n, 2);
|
assert_eq!(n, 2);
|
||||||
assert_eq!(world.resource::<T>().0, 1);
|
assert_eq!(world.resource::<T>().0, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Resource, Default, PartialEq, Debug)]
|
||||||
|
struct Counter(u8);
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn count_up(mut counter: ResMut<Counter>) {
|
||||||
|
counter.0 += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn run_two_systems() {
|
||||||
|
let mut world = World::new();
|
||||||
|
world.init_resource::<Counter>();
|
||||||
|
assert_eq!(*world.resource::<Counter>(), Counter(0));
|
||||||
|
world.run_system_once(count_up);
|
||||||
|
assert_eq!(*world.resource::<Counter>(), Counter(1));
|
||||||
|
world.run_system_once(count_up);
|
||||||
|
assert_eq!(*world.resource::<Counter>(), Counter(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn spawn_entity(mut commands: Commands) {
|
||||||
|
commands.spawn_empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn command_processing() {
|
||||||
|
let mut world = World::new();
|
||||||
|
assert_eq!(world.entities.len(), 0);
|
||||||
|
world.run_system_once(spawn_entity);
|
||||||
|
assert_eq!(world.entities.len(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn non_send_resources() {
|
||||||
|
fn non_send_count_down(mut ns: NonSendMut<Counter>) {
|
||||||
|
ns.0 -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut world = World::new();
|
||||||
|
world.insert_non_send_resource(Counter(10));
|
||||||
|
assert_eq!(*world.non_send_resource::<Counter>(), Counter(10));
|
||||||
|
world.run_system_once(non_send_count_down);
|
||||||
|
assert_eq!(*world.non_send_resource::<Counter>(), Counter(9));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
301
crates/bevy_ecs/src/system/system_registry.rs
Normal file
301
crates/bevy_ecs/src/system/system_registry.rs
Normal file
|
@ -0,0 +1,301 @@
|
||||||
|
use crate::entity::Entity;
|
||||||
|
use crate::system::{BoxedSystem, Command, IntoSystem};
|
||||||
|
use crate::world::World;
|
||||||
|
use crate::{self as bevy_ecs};
|
||||||
|
use bevy_ecs_macros::Component;
|
||||||
|
|
||||||
|
/// A small wrapper for [`BoxedSystem`] that also keeps track whether or not the system has been initialized.
|
||||||
|
#[derive(Component)]
|
||||||
|
struct RegisteredSystem {
|
||||||
|
initialized: bool,
|
||||||
|
system: BoxedSystem,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A system that has been removed from the registry.
|
||||||
|
/// It contains the system and whether or not it has been initialized.
|
||||||
|
///
|
||||||
|
/// This struct is returned by [`World::remove_system`].
|
||||||
|
pub struct RemovedSystem {
|
||||||
|
initialized: bool,
|
||||||
|
system: BoxedSystem,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RemovedSystem {
|
||||||
|
/// Is the system initialized?
|
||||||
|
/// A system is initialized the first time it's ran.
|
||||||
|
pub fn initialized(&self) -> bool {
|
||||||
|
self.initialized
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The system removed from the storage.
|
||||||
|
pub fn system(self) -> BoxedSystem {
|
||||||
|
self.system
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An identifier for a registered system.
|
||||||
|
///
|
||||||
|
/// These are opaque identifiers, keyed to a specific [`World`],
|
||||||
|
/// and are created via [`World::register_system`].
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub struct SystemId(Entity);
|
||||||
|
|
||||||
|
impl World {
|
||||||
|
/// Registers a system and returns a [`SystemId`] so it can later be called by [`World::run_system`].
|
||||||
|
///
|
||||||
|
/// It's possible to register the same systems more than once, they'll be stored seperately.
|
||||||
|
///
|
||||||
|
/// This is different from adding systems to a [`Schedule`](crate::schedule::Schedule),
|
||||||
|
/// because the [`SystemId`] that is returned can be used anywhere in the [`World`] to run the associated system.
|
||||||
|
/// This allows for running systems in a pushed-based fashion.
|
||||||
|
/// Using a [`Schedule`](crate::schedule::Schedule) is still preferred for most cases
|
||||||
|
/// due to its better performance and abillity to run non-conflicting systems simultaneously.
|
||||||
|
pub fn register_system<M, S: IntoSystem<(), (), M> + 'static>(
|
||||||
|
&mut self,
|
||||||
|
system: S,
|
||||||
|
) -> SystemId {
|
||||||
|
SystemId(
|
||||||
|
self.spawn(RegisteredSystem {
|
||||||
|
initialized: false,
|
||||||
|
system: Box::new(IntoSystem::into_system(system)),
|
||||||
|
})
|
||||||
|
.id(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes a registered system and returns the system, if it exists.
|
||||||
|
/// After removing a system, the [`SystemId`] becomes invalid and attempting to use it afterwards will result in errors.
|
||||||
|
/// Re-adding the removed system will register it on a new [`SystemId`].
|
||||||
|
///
|
||||||
|
/// If no system corresponds to the given [`SystemId`], this method returns an error.
|
||||||
|
/// Systems are also not allowed to remove themselves, this returns an error too.
|
||||||
|
pub fn remove_system(&mut self, id: SystemId) -> Result<RemovedSystem, RegisteredSystemError> {
|
||||||
|
match self.get_entity_mut(id.0) {
|
||||||
|
Some(mut entity) => {
|
||||||
|
let registered_system = entity
|
||||||
|
.take::<RegisteredSystem>()
|
||||||
|
.ok_or(RegisteredSystemError::SelfRemove(id))?;
|
||||||
|
entity.despawn();
|
||||||
|
Ok(RemovedSystem {
|
||||||
|
initialized: registered_system.initialized,
|
||||||
|
system: registered_system.system,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
None => Err(RegisteredSystemError::SystemIdNotRegistered(id)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run stored systems by their [`SystemId`].
|
||||||
|
/// Before running a system, it must first be registered.
|
||||||
|
/// The method [`World::register_system`] stores a given system and returns a [`SystemId`].
|
||||||
|
/// This is different from [`RunSystemOnce::run_system_once`](crate::system::RunSystemOnce::run_system_once),
|
||||||
|
/// because it keeps local state between calls and change detection works correctly.
|
||||||
|
///
|
||||||
|
/// # Limitations
|
||||||
|
///
|
||||||
|
/// - Stored systems cannot be chained: they can neither have an [`In`](crate::system::In) nor return any values.
|
||||||
|
/// - Stored systems cannot be recursive, they cannot call themselves through [`Commands::run_system`](crate::system::Commands).
|
||||||
|
/// - Exclusive systems cannot be used.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use bevy_ecs::prelude::*;
|
||||||
|
/// #[derive(Resource, Default)]
|
||||||
|
/// struct Counter(u8);
|
||||||
|
///
|
||||||
|
/// fn increment(mut counter: Local<Counter>) {
|
||||||
|
/// counter.0 += 1;
|
||||||
|
/// println!("{}", counter.0);
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// let mut world = World::default();
|
||||||
|
/// let counter_one = world.register_system(increment);
|
||||||
|
/// let counter_two = world.register_system(increment);
|
||||||
|
/// world.run_system(counter_one); // -> 1
|
||||||
|
/// world.run_system(counter_one); // -> 2
|
||||||
|
/// world.run_system(counter_two); // -> 1
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Change detection:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use bevy_ecs::prelude::*;
|
||||||
|
/// #[derive(Resource, Default)]
|
||||||
|
/// struct ChangeDetector;
|
||||||
|
///
|
||||||
|
/// let mut world = World::default();
|
||||||
|
/// world.init_resource::<ChangeDetector>();
|
||||||
|
/// let detector = world.register_system(|change_detector: ResMut<ChangeDetector>| {
|
||||||
|
/// if change_detector.is_changed() {
|
||||||
|
/// println!("Something happened!");
|
||||||
|
/// } else {
|
||||||
|
/// println!("Nothing happened.");
|
||||||
|
/// }
|
||||||
|
/// });
|
||||||
|
///
|
||||||
|
/// // Resources are changed when they are first added
|
||||||
|
/// let _ = world.run_system(detector); // -> Something happened!
|
||||||
|
/// let _ = world.run_system(detector); // -> Nothing happened.
|
||||||
|
/// world.resource_mut::<ChangeDetector>().set_changed();
|
||||||
|
/// let _ = world.run_system(detector); // -> Something happened!
|
||||||
|
/// ```
|
||||||
|
pub fn run_system(&mut self, id: SystemId) -> Result<(), RegisteredSystemError> {
|
||||||
|
// lookup
|
||||||
|
let mut entity = self
|
||||||
|
.get_entity_mut(id.0)
|
||||||
|
.ok_or(RegisteredSystemError::SystemIdNotRegistered(id))?;
|
||||||
|
|
||||||
|
// take ownership of system trait object
|
||||||
|
let RegisteredSystem {
|
||||||
|
mut initialized,
|
||||||
|
mut system,
|
||||||
|
} = entity
|
||||||
|
.take::<RegisteredSystem>()
|
||||||
|
.ok_or(RegisteredSystemError::Recursive(id))?;
|
||||||
|
|
||||||
|
// run the system
|
||||||
|
if !initialized {
|
||||||
|
system.initialize(self);
|
||||||
|
initialized = true;
|
||||||
|
}
|
||||||
|
system.run((), self);
|
||||||
|
system.apply_deferred(self);
|
||||||
|
|
||||||
|
// return ownership of system trait object (if entity still exists)
|
||||||
|
if let Some(mut entity) = self.get_entity_mut(id.0) {
|
||||||
|
entity.insert::<RegisteredSystem>(RegisteredSystem {
|
||||||
|
initialized,
|
||||||
|
system,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The [`Command`] type for [`World::run_system`].
|
||||||
|
///
|
||||||
|
/// This command runs systems in an exclusive and single threaded way.
|
||||||
|
/// Running slow systems can become a bottleneck.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct RunSystem {
|
||||||
|
system_id: SystemId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RunSystem {
|
||||||
|
/// Creates a new [`Command`] struct, which can be added to [`Commands`](crate::system::Commands)
|
||||||
|
pub fn new(system_id: SystemId) -> Self {
|
||||||
|
Self { system_id }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Command for RunSystem {
|
||||||
|
#[inline]
|
||||||
|
fn apply(self, world: &mut World) {
|
||||||
|
let _ = world.run_system(self.system_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An operation with stored systems failed.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum RegisteredSystemError {
|
||||||
|
/// A system was run by id, but no system with that id was found.
|
||||||
|
///
|
||||||
|
/// Did you forget to register it?
|
||||||
|
SystemIdNotRegistered(SystemId),
|
||||||
|
/// A system tried to run itself recursively.
|
||||||
|
Recursive(SystemId),
|
||||||
|
/// A system tried to remove itself.
|
||||||
|
SelfRemove(SystemId),
|
||||||
|
}
|
||||||
|
|
||||||
|
mod tests {
|
||||||
|
use crate as bevy_ecs;
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Resource, Default, PartialEq, Debug)]
|
||||||
|
struct Counter(u8);
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn change_detection() {
|
||||||
|
#[derive(Resource, Default)]
|
||||||
|
struct ChangeDetector;
|
||||||
|
|
||||||
|
fn count_up_iff_changed(
|
||||||
|
mut counter: ResMut<Counter>,
|
||||||
|
change_detector: ResMut<ChangeDetector>,
|
||||||
|
) {
|
||||||
|
if change_detector.is_changed() {
|
||||||
|
counter.0 += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut world = World::new();
|
||||||
|
world.init_resource::<ChangeDetector>();
|
||||||
|
world.init_resource::<Counter>();
|
||||||
|
assert_eq!(*world.resource::<Counter>(), Counter(0));
|
||||||
|
// Resources are changed when they are first added.
|
||||||
|
let id = world.register_system(count_up_iff_changed);
|
||||||
|
let _ = world.run_system(id);
|
||||||
|
assert_eq!(*world.resource::<Counter>(), Counter(1));
|
||||||
|
// Nothing changed
|
||||||
|
let _ = world.run_system(id);
|
||||||
|
assert_eq!(*world.resource::<Counter>(), Counter(1));
|
||||||
|
// Making a change
|
||||||
|
world.resource_mut::<ChangeDetector>().set_changed();
|
||||||
|
let _ = world.run_system(id);
|
||||||
|
assert_eq!(*world.resource::<Counter>(), Counter(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn local_variables() {
|
||||||
|
// The `Local` begins at the default value of 0
|
||||||
|
fn doubling(last_counter: Local<Counter>, mut counter: ResMut<Counter>) {
|
||||||
|
counter.0 += last_counter.0 .0;
|
||||||
|
last_counter.0 .0 = counter.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut world = World::new();
|
||||||
|
world.insert_resource(Counter(1));
|
||||||
|
assert_eq!(*world.resource::<Counter>(), Counter(1));
|
||||||
|
let id = world.register_system(doubling);
|
||||||
|
let _ = world.run_system(id);
|
||||||
|
assert_eq!(*world.resource::<Counter>(), Counter(1));
|
||||||
|
let _ = world.run_system(id);
|
||||||
|
assert_eq!(*world.resource::<Counter>(), Counter(2));
|
||||||
|
let _ = world.run_system(id);
|
||||||
|
assert_eq!(*world.resource::<Counter>(), Counter(4));
|
||||||
|
let _ = world.run_system(id);
|
||||||
|
assert_eq!(*world.resource::<Counter>(), Counter(8));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nested_systems() {
|
||||||
|
use crate::system::SystemId;
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct Callback(SystemId);
|
||||||
|
|
||||||
|
fn nested(query: Query<&Callback>, mut commands: Commands) {
|
||||||
|
for callback in query.iter() {
|
||||||
|
commands.run_system(callback.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut world = World::new();
|
||||||
|
world.insert_resource(Counter(0));
|
||||||
|
|
||||||
|
let increment_two = world.register_system(|mut counter: ResMut<Counter>| {
|
||||||
|
counter.0 += 2;
|
||||||
|
});
|
||||||
|
let increment_three = world.register_system(|mut counter: ResMut<Counter>| {
|
||||||
|
counter.0 += 3;
|
||||||
|
});
|
||||||
|
let nested_id = world.register_system(nested);
|
||||||
|
|
||||||
|
world.spawn(Callback(increment_two));
|
||||||
|
world.spawn(Callback(increment_three));
|
||||||
|
let _ = world.run_system(nested_id);
|
||||||
|
assert_eq!(*world.resource::<Counter>(), Counter(5));
|
||||||
|
}
|
||||||
|
}
|
|
@ -179,7 +179,7 @@ macro_rules! define_label {
|
||||||
}
|
}
|
||||||
|
|
||||||
$(#[$label_attr])*
|
$(#[$label_attr])*
|
||||||
pub trait $label_name: 'static {
|
pub trait $label_name: Send + Sync + 'static {
|
||||||
/// Converts this type into an opaque, strongly-typed label.
|
/// Converts this type into an opaque, strongly-typed label.
|
||||||
fn as_label(&self) -> $id_name {
|
fn as_label(&self) -> $id_name {
|
||||||
let id = self.type_id();
|
let id = self.type_id();
|
||||||
|
|
|
@ -223,6 +223,7 @@ Example | Description
|
||||||
[Hierarchy](../examples/ecs/hierarchy.rs) | Creates a hierarchy of parents and children entities
|
[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
|
[Iter Combinations](../examples/ecs/iter_combinations.rs) | Shows how to iterate over combinations of query results
|
||||||
[Nondeterministic System Order](../examples/ecs/nondeterministic_system_order.rs) | Systems run in parallel, but their order isn't always deterministic. Here's how to detect and fix this.
|
[Nondeterministic System Order](../examples/ecs/nondeterministic_system_order.rs) | Systems run in parallel, but their order isn't always deterministic. Here's how to detect and fix this.
|
||||||
|
[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`
|
[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 earlier in the current frame
|
[Removal Detection](../examples/ecs/removal_detection.rs) | Query for entities that had a specific component removed earlier in the current frame
|
||||||
[Run Conditions](../examples/ecs/run_conditions.rs) | Run systems only when one or multiple conditions are met
|
[Run Conditions](../examples/ecs/run_conditions.rs) | Run systems only when one or multiple conditions are met
|
||||||
|
|
59
examples/ecs/one_shot_systems.rs
Normal file
59
examples/ecs/one_shot_systems.rs
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
//! 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 [`World::run_system`](bevy::ecs::World::run_system) or
|
||||||
|
//! [`World::run_system_once`](bevy::ecs::World::run_system_once) docs for more
|
||||||
|
//! details.
|
||||||
|
|
||||||
|
use bevy::{
|
||||||
|
ecs::system::{RunSystemOnce, SystemId},
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_systems(Startup, (count_entities, setup))
|
||||||
|
.add_systems(PostUpdate, count_entities)
|
||||||
|
.add_systems(Update, evaluate_callbacks)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any ordinary system can be run via commands.run_system or world.run_system.
|
||||||
|
fn count_entities(all_entities: Query<()>) {
|
||||||
|
dbg!(all_entities.iter().count());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct Callback(SystemId);
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct Triggered;
|
||||||
|
|
||||||
|
fn setup(world: &mut World) {
|
||||||
|
let button_pressed_id = world.register_system(button_pressed);
|
||||||
|
world.spawn((Callback(button_pressed_id), Triggered));
|
||||||
|
// This entity does not have a Triggered component, so its callback won't run.
|
||||||
|
let slider_toggled_id = world.register_system(slider_toggled);
|
||||||
|
world.spawn(Callback(slider_toggled_id));
|
||||||
|
world.run_system_once(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<Triggered>>, mut commands: Commands) {
|
||||||
|
for callback in query.iter() {
|
||||||
|
commands.run_system(callback.0);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue