Skip to content

Commit 45d8326

Browse files
bilsenItsDoot
authored andcommitted
ParamSet for conflicting SystemParam:s (bevyengine#2765)
# Objective Add a system parameter `ParamSet` to be used as container for conflicting parameters. ## Solution Added two methods to the SystemParamState trait, which gives the access used by the parameter. Did the implementation. Added some convenience methods to FilteredAccessSet. Changed `get_conflicts` to return every conflicting component instead of breaking on the first conflicting `FilteredAccess`. Co-authored-by: bilsen <[email protected]>
1 parent 5330510 commit 45d8326

File tree

20 files changed

+180
-166
lines changed

20 files changed

+180
-166
lines changed

.github/contributing/example_style_guide.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ For more advice on writing examples, see the [relevant section](../../CONTRIBUTI
3636
2. Prefer `for` loops over `.for_each`. The latter is faster (for now), but it is less clear for beginners, less idiomatic, and less flexible.
3737
3. Use `.single` and `.single_mut` where appropriate.
3838
4. In Queries, prefer `With<T>` filters over actually fetching unused data with `&T`.
39-
5. Prefer disjoint queries using `With` and `Without` over query sets when you need more than one query in a single system.
39+
5. Prefer disjoint queries using `With` and `Without` over param sets when you need more than one query in a single system.
4040
6. Prefer structs with named fields over tuple structs except in the case of single-field wrapper types.
4141
7. Use enum-labels over string-labels for system / stage / etc. labels.
4242

crates/bevy_ecs/macros/src/lib.rs

+48-53
Original file line numberDiff line numberDiff line change
@@ -178,88 +178,83 @@ fn get_idents(fmt_string: fn(usize) -> String, count: usize) -> Vec<Ident> {
178178
}
179179

180180
#[proc_macro]
181-
pub fn impl_query_set(_input: TokenStream) -> TokenStream {
181+
pub fn impl_param_set(_input: TokenStream) -> TokenStream {
182182
let mut tokens = TokenStream::new();
183-
let max_queries = 4;
184-
let queries = get_idents(|i| format!("Q{}", i), max_queries);
185-
let filters = get_idents(|i| format!("F{}", i), max_queries);
186-
let mut query_fn_muts = Vec::new();
187-
for i in 0..max_queries {
188-
let query = &queries[i];
189-
let filter = &filters[i];
190-
let fn_name = Ident::new(&format!("q{}", i), Span::call_site());
183+
let max_params = 8;
184+
let params = get_idents(|i| format!("P{}", i), max_params);
185+
let params_fetch = get_idents(|i| format!("PF{}", i), max_params);
186+
let metas = get_idents(|i| format!("m{}", i), max_params);
187+
let mut param_fn_muts = Vec::new();
188+
for (i, param) in params.iter().enumerate() {
189+
let fn_name = Ident::new(&format!("p{}", i), Span::call_site());
191190
let index = Index::from(i);
192-
query_fn_muts.push(quote! {
193-
pub fn #fn_name(&mut self) -> Query<'_, '_, #query, #filter> {
191+
param_fn_muts.push(quote! {
192+
pub fn #fn_name<'a>(&'a mut self) -> <#param::Fetch as SystemParamFetch<'a, 'a>>::Item {
194193
// SAFE: systems run without conflicts with other systems.
195-
// Conflicting queries in QuerySet are not accessible at the same time
196-
// QuerySets are guaranteed to not conflict with other SystemParams
194+
// Conflicting params in ParamSet are not accessible at the same time
195+
// ParamSets are guaranteed to not conflict with other SystemParams
197196
unsafe {
198-
Query::new(self.world, &self.query_states.#index, self.last_change_tick, self.change_tick)
197+
<#param::Fetch as SystemParamFetch<'a, 'a>>::get_param(&mut self.param_states.#index, &self.system_meta, self.world, self.change_tick)
199198
}
200199
}
201200
});
202201
}
203202

204-
for query_count in 1..=max_queries {
205-
let query = &queries[0..query_count];
206-
let filter = &filters[0..query_count];
207-
let query_fn_mut = &query_fn_muts[0..query_count];
203+
for param_count in 1..=max_params {
204+
let param = &params[0..param_count];
205+
let param_fetch = &params_fetch[0..param_count];
206+
let meta = &metas[0..param_count];
207+
let param_fn_mut = &param_fn_muts[0..param_count];
208208
tokens.extend(TokenStream::from(quote! {
209-
impl<'w, 's, #(#query: WorldQuery + 'static,)* #(#filter: WorldQuery + 'static,)*> SystemParam for QuerySet<'w, 's, (#(QueryState<#query, #filter>,)*)>
210-
where #(#filter::Fetch: FilterFetch,)*
209+
impl<'w, 's, #(#param: SystemParam,)*> SystemParam for ParamSet<'w, 's, (#(#param,)*)>
211210
{
212-
type Fetch = QuerySetState<(#(QueryState<#query, #filter>,)*)>;
211+
type Fetch = ParamSetState<(#(#param::Fetch,)*)>;
213212
}
214213

215-
// SAFE: All Queries are constrained to ReadOnlyFetch, so World is only read
216-
unsafe impl<#(#query: WorldQuery + 'static,)* #(#filter: WorldQuery + 'static,)*> ReadOnlySystemParamFetch for QuerySetState<(#(QueryState<#query, #filter>,)*)>
217-
where #(#query::Fetch: ReadOnlyFetch,)* #(#filter::Fetch: FilterFetch,)*
214+
// SAFE: All parameters are constrained to ReadOnlyFetch, so World is only read
215+
216+
unsafe impl<#(#param_fetch: for<'w1, 's1> SystemParamFetch<'w1, 's1>,)*> ReadOnlySystemParamFetch for ParamSetState<(#(#param_fetch,)*)>
217+
where #(#param_fetch: ReadOnlySystemParamFetch,)*
218218
{ }
219219

220-
// SAFE: Relevant query ComponentId and ArchetypeComponentId access is applied to SystemMeta. If any QueryState conflicts
220+
// SAFE: Relevant parameter ComponentId and ArchetypeComponentId access is applied to SystemMeta. If any ParamState conflicts
221221
// with any prior access, a panic will occur.
222-
unsafe impl<#(#query: WorldQuery + 'static,)* #(#filter: WorldQuery + 'static,)*> SystemParamState for QuerySetState<(#(QueryState<#query, #filter>,)*)>
223-
where #(#filter::Fetch: FilterFetch,)*
222+
223+
unsafe impl<#(#param_fetch: for<'w1, 's1> SystemParamFetch<'w1, 's1>,)*> SystemParamState for ParamSetState<(#(#param_fetch,)*)>
224224
{
225225
fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self {
226226
#(
227-
let mut #query = QueryState::<#query, #filter>::new(world);
228-
assert_component_access_compatibility(
229-
&system_meta.name,
230-
std::any::type_name::<#query>(),
231-
std::any::type_name::<#filter>(),
232-
&system_meta.component_access_set,
233-
&#query.component_access,
234-
world,
235-
);
227+
// Pretend to add each param to the system alone, see if it conflicts
228+
let mut #meta = system_meta.clone();
229+
#meta.component_access_set.clear();
230+
#meta.archetype_component_access.clear();
231+
#param_fetch::init(world, &mut #meta);
232+
let #param = #param_fetch::init(world, &mut system_meta.clone());
236233
)*
237234
#(
238235
system_meta
239236
.component_access_set
240-
.add(#query.component_access.clone());
237+
.extend(#meta.component_access_set);
241238
system_meta
242239
.archetype_component_access
243-
.extend(&#query.archetype_component_access);
240+
.extend(&#meta.archetype_component_access);
244241
)*
245-
QuerySetState((#(#query,)*))
242+
ParamSetState((#(#param,)*))
246243
}
247244

248245
fn new_archetype(&mut self, archetype: &Archetype, system_meta: &mut SystemMeta) {
249-
let (#(#query,)*) = &mut self.0;
246+
let (#(#param,)*) = &mut self.0;
250247
#(
251-
#query.new_archetype(archetype);
252-
system_meta
253-
.archetype_component_access
254-
.extend(&#query.archetype_component_access);
248+
#param.new_archetype(archetype, system_meta);
255249
)*
256250
}
257251
}
258252

259-
impl<'w, 's, #(#query: WorldQuery + 'static,)* #(#filter: WorldQuery + 'static,)*> SystemParamFetch<'w, 's> for QuerySetState<(#(QueryState<#query, #filter>,)*)>
260-
where #(#filter::Fetch: FilterFetch,)*
253+
254+
255+
impl<'w, 's, #(#param_fetch: for<'w1, 's1> SystemParamFetch<'w1, 's1>,)*> SystemParamFetch<'w, 's> for ParamSetState<(#(#param_fetch,)*)>
261256
{
262-
type Item = QuerySet<'w, 's, (#(QueryState<#query, #filter>,)*)>;
257+
type Item = ParamSet<'w, 's, (#(<#param_fetch as SystemParamFetch<'w, 's>>::Item,)*)>;
263258

264259
#[inline]
265260
unsafe fn get_param(
@@ -268,19 +263,19 @@ pub fn impl_query_set(_input: TokenStream) -> TokenStream {
268263
world: &'w World,
269264
change_tick: u32,
270265
) -> Self::Item {
271-
QuerySet {
272-
query_states: &state.0,
266+
ParamSet {
267+
param_states: &mut state.0,
268+
system_meta: system_meta.clone(),
273269
world,
274-
last_change_tick: system_meta.last_change_tick,
275270
change_tick,
276271
}
277272
}
278273
}
279274

280-
impl<'w, 's, #(#query: WorldQuery,)* #(#filter: WorldQuery,)*> QuerySet<'w, 's, (#(QueryState<#query, #filter>,)*)>
281-
where #(#filter::Fetch: FilterFetch,)*
275+
impl<'w, 's, #(#param: SystemParam,)*> ParamSet<'w, 's, (#(#param,)*)>
282276
{
283-
#(#query_fn_mut)*
277+
278+
#(#param_fn_mut)*
284279
}
285280
}));
286281
}

crates/bevy_ecs/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ pub mod prelude {
3434
},
3535
system::{
3636
Commands, In, IntoChainSystem, IntoExclusiveSystem, IntoSystem, Local, NonSend,
37-
NonSendMut, Query, QuerySet, RemovedComponents, Res, ResMut, System,
37+
NonSendMut, ParamSet, Query, RemovedComponents, Res, ResMut, System,
3838
SystemParamFunction,
3939
},
4040
world::{FromWorld, Mut, World},

crates/bevy_ecs/src/query/access.rs

+35-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::storage::SparseSetIndex;
2+
use bevy_utils::HashSet;
23
use fixedbitset::FixedBitSet;
34
use std::marker::PhantomData;
45

@@ -129,7 +130,7 @@ impl<T: SparseSetIndex> Access<T> {
129130
}
130131
}
131132

132-
#[derive(Clone, Eq, PartialEq)]
133+
#[derive(Clone, Eq, PartialEq, Debug)]
133134
pub struct FilteredAccess<T: SparseSetIndex> {
134135
access: Access<T>,
135136
with: FixedBitSet,
@@ -146,6 +147,14 @@ impl<T: SparseSetIndex> Default for FilteredAccess<T> {
146147
}
147148
}
148149

150+
impl<T: SparseSetIndex> From<FilteredAccess<T>> for FilteredAccessSet<T> {
151+
fn from(filtered_access: FilteredAccess<T>) -> Self {
152+
let mut base = FilteredAccessSet::<T>::default();
153+
base.add(filtered_access);
154+
base
155+
}
156+
}
157+
149158
impl<T: SparseSetIndex> FilteredAccess<T> {
150159
#[inline]
151160
pub fn access(&self) -> &Access<T> {
@@ -191,7 +200,7 @@ impl<T: SparseSetIndex> FilteredAccess<T> {
191200
self.access.read_all();
192201
}
193202
}
194-
203+
#[derive(Clone, Debug)]
195204
pub struct FilteredAccessSet<T: SparseSetIndex> {
196205
combined_access: Access<T>,
197206
filtered_accesses: Vec<FilteredAccess<T>>,
@@ -211,22 +220,42 @@ impl<T: SparseSetIndex> FilteredAccessSet<T> {
211220
pub fn get_conflicts(&self, filtered_access: &FilteredAccess<T>) -> Vec<T> {
212221
// if combined unfiltered access is incompatible, check each filtered access for
213222
// compatibility
223+
let mut conflicts = HashSet::<usize>::default();
214224
if !filtered_access.access.is_compatible(&self.combined_access) {
215225
for current_filtered_access in &self.filtered_accesses {
216226
if !current_filtered_access.is_compatible(filtered_access) {
217-
return current_filtered_access
218-
.access
219-
.get_conflicts(&filtered_access.access);
227+
conflicts.extend(
228+
current_filtered_access
229+
.access
230+
.get_conflicts(&filtered_access.access)
231+
.iter()
232+
.map(|ind| ind.sparse_set_index()),
233+
);
220234
}
221235
}
222236
}
223-
Vec::new()
237+
conflicts
238+
.iter()
239+
.map(|ind| T::get_sparse_set_index(*ind))
240+
.collect()
224241
}
225242

226243
pub fn add(&mut self, filtered_access: FilteredAccess<T>) {
227244
self.combined_access.extend(&filtered_access.access);
228245
self.filtered_accesses.push(filtered_access);
229246
}
247+
248+
pub fn extend(&mut self, filtered_access_set: FilteredAccessSet<T>) {
249+
self.combined_access
250+
.extend(&filtered_access_set.combined_access);
251+
self.filtered_accesses
252+
.extend(filtered_access_set.filtered_accesses);
253+
}
254+
255+
pub fn clear(&mut self) {
256+
self.combined_access.clear();
257+
self.filtered_accesses.clear();
258+
}
230259
}
231260

232261
impl<T: SparseSetIndex> Default for FilteredAccessSet<T> {

crates/bevy_ecs/src/system/function_system.rs

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use bevy_ecs_macros::all_tuples;
1313
use std::{borrow::Cow, fmt::Debug, hash::Hash, marker::PhantomData};
1414

1515
/// The metadata of a [`System`].
16+
#[derive(Clone)]
1617
pub struct SystemMeta {
1718
pub(crate) name: Cow<'static, str>,
1819
pub(crate) component_access_set: FilteredAccessSet<ComponentId>,

crates/bevy_ecs/src/system/mod.rs

+13-20
Original file line numberDiff line numberDiff line change
@@ -100,10 +100,10 @@ mod tests {
100100
bundle::Bundles,
101101
component::{Component, Components},
102102
entity::{Entities, Entity},
103-
query::{Added, Changed, Or, QueryState, With, Without},
103+
query::{Added, Changed, Or, With, Without},
104104
schedule::{Schedule, Stage, SystemStage},
105105
system::{
106-
IntoExclusiveSystem, IntoSystem, Local, NonSend, NonSendMut, Query, QuerySet,
106+
IntoExclusiveSystem, IntoSystem, Local, NonSend, NonSendMut, ParamSet, Query,
107107
RemovedComponents, Res, ResMut, System, SystemState,
108108
},
109109
world::{FromWorld, World},
@@ -211,17 +211,17 @@ mod tests {
211211
}
212212

213213
#[test]
214-
fn or_query_set_system() {
214+
fn or_param_set_system() {
215215
// Regression test for issue #762
216216
fn query_system(
217217
mut ran: ResMut<bool>,
218-
mut set: QuerySet<(
219-
QueryState<(), Or<(Changed<A>, Changed<B>)>>,
220-
QueryState<(), Or<(Added<A>, Added<B>)>>,
218+
mut set: ParamSet<(
219+
Query<(), Or<(Changed<A>, Changed<B>)>>,
220+
Query<(), Or<(Added<A>, Added<B>)>>,
221221
)>,
222222
) {
223-
let changed = set.q0().iter().count();
224-
let added = set.q1().iter().count();
223+
let changed = set.p0().iter().count();
224+
let added = set.p1().iter().count();
225225

226226
assert_eq!(changed, 1);
227227
assert_eq!(added, 1);
@@ -320,15 +320,15 @@ mod tests {
320320

321321
#[test]
322322
fn query_set_system() {
323-
fn sys(mut _set: QuerySet<(QueryState<&mut A>, QueryState<&A>)>) {}
323+
fn sys(mut _set: ParamSet<(Query<&mut A>, Query<&A>)>) {}
324324
let mut world = World::default();
325325
run_system(&mut world, sys);
326326
}
327327

328328
#[test]
329329
#[should_panic]
330330
fn conflicting_query_with_query_set_system() {
331-
fn sys(_query: Query<&mut A>, _set: QuerySet<(QueryState<&mut A>, QueryState<&B>)>) {}
331+
fn sys(_query: Query<&mut A>, _set: ParamSet<(Query<&mut A>, Query<&B>)>) {}
332332

333333
let mut world = World::default();
334334
run_system(&mut world, sys);
@@ -337,11 +337,7 @@ mod tests {
337337
#[test]
338338
#[should_panic]
339339
fn conflicting_query_sets_system() {
340-
fn sys(
341-
_set_1: QuerySet<(QueryState<&mut A>,)>,
342-
_set_2: QuerySet<(QueryState<&mut A>, QueryState<&B>)>,
343-
) {
344-
}
340+
fn sys(_set_1: ParamSet<(Query<&mut A>,)>, _set_2: ParamSet<(Query<&mut A>, Query<&B>)>) {}
345341

346342
let mut world = World::default();
347343
run_system(&mut world, sys);
@@ -674,11 +670,8 @@ mod tests {
674670
world.insert_resource(A(42));
675671
world.spawn().insert(B(7));
676672

677-
let mut system_state: SystemState<(
678-
Res<A>,
679-
Query<&B>,
680-
QuerySet<(QueryState<&C>, QueryState<&D>)>,
681-
)> = SystemState::new(&mut world);
673+
let mut system_state: SystemState<(Res<A>, Query<&B>, ParamSet<(Query<&C>, Query<&D>)>)> =
674+
SystemState::new(&mut world);
682675
let (a, query, _) = system_state.get(&world);
683676
assert_eq!(*a, A(42), "returned resource matches initial value");
684677
assert_eq!(

crates/bevy_ecs/src/system/query.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ use thiserror::Error;
8383
///
8484
/// Similarly, a system cannot contain two queries that would break Rust's mutability Rules.
8585
/// If you need such Queries, you can use Filters to make the Queries disjoint or use a
86-
/// [`QuerySet`](super::QuerySet).
86+
/// [`ParamSet`](super::ParamSet).
8787
///
8888
/// ## Entity ID access
8989
///

0 commit comments

Comments
 (0)