Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - ParamSet for conflicting SystemParam:s #2765

Closed
wants to merge 42 commits into from
Closed
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
f8c9a73
fix get_conflicts to return every conflict. added get_conflicts_set
bilsen Sep 2, 2021
3149b56
add extend method to `FilteredAccessSet`
bilsen Sep 2, 2021
2501b97
impl Into for FilteredAccess
bilsen Sep 2, 2021
6ab43ea
refactor SystemParamState init, added methods
bilsen Sep 2, 2021
79fb544
add and impl ParamSet
bilsen Sep 2, 2021
aeffdad
add some tests
bilsen Sep 2, 2021
142b123
add safety comments
bilsen Sep 2, 2021
33ac800
fix access bug
bilsen Sep 2, 2021
089b904
run cargo fmt
bilsen Sep 2, 2021
907b02d
fix clippy stuff
bilsen Sep 2, 2021
5955bfe
add specialized panic messages
bilsen Sep 2, 2021
3f9056a
remove debug comments
bilsen Sep 2, 2021
3b77ea3
add ParamSet to prelude
bilsen Sep 2, 2021
d678368
added paramset to prelude
bilsen Sep 2, 2021
bc72f56
add docstrings
bilsen Sep 2, 2021
90a5236
remove ParamConflictType
bilsen Sep 2, 2021
dae0456
remove QuerySet in favour of ParamSet
bilsen Sep 3, 2021
5f011c9
Update add context in ParamSet docs
bilsen Sep 3, 2021
159ced1
use HashSet from `bevy_util`
bilsen Sep 11, 2021
330c95f
up max parameters in `ParamSet` from 4 to 8
bilsen Oct 31, 2021
c875753
fix rebase
bilsen Dec 17, 2021
cf2c51c
fix tests
bilsen Dec 17, 2021
7a28816
remove more references to querysets
bilsen Dec 17, 2021
7b520e5
remove broken doc links
bilsen Dec 17, 2021
b06818b
Merge branch 'main' into param-set-merge
bilsen Dec 23, 2021
2c53e1d
Merge branch 'main' into param-set-merge
bilsen Jan 6, 2022
107003d
Merge branch 'main' into param-set-merge
bilsen Feb 4, 2022
e466cf4
Better docs
bilsen Feb 4, 2022
0ebd608
Fix &World SystemParam implementation
bilsen Feb 4, 2022
74cc0cf
Wording
bilsen Feb 4, 2022
538a12e
Add ParamSets and remove QuerySets
cart Feb 25, 2022
88eaefc
Merge remote-tracking branch 'cart/param-set' into param-set-merge
bilsen Mar 19, 2022
3236779
fix merge
bilsen Mar 19, 2022
385bdc7
Merge branch 'param-set-merge' of github.com:bilsen/bevy into param-s…
bilsen Mar 19, 2022
265f8fd
Merge remote-tracking branch 'upstream/main' into param-set-merge
bilsen Mar 19, 2022
c6964fc
cargo fmt
bilsen Mar 19, 2022
2b62288
remove paramset link
bilsen Mar 19, 2022
18a84b6
Merge remote-tracking branch 'upstream/main' into param-set-merge
bilsen Mar 25, 2022
16c8258
remove old compile_fail tests
cart Mar 29, 2022
3a4eee0
remove unused Access function
cart Mar 29, 2022
6503fed
Merge remote-tracking branch 'origin/main' into pr/bilsen/2765
cart Mar 29, 2022
a2dc8ae
remove unused
cart Mar 29, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/contributing/example_style_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ For more advice on writing examples, see the [relevant section](../../CONTRIBUTI
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.
3. Use `.single` and `.single_mut` where appropriate.
4. In Queries, prefer `With<T>` filters over actually fetching unused data with `&T`.
5. Prefer disjoint queries using `With` and `Without` over query sets when you need more than one query in a single system.
5. Prefer disjoint queries using `With` and `Without` over param sets when you need more than one query in a single system.
6. Prefer structs with named fields over tuple structs except in the case of single-field wrapper types.
7. Use enum-labels over string-labels for system / stage / etc. labels.

Expand Down
132 changes: 77 additions & 55 deletions crates/bevy_ecs/macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,91 +176,105 @@ fn get_idents(fmt_string: fn(usize) -> String, count: usize) -> Vec<Ident> {
}

#[proc_macro]
pub fn impl_query_set(_input: TokenStream) -> TokenStream {
pub fn impl_param_set(_input: TokenStream) -> TokenStream {
let mut tokens = TokenStream::new();
let max_queries = 4;
let queries = get_idents(|i| format!("Q{}", i), max_queries);
let filters = get_idents(|i| format!("F{}", i), max_queries);
let mut query_fn_muts = Vec::new();
for i in 0..max_queries {
let query = &queries[i];
let filter = &filters[i];
let fn_name = Ident::new(&format!("q{}", i), Span::call_site());
let max_params = 8;
let params = get_idents(|i| format!("P{}", i), max_params);
let params_fetch = get_idents(|i| format!("PF{}", i), max_params);
let values = get_idents(|i| format!("p{}", i), max_params);
let mut param_fn_muts = Vec::new();
for (i, param) in params.iter().enumerate() {
let fn_name = Ident::new(&format!("p{}", i), Span::call_site());
let index = Index::from(i);
query_fn_muts.push(quote! {
pub fn #fn_name(&mut self) -> Query<'_, '_, #query, #filter> {
param_fn_muts.push(quote! {
pub fn #fn_name<'a>(&'a mut self) -> <#param::Fetch as SystemParamFetch<'a, 'a>>::Item {
// SAFE: systems run without conflicts with other systems.
// Conflicting queries in QuerySet are not accessible at the same time
// QuerySets are guaranteed to not conflict with other SystemParams
// Conflicting params in ParamSet are not accessible at the same time
// ParamSets are guaranteed to not conflict with other SystemParams
unsafe {
Query::new(self.world, &self.query_states.#index, self.last_change_tick, self.change_tick)
<#param::Fetch as SystemParamFetch<'a, 'a>>::get_param(&mut self.param_states.#index, &self.system_meta, self.world, self.change_tick)
}
}
});
}

for query_count in 1..=max_queries {
let query = &queries[0..query_count];
let filter = &filters[0..query_count];
let query_fn_mut = &query_fn_muts[0..query_count];
for param_count in 1..=max_params {
let param = &params[0..param_count];
let param_fetch = &params_fetch[0..param_count];
let value = &values[0..param_count];
let param_fn_mut = &param_fn_muts[0..param_count];
tokens.extend(TokenStream::from(quote! {
impl<'w, 's, #(#query: WorldQuery + 'static,)* #(#filter: WorldQuery + 'static,)*> SystemParam for QuerySet<'w, 's, (#(QueryState<#query, #filter>,)*)>
where #(#filter::Fetch: FilterFetch,)*
impl<'w, 's, #(#param: SystemParam,)*> SystemParam for ParamSet<'w, 's, (#(#param,)*)>
{
type Fetch = QuerySetState<(#(QueryState<#query, #filter>,)*)>;
type Fetch = ParamSetState<(#(#param::Fetch,)*)>;
}

// SAFE: All Queries are constrained to ReadOnlyFetch, so World is only read
unsafe impl<#(#query: WorldQuery + 'static,)* #(#filter: WorldQuery + 'static,)*> ReadOnlySystemParamFetch for QuerySetState<(#(QueryState<#query, #filter>,)*)>
where #(#query::Fetch: ReadOnlyFetch,)* #(#filter::Fetch: FilterFetch,)*
// SAFE: All parameters are constrained to ReadOnlyFetch, so World is only read

unsafe impl<#(#param_fetch: for<'w1, 's1> SystemParamFetch<'w1, 's1>,)*> ReadOnlySystemParamFetch for ParamSetState<(#(#param_fetch,)*)>
where #(#param_fetch: ReadOnlySystemParamFetch,)*
{ }

// SAFE: Relevant query ComponentId and ArchetypeComponentId access is applied to SystemMeta. If any QueryState conflicts
// SAFE: Relevant parameter ComponentId and ArchetypeComponentId access is applied to SystemMeta. If any ParamState conflicts
// with any prior access, a panic will occur.
unsafe impl<#(#query: WorldQuery + 'static,)* #(#filter: WorldQuery + 'static,)*> SystemParamState for QuerySetState<(#(QueryState<#query, #filter>,)*)>
where #(#filter::Fetch: FilterFetch,)*

unsafe impl<#(#param_fetch: for<'w1, 's1> SystemParamFetch<'w1, 's1>,)*> SystemParamState for ParamSetState<(#(#param_fetch,)*)>
{
type Config = ();
type Config = (#(#param_fetch::Config,)*);


fn init(world: &mut World, system_meta: &mut SystemMeta, config: Self::Config) -> Self {
let (#(#value,)*) = config;
#(
let mut #query = QueryState::<#query, #filter>::new(world);
assert_component_access_compatibility(
&system_meta.name,
std::any::type_name::<#query>(),
std::any::type_name::<#filter>(),
&system_meta.component_access_set,
&#query.component_access,
world,
);
// Pretend to add each param to the system alone, see if it conflicts
let #param = #param_fetch::init(world, &mut system_meta.clone(), #value);
)*
#(
system_meta
.component_access_set
.add(#query.component_access.clone());
.extend(#param.component_access_set());
system_meta
.archetype_component_access
.extend(&#query.archetype_component_access);
.extend(&#param.archetype_component_access());
)*
QuerySetState((#(#query,)*))
ParamSetState((#(#param,)*))
}

fn new_archetype(&mut self, archetype: &Archetype, system_meta: &mut SystemMeta) {
let (#(#query,)*) = &mut self.0;
let (#(#param,)*) = &mut self.0;
#(
#query.new_archetype(archetype);
system_meta
.archetype_component_access
.extend(&#query.archetype_component_access);
#param.new_archetype(archetype, system_meta);
)*
}

fn default_config() {}
fn archetype_component_access(&self) -> Access<ArchetypeComponentId> {
let (#(#param,)*) = &self.0;
let mut combined_access = Access::<ArchetypeComponentId>::default();
#({
combined_access.extend(&#param.archetype_component_access());
})*;
combined_access
}

fn component_access_set(&self) -> FilteredAccessSet<ComponentId> {
let (#(#param,)*) = &self.0;
let mut combined_access = FilteredAccessSet::<ComponentId>::default();
#({
combined_access.extend(#param.component_access_set());
})*;
combined_access
}

fn default_config() -> Self::Config {
(#(#param_fetch::default_config(),)*)
}
}

impl<'w, 's, #(#query: WorldQuery + 'static,)* #(#filter: WorldQuery + 'static,)*> SystemParamFetch<'w, 's> for QuerySetState<(#(QueryState<#query, #filter>,)*)>
where #(#filter::Fetch: FilterFetch,)*


impl<'w, 's, #(#param_fetch: for<'w1, 's1> SystemParamFetch<'w1, 's1>,)*> SystemParamFetch<'w, 's> for ParamSetState<(#(#param_fetch,)*)>
{
type Item = QuerySet<'w, 's, (#(QueryState<#query, #filter>,)*)>;
type Item = ParamSet<'w, 's, (#(<#param_fetch as SystemParamFetch<'w, 's>>::Item,)*)>;

#[inline]
unsafe fn get_param(
Expand All @@ -269,19 +283,19 @@ pub fn impl_query_set(_input: TokenStream) -> TokenStream {
world: &'w World,
change_tick: u32,
) -> Self::Item {
QuerySet {
query_states: &state.0,
ParamSet {
param_states: &mut state.0,
system_meta: system_meta.clone(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should remove this clone (ex: by making this a borrow). Cloning SystemMeta might allocate a string (for the system name, if the Cow is owned), and cloning the accesses might also allocate (internally uses Vecs and fixedbitsets, which both might allocate). This clone could be non-trivially expensive, especially for more complicated systems. Not something we want to do every time a system runs.

Sadly making this a borrow breaks SystemState because it needs to be able to write the last_change_tick. One solution to this might be to change the get_param() interface to not require full SystemMeta access.

world,
last_change_tick: system_meta.last_change_tick,
change_tick,
}
}
}

impl<'w, 's, #(#query: WorldQuery,)* #(#filter: WorldQuery,)*> QuerySet<'w, 's, (#(QueryState<#query, #filter>,)*)>
where #(#filter::Fetch: FilterFetch,)*
impl<'w, 's, #(#param: SystemParam,)*> ParamSet<'w, 's, (#(#param,)*)>
{
#(#query_fn_mut)*

#(#param_fn_mut)*
}
}));
}
Expand Down Expand Up @@ -395,6 +409,14 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
}
}

fn archetype_component_access(&self) -> #path::query::Access<#path::archetype::ArchetypeComponentId> {
self.state.archetype_component_access()
}

fn component_access_set(&self) -> #path::query::FilteredAccessSet<#path::component::ComponentId> {
self.state.component_access_set()
}

fn new_archetype(&mut self, archetype: &#path::archetype::Archetype, system_meta: &mut #path::system::SystemMeta) {
self.state.new_archetype(archetype, system_meta)
}
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_ecs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ pub mod prelude {
},
system::{
Commands, ConfigurableSystem, In, IntoChainSystem, IntoExclusiveSystem, IntoSystem,
Local, NonSend, NonSendMut, Query, QuerySet, RemovedComponents, Res, ResMut, System,
Local, NonSend, NonSendMut, ParamSet, Query, RemovedComponents, Res, ResMut, System,
},
world::{FromWorld, Mut, World},
};
Expand Down
58 changes: 52 additions & 6 deletions crates/bevy_ecs/src/query/access.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::storage::SparseSetIndex;
use bevy_utils::HashSet;
use fixedbitset::FixedBitSet;
use std::marker::PhantomData;

Expand Down Expand Up @@ -129,7 +130,7 @@ impl<T: SparseSetIndex> Access<T> {
}
}

#[derive(Clone, Eq, PartialEq)]
#[derive(Clone, Eq, PartialEq, Debug)]
pub struct FilteredAccess<T: SparseSetIndex> {
access: Access<T>,
with: FixedBitSet,
Expand All @@ -146,6 +147,14 @@ impl<T: SparseSetIndex> Default for FilteredAccess<T> {
}
}

impl<T: SparseSetIndex> From<FilteredAccess<T>> for FilteredAccessSet<T> {
fn from(filtered_access: FilteredAccess<T>) -> Self {
let mut base = FilteredAccessSet::<T>::default();
base.add(filtered_access);
base
}
}

impl<T: SparseSetIndex> FilteredAccess<T> {
#[inline]
pub fn access(&self) -> &Access<T> {
Expand Down Expand Up @@ -191,7 +200,7 @@ impl<T: SparseSetIndex> FilteredAccess<T> {
self.access.read_all();
}
}

#[derive(Clone, Debug)]
pub struct FilteredAccessSet<T: SparseSetIndex> {
combined_access: Access<T>,
filtered_accesses: Vec<FilteredAccess<T>>,
Expand All @@ -211,22 +220,59 @@ impl<T: SparseSetIndex> FilteredAccessSet<T> {
pub fn get_conflicts(&self, filtered_access: &FilteredAccess<T>) -> Vec<T> {
// if combined unfiltered access is incompatible, check each filtered access for
// compatibility
let mut conflicts = HashSet::<usize>::default();
if !filtered_access.access.is_compatible(&self.combined_access) {
for current_filtered_access in self.filtered_accesses.iter() {
if !current_filtered_access.is_compatible(filtered_access) {
return current_filtered_access
.access
.get_conflicts(&filtered_access.access);
conflicts.extend(
current_filtered_access
.access
.get_conflicts(&filtered_access.access)
.iter()
.map(|ind| ind.sparse_set_index()),
);
}
}
}
Vec::new()
conflicts
.iter()
.map(|ind| T::get_sparse_set_index(*ind))
.collect()
}

pub fn get_conflicts_set(&self, filtered_access_set: &FilteredAccessSet<T>) -> Vec<T> {
// if combined unfiltered access is incompatible, check each filtered access for
// compatibility with the set
let mut conflicts = HashSet::<usize>::default();
if !filtered_access_set
.combined_access
.is_compatible(&self.combined_access)
{
for current_filtered_access in filtered_access_set.filtered_accesses.iter() {
conflicts.extend(
self.get_conflicts(current_filtered_access)
.iter()
.map(|ind| ind.sparse_set_index()),
);
}
}
conflicts
.iter()
.map(|ind| T::get_sparse_set_index(*ind))
.collect()
}

pub fn add(&mut self, filtered_access: FilteredAccess<T>) {
self.combined_access.extend(&filtered_access.access);
self.filtered_accesses.push(filtered_access);
}

pub fn extend(&mut self, filtered_access_set: FilteredAccessSet<T>) {
self.combined_access
.extend(&filtered_access_set.combined_access);
self.filtered_accesses
.extend(filtered_access_set.filtered_accesses);
}
}

impl<T: SparseSetIndex> Default for FilteredAccessSet<T> {
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_ecs/src/system/function_system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use bevy_ecs_macros::all_tuples;
use std::{borrow::Cow, marker::PhantomData};

/// The metadata of a [`System`].
#[derive(Clone)]
pub struct SystemMeta {
pub(crate) name: Cow<'static, str>,
pub(crate) component_access_set: FilteredAccessSet<ComponentId>,
Expand Down
Loading