From 1c86cb5d9c59424643aafea51c82d18b71f2ea7c Mon Sep 17 00:00:00 2001 From: Chris Russell <8494645+chescock@users.noreply.github.com> Date: Mon, 9 Dec 2024 22:23:26 -0500 Subject: [PATCH] More complete documentation of valid query transmutes (#16691) # Objective The documentation for `Query::transmute_lens` lists some allowed transmutes, but the list is incomplete. ## Solution Document the underlying rules for what transmutes are allowed. Add a longer list of examples. Write them as doc tests to ensure that those examples are actually allowed. I'm assuming that anything that can be done today is intended to be supported! If any of these examples are things we plan to prohibit in the future then we can add some warnings to that effect. --- crates/bevy_ecs/src/system/query.rs | 109 +++++++++++++++++++++++++--- 1 file changed, 99 insertions(+), 10 deletions(-) diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index 26440c0c64..b36be3fee7 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -1335,7 +1335,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// /// ## Panics /// - /// This will panic if `NewD` is not a subset of the original fetch `Q` + /// This will panic if `NewD` is not a subset of the original fetch `D` /// /// ## Example /// @@ -1376,19 +1376,108 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// /// ## Allowed Transmutes /// - /// Besides removing parameters from the query, you can also - /// make limited changes to the types of parameters. + /// Besides removing parameters from the query, + /// you can also make limited changes to the types of parameters. + /// The new query must have a subset of the *read*, *write*, and *required* access of the original query. /// - /// * Can always add/remove [`Entity`] - /// * Can always add/remove [`EntityLocation`] - /// * Can always add/remove [`&Archetype`] - /// * `Ref` <-> `&T` - /// * `&mut T` -> `&T` - /// * `&mut T` -> `Ref` - /// * [`EntityMut`](crate::world::EntityMut) -> [`EntityRef`](crate::world::EntityRef) + /// * `&mut T` and [`Mut`](crate::change_detection::Mut) have read, write, and required access to `T` + /// * `&T` and [`Ref`](crate::change_detection::Ref) have read and required access to `T` + /// * [`Option`] and [`AnyOf<(D, ...)>`](crate::query::AnyOf) have the read and write access of the subqueries, but no required access + /// * Tuples of query data and `#[derive(QueryData)]` structs have the union of the access of their subqueries + /// * [`EntityMut`](crate::world::EntityMut) has read and write access to all components, but no required access + /// * [`EntityRef`](crate::world::EntityRef) has read access to all components, but no required access + /// * [`Entity`], [`EntityLocation`], [`&Archetype`], [`Has`], and [`PhantomData`] have no access at all, + /// so can be added to any query + /// * [`FilteredEntityRef`](crate::world::FilteredEntityRef) and [`FilteredEntityMut`](crate::world::FilteredEntityMut) + /// have access determined by the [`QueryBuilder`](crate::query::QueryBuilder) used to construct them. + /// Any query can be transmuted to them, and they will receive the access of the source query, + /// but only if they are the top-level query and not nested + /// * [`Added`](crate::query::Added) and [`Changed`](crate::query::Changed) filters have read and required access to `T` + /// * [`With`](crate::query::With) and [`Without`](crate::query::Without) filters have no access at all, + /// so can be added to any query + /// * Tuples of query filters and `#[derive(QueryFilter)]` structs have the union of the access of their subqueries + /// * [`Or<(F, ...)>`](crate::query::Or) filters have the read access of the subqueries, but no required access + /// + /// ### Examples of valid transmutes + /// + /// ```rust + /// # use bevy_ecs::{ + /// # prelude::*, + /// # archetype::Archetype, + /// # entity::EntityLocation, + /// # query::{QueryData, QueryFilter}, + /// # world::{FilteredEntityMut, FilteredEntityRef}, + /// # }; + /// # use std::marker::PhantomData; + /// # + /// # fn assert_valid_transmute() { + /// # assert_valid_transmute_filtered::(); + /// # } + /// # + /// # fn assert_valid_transmute_filtered() { + /// # let mut world = World::new(); + /// # // Make sure all components in the new query are initialized + /// # let state = world.query_filtered::(); + /// # let state = world.query_filtered::(); + /// # state.transmute_filtered::(&world); + /// # } + /// # + /// # #[derive(Component)] + /// # struct T; + /// # + /// # #[derive(Component)] + /// # struct U; + /// # + /// # #[derive(Component)] + /// # struct V; + /// # + /// // `&mut T` and `Mut` access the same data and can be transmuted to each other, + /// // `&T` and `Ref` access the same data and can be transmuted to each other, + /// // and mutable versions can be transmuted to read-only versions + /// assert_valid_transmute::<&mut T, &T>(); + /// assert_valid_transmute::<&mut T, Mut>(); + /// assert_valid_transmute::, &mut T>(); + /// assert_valid_transmute::<&T, Ref>(); + /// assert_valid_transmute::, &T>(); + /// + /// // The structure can be rearranged, or subqueries dropped + /// assert_valid_transmute::<(&T, &U), &T>(); + /// assert_valid_transmute::<((&T, &U), &V), (&T, (&U, &V))>(); + /// assert_valid_transmute::, (Option<&T>, Option<&U>)>(); + /// + /// // Queries with no access can be freely added + /// assert_valid_transmute::< + /// &T, + /// (&T, Entity, EntityLocation, &Archetype, Has, PhantomData), + /// >(); + /// + /// // Required access can be transmuted to optional, + /// // and optional access can be transmuted to other optional access + /// assert_valid_transmute::<&T, Option<&T>>(); + /// assert_valid_transmute::, Option<&T>>(); + /// // Note that removing subqueries from `AnyOf` will result + /// // in an `AnyOf` where all subqueries can yield `None`! + /// assert_valid_transmute::, AnyOf<(&T, &U)>>(); + /// assert_valid_transmute::>(); + /// + /// // Anything can be transmuted to `FilteredEntityRef` or `FilteredEntityMut` + /// // This will create a `FilteredEntityMut` that only has read access to `T` + /// assert_valid_transmute::<&T, FilteredEntityMut>(); + /// // This transmute will succeed, but the `FilteredEntityMut` will have no access! + /// // It must be the top-level query to be given access, but here it is nested in a tuple. + /// assert_valid_transmute::<&T, (Entity, FilteredEntityMut)>(); + /// + /// // `Added` and `Changed` filters have the same access as `&T` data + /// // Remember that they are only evaluated on the transmuted query, not the original query! + /// assert_valid_transmute_filtered::, &T, ()>(); + /// assert_valid_transmute_filtered::<&mut T, (), &T, Added>(); + /// // Nested inside of an `Or` filter, they have the same access as `Option<&T>`. + /// assert_valid_transmute_filtered::, (), Entity, Or<(Changed, With)>>(); + /// ``` /// /// [`EntityLocation`]: crate::entity::EntityLocation /// [`&Archetype`]: crate::archetype::Archetype + /// [`Has`]: crate::query::Has #[track_caller] pub fn transmute_lens(&mut self) -> QueryLens<'_, NewD> { self.transmute_lens_filtered::()