Skip to content

Commit 78ee17e

Browse files
TheRawMeatballjames7132
authored andcommitted
Add ParallelCommands system parameter (bevyengine#4749)
(follow-up to bevyengine#4423) # Objective Currently, it isn't possible to easily fire commands from within par_for_each blocks. This PR allows for issuing commands from within parallel scopes.
1 parent ddb32e6 commit 78ee17e

File tree

4 files changed

+102
-1
lines changed

4 files changed

+102
-1
lines changed

crates/bevy_ecs/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ bevy_utils = { path = "../bevy_utils", version = "0.8.0-dev" }
2121
bevy_ecs_macros = { path = "macros", version = "0.8.0-dev" }
2222

2323
async-channel = "1.4"
24+
thread_local = "1.1.4"
2425
fixedbitset = "0.4"
2526
fxhash = "0.2"
2627
downcast-rs = "1.2"

crates/bevy_ecs/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ pub mod prelude {
3939
},
4040
system::{
4141
Commands, In, IntoChainSystem, IntoExclusiveSystem, IntoSystem, Local, NonSend,
42-
NonSendMut, ParamSet, Query, RemovedComponents, Res, ResMut, System,
42+
NonSendMut, ParallelCommands, ParamSet, Query, RemovedComponents, Res, ResMut, System,
4343
SystemParamFunction,
4444
},
4545
world::{FromWorld, Mut, World},

crates/bevy_ecs/src/system/commands/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
mod command_queue;
2+
mod parallel_scope;
23

34
use crate::{
45
bundle::Bundle,
@@ -8,6 +9,7 @@ use crate::{
89
};
910
use bevy_utils::tracing::{error, warn};
1011
pub use command_queue::CommandQueue;
12+
pub use parallel_scope::*;
1113
use std::marker::PhantomData;
1214

1315
use super::Resource;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
use std::cell::Cell;
2+
3+
use thread_local::ThreadLocal;
4+
5+
use crate::{
6+
entity::Entities,
7+
prelude::World,
8+
system::{SystemParam, SystemParamFetch, SystemParamState},
9+
};
10+
11+
use super::{CommandQueue, Commands};
12+
13+
#[doc(hidden)]
14+
#[derive(Default)]
15+
/// The internal [`SystemParamState`] of the [`ParallelCommands`] type
16+
pub struct ParallelCommandsState {
17+
thread_local_storage: ThreadLocal<Cell<CommandQueue>>,
18+
}
19+
20+
/// 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)
21+
///
22+
/// Note: Because command application order will depend on how many threads are ran, non-commutative commands may result in non-deterministic results.
23+
///
24+
/// Example:
25+
/// ```
26+
/// # use bevy_ecs::prelude::*;
27+
/// # use bevy_tasks::ComputeTaskPool;
28+
/// #
29+
/// # #[derive(Component)]
30+
/// # struct Velocity;
31+
/// # impl Velocity { fn magnitude(&self) -> f32 { 42.0 } }
32+
/// fn parallel_command_system(
33+
/// mut query: Query<(Entity, &Velocity)>,
34+
/// par_commands: ParallelCommands
35+
/// ) {
36+
/// query.par_for_each(32, |(entity, velocity)| {
37+
/// if velocity.magnitude() > 10.0 {
38+
/// par_commands.command_scope(|mut commands| {
39+
/// commands.entity(entity).despawn();
40+
/// });
41+
/// }
42+
/// });
43+
/// }
44+
/// # bevy_ecs::system::assert_is_system(parallel_command_system);
45+
///```
46+
pub struct ParallelCommands<'w, 's> {
47+
state: &'s mut ParallelCommandsState,
48+
entities: &'w Entities,
49+
}
50+
51+
impl SystemParam for ParallelCommands<'_, '_> {
52+
type Fetch = ParallelCommandsState;
53+
}
54+
55+
impl<'w, 's> SystemParamFetch<'w, 's> for ParallelCommandsState {
56+
type Item = ParallelCommands<'w, 's>;
57+
58+
unsafe fn get_param(
59+
state: &'s mut Self,
60+
_: &crate::system::SystemMeta,
61+
world: &'w World,
62+
_: u32,
63+
) -> Self::Item {
64+
ParallelCommands {
65+
state,
66+
entities: world.entities(),
67+
}
68+
}
69+
}
70+
71+
// SAFE: no component or resource access to report
72+
unsafe impl SystemParamState for ParallelCommandsState {
73+
fn init(_: &mut World, _: &mut crate::system::SystemMeta) -> Self {
74+
Self::default()
75+
}
76+
77+
fn apply(&mut self, world: &mut World) {
78+
for cq in self.thread_local_storage.iter_mut() {
79+
cq.get_mut().apply(world);
80+
}
81+
}
82+
}
83+
84+
impl<'w, 's> ParallelCommands<'w, 's> {
85+
pub fn command_scope<R>(&self, f: impl FnOnce(Commands) -> R) -> R {
86+
let store = &self.state.thread_local_storage;
87+
let command_queue_cell = store.get_or_default();
88+
let mut command_queue = command_queue_cell.take();
89+
90+
let r = f(Commands::new_from_entities(
91+
&mut command_queue,
92+
self.entities,
93+
));
94+
95+
command_queue_cell.set(command_queue);
96+
r
97+
}
98+
}

0 commit comments

Comments
 (0)