Skip to content

Commit

Permalink
Generalize component reflection to operate on FilteredEntityRef and…
Browse files Browse the repository at this point in the history
… `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.
  • Loading branch information
pcwalton authored May 28, 2024
1 parent d98d6d8 commit 05288ff
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 9 deletions.
21 changes: 12 additions & 9 deletions crates/bevy_ecs/src/reflect/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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())
}
Expand Down
13 changes: 13 additions & 0 deletions crates/bevy_ecs/src/world/entity_ref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down

0 comments on commit 05288ff

Please sign in to comment.