Implement WorldQuery and QueryData on Mut. (#13338)

# Objective

Provides a `WorldQuery` implementation on `Mut<T>` that forwards to the
implementation on `&mut T`, and give users a way to opt-in to change
detection in auto-generated `QueryData::ReadOnly` types.

Fixes #13329.

## Solution

I implemented `WorldQuery` on `Mut<'w, T>` as a forwarding
implementation to `&mut T`, setting the `QueryData::ReadOnly` associated
type to `Ref<'w, T>`. This provides users the ability to explicitly
opt-in to change detection in the read-only forms of queries.

## Testing

A documentation test was added to `Mut` showcasing the new
functionality.

---

## Changelog

### Added

- Added an implementation of `WorldQuery` and `QueryData` on
`bevy_ecs::change_detection::Mut`.
This commit is contained in:
Jamie Ridding 2024-05-14 13:38:31 +01:00 committed by GitHub
parent 6482a036cb
commit dcf24dfd6b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 160 additions and 0 deletions

View file

@ -728,6 +728,65 @@ change_detection_impl!(Ref<'w, T>, T,);
impl_debug!(Ref<'w, T>,);
/// Unique mutable borrow of an entity's component or of a resource.
///
/// This can be used in queries to opt into change detection on both their mutable and immutable forms, as opposed to
/// `&mut T`, which only provides access to change detection while in its mutable form:
///
/// ```rust
/// # use bevy_ecs::prelude::*;
/// # use bevy_ecs::query::QueryData;
/// #
/// #[derive(Component, Clone)]
/// struct Name(String);
///
/// #[derive(Component, Clone, Copy)]
/// struct Health(f32);
///
/// #[derive(Component, Clone, Copy)]
/// struct Position {
/// x: f32,
/// y: f32,
/// };
///
/// #[derive(Component, Clone, Copy)]
/// struct Player {
/// id: usize,
/// };
///
/// #[derive(QueryData)]
/// #[query_data(mutable)]
/// struct PlayerQuery {
/// id: &'static Player,
///
/// // Reacting to `PlayerName` changes is expensive, so we need to enable change detection when reading it.
/// name: Mut<'static, Name>,
///
/// health: &'static mut Health,
/// position: &'static mut Position,
/// }
///
/// fn update_player_avatars(players_query: Query<PlayerQuery>) {
/// // The item returned by the iterator is of type `PlayerQueryReadOnlyItem`.
/// for player in players_query.iter() {
/// if player.name.is_changed() {
/// // Update the player's name. This clones a String, and so is more expensive.
/// update_player_name(player.id, player.name.clone());
/// }
///
/// // Update the health bar.
/// update_player_health(player.id, *player.health);
///
/// // Update the player's position.
/// update_player_position(player.id, *player.position);
/// }
/// }
///
/// # bevy_ecs::system::assert_is_system(update_player_avatars);
///
/// # fn update_player_name(player: &Player, new_name: Name) {}
/// # fn update_player_health(player: &Player, new_health: Health) {}
/// # fn update_player_position(player: &Player, new_position: Position) {}
/// ```
pub struct Mut<'w, T: ?Sized> {
pub(crate) value: &'w mut T,
pub(crate) ticks: TicksMut<'w>,

View file

@ -1378,6 +1378,107 @@ unsafe impl<'__w, T: Component> QueryData for &'__w mut T {
type ReadOnly = &'__w T;
}
/// When `Mut<T>` is used in a query, it will be converted to `Ref<T>` when transformed into its read-only form, providing access to change detection methods.
///
/// By contrast `&mut T` will result in a `Mut<T>` item in mutable form to record mutations, but result in a bare `&T` in read-only form.
///
/// SAFETY:
/// `fetch` accesses a single component mutably.
/// This is sound because `update_component_access` and `update_archetype_component_access` add write access for that component and panic when appropriate.
/// `update_component_access` adds a `With` filter for a component.
/// This is sound because `matches_component_set` returns whether the set contains that component.
unsafe impl<'__w, T: Component> WorldQuery for Mut<'__w, T> {
type Item<'w> = Mut<'w, T>;
type Fetch<'w> = WriteFetch<'w, T>;
type State = ComponentId;
// Forwarded to `&mut T`
fn shrink<'wlong: 'wshort, 'wshort>(item: Mut<'wlong, T>) -> Mut<'wshort, T> {
<&mut T as WorldQuery>::shrink(item)
}
#[inline]
// Forwarded to `&mut T`
unsafe fn init_fetch<'w>(
world: UnsafeWorldCell<'w>,
state: &ComponentId,
last_run: Tick,
this_run: Tick,
) -> WriteFetch<'w, T> {
<&mut T as WorldQuery>::init_fetch(world, state, last_run, this_run)
}
// Forwarded to `&mut T`
const IS_DENSE: bool = <&mut T as WorldQuery>::IS_DENSE;
#[inline]
// Forwarded to `&mut T`
unsafe fn set_archetype<'w>(
fetch: &mut WriteFetch<'w, T>,
state: &ComponentId,
archetype: &'w Archetype,
table: &'w Table,
) {
<&mut T as WorldQuery>::set_archetype(fetch, state, archetype, table);
}
#[inline]
// Forwarded to `&mut T`
unsafe fn set_table<'w>(fetch: &mut WriteFetch<'w, T>, state: &ComponentId, table: &'w Table) {
<&mut T as WorldQuery>::set_table(fetch, state, table);
}
#[inline(always)]
// Forwarded to `&mut T`
unsafe fn fetch<'w>(
// Rust complains about lifetime bounds not matching the trait if I directly use `WriteFetch<'w, T>` right here.
// But it complains nowhere else in the entire trait implementation.
fetch: &mut Self::Fetch<'w>,
entity: Entity,
table_row: TableRow,
) -> Mut<'w, T> {
<&mut T as WorldQuery>::fetch(fetch, entity, table_row)
}
// NOT forwarded to `&mut T`
fn update_component_access(
&component_id: &ComponentId,
access: &mut FilteredAccess<ComponentId>,
) {
// Update component access here instead of in `<&mut T as WorldQuery>` to avoid erroneously referencing
// `&mut T` in error message.
assert!(
!access.access().has_read(component_id),
"Mut<{}> conflicts with a previous access in this query. Mutable component access mut be unique.",
std::any::type_name::<T>(),
);
access.add_write(component_id);
}
// Forwarded to `&mut T`
fn init_state(world: &mut World) -> ComponentId {
<&mut T as WorldQuery>::init_state(world)
}
// Forwarded to `&mut T`
fn get_state(components: &Components) -> Option<ComponentId> {
<&mut T as WorldQuery>::get_state(components)
}
// Forwarded to `&mut T`
fn matches_component_set(
state: &ComponentId,
set_contains_id: &impl Fn(ComponentId) -> bool,
) -> bool {
<&mut T as WorldQuery>::matches_component_set(state, set_contains_id)
}
}
// SAFETY: access of `Ref<T>` is a subset of `Mut<T>`
unsafe impl<'__w, T: Component> QueryData for Mut<'__w, T> {
type ReadOnly = Ref<'__w, T>;
}
#[doc(hidden)]
pub struct OptionFetch<'w, T: WorldQuery> {
fetch: T::Fetch<'w>,