mirror of
https://github.com/bevyengine/bevy
synced 2025-02-16 22:18:33 +00:00
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:
parent
6482a036cb
commit
dcf24dfd6b
2 changed files with 160 additions and 0 deletions
|
@ -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>,
|
||||
|
|
|
@ -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>,
|
||||
|
|
Loading…
Add table
Reference in a new issue