mirror of
https://github.com/bevyengine/bevy
synced 2024-11-21 20:23:28 +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)"
|
||||
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"
|
||||
|
|
|
@ -81,6 +81,7 @@ impl<T> Hash for SystemTypeSet<T> {
|
|||
// all systems of a given type are the same
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for SystemTypeSet<T> {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
|
|
|
@ -5,6 +5,7 @@ use crate::{
|
|||
self as bevy_ecs,
|
||||
bundle::Bundle,
|
||||
entity::{Entities, Entity},
|
||||
system::{RunSystem, SystemId},
|
||||
world::{EntityWorldMut, FromWorld, World},
|
||||
};
|
||||
use bevy_ecs_macros::SystemParam;
|
||||
|
@ -517,11 +518,19 @@ impl<'w, 's> Commands<'w, 's> {
|
|||
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.
|
||||
///
|
||||
/// `command` can be a built-in command, custom struct that implements [`Command`] or a closure
|
||||
/// that takes [`&mut World`](World) as an argument.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
|
|
|
@ -72,8 +72,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 [`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,
|
||||
/// 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
|
||||
/// - [`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
|
||||
///
|
||||
/// Basic usage:
|
||||
|
|
|
@ -112,6 +112,7 @@ mod query;
|
|||
#[allow(clippy::module_inception)]
|
||||
mod system;
|
||||
mod system_param;
|
||||
mod system_registry;
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
|
@ -124,6 +125,7 @@ pub use function_system::*;
|
|||
pub use query::*;
|
||||
pub use system::*;
|
||||
pub use system_param::*;
|
||||
pub use system_registry::*;
|
||||
|
||||
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
|
||||
/// 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
|
||||
/// 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
|
||||
|
@ -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):
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # use bevy_ecs::system::RunSystem;
|
||||
/// # use bevy_ecs::system::RunSystemOnce;
|
||||
/// 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()
|
||||
/// });
|
||||
/// # 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:
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # use bevy_ecs::system::RunSystem;
|
||||
/// # use bevy_ecs::system::RunSystemOnce;
|
||||
///
|
||||
/// #[derive(Component)]
|
||||
/// 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(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()
|
||||
/// });
|
||||
///
|
||||
|
@ -213,7 +237,7 @@ impl<In: 'static, Out: 'static> Debug for dyn System<In = In, Out = Out> {
|
|||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # use bevy_ecs::system::RunSystem;
|
||||
/// # use bevy_ecs::system::RunSystemOnce;
|
||||
///
|
||||
/// #[derive(Component)]
|
||||
/// 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(1));
|
||||
/// world.spawn(T(1));
|
||||
/// let count = world.run_system(count);
|
||||
/// let count = world.run_system_once(count);
|
||||
///
|
||||
/// # assert_eq!(count, 2);
|
||||
/// ```
|
||||
pub trait RunSystem: Sized {
|
||||
pub trait RunSystemOnce: Sized {
|
||||
/// Runs a system and applies its deferred parameters.
|
||||
fn run_system<T: IntoSystem<(), Out, Marker>, Out, Marker>(self, system: T) -> Out {
|
||||
self.run_system_with((), system)
|
||||
fn run_system_once<T: IntoSystem<(), Out, Marker>, Out, Marker>(self, system: T) -> Out {
|
||||
self.run_system_once_with((), system)
|
||||
}
|
||||
|
||||
/// 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,
|
||||
input: In,
|
||||
system: T,
|
||||
) -> Out;
|
||||
}
|
||||
|
||||
impl RunSystem for &mut World {
|
||||
fn run_system_with<T: IntoSystem<In, Out, Marker>, In, Out, Marker>(
|
||||
impl RunSystemOnce for &mut World {
|
||||
fn run_system_once_with<T: IntoSystem<In, Out, Marker>, In, Out, Marker>(
|
||||
self,
|
||||
input: In,
|
||||
system: T,
|
||||
|
@ -261,10 +285,11 @@ impl RunSystem for &mut World {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate as bevy_ecs;
|
||||
use crate::prelude::*;
|
||||
|
||||
#[test]
|
||||
fn run_system() {
|
||||
fn run_system_once() {
|
||||
struct T(usize);
|
||||
|
||||
impl Resource for T {}
|
||||
|
@ -275,8 +300,53 @@ mod tests {
|
|||
}
|
||||
|
||||
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!(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])*
|
||||
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();
|
||||
|
|
|
@ -223,6 +223,7 @@ Example | Description
|
|||
[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
|
||||
[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`
|
||||
[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
|
||||
|
|
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