Skip to content

Commit 407c080

Browse files
committed
Replace ReadOnlyFetch with ReadOnlyWorldQuery (#4626)
# Objective - Fix a type inference regression introduced by #3001 - Make read only bounds on world queries more user friendly ptrification required you to write `Q::Fetch: ReadOnlyFetch` as `for<'w> QueryFetch<'w, Q>: ReadOnlyFetch` which has the same type inference problem as `for<'w> QueryFetch<'w, Q>: FilterFetch<'w>` had, i.e. the following code would error: ```rust #[derive(Component)] struct Foo; fn bar(a: Query<(&Foo, Without<Foo>)>) { foo(a); } fn foo<Q: WorldQuery>(a: Query<Q, ()>) where for<'w> QueryFetch<'w, Q>: ReadOnlyFetch, { } ``` `for<..>` bounds are also rather user unfriendly.. ## Solution Remove the `ReadOnlyFetch` trait in favour of a `ReadOnlyWorldQuery` trait, and remove `WorldQueryGats::ReadOnlyFetch` in favor of `WorldQuery::ReadOnly` allowing the previous code snippet to be written as: ```rust #[derive(Component)] struct Foo; fn bar(a: Query<(&Foo, Without<Foo>)>) { foo(a); } fn foo<Q: ReadOnlyWorldQuery>(a: Query<Q, ()>) {} ``` This avoids the `for<...>` bound which makes the code simpler and also fixes the type inference issue. The reason for moving the two functions out of `FetchState` and into `WorldQuery` is to allow the world query `&mut T` to share a `State` with the `&T` world query so that it can have `type ReadOnly = &T`. Presumably it would be possible to instead have a `ReadOnlyRefMut<T>` world query and then do `type ReadOnly = ReadOnlyRefMut<T>` much like how (before this PR) we had a `ReadOnlyWriteFetch<T>`. A side benefit of the current solution in this PR is that it will likely make it easier in the future to support an API such as `Query<&mut T> -> Query<&T>`. The primary benefit IMO is just that `ReadOnlyRefMut<T>` and its associated fetch would have to reimplement all of the logic that the `&T` world query impl does but this solution avoids that :) --- ## Changelog/Migration Guide The trait `ReadOnlyFetch` has been replaced with `ReadOnlyWorldQuery` along with the `WorldQueryGats::ReadOnlyFetch` assoc type which has been replaced with `<WorldQuery::ReadOnly as WorldQueryGats>::Fetch` - Any where clauses such as `QueryFetch<Q>: ReadOnlyFetch` should be replaced with `Q: ReadOnlyWorldQuery`. - Any custom world query impls should implement `ReadOnlyWorldQuery` insead of `ReadOnlyFetch` Functions `update_component_access` and `update_archetype_component_access` have been moved from the `FetchState` trait to `WorldQuery` - Any callers should now call `Q::update_component_access(state` instead of `state.update_component_access` (and `update_archetype_component_access` respectively) - Any custom world query impls should move the functions from the `FetchState` impl to `WorldQuery` impl `WorldQuery` has been made an `unsafe trait`, `FetchState` has been made a safe `trait`. (I think this is how it should have always been, but regardless this is _definitely_ necessary now that the two functions have been moved to `WorldQuery`) - If you have a custom `FetchState` impl make it a normal `impl` instead of `unsafe impl` - If you have a custom `WorldQuery` impl make it an `unsafe impl`, if your code was sound before it is going to still be sound
1 parent 4050c8a commit 407c080

File tree

9 files changed

+441
-479
lines changed

9 files changed

+441
-479
lines changed

crates/bevy_ecs/macros/src/fetch.rs

Lines changed: 73 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,11 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream {
9090
user_generics_with_world.split_for_impl();
9191

9292
let struct_name = ast.ident.clone();
93+
let read_only_struct_name = if fetch_struct_attributes.is_mutable {
94+
Ident::new(&format!("{}ReadOnly", struct_name), Span::call_site())
95+
} else {
96+
struct_name.clone()
97+
};
9398

9499
let item_struct_name = Ident::new(&format!("{}Item", struct_name), Span::call_site());
95100
let read_only_item_struct_name = if fetch_struct_attributes.is_mutable {
@@ -178,7 +183,8 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream {
178183
#(#ignored_field_idents: #ignored_field_types,)*
179184
}
180185

181-
impl #user_impl_generics_with_world #path::query::Fetch<'__w>
186+
// SAFETY: `update_component_access` and `update_archetype_component_access` are called on every field
187+
unsafe impl #user_impl_generics_with_world #path::query::Fetch<'__w>
182188
for #fetch_struct_name #user_ty_generics_with_world #user_where_clauses_with_world {
183189

184190
type Item = #item_struct_name #user_ty_generics_with_world;
@@ -248,6 +254,17 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream {
248254
unsafe fn archetype_filter_fetch(&mut self, _archetype_index: usize) -> bool {
249255
true #(&& self.#field_idents.archetype_filter_fetch(_archetype_index))*
250256
}
257+
258+
fn update_component_access(state: &Self::State, _access: &mut #path::query::FilteredAccess<#path::component::ComponentId>) {
259+
#( #path::query::#fetch_type_alias::<'static, #field_types> :: update_component_access(&state.#field_idents, _access); )*
260+
}
261+
262+
fn update_archetype_component_access(state: &Self::State, _archetype: &#path::archetype::Archetype, _access: &mut #path::query::Access<#path::archetype::ArchetypeComponentId>) {
263+
#(
264+
#path::query::#fetch_type_alias::<'static, #field_types>
265+
:: update_archetype_component_access(&state.#field_idents, _archetype, _access);
266+
)*
267+
}
251268
}
252269
}
253270
};
@@ -262,23 +279,14 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream {
262279
#(#ignored_field_idents: #ignored_field_types,)*
263280
}
264281

265-
// SAFETY: `update_component_access` and `update_archetype_component_access` are called for each item in the struct
266-
unsafe impl #user_impl_generics #path::query::FetchState for #state_struct_name #user_ty_generics #user_where_clauses {
282+
impl #user_impl_generics #path::query::FetchState for #state_struct_name #user_ty_generics #user_where_clauses {
267283
fn init(world: &mut #path::world::World) -> Self {
268284
#state_struct_name {
269285
#(#field_idents: <<#field_types as #path::query::WorldQuery>::State as #path::query::FetchState>::init(world),)*
270286
#(#ignored_field_idents: Default::default(),)*
271287
}
272288
}
273289

274-
fn update_component_access(&self, _access: &mut #path::query::FilteredAccess<#path::component::ComponentId>) {
275-
#(self.#field_idents.update_component_access(_access);)*
276-
}
277-
278-
fn update_archetype_component_access(&self, _archetype: &#path::archetype::Archetype, _access: &mut #path::query::Access<#path::archetype::ArchetypeComponentId>) {
279-
#(self.#field_idents.update_archetype_component_access(_archetype, _access);)*
280-
}
281-
282290
fn matches_component_set(&self, _set_contains_id: &impl Fn(#path::component::ComponentId) -> bool) -> bool {
283291
true #(&& self.#field_idents.matches_component_set(_set_contains_id))*
284292

@@ -290,29 +298,66 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream {
290298
impl_fetch(
291299
true,
292300
read_only_fetch_struct_name.clone(),
293-
read_only_item_struct_name,
301+
read_only_item_struct_name.clone(),
294302
)
295303
} else {
296304
quote! {}
297305
};
298306

307+
let read_only_world_query_impl = if fetch_struct_attributes.is_mutable {
308+
quote! {
309+
#[automatically_derived]
310+
#visibility struct #read_only_struct_name #user_impl_generics #user_where_clauses {
311+
#( #field_idents: < #field_types as #path::query::WorldQuery >::ReadOnly, )*
312+
#(#(#ignored_field_attrs)* #ignored_field_visibilities #ignored_field_idents: #ignored_field_types,)*
313+
}
314+
315+
// SAFETY: `ROQueryFetch<Self>` is the same as `QueryFetch<Self>`
316+
unsafe impl #user_impl_generics #path::query::WorldQuery for #read_only_struct_name #user_ty_generics #user_where_clauses {
317+
type ReadOnly = Self;
318+
type State = #state_struct_name #user_ty_generics;
319+
320+
fn shrink<'__wlong: '__wshort, '__wshort>(item: #path::query::#item_type_alias<'__wlong, Self>)
321+
-> #path::query::#item_type_alias<'__wshort, Self> {
322+
#read_only_item_struct_name {
323+
#(
324+
#field_idents : <
325+
< #field_types as #path::query::WorldQuery >::ReadOnly as #path::query::WorldQuery
326+
> :: shrink( item.#field_idents ),
327+
)*
328+
#(
329+
#ignored_field_idents: item.#ignored_field_idents,
330+
)*
331+
}
332+
}
333+
}
334+
335+
impl #user_impl_generics_with_world #path::query::WorldQueryGats<'__w> for #read_only_struct_name #user_ty_generics #user_where_clauses {
336+
type Fetch = #read_only_fetch_struct_name #user_ty_generics_with_world;
337+
type _State = #state_struct_name #user_ty_generics;
338+
}
339+
}
340+
} else {
341+
quote! {}
342+
};
343+
299344
let read_only_asserts = if fetch_struct_attributes.is_mutable {
300345
quote! {
301-
// Double-check that the data fetched by `ROQueryFetch` is read-only.
302-
// This is technically unnecessary as `<_ as WorldQueryGats<'world>>::ReadOnlyFetch: ReadOnlyFetch`
303-
// but to protect against future mistakes we assert the assoc type implements `ReadOnlyFetch` anyway
304-
#( assert_readonly::<#path::query::ROQueryFetch<'__w, #field_types>>(); )*
346+
// Double-check that the data fetched by `<_ as WorldQuery>::ReadOnly` is read-only.
347+
// This is technically unnecessary as `<_ as WorldQuery>::ReadOnly: ReadOnlyWorldQuery`
348+
// but to protect against future mistakes we assert the assoc type implements `ReadOnlyWorldQuery` anyway
349+
#( assert_readonly::< < #field_types as #path::query::WorldQuery > :: ReadOnly >(); )*
305350
}
306351
} else {
307352
quote! {
308-
// Statically checks that the safety guarantee of `ReadOnlyFetch` for `$fetch_struct_name` actually holds true.
309-
// We need this to make sure that we don't compile `ReadOnlyFetch` if our struct contains nested `WorldQuery`
353+
// Statically checks that the safety guarantee of `ReadOnlyWorldQuery` for `$fetch_struct_name` actually holds true.
354+
// We need this to make sure that we don't compile `ReadOnlyWorldQuery` if our struct contains nested `WorldQuery`
310355
// members that don't implement it. I.e.:
311356
// ```
312357
// #[derive(WorldQuery)]
313358
// pub struct Foo { a: &'static mut MyComponent }
314359
// ```
315-
#( assert_readonly::<#path::query::QueryFetch<'__w, #field_types>>(); )*
360+
#( assert_readonly::<#field_types>(); )*
316361
}
317362
};
318363

@@ -323,7 +368,12 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream {
323368

324369
#read_only_fetch_impl
325370

326-
impl #user_impl_generics #path::query::WorldQuery for #struct_name #user_ty_generics #user_where_clauses {
371+
#read_only_world_query_impl
372+
373+
// SAFETY: if the worldquery is mutable this defers to soundness of the `#field_types: WorldQuery` impl, otherwise
374+
// if the world query is immutable then `#read_only_struct_name #user_ty_generics` is the same type as `#struct_name #user_ty_generics`
375+
unsafe impl #user_impl_generics #path::query::WorldQuery for #struct_name #user_ty_generics #user_where_clauses {
376+
type ReadOnly = #read_only_struct_name #user_ty_generics;
327377
type State = #state_struct_name #user_ty_generics;
328378
fn shrink<'__wlong: '__wshort, '__wshort>(item: #path::query::#item_type_alias<'__wlong, Self>)
329379
-> #path::query::#item_type_alias<'__wshort, Self> {
@@ -340,19 +390,18 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream {
340390

341391
impl #user_impl_generics_with_world #path::query::WorldQueryGats<'__w> for #struct_name #user_ty_generics #user_where_clauses {
342392
type Fetch = #fetch_struct_name #user_ty_generics_with_world;
343-
type ReadOnlyFetch = #read_only_fetch_struct_name #user_ty_generics_with_world;
344393
type _State = #state_struct_name #user_ty_generics;
345394
}
346395

347396
/// SAFETY: each item in the struct is read only
348-
unsafe impl #user_impl_generics_with_world #path::query::ReadOnlyFetch
349-
for #read_only_fetch_struct_name #user_ty_generics_with_world #user_where_clauses_with_world {}
397+
unsafe impl #user_impl_generics #path::query::ReadOnlyWorldQuery
398+
for #read_only_struct_name #user_ty_generics #user_where_clauses {}
350399

351400
#[allow(dead_code)]
352401
const _: () = {
353402
fn assert_readonly<T>()
354403
where
355-
T: #path::query::ReadOnlyFetch,
404+
T: #path::query::ReadOnlyWorldQuery,
356405
{
357406
}
358407

0 commit comments

Comments
 (0)