EntityRef/Mut get_components (immutable variants only) (#15089)

# Objective

Smaller scoped version of #13375 without the `_mut` variants which
currently have unsoundness issues.

## Solution

Same as #13375, but without the `_mut` variants.

## Testing

- The same test from #13375 is reused.

---

## Migration Guide

- Renamed `FilteredEntityRef::components` to
`FilteredEntityRef::accessed_components` and
`FilteredEntityMut::components` to
`FilteredEntityMut::accessed_components`.

---------

Co-authored-by: Carter Anderson <mcanders1@gmail.com>
Co-authored-by: Periwink <charlesbour@gmail.com>
This commit is contained in:
Christian Hughes 2024-09-09 11:29:44 -05:00 committed by GitHub
parent 245d03a78a
commit 79f6fcd1eb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 125 additions and 4 deletions

View file

@ -6,7 +6,7 @@ use crate::{
entity::{Entities, Entity, EntityLocation},
event::Event,
observer::{Observer, Observers},
query::Access,
query::{Access, ReadOnlyQueryData},
removal_detection::RemovedComponentEvents,
storage::Storages,
system::IntoObserverSystem,
@ -156,6 +156,22 @@ impl<'w> EntityRef<'w> {
// SAFETY: We have read-only access to all components of this entity.
unsafe { self.0.get_by_id(component_id) }
}
/// Returns read-only components for the current entity that match the query `Q`.
///
/// # Panics
///
/// If the entity does not have the components required by the query `Q`.
pub fn components<Q: ReadOnlyQueryData>(&self) -> Q::Item<'w> {
self.get_components::<Q>().expect(QUERY_MISMATCH_ERROR)
}
/// Returns read-only components for the current entity that match the query `Q`,
/// or `None` if the entity does not have the components required by the query `Q`.
pub fn get_components<Q: ReadOnlyQueryData>(&self) -> Option<Q::Item<'w>> {
// SAFETY: We have read-only access to all components of this entity.
unsafe { self.0.get_components::<Q>() }
}
}
impl<'w> From<EntityWorldMut<'w>> for EntityRef<'w> {
@ -351,6 +367,22 @@ impl<'w> EntityMut<'w> {
self.as_readonly().get()
}
/// Returns read-only components for the current entity that match the query `Q`.
///
/// # Panics
///
/// If the entity does not have the components required by the query `Q`.
pub fn components<Q: ReadOnlyQueryData>(&self) -> Q::Item<'_> {
self.get_components::<Q>().expect(QUERY_MISMATCH_ERROR)
}
/// Returns read-only components for the current entity that match the query `Q`,
/// or `None` if the entity does not have the components required by the query `Q`.
pub fn get_components<Q: ReadOnlyQueryData>(&self) -> Option<Q::Item<'_>> {
// SAFETY: We have read-only access to all components of this entity.
unsafe { self.0.get_components::<Q>() }
}
/// Consumes `self` and gets access to the component of type `T` with the
/// world `'w` lifetime for the current entity.
///
@ -648,6 +680,23 @@ impl<'w> EntityWorldMut<'w> {
EntityRef::from(self).get()
}
/// Returns read-only components for the current entity that match the query `Q`.
///
/// # Panics
///
/// If the entity does not have the components required by the query `Q`.
#[inline]
pub fn components<Q: ReadOnlyQueryData>(&self) -> Q::Item<'_> {
EntityRef::from(self).components::<Q>()
}
/// Returns read-only components for the current entity that match the query `Q`,
/// or `None` if the entity does not have the components required by the query `Q`.
#[inline]
pub fn get_components<Q: ReadOnlyQueryData>(&self) -> Option<Q::Item<'_>> {
EntityRef::from(self).get_components::<Q>()
}
/// Consumes `self` and gets 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`.
@ -1491,6 +1540,8 @@ unsafe fn trigger_on_replace_and_on_remove_hooks_and_observers(
}
}
const QUERY_MISMATCH_ERROR: &str = "Query does not match the current entity";
/// A view into a single entity and component in a world, which may either be vacant or occupied.
///
/// This `enum` can only be constructed from the [`entry`] method on [`EntityWorldMut`].
@ -1878,7 +1929,7 @@ impl<'w> FilteredEntityRef<'w> {
/// Returns an iterator over the component ids that are accessed by self.
#[inline]
pub fn components(&self) -> impl Iterator<Item = ComponentId> + '_ {
pub fn accessed_components(&self) -> impl Iterator<Item = ComponentId> + '_ {
self.access.component_reads_and_writes()
}
@ -2135,7 +2186,7 @@ impl<'w> FilteredEntityMut<'w> {
/// Returns an iterator over the component ids that are accessed by self.
#[inline]
pub fn components(&self) -> impl Iterator<Item = ComponentId> + '_ {
pub fn accessed_components(&self) -> impl Iterator<Item = ComponentId> + '_ {
self.access.component_reads_and_writes()
}
@ -3115,4 +3166,24 @@ mod tests {
assert!(e.get_mut_by_id(a_id).is_none());
assert!(e.get_change_ticks_by_id(a_id).is_none());
}
#[test]
fn get_components() {
#[derive(Component, PartialEq, Eq, Debug)]
struct X(usize);
#[derive(Component, PartialEq, Eq, Debug)]
struct Y(usize);
let mut world = World::default();
let e1 = world.spawn((X(7), Y(10))).id();
let e2 = world.spawn(X(8)).id();
let e3 = world.spawn_empty().id();
assert_eq!(
Some((&X(7), &Y(10))),
world.entity(e1).get_components::<(&X, &Y)>()
);
assert_eq!(None, world.entity(e2).get_components::<(&X, &Y)>());
assert_eq!(None, world.entity(e3).get_components::<(&X, &Y)>());
}
}

View file

@ -11,6 +11,7 @@ use crate::{
entity::{Entities, Entity, EntityLocation},
observer::Observers,
prelude::Component,
query::{DebugCheckedUnwrap, ReadOnlyQueryData},
removal_detection::RemovedComponentEvents,
storage::{Column, ComponentSparseSet, Storages},
system::{Res, Resource},
@ -882,6 +883,55 @@ impl<'w> UnsafeEntityCell<'w> {
})
}
}
/// Returns read-only components for the current entity that match the query `Q`,
/// or `None` if the entity does not have the components required by the query `Q`.
///
/// # Safety
/// It is the callers responsibility to ensure that
/// - the [`UnsafeEntityCell`] has permission to access the queried data immutably
/// - no mutable references to the queried data exist at the same time
pub(crate) unsafe fn get_components<Q: ReadOnlyQueryData>(&self) -> Option<Q::Item<'w>> {
// SAFETY: World is only used to access query data and initialize query state
let state = unsafe {
let world = self.world().world();
Q::get_state(world.components())?
};
let location = self.location();
// SAFETY: Location is guaranteed to exist
let archetype = unsafe {
self.world
.archetypes()
.get(location.archetype_id)
.debug_checked_unwrap()
};
if Q::matches_component_set(&state, &|id| archetype.contains(id)) {
// SAFETY: state was initialized above using the world passed into this function
let mut fetch = unsafe {
Q::init_fetch(
self.world,
&state,
self.world.last_change_tick(),
self.world.change_tick(),
)
};
// SAFETY: Table is guaranteed to exist
let table = unsafe {
self.world
.storages()
.tables
.get(location.table_id)
.debug_checked_unwrap()
};
// SAFETY: Archetype and table are from the same world used to initialize state and fetch.
// Table corresponds to archetype. State is the same state used to init fetch above.
unsafe { Q::set_archetype(&mut fetch, &state, archetype, table) }
// SAFETY: Called after set_archetype above. Entity and location are guaranteed to exist.
unsafe { Some(Q::fetch(&mut fetch, self.id(), location.table_row)) }
} else {
None
}
}
}
impl<'w> UnsafeEntityCell<'w> {

View file

@ -151,7 +151,7 @@ fn main() {
query.iter_mut(&mut world).for_each(|filtered_entity| {
let terms = filtered_entity
.components()
.accessed_components()
.map(|id| {
let ptr = filtered_entity.get_by_id(id).unwrap();
let info = component_info.get(&id).unwrap();