Generalize component reflection to operate on FilteredEntityRef and FilteredEntityMut, not EntityRef and EntityMut. (#13549)

Currently, either an `EntityRef` or `EntityMut` is required in order to
reflect a component on an entity. This can, however, be generalized to
`FilteredEntityRef` and `FilteredEntityMut`, which are versions of
`EntityRef` and `EntityMut` that restrict the components that can be
accessed. This is useful because dynamic queries yield
`FilteredEntityRef` and `FilteredEntityMut` rows when iterated over.

This commit changes `ReflectComponent::contains()`,
`ReflectComponent::reflect()`, and `ReflectComponent::reflect_mut()` to
take an `Into<FilteredEntityRef>` (in the case of `contains()` and
`reflect()`) and `Into<FilteredEntityMut>` (in the case of
`reflect_mut()`). Fortunately, `EntityRef` and `EntityMut` already
implement the corresponding trait, so nothing else has to be done to the
public API. Note that in order to implement
`ReflectComponent::reflect_mut()` properly, an additional method
`FilteredEntityMut::into_mut()` was required, to match the one on
`EntityMut`.

I ran into this when attempting to implement `QUERY` in the Bevy Remote
Protocol when trying to iterate over rows of dynamic queries and fetch
the associated components without unsafe code. There were other
potential ways to work around this problem, but they required either
reimplementing the query logic myself instead of using regular Bevy
queries or storing entity IDs and then issuing another query to fetch
the associated `EntityRef`. Both of these seemed worse than just
improving the `reflect()` function.

## Migration Guide

* `ReflectComponent::contains`, `ReflectComponent::reflect`, and
`ReflectComponent::reflect_mut` now take `FilteredEntityRef` (in the
case of `contains()` and `reflect()`) and `FilteredEntityMut` (in the
case of `reflect_mut()`) parameters. `FilteredEntityRef` and
`FilteredEntityMut` have very similar APIs to `EntityRef` and
`EntityMut` respectively, but optionally restrict the components that
can be accessed.
This commit is contained in:
Patrick Walton 2024-05-28 07:02:09 -07:00 committed by GitHub
parent d98d6d8d00
commit 05288ffa32
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 25 additions and 9 deletions

View file

@ -62,7 +62,10 @@ use crate::{
change_detection::Mut,
component::Component,
entity::Entity,
world::{unsafe_world_cell::UnsafeEntityCell, EntityMut, EntityRef, EntityWorldMut, World},
world::{
unsafe_world_cell::UnsafeEntityCell, EntityMut, EntityWorldMut, FilteredEntityMut,
FilteredEntityRef, World,
},
};
use bevy_reflect::{FromReflect, FromType, Reflect, TypeRegistry};
@ -104,11 +107,11 @@ pub struct ReflectComponentFns {
/// Function pointer implementing [`ReflectComponent::remove()`].
pub remove: fn(&mut EntityWorldMut),
/// Function pointer implementing [`ReflectComponent::contains()`].
pub contains: fn(EntityRef) -> bool,
pub contains: fn(FilteredEntityRef) -> bool,
/// Function pointer implementing [`ReflectComponent::reflect()`].
pub reflect: fn(EntityRef) -> Option<&dyn Reflect>,
pub reflect: fn(FilteredEntityRef) -> Option<&dyn Reflect>,
/// Function pointer implementing [`ReflectComponent::reflect_mut()`].
pub reflect_mut: fn(EntityMut) -> Option<Mut<dyn Reflect>>,
pub reflect_mut: fn(FilteredEntityMut) -> Option<Mut<dyn Reflect>>,
/// Function pointer implementing [`ReflectComponent::reflect_unchecked_mut()`].
///
/// # Safety
@ -165,19 +168,19 @@ impl ReflectComponent {
}
/// Returns whether entity contains this [`Component`]
pub fn contains(&self, entity: EntityRef) -> bool {
(self.0.contains)(entity)
pub fn contains<'a>(&self, entity: impl Into<FilteredEntityRef<'a>>) -> bool {
(self.0.contains)(entity.into())
}
/// Gets the value of this [`Component`] type from the entity as a reflected reference.
pub fn reflect<'a>(&self, entity: EntityRef<'a>) -> Option<&'a dyn Reflect> {
(self.0.reflect)(entity)
pub fn reflect<'a>(&self, entity: impl Into<FilteredEntityRef<'a>>) -> Option<&'a dyn Reflect> {
(self.0.reflect)(entity.into())
}
/// Gets the value of this [`Component`] type from the entity as a mutable reflected reference.
pub fn reflect_mut<'a>(
&self,
entity: impl Into<EntityMut<'a>>,
entity: impl Into<FilteredEntityMut<'a>>,
) -> Option<Mut<'a, dyn Reflect>> {
(self.0.reflect_mut)(entity.into())
}

View file

@ -2102,6 +2102,19 @@ impl<'w> FilteredEntityMut<'w> {
.flatten()
}
/// Consumes self and gets mutable access to the component of type `T`
/// with the world `'w` lifetime for the current entity.
/// Returns `None` if the entity does not have a component of type `T`.
#[inline]
pub fn into_mut<T: Component>(self) -> Option<Mut<'w, T>> {
let id = self.entity.world().components().get_id(TypeId::of::<T>())?;
self.access
.has_write(id)
// SAFETY: We have write access
.then(|| unsafe { self.entity.get_mut() })
.flatten()
}
/// Retrieves the change ticks for the given component. This can be useful for implementing change
/// detection in custom runtimes.
#[inline]