diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index 07d01a8c870ab..c97d440e1de1c 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -306,6 +306,9 @@ pub fn impl_param_set(_input: TokenStream) -> TokenStream { } } + impl<'_w, '_s, #(#param: CommandSystemParam,)*> CommandSystemParam for ParamSet<'_w, '_s, (#(#param,)*)> + {} + impl<'w, 's, #(#param: SystemParam,)*> ParamSet<'w, 's, (#(#param,)*)> { #(#param_fn_mut)* @@ -458,6 +461,16 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream { .push(syn::parse_quote!(#field_type: #path::system::ReadOnlySystemParam)); } + // Create a where clause for the `CommandSystemParam` impl. + // Ensure that each field implements `CommandSystemParam`. + let mut command_param_generics = generics.clone(); + let command_param_where_clause = command_param_generics.make_where_clause(); + for field_type in &field_types { + command_param_where_clause + .predicates + .push(syn::parse_quote!(#field_type: #path::system::CommandSystemParam)); + } + let struct_name = &ast.ident; let state_struct_visibility = &ast.vis; @@ -513,6 +526,8 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream { // Safety: Each field is `ReadOnlySystemParam`, so this can only read from the `World` unsafe impl<'w, 's, #punctuated_generics> #path::system::ReadOnlySystemParam for #struct_name #ty_generics #read_only_where_clause {} + + impl<'w, 's, #punctuated_generics> #path::system::CommandSystemParam for #struct_name #ty_generics #command_param_where_clause {} }; }) } diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index ed2d469b9b2f5..2e8b1d6033601 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -1,5 +1,6 @@ mod command_queue; mod parallel_scope; +mod system_command; use crate::{ bundle::Bundle, @@ -10,6 +11,7 @@ use bevy_utils::tracing::{error, info}; pub use command_queue::CommandQueue; pub use parallel_scope::*; use std::marker::PhantomData; +pub use system_command::CommandSystemParam; use super::Resource; @@ -44,6 +46,18 @@ pub trait Command: Send + 'static { fn write(self, world: &mut World); } +pub trait IntoCommand { + type Command: Command; + fn into_command(this: Self) -> Self::Command; +} + +impl IntoCommand<()> for T { + type Command = Self; + fn into_command(this: Self) -> Self::Command { + this + } +} + /// A [`Command`] queue to perform impactful changes to the [`World`]. /// /// Since each command requires exclusive access to the `World`, @@ -522,8 +536,8 @@ impl<'w, 's> Commands<'w, 's> { /// # bevy_ecs::system::assert_is_system(add_three_to_counter_system); /// # bevy_ecs::system::assert_is_system(add_twenty_five_to_counter_system); /// ``` - pub fn add(&mut self, command: C) { - self.queue.push(command); + pub fn add>(&mut self, command: C) { + self.queue.push(IntoCommand::into_command(command)); } } @@ -992,6 +1006,7 @@ mod tests { use crate::{ self as bevy_ecs, component::Component, + event::{EventWriter, Events}, system::{CommandQueue, Commands, Resource}, world::World, }; @@ -1146,4 +1161,23 @@ mod tests { assert!(!world.contains_resource::>()); assert!(world.contains_resource::>()); } + + #[test] + fn system_commands() { + struct E; + + let mut world = World::new(); + world.init_resource::>(); + + let mut command_queue = CommandQueue::default(); + + let mut commands = Commands::new(&mut command_queue, &world); + commands.add(|mut events: EventWriter| { + events.send(E); + }); + + command_queue.apply(&mut world); + + assert!(!world.resource::>().is_empty()); + } } diff --git a/crates/bevy_ecs/src/system/commands/system_command.rs b/crates/bevy_ecs/src/system/commands/system_command.rs new file mode 100644 index 0000000000000..83ed88cff5bea --- /dev/null +++ b/crates/bevy_ecs/src/system/commands/system_command.rs @@ -0,0 +1,65 @@ +use std::marker::PhantomData; + +use crate::{ + prelude::World, + system::{IntoSystem, SystemMeta, SystemParam, SystemParamFunction}, +}; + +use super::{Command, IntoCommand}; + +pub trait CommandSystemParam: SystemParam {} + +impl> IntoCommand<(IsSystemCommand, Marker)> for T +where + T::System: Command, +{ + type Command = T::System; + fn into_command(this: Self) -> Self::Command { + IntoSystem::into_system(this) + } +} + +#[doc(hidden)] +pub struct IsSystemCommand; + +pub struct SystemCommand +where + Param: CommandSystemParam, +{ + func: F, + marker: PhantomData Marker>, +} + +impl IntoCommand<(IsSystemCommand, Param, Marker)> for F +where + Param: CommandSystemParam + 'static, + Marker: 'static, + F: SystemParamFunction<(), (), Param, Marker>, +{ + type Command = SystemCommand; + fn into_command(func: Self) -> Self::Command { + SystemCommand { + func, + marker: PhantomData, + } + } +} + +impl Command for SystemCommand +where + Param: CommandSystemParam + 'static, + Marker: 'static, + F: SystemParamFunction<(), (), Param, Marker>, +{ + fn write(mut self, world: &mut World) { + let change_tick = world.change_tick(); + + let mut system_meta = SystemMeta::new::(); + let mut param_state = Param::init_state(world, &mut system_meta); + let params = + // SAFETY: We have exclusive world access. + unsafe { Param::get_param(&mut param_state, &system_meta, world, change_tick) }; + self.func.run((), params); + Param::apply(&mut param_state, &system_meta, world); + } +} diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 921bba36b2710..2c4caecd5d552 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -8,7 +8,7 @@ use crate::{ query::{ Access, FilteredAccess, FilteredAccessSet, QueryState, ReadOnlyWorldQuery, WorldQuery, }, - system::{CommandQueue, Commands, Query, SystemMeta}, + system::{CommandQueue, CommandSystemParam, Commands, Query, SystemMeta}, world::{FromWorld, World}, }; pub use bevy_ecs_macros::Resource; @@ -233,6 +233,11 @@ unsafe impl SystemPara } } +impl CommandSystemParam + for Query<'_, '_, Q, F> +{ +} + fn assert_component_access_compatibility( system_name: &str, query_type: &'static str, @@ -463,6 +468,8 @@ unsafe impl<'a, T: Resource> SystemParam for Res<'a, T> { } } +impl CommandSystemParam for Res<'_, T> {} + // SAFETY: Only reads a single World resource unsafe impl<'a, T: Resource> ReadOnlySystemParam for Option> {} @@ -496,6 +503,8 @@ unsafe impl<'a, T: Resource> SystemParam for Option> { } } +impl CommandSystemParam for Option> {} + // SAFETY: Res ComponentId and ArchetypeComponentId access is applied to SystemMeta. If this Res // conflicts with any prior access, a panic will occur. unsafe impl<'a, T: Resource> SystemParam for ResMut<'a, T> { @@ -556,6 +565,8 @@ unsafe impl<'a, T: Resource> SystemParam for ResMut<'a, T> { } } +impl CommandSystemParam for ResMut<'_, T> {} + // SAFETY: this impl defers to `ResMut`, which initializes and validates the correct world access. unsafe impl<'a, T: Resource> SystemParam for Option> { type State = ComponentId; @@ -586,6 +597,8 @@ unsafe impl<'a, T: Resource> SystemParam for Option> { } } +impl CommandSystemParam for Option> {} + // SAFETY: Commands only accesses internal state unsafe impl<'w, 's> ReadOnlySystemParam for Commands<'w, 's> {} @@ -659,6 +672,8 @@ unsafe impl SystemParam for &'_ World { } } +impl CommandSystemParam for &World {} + /// A system local [`SystemParam`]. /// /// A local may only be accessed by the system itself and is therefore not visible to other systems. @@ -1115,6 +1130,8 @@ unsafe impl<'a> SystemParam for &'a Archetypes { } } +impl CommandSystemParam for &Archetypes {} + // SAFETY: Only reads World components unsafe impl<'a> ReadOnlySystemParam for &'a Components {} @@ -1136,6 +1153,8 @@ unsafe impl<'a> SystemParam for &'a Components { } } +impl CommandSystemParam for &Components {} + // SAFETY: Only reads World entities unsafe impl<'a> ReadOnlySystemParam for &'a Entities {} @@ -1157,6 +1176,8 @@ unsafe impl<'a> SystemParam for &'a Entities { } } +impl CommandSystemParam for &Entities {} + // SAFETY: Only reads World bundles unsafe impl<'a> ReadOnlySystemParam for &'a Bundles {} @@ -1178,6 +1199,8 @@ unsafe impl<'a> SystemParam for &'a Bundles { } } +impl CommandSystemParam for &Bundles {} + /// A [`SystemParam`] that reads the previous and current change ticks of the system. /// /// A system's change ticks are updated each time it runs: @@ -1341,6 +1364,9 @@ macro_rules! impl_system_param_tuple { ($($param::get_param($param, _system_meta, _world, _change_tick),)*) } } + + #[allow(non_snake_case)] + impl<$($param: CommandSystemParam),*> CommandSystemParam for ($($param,)*) {} }; }