diff --git a/_release-content/migration-guides/one-shot-systems.md b/_release-content/migration-guides/one-shot-systems.md new file mode 100644 index 0000000000000..3c31799a49dc3 --- /dev/null +++ b/_release-content/migration-guides/one-shot-systems.md @@ -0,0 +1,30 @@ +--- +title: "One-shot systems are now registered as `SystemArc`s rather than `BoxedSystem`s" +pull_requests: [24072] +--- + +In order to support `bsn!` templating of `SystemId`s, one-shot systems are now +stored as `SystemArc`s rather than `BoxedSystem`s. + +- `World::register_boxed_system` was replaced with `World::register_system_arc`. +- Rather than storing `Box`s and passing them to `register_boxed_system`, + store `SystemArc`s and pass them to `register_system_arc`. + +```rust +struct Foo; +struct Bar; + +// 0.18 +let my_boxed_system: Box, Out = Bar>> = + Box::new(IntoSystem::into_system( + |foo| { Bar } + )); + +world.register_boxed_system(my_boxed_system); + +// 0.19 +let my_system_arc: SystemArc, Out = Bar>> = + SystemArc::new_dyn(|foo| { Bar }); + +world.register_system_arc(my_system_arc); +``` diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index b78fbedd1a241..aa6d120c76d77 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -19,7 +19,7 @@ use bevy_ecs::{ InternedSystemSet, ScheduleBuildSettings, ScheduleCleanupPolicy, ScheduleError, ScheduleLabel, }, - system::{ScheduleSystem, SystemId, SystemInput}, + system::ScheduleSystem, }; use bevy_platform::collections::HashMap; #[cfg(feature = "bevy_reflect")] @@ -27,6 +27,10 @@ use bevy_reflect::{FromType, Reflect, TypeData, TypePath}; use core::{fmt::Debug, num::NonZero, panic::AssertUnwindSafe}; use log::debug; +bevy_platform::cfg::arc! { + use bevy_ecs::system::{SystemId, SystemInput}; +} + #[cfg(feature = "trace")] use tracing::info_span; @@ -362,24 +366,27 @@ impl App { self.main_mut().remove_systems_in_set(schedule, set, policy) } - /// 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( - &mut self, - system: impl IntoSystem + 'static, - ) -> SystemId - where - I: SystemInput + 'static, - O: 'static, - { - self.main_mut().register_system(system) + // Check bevy_ecs::system::SystemArc file for why this is gated on `arc`. + bevy_platform::cfg::arc! { + /// 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( + &mut self, + system: impl IntoSystem + 'static, + ) -> SystemId + where + I: SystemInput + 'static, + O: 'static, + { + self.main_mut().register_system(system) + } } /// Configures a collection of system sets in the provided schedule, adding any sets that do not exist. diff --git a/crates/bevy_app/src/sub_app.rs b/crates/bevy_app/src/sub_app.rs index 1b20aab2ff5d3..6560ddefe3355 100644 --- a/crates/bevy_app/src/sub_app.rs +++ b/crates/bevy_app/src/sub_app.rs @@ -8,11 +8,15 @@ use bevy_ecs::{ InternedScheduleLabel, InternedSystemSet, ScheduleBuildSettings, ScheduleCleanupPolicy, ScheduleError, ScheduleLabel, }, - system::{ScheduleSystem, SystemId, SystemInput}, + system::ScheduleSystem, }; use bevy_platform::collections::{HashMap, HashSet}; use core::fmt::Debug; +bevy_platform::cfg::arc! { + use bevy_ecs::system::{SystemId, SystemInput}; +} + #[cfg(feature = "trace")] use tracing::{info_span, warn}; @@ -235,16 +239,19 @@ impl SubApp { }) } - /// See [`App::register_system`]. - pub fn register_system( - &mut self, - system: impl IntoSystem + 'static, - ) -> SystemId - where - I: SystemInput + 'static, - O: 'static, - { - self.world.register_system(system) + // Check bevy_ecs::system::SystemArc file for why this is gated on `arc`. + bevy_platform::cfg::arc! { + /// See [`App::register_system`]. + pub fn register_system( + &mut self, + system: impl IntoSystem + 'static, + ) -> SystemId + where + I: SystemInput + 'static, + O: 'static, + { + self.world.register_system(system) + } } /// See [`App::configure_sets`]. diff --git a/crates/bevy_ecs/src/system/commands/command.rs b/crates/bevy_ecs/src/system/commands/command.rs index 2f384bdf25a32..921bf5b49d700 100644 --- a/crates/bevy_ecs/src/system/commands/command.rs +++ b/crates/bevy_ecs/src/system/commands/command.rs @@ -15,7 +15,6 @@ use crate::{ message::{Message, Messages}, resource::Resource, schedule::ScheduleLabel, - system::{IntoSystem, SystemId, SystemInput}, world::{FromWorld, SpawnBatchIter, World}, }; @@ -181,83 +180,88 @@ pub fn remove_resource() -> impl Command { } } -/// A [`Command`] that runs the system corresponding to the given [`SystemId`]. -pub fn run_system(id: SystemId<(), O>) -> impl Command { - move |world: &mut World| -> Result { - world.run_system(id)?; - Ok(()) +// Check bevy_ecs::system::SystemArc file for why this is gated on `arc`. +bevy_platform::cfg::arc! { + use crate::system::{IntoSystem, SystemInput, SystemId}; + + /// A [`Command`] that runs the system corresponding to the given [`SystemId`]. + pub fn run_system(id: SystemId<(), O>) -> impl Command { + move |world: &mut World| -> Result { + world.run_system(id)?; + Ok(()) + } } -} -/// A [`Command`] that runs the system corresponding to the given [`SystemId`] -/// and provides the given input value. -pub fn run_system_with(id: SystemId, input: I::Inner<'static>) -> impl Command -where - I: SystemInput: Send> + 'static, -{ - move |world: &mut World| -> Result { - world.run_system_with(id, input)?; - Ok(()) + /// A [`Command`] that runs the system corresponding to the given [`SystemId`] + /// and provides the given input value. + pub fn run_system_with(id: SystemId, input: I::Inner<'static>) -> impl Command + where + I: SystemInput: Send> + 'static, + { + move |world: &mut World| -> Result { + world.run_system_with(id, input)?; + Ok(()) + } } -} -/// A [`Command`] that runs the given system, -/// caching its [`SystemId`] in a [`CachedSystemId`](crate::system::CachedSystemId) resource. -pub fn run_system_cached(system: S) -> impl Command -where - M: 'static, - S: IntoSystem<(), (), M> + Send + 'static, -{ - move |world: &mut World| -> Result { - world.run_system_cached(system)?; - Ok(()) + /// A [`Command`] that runs the given system, + /// caching its [`SystemId`] in a [`CachedSystemId`](crate::system::CachedSystemId) resource. + pub fn run_system_cached(system: S) -> impl Command + where + M: 'static, + S: IntoSystem<(), (), M> + Send + 'static, + { + move |world: &mut World| -> Result { + world.run_system_cached(system)?; + Ok(()) + } } -} -/// A [`Command`] that runs the given system with the given input value, -/// caching its [`SystemId`] in a [`CachedSystemId`](crate::system::CachedSystemId) resource. -/// -/// To use the supplied input, the system should have a [`SystemInput`] as the first parameter. -pub fn run_system_cached_with(system: S, input: I::Inner<'static>) -> impl Command -where - I: SystemInput: Send> + Send + 'static, - M: 'static, - S: IntoSystem + Send + 'static, -{ - move |world: &mut World| -> Result { - world.run_system_cached_with(system, input)?; - Ok(()) + /// A [`Command`] that runs the given system with the given input value, + /// caching its [`SystemId`] in a [`CachedSystemId`](crate::system::CachedSystemId) resource. + /// + /// To use the supplied input, the system should have a [`SystemInput`] as the first parameter. + pub fn run_system_cached_with(system: S, input: I::Inner<'static>) -> impl Command + where + I: SystemInput: Send> + Send + 'static, + M: 'static, + S: IntoSystem + Send + 'static, + { + move |world: &mut World| -> Result { + world.run_system_cached_with(system, input)?; + Ok(()) + } } -} -/// A [`Command`] that removes a system previously registered with -/// [`Commands::register_system`](crate::system::Commands::register_system) or -/// [`World::register_system`]. -pub fn unregister_system(system_id: SystemId) -> impl Command -where - I: SystemInput + Send + 'static, - O: Send + 'static, -{ - move |world: &mut World| -> Result { - world.unregister_system(system_id)?; - Ok(()) + /// A [`Command`] that removes a system previously registered with + /// [`Commands::register_system`](crate::system::Commands::register_system) or + /// [`World::register_system`]. + pub fn unregister_system(system_id: SystemId) -> impl Command + where + I: SystemInput + Send + 'static, + O: Send + 'static, + { + move |world: &mut World| -> Result { + world.unregister_system(system_id)?; + Ok(()) + } } -} -/// A [`Command`] that removes a system previously registered with one of the following: -/// - [`Commands::run_system_cached`](crate::system::Commands::run_system_cached) -/// - [`World::run_system_cached`] -/// - [`World::register_system_cached`] -pub fn unregister_system_cached(system: S) -> impl Command -where - I: SystemInput + Send + 'static, - O: 'static, - M: 'static, - S: IntoSystem + Send + 'static, -{ - move |world: &mut World| -> Result { - world.unregister_system_cached(system)?; - Ok(()) + /// A [`Command`] that removes a system previously registered with one of the following: + /// - [`Commands::run_system_cached`](crate::system::Commands::run_system_cached) + /// - [`World::run_system_cached`] + /// - [`World::register_system_cached`] + pub fn unregister_system_cached(system: S) -> impl Command + where + I: SystemInput + Send + 'static, + O: 'static, + M: 'static, + S: IntoSystem + Send + 'static, + { + move |world: &mut World| -> Result { + world.unregister_system_cached(system)?; + Ok(()) + } } } diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index f99b5948c96c6..58e566b7cadd8 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -11,7 +11,6 @@ pub use entity_command::EntityCommand; #[cfg(feature = "std")] pub use parallel_scope::*; -use alloc::boxed::Box; use core::marker::PhantomData; use crate::{ @@ -30,15 +29,17 @@ use crate::{ relationship::RelationshipHookMode, resource::Resource, schedule::ScheduleLabel, - system::{ - Deferred, IntoSystem, RegisteredSystem, SystemId, SystemInput, SystemParamValidationError, - }, + system::{Deferred, SystemParamValidationError}, world::{ command_queue::RawCommandQueue, unsafe_world_cell::UnsafeWorldCell, CommandQueue, EntityWorldMut, FromWorld, World, }, }; +bevy_platform::cfg::arc! { + use crate::system::{IntoSystem, RegisteredSystem, SystemArc, SystemId, SystemInput}; +} + /// A [`Command`] queue to perform structural changes to the [`World`]. /// /// Since each command requires exclusive access to the `World`, @@ -917,230 +918,233 @@ impl<'w, 's> Commands<'w, 's> { self.queue(command::remove_resource::()); } - /// Runs the system corresponding to the given [`SystemId`]. - /// Before running a system, it must first be registered via - /// [`Commands::register_system`] or [`World::register_system`]. - /// - /// The system is run in an exclusive and single-threaded way. - /// Running slow systems can become a bottleneck. - /// - /// There is no way to get the output of a system when run as a command, because the - /// execution of the system happens later. To get the output of a system, use - /// [`World::run_system`] or [`World::run_system_with`] instead of running the system as a command. - /// - /// # Fallible - /// - /// This command will fail if the given [`SystemId`] - /// does not correspond to a [`System`](crate::system::System). - /// - /// It will internally return a [`RegisteredSystemError`](crate::system::system_registry::RegisteredSystemError), - /// which will be handled by [logging the error at the `warn` level](warn). - pub fn run_system(&mut self, id: SystemId) { - self.queue(command::run_system(id).handle_error_with(warn)); - } + // Check bevy_ecs::system::SystemArc file for why this is gated on `arc`. + bevy_platform::cfg::arc! { + /// Runs the system corresponding to the given [`SystemId`]. + /// Before running a system, it must first be registered via + /// [`Commands::register_system`] or [`World::register_system`]. + /// + /// The system is run in an exclusive and single-threaded way. + /// Running slow systems can become a bottleneck. + /// + /// There is no way to get the output of a system when run as a command, because the + /// execution of the system happens later. To get the output of a system, use + /// [`World::run_system`] or [`World::run_system_with`] instead of running the system as a command. + /// + /// # Fallible + /// + /// This command will fail if the given [`SystemId`] + /// does not correspond to a [`System`](crate::system::System). + /// + /// It will internally return a [`RegisteredSystemError`](crate::system::system_registry::RegisteredSystemError), + /// which will be handled by [logging the error at the `warn` level](warn). + pub fn run_system(&mut self, id: SystemId) { + self.queue(command::run_system(id).handle_error_with(warn)); + } - /// Runs the system corresponding to the given [`SystemId`] with input. - /// Before running a system, it must first be registered via - /// [`Commands::register_system`] or [`World::register_system`]. - /// - /// The system is run in an exclusive and single-threaded way. - /// Running slow systems can become a bottleneck. - /// - /// There is no way to get the output of a system when run as a command, because the - /// execution of the system happens later. To get the output of a system, use - /// [`World::run_system`] or [`World::run_system_with`] instead of running the system as a command. - /// - /// # Fallible - /// - /// This command will fail if the given [`SystemId`] - /// does not correspond to a [`System`](crate::system::System). - /// - /// It will internally return a [`RegisteredSystemError`](crate::system::system_registry::RegisteredSystemError), - /// which will be handled by [logging the error at the `warn` level](warn). - pub fn run_system_with(&mut self, id: SystemId, input: I::Inner<'static>) - where - I: SystemInput: Send> + 'static, - { - self.queue(command::run_system_with(id, input).handle_error_with(warn)); - } + /// Runs the system corresponding to the given [`SystemId`] with input. + /// Before running a system, it must first be registered via + /// [`Commands::register_system`] or [`World::register_system`]. + /// + /// The system is run in an exclusive and single-threaded way. + /// Running slow systems can become a bottleneck. + /// + /// There is no way to get the output of a system when run as a command, because the + /// execution of the system happens later. To get the output of a system, use + /// [`World::run_system`] or [`World::run_system_with`] instead of running the system as a command. + /// + /// # Fallible + /// + /// This command will fail if the given [`SystemId`] + /// does not correspond to a [`System`](crate::system::System). + /// + /// It will internally return a [`RegisteredSystemError`](crate::system::system_registry::RegisteredSystemError), + /// which will be handled by [logging the error at the `warn` level](warn). + pub fn run_system_with(&mut self, id: SystemId, input: I::Inner<'static>) + where + I: SystemInput: Send> + 'static, + { + self.queue(command::run_system_with(id, input).handle_error_with(warn)); + } - /// Registers a system and returns its [`SystemId`] so it can later be called by - /// [`Commands::run_system`] or [`World::run_system`]. - /// - /// 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. - /// - /// 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. - /// - /// # Note - /// - /// If the same system is registered more than once, - /// each registration will be considered a different system, - /// and they will each be given their own [`SystemId`]. - /// - /// If you want to avoid registering the same system multiple times, - /// consider using [`Commands::run_system_cached`] or storing the [`SystemId`] - /// in a [`Local`](crate::system::Local). - /// - /// # Example - /// - /// ``` - /// # use bevy_ecs::{prelude::*, world::CommandQueue, system::SystemId}; - /// #[derive(Resource)] - /// struct Counter(i32); - /// - /// fn register_system( - /// mut commands: Commands, - /// mut local_system: Local>, - /// ) { - /// if let Some(system) = *local_system { - /// commands.run_system(system); - /// } else { - /// *local_system = Some(commands.register_system(increment_counter)); - /// } - /// } - /// - /// fn increment_counter(mut value: ResMut) { - /// 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_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::().0); - /// # bevy_ecs::system::assert_is_system(register_system); - /// ``` - pub fn register_system( - &mut self, - system: impl IntoSystem + 'static, - ) -> SystemId - where - I: SystemInput + Send + 'static, - O: Send + 'static, - { - let entity = self.spawn_empty().id(); - let system = RegisteredSystem::::new(Box::new(IntoSystem::into_system(system))); - self.entity(entity).insert(system); - SystemId::from_entity(entity) - } + /// Registers a system and returns its [`SystemId`] so it can later be called by + /// [`Commands::run_system`] or [`World::run_system`]. + /// + /// 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. + /// + /// 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. + /// + /// # Note + /// + /// If the same system is registered more than once, + /// each registration will be considered a different system, + /// and they will each be given their own [`SystemId`]. + /// + /// If you want to avoid registering the same system multiple times, + /// consider using [`Commands::run_system_cached`] or storing the [`SystemId`] + /// in a [`Local`](crate::system::Local). + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::{prelude::*, world::CommandQueue, system::SystemId}; + /// #[derive(Resource)] + /// struct Counter(i32); + /// + /// fn register_system( + /// mut commands: Commands, + /// mut local_system: Local>, + /// ) { + /// if let Some(system) = *local_system { + /// commands.run_system(system); + /// } else { + /// *local_system = Some(commands.register_system(increment_counter)); + /// } + /// } + /// + /// fn increment_counter(mut value: ResMut) { + /// 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_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::().0); + /// # bevy_ecs::system::assert_is_system(register_system); + /// ``` + pub fn register_system( + &mut self, + system: impl IntoSystem + 'static, + ) -> SystemId + where + I: SystemInput + Send + 'static, + O: Send + 'static, + { + let entity = self.spawn_empty().id(); + let system = RegisteredSystem::::new(SystemArc::new_dyn(system)); + self.entity(entity).insert(system); + SystemId::from_entity(entity) + } - /// Removes a system previously registered with [`Commands::register_system`] - /// or [`World::register_system`]. - /// - /// After removing a system, the [`SystemId`] becomes invalid - /// and attempting to use it afterwards will result in an error. - /// Re-adding the removed system will register it with a new `SystemId`. - /// - /// # Fallible - /// - /// This command will fail if the given [`SystemId`] - /// does not correspond to a [`System`](crate::system::System). - /// - /// It will internally return a [`RegisteredSystemError`](crate::system::system_registry::RegisteredSystemError), - /// which will be handled by [logging the error at the `warn` level](warn). - pub fn unregister_system(&mut self, system_id: SystemId) - where - I: SystemInput + Send + 'static, - O: Send + 'static, - { - self.queue(command::unregister_system(system_id).handle_error_with(warn)); - } + /// Removes a system previously registered with [`Commands::register_system`] + /// or [`World::register_system`]. + /// + /// After removing a system, the [`SystemId`] becomes invalid + /// and attempting to use it afterwards will result in an error. + /// Re-adding the removed system will register it with a new `SystemId`. + /// + /// # Fallible + /// + /// This command will fail if the given [`SystemId`] + /// does not correspond to a [`System`](crate::system::System). + /// + /// It will internally return a [`RegisteredSystemError`](crate::system::system_registry::RegisteredSystemError), + /// which will be handled by [logging the error at the `warn` level](warn). + pub fn unregister_system(&mut self, system_id: SystemId) + where + I: SystemInput + Send + 'static, + O: Send + 'static, + { + self.queue(command::unregister_system(system_id).handle_error_with(warn)); + } - /// Removes a system previously registered with one of the following: - /// - [`Commands::run_system_cached`] - /// - [`World::run_system_cached`] - /// - [`World::register_system_cached`] - /// - /// # Fallible - /// - /// This command will fail if the given system - /// is not currently cached in a [`CachedSystemId`](crate::system::CachedSystemId) resource. - /// - /// It will internally return a [`RegisteredSystemError`](crate::system::system_registry::RegisteredSystemError), - /// which will be handled by [logging the error at the `warn` level](warn). - pub fn unregister_system_cached(&mut self, system: S) - where - I: SystemInput + Send + 'static, - O: 'static, - M: 'static, - S: IntoSystem + Send + 'static, - { - self.queue(command::unregister_system_cached(system).handle_error_with(warn)); - } + /// Removes a system previously registered with one of the following: + /// - [`Commands::run_system_cached`] + /// - [`World::run_system_cached`] + /// - [`World::register_system_cached`] + /// + /// # Fallible + /// + /// This command will fail if the given system + /// is not currently cached in a [`CachedSystemId`](crate::system::CachedSystemId) resource. + /// + /// It will internally return a [`RegisteredSystemError`](crate::system::system_registry::RegisteredSystemError), + /// which will be handled by [logging the error at the `warn` level](warn). + pub fn unregister_system_cached(&mut self, system: S) + where + I: SystemInput + Send + 'static, + O: 'static, + M: 'static, + S: IntoSystem + Send + 'static, + { + self.queue(command::unregister_system_cached(system).handle_error_with(warn)); + } - /// Runs a cached system, registering it if necessary. - /// - /// Unlike [`Commands::run_system`], this method does not require manual registration. - /// - /// The first time this method is called for a particular system, - /// it will register the system and store its [`SystemId`] in a - /// [`CachedSystemId`](crate::system::CachedSystemId) resource for later. - /// - /// If you would rather manage the [`SystemId`] yourself, - /// or register multiple copies of the same system, - /// use [`Commands::register_system`] instead. - /// - /// # Limitations - /// - /// This method only accepts ZST (zero-sized) systems to guarantee that any two systems of - /// the same type must be equal. This means that closures that capture the environment, and - /// function pointers, are not accepted. - /// - /// If you want to access values from the environment within a system, - /// consider passing them in as inputs via [`Commands::run_system_cached_with`]. - /// - /// If that's not an option, consider [`Commands::register_system`] instead. - pub fn run_system_cached(&mut self, system: S) - where - M: 'static, - S: IntoSystem<(), (), M> + Send + 'static, - { - self.queue(command::run_system_cached(system).handle_error_with(warn)); - } + /// Runs a cached system, registering it if necessary. + /// + /// Unlike [`Commands::run_system`], this method does not require manual registration. + /// + /// The first time this method is called for a particular system, + /// it will register the system and store its [`SystemId`] in a + /// [`CachedSystemId`](crate::system::CachedSystemId) resource for later. + /// + /// If you would rather manage the [`SystemId`] yourself, + /// or register multiple copies of the same system, + /// use [`Commands::register_system`] instead. + /// + /// # Limitations + /// + /// This method only accepts ZST (zero-sized) systems to guarantee that any two systems of + /// the same type must be equal. This means that closures that capture the environment, and + /// function pointers, are not accepted. + /// + /// If you want to access values from the environment within a system, + /// consider passing them in as inputs via [`Commands::run_system_cached_with`]. + /// + /// If that's not an option, consider [`Commands::register_system`] instead. + pub fn run_system_cached(&mut self, system: S) + where + M: 'static, + S: IntoSystem<(), (), M> + Send + 'static, + { + self.queue(command::run_system_cached(system).handle_error_with(warn)); + } - /// Runs a cached system with an input, registering it if necessary. - /// - /// Unlike [`Commands::run_system_with`], this method does not require manual registration. - /// - /// To use the supplied input, the system should have a [`SystemInput`] as the first parameter. - /// - /// The first time this method is called for a particular system, - /// it will register the system and store its [`SystemId`] in a - /// [`CachedSystemId`](crate::system::CachedSystemId) resource for later. - /// - /// If you would rather manage the [`SystemId`] yourself, - /// or register multiple copies of the same system, - /// use [`Commands::register_system`] instead. - /// - /// # Limitations - /// - /// This method only accepts ZST (zero-sized) systems to guarantee that any two systems of - /// the same type must be equal. This means that closures that capture the environment, and - /// function pointers, are not accepted. - /// - /// If you want to access values from the environment within a system, - /// consider passing them in as inputs. - /// - /// If that's not an option, consider [`Commands::register_system`] instead. - pub fn run_system_cached_with(&mut self, system: S, input: I::Inner<'static>) - where - I: SystemInput: Send> + Send + 'static, - M: 'static, - S: IntoSystem + Send + 'static, - { - self.queue(command::run_system_cached_with(system, input).handle_error_with(warn)); + /// Runs a cached system with an input, registering it if necessary. + /// + /// Unlike [`Commands::run_system_with`], this method does not require manual registration. + /// + /// To use the supplied input, the system should have a [`SystemInput`] as the first parameter. + /// + /// The first time this method is called for a particular system, + /// it will register the system and store its [`SystemId`] in a + /// [`CachedSystemId`](crate::system::CachedSystemId) resource for later. + /// + /// If you would rather manage the [`SystemId`] yourself, + /// or register multiple copies of the same system, + /// use [`Commands::register_system`] instead. + /// + /// # Limitations + /// + /// This method only accepts ZST (zero-sized) systems to guarantee that any two systems of + /// the same type must be equal. This means that closures that capture the environment, and + /// function pointers, are not accepted. + /// + /// If you want to access values from the environment within a system, + /// consider passing them in as inputs. + /// + /// If that's not an option, consider [`Commands::register_system`] instead. + pub fn run_system_cached_with(&mut self, system: S, input: I::Inner<'static>) + where + I: SystemInput: Send> + Send + 'static, + M: 'static, + S: IntoSystem + Send + 'static, + { + self.queue(command::run_system_cached_with(system, input).handle_error_with(warn)); + } } /// Triggers the given [`Event`], which will run any [`Observer`]s watching for it. diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index 91f7ab26329f5..5aa42bbc5dcf8 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -131,9 +131,9 @@ mod observer_system; mod query; mod schedule_system; mod system; +mod system_arc; mod system_name; mod system_param; -mod system_registry; use core::any::TypeId; @@ -149,9 +149,14 @@ pub use observer_system::*; pub use query::*; pub use schedule_system::*; pub use system::*; +pub use system_arc::*; pub use system_name::*; pub use system_param::*; -pub use system_registry::*; + +bevy_platform::cfg::arc! { + mod system_registry; + pub use system_registry::*; +} use crate::world::{FromWorld, World}; diff --git a/crates/bevy_ecs/src/system/system_arc.rs b/crates/bevy_ecs/src/system/system_arc.rs new file mode 100644 index 0000000000000..8992b465bf845 --- /dev/null +++ b/crates/bevy_ecs/src/system/system_arc.rs @@ -0,0 +1,192 @@ +use alloc::vec::Vec; +use core::{any::TypeId, fmt}; + +use bevy_platform::sync::{Arc, Mutex, MutexGuard}; +use bevy_utils::DebugName; + +use crate::{ + change_detection::{CheckChangeTicks, Tick}, + prelude::World, + query::FilteredAccessSet, + schedule::InternedSystemSet, + system::{IntoSystem, System}, + world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld}, +}; + +use super::{RunSystemError, SystemIn, SystemStateFlags}; + +/// A type alias for a [`SystemArc`]. +pub type SystemArcDyn = SystemArc>; + +/// A shareable, mutable reference to a [`System`] protected by a [`Mutex`] and +/// reference-counted by an [`Arc`]. +pub struct SystemArc { + system: Arc>>, +} + +impl SystemArc { + /// Creates a new [`SystemArc`] by converting the given `system` into a + /// [`System`] and wrapping it in an [`Arc`] and [`Mutex`]. + pub fn new(system: impl IntoSystem) -> Self { + Self { + system: Arc::new(Mutex::new(SystemArcInner { + initialized: false, + system: IntoSystem::into_system(system), + })), + } + } +} + +impl SystemArc { + /// Locks the system for mutable access, returning a [`MutexGuard`] to the + /// inner [`SystemArcInner`]. + pub fn lock(&self) -> MutexGuard<'_, SystemArcInner> { + self.system.lock().unwrap() + } +} + +impl Clone for SystemArc { + fn clone(&self) -> Self { + Self { + system: self.system.clone(), + } + } +} + +impl fmt::Debug for SystemArc { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let system = self.lock(); + f.debug_struct("SystemArc") + .field("name", &system.name()) + .field("is_exclusive", &system.is_exclusive()) + .field("is_send", &system.is_send()) + .finish_non_exhaustive() + } +} + +impl From for SystemArc { + fn from(system: S) -> Self { + Self::new(system) + } +} + +// TODO: These are feature gated because they require portable_atomic_util::Arc +// to be CoerceUnsized, which is currently unstable until #[derive(CoerceePointee)] +// is stabilized. Once that is stabilized and derived for portable_atomic_util::Arc, +// we can remove the feature gate. +bevy_platform::cfg::arc! { + use crate::system::SystemInput; + + impl SystemArc { + /// Erases the concrete type of the system, returning a [`SystemArc`]. + /// Useful for storing systems of different types in a homogeneous collection. + pub fn erase(self) -> SystemArcDyn { + SystemArc { + system: self.system, + } + } + } + + impl SystemArc> { + /// Creates a new [`SystemArc`] by converting the given `system` into a + /// [`System`], wrapping it in an [`Arc`] and [`Mutex`], and erasing its concrete type. + pub fn new_dyn(system: impl IntoSystem) -> Self { + Self { + system: Arc::new(Mutex::new(SystemArcInner { + initialized: false, + system: IntoSystem::into_system(system), + })), + } + } + } + + impl From> for SystemArcDyn { + fn from(system_arc: SystemArc) -> Self { + system_arc.erase() + } + } +} + +/// The inner data of a [`SystemArc`], containing the actual system and a flag +/// indicating whether the system has been initialized. +pub struct SystemArcInner { + initialized: bool, + system: S, +} + +impl SystemArcInner { + /// Returns `true` if the system has been initialized. + pub fn is_initialized(&self) -> bool { + self.initialized + } + + /// Initializes the system and sets the initialized flag, if it has not + /// already been initialized. Does nothing if the system is already initialized. + pub fn ensure_initialized(&mut self, world: &mut World) { + if !self.initialized { + self.system.initialize(world); + self.initialized = true; + } + } +} + +impl System for SystemArcInner { + type In = S::In; + type Out = S::Out; + + fn name(&self) -> DebugName { + self.system.name() + } + + fn system_type(&self) -> TypeId { + TypeId::of::() + } + + fn flags(&self) -> SystemStateFlags { + self.system.flags() + } + + unsafe fn run_unsafe( + &mut self, + input: SystemIn<'_, Self>, + world: UnsafeWorldCell, + ) -> Result { + // SAFETY: Upheld by caller + unsafe { self.system.run_unsafe(input, world) } + } + + #[cfg(feature = "hotpatching")] + fn refresh_hotpatch(&mut self) { + self.system.refresh_hotpatch(); + } + + fn apply_deferred(&mut self, world: &mut World) { + self.system.apply_deferred(world); + } + + fn queue_deferred(&mut self, world: DeferredWorld) { + self.system.queue_deferred(world); + } + + fn initialize(&mut self, world: &mut World) -> FilteredAccessSet { + let access = self.system.initialize(world); + self.initialized = true; + access + } + + fn check_change_tick(&mut self, check: CheckChangeTicks) { + self.system.check_change_tick(check); + } + + fn default_system_sets(&self) -> Vec { + self.system.default_system_sets() + } + + fn get_last_run(&self) -> Tick { + self.system.get_last_run() + } + + fn set_last_run(&mut self, last_run: Tick) { + self.system.set_last_run(last_run); + } +} diff --git a/crates/bevy_ecs/src/system/system_registry.rs b/crates/bevy_ecs/src/system/system_registry.rs index 819f78c8c9c44..d8099c19254f0 100644 --- a/crates/bevy_ecs/src/system/system_registry.rs +++ b/crates/bevy_ecs/src/system/system_registry.rs @@ -3,32 +3,29 @@ use crate::{change_detection::DetectChanges, HotPatchChanges}; use crate::{ change_detection::Mut, entity::Entity, - error::BevyError, + error::{BevyError, Result}, system::{ - input::SystemInput, BoxedSystem, IntoSystem, RunSystemError, SystemParamValidationError, + input::SystemInput, IntoSystem, RunSystemError, System, SystemArc, SystemArcDyn, + SystemParamValidationError, }, + template::{FromTemplate, Template, TemplateContext}, world::World, }; -use alloc::boxed::Box; use bevy_ecs_macros::{Component, Resource}; use bevy_utils::prelude::DebugName; use core::{any::TypeId, marker::PhantomData}; use thiserror::Error; -/// A small wrapper for [`BoxedSystem`] that also keeps track whether or not the system has been initialized. +/// A small wrapper for [`SystemArc`] that also keeps track whether or not the system has been initialized. #[derive(Component)] #[require(SystemIdMarker = SystemIdMarker::typed_system_id_marker::())] -pub(crate) struct RegisteredSystem { - initialized: bool, - system: Option>, +pub(crate) struct RegisteredSystem { + system: SystemArcDyn, } -impl RegisteredSystem { - pub fn new(system: BoxedSystem) -> Self { - RegisteredSystem { - initialized: false, - system: Some(system), - } +impl RegisteredSystem { + pub fn new(system: SystemArcDyn) -> Self { + RegisteredSystem { system } } } @@ -76,20 +73,19 @@ impl SystemIdMarker { /// It contains the system and whether or not it has been initialized. /// /// This struct is returned by [`World::unregister_system`]. -pub struct RemovedSystem { - initialized: bool, - system: BoxedSystem, +pub struct RemovedSystem { + system: SystemArcDyn, } -impl RemovedSystem { +impl RemovedSystem { /// Is the system initialized? /// A system is initialized the first time it's ran. pub fn initialized(&self) -> bool { - self.initialized + self.system.lock().is_initialized() } /// The system removed from the storage. - pub fn system(self) -> BoxedSystem { + pub fn system(self) -> SystemArcDyn { self.system } } @@ -158,6 +154,60 @@ impl core::fmt::Debug for SystemId { } } +impl FromTemplate for SystemId { + type Template = SystemIdTemplate; +} + +/// A [`Template`] that produces a [`SystemId`]. +pub enum SystemIdTemplate { + /// Creates a [`SystemId`] by copying the given [`SystemId`] value. + Id(SystemId), + /// Creates a [`SystemId`] by registering the given system to the [`World`] + /// using [`World::register_system_arc`]. + System(SystemArcDyn), +} + +impl Template for SystemIdTemplate { + type Output = SystemId; + + fn build_template(&self, context: &mut TemplateContext) -> Result { + match self { + Self::Id(id) => Ok(*id), + Self::System(system) => Ok(context + .entity + .world_scope(|world| world.register_system_arc(system.clone()))), + } + } + + fn clone_template(&self) -> Self { + match self { + Self::Id(id) => Self::Id(*id), + Self::System(system) => Self::System(system.clone()), + } + } +} + +impl Default for SystemIdTemplate { + fn default() -> Self { + Self::Id(SystemId::from_entity(Entity::PLACEHOLDER)) + } +} + +impl From> for SystemIdTemplate { + fn from(id: SystemId) -> Self { + Self::Id(id) + } +} + +/// This will create a new [`SystemIdTemplate`] for the given `system` value. +/// This makes it possible to define systems "inline" in templates / scenes that +/// produce a [`SystemId`]. +pub fn system_value( + system: impl IntoSystem, +) -> SystemIdTemplate { + SystemIdTemplate::System(SystemArc::new_dyn(system)) +} + /// A cached [`SystemId`] distinguished by the unique function type of its system. /// /// This resource is inserted by [`World::register_system_cached`]. @@ -198,14 +248,14 @@ impl World { I: SystemInput + 'static, O: 'static, { - self.register_boxed_system(Box::new(IntoSystem::into_system(system))) + self.register_system_arc(SystemArc::new_dyn(system)) } - /// Similar to [`Self::register_system`], but allows passing in a [`BoxedSystem`]. + /// Similar to [`Self::register_system`], but allows passing in a [`SystemArcDyn`]. /// - /// This is useful if the [`IntoSystem`] implementor has already been turned into a - /// [`System`](crate::system::System) trait object and put in a [`Box`]. - pub fn register_boxed_system(&mut self, system: BoxedSystem) -> SystemId + /// This is useful if the [`IntoSystem`] implementor has already been turned + /// into a [`System`] trait object and put in a [`SystemArcDyn`]. + pub fn register_system_arc(&mut self, system: SystemArcDyn) -> SystemId where I: SystemInput + 'static, O: 'static, @@ -230,16 +280,11 @@ impl World { { match self.get_entity_mut(id.entity) { Ok(mut entity) => { - let registered_system = entity + let RegisteredSystem { system } = entity .take::>() .ok_or(RegisteredSystemError::SelfRemove(id))?; entity.despawn(); - Ok(RemovedSystem { - initialized: registered_system.initialized, - system: registered_system - .system - .ok_or(RegisteredSystemError::SystemMissing(id))?, - }) + Ok(RemovedSystem { system }) } Err(_) => Err(RegisteredSystemError::SystemIdNotRegistered(id)), } @@ -372,12 +417,10 @@ impl World { O: 'static, { // Lookup - let mut entity = self - .get_entity_mut(id.entity) + let entity = self + .get_entity(id.entity) .map_err(|_| RegisteredSystemError::SystemIdNotRegistered(id))?; - - // Take ownership of system trait object - let Some(mut registered_system) = entity.get_mut::>() else { + let Some(registered_system) = entity.get::>() else { let Some(system_id_marker) = entity.get::() else { return Err(RegisteredSystemError::SystemIdNotRegistered(id)); }; @@ -392,37 +435,32 @@ impl World { return Err(RegisteredSystemError::MissingRegisteredSystemComponent(id)); }; - let mut system = registered_system - .system - .take() - .ok_or(RegisteredSystemError::SystemMissing(id))?; + let system = registered_system.system.clone(); - // Initialize the system - if !registered_system.initialized { - system.initialize(self); - } + // Wrap the system locking in a block to ensure it gets dropped before we flush commands. + // This is needed to allow systems to recursively call themselves. + let result = { + let mut system = system.lock(); - // refresh hotpatches for stored systems - #[cfg(feature = "hotpatching")] - if self - .get_resource_ref::() - .is_none_or(|r| r.is_changed_after(system.get_last_run())) - { - system.refresh_hotpatch(); - } + // Initialize the system + system.ensure_initialized(self); - // Wait to run the commands until the system is available again. - // This is needed so the systems can recursively run themselves. - let result = system.run_without_applying_deferred(input, self); - system.queue_deferred(self.into()); + // refresh hotpatches for stored systems + #[cfg(feature = "hotpatching")] + if self + .get_resource_ref::() + .is_none_or(|r| r.is_changed_after(system.get_last_run())) + { + system.refresh_hotpatch(); + } - // Return ownership of system trait object (if entity still exists) - if let Ok(mut entity) = self.get_entity_mut(id.entity) - && let Some(mut registered_system) = entity.get_mut::>() - { - registered_system.system = Some(system); - registered_system.initialized = true; - } + // Wait to run the commands until the system is available again. + // This is needed so the systems can recursively run themselves. + let result = system.run_without_applying_deferred(input, self); + system.queue_deferred(self.into()); + + result + }; // Run any commands enqueued by the system self.flush(); @@ -470,9 +508,7 @@ impl World { self.resource_scope(|world, mut id: Mut>| { if let Ok(mut entity) = world.get_entity_mut(id.entity) { if !entity.contains::>() { - entity.insert(RegisteredSystem::new(Box::new(IntoSystem::into_system( - system, - )))); + entity.insert(RegisteredSystem::new(SystemArc::new_dyn(system))); } } else { id.entity = world.register_system(system).entity(); @@ -604,7 +640,7 @@ mod tests { use crate::{ prelude::*, - system::{RegisteredSystemError, SystemId}, + system::{system_value, RegisteredSystemError, SystemId, SystemIdTemplate}, }; #[derive(Resource, Default, PartialEq, Debug)] @@ -1074,4 +1110,27 @@ mod tests { Err(err) => panic!("Failed with wrong error. `{:?}`", err), } } + + #[test] + fn system_id_template() { + fn my_system() {} + + let mut world = World::new(); + + { + let my_system_id = world.register_system(my_system); + let system_id = world + .spawn_empty() + .build_template(&SystemIdTemplate::Id(my_system_id)) + .unwrap(); + assert_eq!(system_id, my_system_id); + } + + { + world + .spawn_empty() + .build_template(&system_value(my_system)) + .unwrap(); + } + } } diff --git a/crates/bevy_remote/src/lib.rs b/crates/bevy_remote/src/lib.rs index ec1f9b3b2c57f..fd137289c9856 100644 --- a/crates/bevy_remote/src/lib.rs +++ b/crates/bevy_remote/src/lib.rs @@ -545,7 +545,7 @@ use bevy_ecs::{ InternedScheduleLabel, IntoScheduleConfigs, ScheduleBuildMetadata, ScheduleBuilt, ScheduleLabel, SystemSet, }, - system::{Commands, In, IntoSystem, ResMut, System, SystemId}, + system::{Commands, In, IntoSystem, ResMut, SystemArc, SystemArcDyn, SystemId}, world::World, }; use bevy_platform::collections::HashMap; @@ -622,7 +622,7 @@ impl RemotePlugin { .unwrap() .push(( name.into(), - RemoteMethodHandler::Instant(Box::new(IntoSystem::into_system(handler))), + RemoteMethodHandler::Instant(SystemArc::new_dyn(handler)), )); self } @@ -663,7 +663,7 @@ impl RemotePlugin { .unwrap() .push(( name.into(), - RemoteMethodHandler::Watching(Box::new(IntoSystem::into_system(handler))), + RemoteMethodHandler::Watching(SystemArc::new_dyn(handler)), )); self } @@ -812,10 +812,10 @@ impl Plugin for RemotePlugin { name.clone(), match handler { RemoteMethodHandler::Instant(system) => RemoteMethodSystemId::Instant( - app.main_mut().world_mut().register_boxed_system(system), + app.main_mut().world_mut().register_system_arc(system), ), RemoteMethodHandler::Watching(system) => RemoteMethodSystemId::Watching( - app.main_mut().world_mut().register_boxed_system(system), + app.main_mut().world_mut().register_system_arc(system), ), }, ); @@ -869,10 +869,10 @@ impl Plugin for RemotePlugin { name, match handler { RemoteMethodHandler::Instant(system) => RemoteMethodSystemId::Instant( - render_app.world_mut().register_boxed_system(system), + render_app.world_mut().register_system_arc(system), ), RemoteMethodHandler::Watching(system) => RemoteMethodSystemId::Watching( - render_app.world_mut().register_boxed_system(system), + render_app.world_mut().register_system_arc(system), ), }, ); @@ -925,9 +925,9 @@ pub enum RemoteSystems { #[derive(Debug)] pub enum RemoteMethodHandler { /// A handler that only runs once and returns one response. - Instant(Box>, Out = BrpResult>>), + Instant(SystemArcDyn>, BrpResult>), /// A handler that watches for changes and response when a change is detected. - Watching(Box>, Out = BrpResult>>>), + Watching(SystemArcDyn>, BrpResult>>), } /// The [`SystemId`] of a function that implements a remote instant method (`world.get_components`, `world.query`, etc.) diff --git a/examples/ecs/callbacks.rs b/examples/ecs/callbacks.rs index cf1586bf6619e..5ecd03ff6c5cd 100644 --- a/examples/ecs/callbacks.rs +++ b/examples/ecs/callbacks.rs @@ -4,16 +4,25 @@ //! This pattern trades some performance for flexibility and works well for things like cutscenes, scripted events, //! or one-off UI-driven interactions that don't need to run every frame. -use bevy::{ecs::system::SystemId, prelude::*}; +use bevy::{ + ecs::system::{system_value, SystemId}, + prelude::*, + scene::ScenePlugin, +}; fn main() { let mut app = App::new(); + + // These two plugins are only needed when spawning via a scene! + // Spawning via `Commands::spawn` works without them. + app.add_plugins((AssetPlugin::default(), ScenePlugin)); + app.add_systems(Startup, setup_callbacks); app.add_systems(Update, run_callbacks); app.run(); } -#[derive(Component)] +#[derive(Component, FromTemplate)] // `FromTemplate` is only needed when spawning via a scene! struct Callback { system_id: SystemId<(), ()>, } @@ -42,6 +51,14 @@ fn setup_callbacks(mut commands: Commands) { commands.spawn(trivial_callback); commands.spawn(ordinary_system_callback); commands.spawn(exclusive_callback); + + commands.spawn_scene(bsn! { + Callback { + system_id: system_value(|| { + println!("This is a callback spawned via a scene."); + }) + } + }); } // In many cases, you might want to use an observer to detect when a callback should run,