Allow Commands to register systems (#11019)
# Objective - Allow registering of systems from Commands with `Commands::register_one_shot_system` - Make registering of one shot systems more easy ## Solution - Add the Command `RegisterSystem` for Commands use. - Creation of SystemId based on lazy insertion of the System - Changed the privacy of the fields in SystemId so Commands can return the SystemId --- ## Changelog ### Added - Added command `RegisterSystem` - Added function `Commands::register_one_shot_system` - Added function `App::register_one_shot_system` ### Changed - Changed the privacy and the type of struct tuple to regular struct of SystemId ## Migration Guide - Changed SystemId fields from tuple struct to a normal struct If you want to access the entity field, you should use `SystemId::entity` instead of `SystemId::0` ## Showcase > Before, if you wanted to register a system with `Commands`, you would need to do: ```rust commands.add(|world: &mut World| { let id = world.register_system(your_system); // You would need to insert the SystemId inside an entity or similar }) ``` > Now, you can: ```rust let id = commands.register_one_shot_system(your_system); // Do what you want with the Id ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: Pablo Reinhardt <pabloreinhardt@gmail.com>
This commit is contained in:
parent
c9ec95d782
commit
78335a5ddc
@ -6,6 +6,7 @@ use bevy_ecs::{
|
||||
common_conditions::run_once as run_once_condition, run_enter_schedule,
|
||||
InternedScheduleLabel, ScheduleBuildSettings, ScheduleLabel,
|
||||
},
|
||||
system::SystemId,
|
||||
};
|
||||
use bevy_utils::{intern::Interned, tracing::debug, HashMap, HashSet};
|
||||
use std::{
|
||||
@ -453,6 +454,22 @@ impl App {
|
||||
self
|
||||
}
|
||||
|
||||
/// 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 separately.
|
||||
///
|
||||
/// This is different from adding systems to a [`Schedule`] with [`App::add_systems`],
|
||||
/// 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 push-based fashion.
|
||||
/// Using a [`Schedule`] is still preferred for most cases
|
||||
/// due to its better performance and ability to run non-conflicting systems simultaneously.
|
||||
pub fn register_system<I: 'static, O: 'static, M, S: IntoSystem<I, O, M> + 'static>(
|
||||
&mut self,
|
||||
system: S,
|
||||
) -> SystemId<I, O> {
|
||||
self.world.register_system(system)
|
||||
}
|
||||
|
||||
/// Configures a collection of system sets in the provided schedule, adding any sets that do not exist.
|
||||
#[track_caller]
|
||||
pub fn configure_sets(
|
||||
|
@ -1,6 +1,6 @@
|
||||
mod parallel_scope;
|
||||
|
||||
use super::{Deferred, Resource};
|
||||
use super::{Deferred, IntoSystem, RegisterSystem, Resource};
|
||||
use crate::{
|
||||
self as bevy_ecs,
|
||||
bundle::Bundle,
|
||||
@ -520,6 +520,72 @@ impl<'w, 's> Commands<'w, 's> {
|
||||
.push(RunSystemWithInput::new_with_input(id, input));
|
||||
}
|
||||
|
||||
/// 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 separately.
|
||||
///
|
||||
/// 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 push-based fashion.
|
||||
/// Using a [`Schedule`](crate::schedule::Schedule) is still preferred for most cases
|
||||
/// due to its better performance and ability to run non-conflicting systems simultaneously.
|
||||
///
|
||||
/// If you want to prevent Commands from registering the same system multiple times, consider using [`Local`](crate::system::Local)
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::{prelude::*, world::CommandQueue, system::SystemId};
|
||||
///
|
||||
/// #[derive(Resource)]
|
||||
/// struct Counter(i32);
|
||||
///
|
||||
/// fn register_system(mut local_system: Local<Option<SystemId>>, mut commands: Commands) {
|
||||
/// if let Some(system) = *local_system {
|
||||
/// commands.run_system(system);
|
||||
/// } else {
|
||||
/// *local_system = Some(commands.register_one_shot_system(increment_counter));
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// fn increment_counter(mut value: ResMut<Counter>) {
|
||||
/// value.0 += 1;
|
||||
/// }
|
||||
///
|
||||
/// # let mut world = World::default();
|
||||
/// # world.insert_resource(Counter(0));
|
||||
/// # let mut queue_1 = CommandQueue::default();
|
||||
/// # let systemid = {
|
||||
/// # let mut commands = Commands::new(&mut queue_1, &world);
|
||||
/// # commands.register_one_shot_system(increment_counter)
|
||||
/// # };
|
||||
/// # let mut queue_2 = CommandQueue::default();
|
||||
/// # {
|
||||
/// # let mut commands = Commands::new(&mut queue_2, &world);
|
||||
/// # commands.run_system(systemid);
|
||||
/// # }
|
||||
/// # queue_1.append(&mut queue_2);
|
||||
/// # queue_1.apply(&mut world);
|
||||
/// # assert_eq!(1, world.resource::<Counter>().0);
|
||||
/// # bevy_ecs::system::assert_is_system(register_system);
|
||||
/// ```
|
||||
pub fn register_one_shot_system<
|
||||
I: 'static + Send,
|
||||
O: 'static + Send,
|
||||
M,
|
||||
S: IntoSystem<I, O, M> + 'static,
|
||||
>(
|
||||
&mut self,
|
||||
system: S,
|
||||
) -> SystemId<I, O> {
|
||||
let entity = self.spawn_empty().id();
|
||||
self.queue.push(RegisterSystem::new(system, entity));
|
||||
SystemId {
|
||||
entity,
|
||||
marker: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Pushes a generic [`Command`] to the command queue.
|
||||
///
|
||||
/// `command` can be a built-in command, custom struct that implements [`Command`] or a closure
|
||||
@ -764,13 +830,13 @@ impl EntityCommands<'_> {
|
||||
/// commands.entity(player.entity)
|
||||
/// // You can try_insert individual components:
|
||||
/// .try_insert(Defense(10))
|
||||
///
|
||||
///
|
||||
/// // You can also insert tuples of components:
|
||||
/// .try_insert(CombatBundle {
|
||||
/// health: Health(100),
|
||||
/// strength: Strength(40),
|
||||
/// });
|
||||
///
|
||||
///
|
||||
/// // Suppose this occurs in a parallel adjacent system or process
|
||||
/// commands.entity(player.entity)
|
||||
/// .despawn();
|
||||
|
@ -38,9 +38,11 @@ impl<I, O> RemovedSystem<I, O> {
|
||||
///
|
||||
/// These are opaque identifiers, keyed to a specific [`World`],
|
||||
/// and are created via [`World::register_system`].
|
||||
pub struct SystemId<I = (), O = ()>(Entity, std::marker::PhantomData<fn(I) -> O>);
|
||||
pub struct SystemId<I = (), O = ()> {
|
||||
pub(crate) entity: Entity,
|
||||
pub(crate) marker: std::marker::PhantomData<fn(I) -> O>,
|
||||
}
|
||||
|
||||
// A manual impl is used because the trait bounds should ignore the `I` and `O` phantom parameters.
|
||||
impl<I, O> Eq for SystemId<I, O> {}
|
||||
|
||||
// A manual impl is used because the trait bounds should ignore the `I` and `O` phantom parameters.
|
||||
@ -56,22 +58,22 @@ impl<I, O> Clone for SystemId<I, O> {
|
||||
// A manual impl is used because the trait bounds should ignore the `I` and `O` phantom parameters.
|
||||
impl<I, O> PartialEq for SystemId<I, O> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0 == other.0 && self.1 == other.1
|
||||
self.entity == other.entity && self.marker == other.marker
|
||||
}
|
||||
}
|
||||
|
||||
// A manual impl is used because the trait bounds should ignore the `I` and `O` phantom parameters.
|
||||
impl<I, O> std::hash::Hash for SystemId<I, O> {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.0.hash(state);
|
||||
self.entity.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, O> std::fmt::Debug for SystemId<I, O> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_tuple("SystemId")
|
||||
.field(&self.0)
|
||||
.field(&self.1)
|
||||
.field(&self.entity)
|
||||
.field(&self.entity)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
@ -83,7 +85,7 @@ impl<I, O> From<SystemId<I, O>> for Entity {
|
||||
/// is really an entity with associated handler function.
|
||||
///
|
||||
/// For example, this is useful if you want to assign a name label to a system.
|
||||
fn from(SystemId(entity, _): SystemId<I, O>) -> Self {
|
||||
fn from(SystemId { entity, .. }: SystemId<I, O>) -> Self {
|
||||
entity
|
||||
}
|
||||
}
|
||||
@ -113,14 +115,15 @@ impl World {
|
||||
&mut self,
|
||||
system: BoxedSystem<I, O>,
|
||||
) -> SystemId<I, O> {
|
||||
SystemId(
|
||||
self.spawn(RegisteredSystem {
|
||||
initialized: false,
|
||||
system,
|
||||
})
|
||||
.id(),
|
||||
std::marker::PhantomData,
|
||||
)
|
||||
SystemId {
|
||||
entity: self
|
||||
.spawn(RegisteredSystem {
|
||||
initialized: false,
|
||||
system,
|
||||
})
|
||||
.id(),
|
||||
marker: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes a registered system and returns the system, if it exists.
|
||||
@ -133,7 +136,7 @@ impl World {
|
||||
&mut self,
|
||||
id: SystemId<I, O>,
|
||||
) -> Result<RemovedSystem<I, O>, RegisteredSystemError<I, O>> {
|
||||
match self.get_entity_mut(id.0) {
|
||||
match self.get_entity_mut(id.entity) {
|
||||
Some(mut entity) => {
|
||||
let registered_system = entity
|
||||
.take::<RegisteredSystem<I, O>>()
|
||||
@ -275,7 +278,7 @@ impl World {
|
||||
) -> Result<O, RegisteredSystemError<I, O>> {
|
||||
// lookup
|
||||
let mut entity = self
|
||||
.get_entity_mut(id.0)
|
||||
.get_entity_mut(id.entity)
|
||||
.ok_or(RegisteredSystemError::SystemIdNotRegistered(id))?;
|
||||
|
||||
// take ownership of system trait object
|
||||
@ -294,7 +297,7 @@ impl World {
|
||||
let result = system.run(input, self);
|
||||
|
||||
// return ownership of system trait object (if entity still exists)
|
||||
if let Some(mut entity) = self.get_entity_mut(id.0) {
|
||||
if let Some(mut entity) = self.get_entity_mut(id.entity) {
|
||||
entity.insert::<RegisteredSystem<I, O>>(RegisteredSystem {
|
||||
initialized,
|
||||
system,
|
||||
@ -356,6 +359,35 @@ impl<I: 'static + Send> Command for RunSystemWithInput<I> {
|
||||
}
|
||||
}
|
||||
|
||||
/// The [`Command`] type for registering one shot systems from [Commands](crate::system::Commands).
|
||||
///
|
||||
/// This command needs an already boxed system to register, and an already spawned entity
|
||||
pub struct RegisterSystem<I: 'static, O: 'static> {
|
||||
system: BoxedSystem<I, O>,
|
||||
entity: Entity,
|
||||
}
|
||||
|
||||
impl<I: 'static, O: 'static> RegisterSystem<I, O> {
|
||||
/// Creates a new [Command] struct, which can be added to [Commands](crate::system::Commands)
|
||||
pub fn new<M, S: IntoSystem<I, O, M> + 'static>(system: S, entity: Entity) -> Self {
|
||||
Self {
|
||||
system: Box::new(IntoSystem::into_system(system)),
|
||||
entity,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: 'static + Send, O: 'static + Send> Command for RegisterSystem<I, O> {
|
||||
fn apply(self, world: &mut World) {
|
||||
let _ = world.get_entity_mut(self.entity).map(|mut entity| {
|
||||
entity.insert(RegisteredSystem {
|
||||
initialized: false,
|
||||
system: self.system,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// An operation with stored systems failed.
|
||||
#[derive(Error)]
|
||||
pub enum RegisteredSystemError<I = (), O = ()> {
|
||||
|
Loading…
Reference in New Issue
Block a user