From da5e0888496b00839f0ecc96fff177c784dfb4d6 Mon Sep 17 00:00:00 2001 From: TheRawMeatball Date: Tue, 5 Apr 2022 14:24:20 +0300 Subject: [PATCH 1/8] add method --- crates/bevy_ecs/src/system/commands/mod.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 7d3f541dab930..562ff04b65e4a 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -48,6 +48,11 @@ impl<'w, 's> Commands<'w, 's> { } } + /// Create a new `Commands` from a queue and an [`Entities`] reference. + pub fn new_from_entities(queue: &'s mut CommandQueue, entities: &'w Entities) -> Self { + Self { queue, entities } + } + /// Creates a new empty [`Entity`] and returns an [`EntityCommands`] builder for it. /// /// To directly spawn an entity with a [`Bundle`] included, you can use From f4242233e9fea3825c6d5c93c0cb0651adac5e69 Mon Sep 17 00:00:00 2001 From: TheRawMeatball Date: Sat, 14 May 2022 16:16:40 +0300 Subject: [PATCH 2/8] Add ParallelCommands SystemParam --- crates/bevy_ecs/Cargo.toml | 1 + crates/bevy_ecs/src/system/commands/mod.rs | 2 + .../src/system/commands/parallel_scope.rs | 92 +++++++++++++++++++ 3 files changed, 95 insertions(+) create mode 100644 crates/bevy_ecs/src/system/commands/parallel_scope.rs diff --git a/crates/bevy_ecs/Cargo.toml b/crates/bevy_ecs/Cargo.toml index 7262c85c9f491..277ecc480ffc9 100644 --- a/crates/bevy_ecs/Cargo.toml +++ b/crates/bevy_ecs/Cargo.toml @@ -21,6 +21,7 @@ bevy_utils = { path = "../bevy_utils", version = "0.8.0-dev" } bevy_ecs_macros = { path = "macros", version = "0.8.0-dev" } async-channel = "1.4" +thread_local = "1.1.4" fixedbitset = "0.4" fxhash = "0.2" downcast-rs = "1.2" diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 564f1ab3ebe5f..bf40e13fb98b1 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -1,4 +1,5 @@ mod command_queue; +mod parallel_scope; use crate::{ bundle::Bundle, @@ -8,6 +9,7 @@ use crate::{ }; use bevy_utils::tracing::{error, warn}; pub use command_queue::CommandQueue; +pub use parallel_scope::*; use std::marker::PhantomData; use super::Resource; diff --git a/crates/bevy_ecs/src/system/commands/parallel_scope.rs b/crates/bevy_ecs/src/system/commands/parallel_scope.rs new file mode 100644 index 0000000000000..7552e6e73349d --- /dev/null +++ b/crates/bevy_ecs/src/system/commands/parallel_scope.rs @@ -0,0 +1,92 @@ +use std::cell::Cell; + +use thread_local::ThreadLocal; + +use crate::{ + entity::Entities, + prelude::World, + system::{SystemParam, SystemParamFetch, SystemParamState}, +}; + +use super::{CommandQueue, Commands}; + +#[derive(Default)] +pub struct ParallelCommandsState { + tls: ThreadLocal>, +} + +/// An alternative to [`Commands`] that can be used in parallel contexts, such as those in [`Query::par_for_each`] +/// +/// Example: +/// ``` +/// # use bevy_ecs::prelude::*; +/// # +/// # #[derive(Component)] +/// # struct Velocity; +/// # impl Velocity { fn magnitude(&self) -> f32 { 42.0 } } +/// fn parallel_command_system( +/// mut query: Query<(Entity, &Velocity)>, +/// pool: Res, +/// par_commands: ParallelCommands +/// ) { +/// query.par_for_each(&pool, 32, |(entity, velocity)| { +/// if velocity.magnitude() > 10.0 { +/// par_commands.command_scope(|commands| { +/// commands.entity(entity).despawn(); +/// }); +/// } +/// }); +/// } +/// # bevy_ecs::system::assert_is_system(parallel_command_system); +///``` +/// [Query::par_for_each]: crate::system::Query::par_for_each +pub struct ParallelCommands<'w, 's> { + state: &'s mut ParallelCommandsState, + entities: &'w Entities, +} + +impl SystemParam for ParallelCommands<'_, '_> { + type Fetch = ParallelCommandsState; +} + +impl<'w, 's> SystemParamFetch<'w, 's> for ParallelCommandsState { + type Item = ParallelCommands<'w, 's>; + + unsafe fn get_param( + state: &'s mut Self, + _: &crate::system::SystemMeta, + world: &'w World, + _: u32, + ) -> Self::Item { + ParallelCommands { + state, + entities: world.entities(), + } + } +} + +// SAFE: no component or resource access to report +unsafe impl SystemParamState for ParallelCommandsState { + fn init(_: &mut World, _: &mut crate::system::SystemMeta) -> Self { + Self::default() + } + + fn apply(&mut self, world: &mut World) { + for cq in self.tls.iter_mut() { + cq.get_mut().apply(world); + } + } +} + +impl<'w, 's> ParallelCommands<'w, 's> { + pub fn command_scope(&self, f: impl FnOnce(Commands) -> R) -> R { + let tls = &self.state.tls; + let tl_cq = tls.get_or_default(); + let mut cq = tl_cq.take(); + + let r = f(Commands::new_from_entities(&mut cq, self.entities)); + + tl_cq.set(cq); + r + } +} From 9597c9e6cbab40db5815ca332f020c255587e5e7 Mon Sep 17 00:00:00 2001 From: TheRawMeatball Date: Sat, 14 May 2022 17:08:44 +0300 Subject: [PATCH 3/8] fix doctest --- crates/bevy_ecs/src/lib.rs | 2 +- crates/bevy_ecs/src/system/commands/parallel_scope.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 5f1bdc3d0b113..3bca12c5aa6a5 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -39,7 +39,7 @@ pub mod prelude { }, system::{ Commands, In, IntoChainSystem, IntoExclusiveSystem, IntoSystem, Local, NonSend, - NonSendMut, ParamSet, Query, RemovedComponents, Res, ResMut, System, + NonSendMut, ParallelCommands, ParamSet, Query, RemovedComponents, Res, ResMut, System, SystemParamFunction, }, world::{FromWorld, Mut, World}, diff --git a/crates/bevy_ecs/src/system/commands/parallel_scope.rs b/crates/bevy_ecs/src/system/commands/parallel_scope.rs index 7552e6e73349d..f709b9ba95847 100644 --- a/crates/bevy_ecs/src/system/commands/parallel_scope.rs +++ b/crates/bevy_ecs/src/system/commands/parallel_scope.rs @@ -20,6 +20,7 @@ pub struct ParallelCommandsState { /// Example: /// ``` /// # use bevy_ecs::prelude::*; +/// # use bevy_tasks::ComputeTaskPool; /// # /// # #[derive(Component)] /// # struct Velocity; @@ -31,7 +32,7 @@ pub struct ParallelCommandsState { /// ) { /// query.par_for_each(&pool, 32, |(entity, velocity)| { /// if velocity.magnitude() > 10.0 { -/// par_commands.command_scope(|commands| { +/// par_commands.command_scope(|mut commands| { /// commands.entity(entity).despawn(); /// }); /// } From 14c9f2424a6dfa50d464703d6dc836421b34146b Mon Sep 17 00:00:00 2001 From: TheRawMeatball Date: Sat, 14 May 2022 18:39:35 +0300 Subject: [PATCH 4/8] Update crates/bevy_ecs/src/system/commands/parallel_scope.rs Co-authored-by: Alice Cecile --- crates/bevy_ecs/src/system/commands/parallel_scope.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/bevy_ecs/src/system/commands/parallel_scope.rs b/crates/bevy_ecs/src/system/commands/parallel_scope.rs index f709b9ba95847..8394d223a3dea 100644 --- a/crates/bevy_ecs/src/system/commands/parallel_scope.rs +++ b/crates/bevy_ecs/src/system/commands/parallel_scope.rs @@ -11,6 +11,7 @@ use crate::{ use super::{CommandQueue, Commands}; #[derive(Default)] +/// The internal [`SystemParamState`] of the [`ParallelCommands`] type pub struct ParallelCommandsState { tls: ThreadLocal>, } From 16aafd56ddc21f0a60303597e339756e9c761b60 Mon Sep 17 00:00:00 2001 From: TheRawMeatball Date: Mon, 16 May 2022 11:11:26 +0300 Subject: [PATCH 5/8] Update crates/bevy_ecs/src/system/commands/parallel_scope.rs Co-authored-by: James Liu --- crates/bevy_ecs/src/system/commands/parallel_scope.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/bevy_ecs/src/system/commands/parallel_scope.rs b/crates/bevy_ecs/src/system/commands/parallel_scope.rs index 8394d223a3dea..fa5b9ebe75339 100644 --- a/crates/bevy_ecs/src/system/commands/parallel_scope.rs +++ b/crates/bevy_ecs/src/system/commands/parallel_scope.rs @@ -10,6 +10,7 @@ use crate::{ use super::{CommandQueue, Commands}; +#[doc(hidden)] #[derive(Default)] /// The internal [`SystemParamState`] of the [`ParallelCommands`] type pub struct ParallelCommandsState { From 2ca8970cfb5af859187e83dc3b913a6e6305b9d3 Mon Sep 17 00:00:00 2001 From: TheRawMeatball Date: Sun, 22 May 2022 16:54:04 +0300 Subject: [PATCH 6/8] Add determinism warning --- crates/bevy_ecs/src/system/commands/parallel_scope.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/system/commands/parallel_scope.rs b/crates/bevy_ecs/src/system/commands/parallel_scope.rs index fa5b9ebe75339..61541d8eb5b64 100644 --- a/crates/bevy_ecs/src/system/commands/parallel_scope.rs +++ b/crates/bevy_ecs/src/system/commands/parallel_scope.rs @@ -17,7 +17,9 @@ pub struct ParallelCommandsState { tls: ThreadLocal>, } -/// An alternative to [`Commands`] that can be used in parallel contexts, such as those in [`Query::par_for_each`] +/// An alternative to [`Commands`] that can be used in parallel contexts, such as those in [`Query::par_for_each`](crate::system::Query::par_for_each) +/// +/// Note: Because command application order will depend on how many threads are ran, non-commutative commands may result in non-deterministic results. /// /// Example: /// ``` @@ -42,7 +44,6 @@ pub struct ParallelCommandsState { /// } /// # bevy_ecs::system::assert_is_system(parallel_command_system); ///``` -/// [Query::par_for_each]: crate::system::Query::par_for_each pub struct ParallelCommands<'w, 's> { state: &'s mut ParallelCommandsState, entities: &'w Entities, From d97549cf7559f010ea25c557ef555007cd408cae Mon Sep 17 00:00:00 2001 From: TheRawMeatball Date: Sun, 22 May 2022 16:55:02 +0300 Subject: [PATCH 7/8] better variable names --- .../src/system/commands/parallel_scope.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/crates/bevy_ecs/src/system/commands/parallel_scope.rs b/crates/bevy_ecs/src/system/commands/parallel_scope.rs index 61541d8eb5b64..87963247a73bd 100644 --- a/crates/bevy_ecs/src/system/commands/parallel_scope.rs +++ b/crates/bevy_ecs/src/system/commands/parallel_scope.rs @@ -14,7 +14,7 @@ use super::{CommandQueue, Commands}; #[derive(Default)] /// The internal [`SystemParamState`] of the [`ParallelCommands`] type pub struct ParallelCommandsState { - tls: ThreadLocal>, + thread_local_storage: ThreadLocal>, } /// An alternative to [`Commands`] that can be used in parallel contexts, such as those in [`Query::par_for_each`](crate::system::Query::par_for_each) @@ -76,7 +76,7 @@ unsafe impl SystemParamState for ParallelCommandsState { } fn apply(&mut self, world: &mut World) { - for cq in self.tls.iter_mut() { + for cq in self.thread_local_storage.iter_mut() { cq.get_mut().apply(world); } } @@ -84,13 +84,16 @@ unsafe impl SystemParamState for ParallelCommandsState { impl<'w, 's> ParallelCommands<'w, 's> { pub fn command_scope(&self, f: impl FnOnce(Commands) -> R) -> R { - let tls = &self.state.tls; - let tl_cq = tls.get_or_default(); - let mut cq = tl_cq.take(); + let store = &self.state.thread_local_storage; + let command_queue_cell = store.get_or_default(); + let mut command_queue = command_queue_cell.take(); - let r = f(Commands::new_from_entities(&mut cq, self.entities)); + let r = f(Commands::new_from_entities( + &mut command_queue, + self.entities, + )); - tl_cq.set(cq); + command_queue_cell.set(command_queue); r } } From a97bfdca82e12f8d7d1f1de6e7f48087ac93235a Mon Sep 17 00:00:00 2001 From: TheRawMeatball Date: Sun, 5 Jun 2022 15:48:30 +0300 Subject: [PATCH 8/8] update doc --- crates/bevy_ecs/src/system/commands/parallel_scope.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/system/commands/parallel_scope.rs b/crates/bevy_ecs/src/system/commands/parallel_scope.rs index 87963247a73bd..41dc9b7289192 100644 --- a/crates/bevy_ecs/src/system/commands/parallel_scope.rs +++ b/crates/bevy_ecs/src/system/commands/parallel_scope.rs @@ -31,10 +31,9 @@ pub struct ParallelCommandsState { /// # impl Velocity { fn magnitude(&self) -> f32 { 42.0 } } /// fn parallel_command_system( /// mut query: Query<(Entity, &Velocity)>, -/// pool: Res, /// par_commands: ParallelCommands /// ) { -/// query.par_for_each(&pool, 32, |(entity, velocity)| { +/// query.par_for_each(32, |(entity, velocity)| { /// if velocity.magnitude() > 10.0 { /// par_commands.command_scope(|mut commands| { /// commands.entity(entity).despawn();