Add ComponentId-taking functions to Entity{Ref,Mut}Except to mirror FilteredEntity{Ref,Mut} (#17800)

# Objective

Related to #17784. The ticket is actually about just getting rid of
`Entity{Ref,Mut}Except` in favor of `FilteredEntity{Ref,Mut}`, but I got
told the unification of Entity types is a bigger endeavor that has been
going on for a while now (as the "Pointing Fingers" working group) and I
should just add the functions I actually need in the meantime.

## Solution

This PR adds all of the functions necessary to access components by
TypeId or ComponentId instead of static types.

## Testing

> Did you test these changes? If so, how?

Haven't tested it yet, but the changes are mostly copy/paste from other
implementations in the same file, since there is a lot of duplicated
functionality there.

## Not a Migration Guide

There shouldn't be any breaking changes, it's just a few new functions
on existing types.

I had to shuffle around the lifetimes in `From<&EntityMutExcept<'a, B>>
for EntityRefExcept<'a, B>` (originally it was `From<&'a
EntityMutExcept<'_, B>> for EntityRefExcept<'_, B>`) to make the borrow
checker happy, but I don't think that this should have an impact on user
code (correct me if I'm wrong).
This commit is contained in:
Andreas Monitzer 2025-02-12 19:34:35 +01:00 committed by GitHub
parent 2fd4cc4937
commit 267a0d003c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -3287,6 +3287,24 @@ impl<'a> From<&'a EntityWorldMut<'_>> for FilteredEntityRef<'a> {
} }
} }
impl<'a, B: Bundle> From<&'a EntityRefExcept<'_, B>> for FilteredEntityRef<'a> {
fn from(value: &'a EntityRefExcept<'_, B>) -> Self {
// SAFETY:
// - The FilteredEntityRef has the same component access as the given EntityRefExcept.
unsafe {
let mut access = Access::default();
access.read_all();
let components = value.entity.world().components();
B::get_component_ids(components, &mut |maybe_id| {
if let Some(id) = maybe_id {
access.remove_component_read(id);
}
});
FilteredEntityRef::new(value.entity, access)
}
}
}
impl PartialEq for FilteredEntityRef<'_> { impl PartialEq for FilteredEntityRef<'_> {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.entity() == other.entity() self.entity() == other.entity()
@ -3612,6 +3630,24 @@ impl<'a> From<&'a mut EntityWorldMut<'_>> for FilteredEntityMut<'a> {
} }
} }
impl<'a, B: Bundle> From<&'a EntityMutExcept<'_, B>> for FilteredEntityMut<'a> {
fn from(value: &'a EntityMutExcept<'_, B>) -> Self {
// SAFETY:
// - The FilteredEntityMut has the same component access as the given EntityMutExcept.
unsafe {
let mut access = Access::default();
access.write_all();
let components = value.entity.world().components();
B::get_component_ids(components, &mut |maybe_id| {
if let Some(id) = maybe_id {
access.remove_component_read(id);
}
});
FilteredEntityMut::new(value.entity, access)
}
}
}
impl PartialEq for FilteredEntityMut<'_> { impl PartialEq for FilteredEntityMut<'_> {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.entity() == other.entity() self.entity() == other.entity()
@ -3737,6 +3773,93 @@ where
pub fn spawned_by(&self) -> MaybeLocation { pub fn spawned_by(&self) -> MaybeLocation {
self.entity.spawned_by() self.entity.spawned_by()
} }
/// Gets the component of the given [`ComponentId`] from the entity.
///
/// **You should prefer to use the typed API [`Self::get`] where possible and only
/// use this in cases where the actual component types are not known at
/// compile time.**
///
/// Unlike [`EntityRefExcept::get`], this returns a raw pointer to the component,
/// which is only valid while the [`EntityRefExcept`] is alive.
#[inline]
pub fn get_by_id(&self, component_id: ComponentId) -> Option<Ptr<'w>> {
let components = self.entity.world().components();
(!bundle_contains_component::<B>(components, component_id))
.then(|| {
// SAFETY: We have read access for this component
unsafe { self.entity.get_by_id(component_id) }
})
.flatten()
}
/// Returns `true` if the current entity has a component of type `T`.
/// Otherwise, this returns `false`.
///
/// ## Notes
///
/// If you do not know the concrete type of a component, consider using
/// [`Self::contains_id`] or [`Self::contains_type_id`].
#[inline]
pub fn contains<T: Component>(&self) -> bool {
self.contains_type_id(TypeId::of::<T>())
}
/// Returns `true` if the current entity has a component identified by `component_id`.
/// Otherwise, this returns false.
///
/// ## Notes
///
/// - If you know the concrete type of the component, you should prefer [`Self::contains`].
/// - If you know the component's [`TypeId`] but not its [`ComponentId`], consider using
/// [`Self::contains_type_id`].
#[inline]
pub fn contains_id(&self, component_id: ComponentId) -> bool {
self.entity.contains_id(component_id)
}
/// Returns `true` if the current entity has a component with the type identified by `type_id`.
/// Otherwise, this returns false.
///
/// ## Notes
///
/// - If you know the concrete type of the component, you should prefer [`Self::contains`].
/// - If you have a [`ComponentId`] instead of a [`TypeId`], consider using [`Self::contains_id`].
#[inline]
pub fn contains_type_id(&self, type_id: TypeId) -> bool {
self.entity.contains_type_id(type_id)
}
/// Retrieves the change ticks for the given component. This can be useful for implementing change
/// detection in custom runtimes.
#[inline]
pub fn get_change_ticks<T: Component>(&self) -> Option<ComponentTicks> {
let component_id = self.entity.world().components().get_id(TypeId::of::<T>())?;
let components = self.entity.world().components();
(!bundle_contains_component::<B>(components, component_id))
.then(|| {
// SAFETY: We have read access
unsafe { self.entity.get_change_ticks::<T>() }
})
.flatten()
}
/// Retrieves the change ticks for the given [`ComponentId`]. This can be useful for implementing change
/// detection in custom runtimes.
///
/// **You should prefer to use the typed API [`Self::get_change_ticks`] where possible and only
/// use this in cases where the actual component types are not known at
/// compile time.**
#[inline]
pub fn get_change_ticks_by_id(&self, component_id: ComponentId) -> Option<ComponentTicks> {
let components = self.entity.world().components();
(!bundle_contains_component::<B>(components, component_id))
.then(|| {
// SAFETY: We have read access
unsafe { self.entity.get_change_ticks_by_id(component_id) }
})
.flatten()
}
} }
impl<'a, B> From<&'a EntityMutExcept<'_, B>> for EntityRefExcept<'a, B> impl<'a, B> From<&'a EntityMutExcept<'_, B>> for EntityRefExcept<'a, B>
@ -3894,6 +4017,78 @@ where
pub fn spawned_by(&self) -> MaybeLocation { pub fn spawned_by(&self) -> MaybeLocation {
self.entity.spawned_by() self.entity.spawned_by()
} }
/// Returns `true` if the current entity has a component of type `T`.
/// Otherwise, this returns `false`.
///
/// ## Notes
///
/// If you do not know the concrete type of a component, consider using
/// [`Self::contains_id`] or [`Self::contains_type_id`].
#[inline]
pub fn contains<T: Component>(&self) -> bool {
self.contains_type_id(TypeId::of::<T>())
}
/// Returns `true` if the current entity has a component identified by `component_id`.
/// Otherwise, this returns false.
///
/// ## Notes
///
/// - If you know the concrete type of the component, you should prefer [`Self::contains`].
/// - If you know the component's [`TypeId`] but not its [`ComponentId`], consider using
/// [`Self::contains_type_id`].
#[inline]
pub fn contains_id(&self, component_id: ComponentId) -> bool {
self.entity.contains_id(component_id)
}
/// Returns `true` if the current entity has a component with the type identified by `type_id`.
/// Otherwise, this returns false.
///
/// ## Notes
///
/// - If you know the concrete type of the component, you should prefer [`Self::contains`].
/// - If you have a [`ComponentId`] instead of a [`TypeId`], consider using [`Self::contains_id`].
#[inline]
pub fn contains_type_id(&self, type_id: TypeId) -> bool {
self.entity.contains_type_id(type_id)
}
/// Gets the component of the given [`ComponentId`] from the entity.
///
/// **You should prefer to use the typed API [`Self::get`] where possible and only
/// use this in cases where the actual component types are not known at
/// compile time.**
///
/// Unlike [`EntityMutExcept::get`], this returns a raw pointer to the component,
/// which is only valid while the [`EntityMutExcept`] is alive.
#[inline]
pub fn get_by_id(&'w self, component_id: ComponentId) -> Option<Ptr<'w>> {
self.as_readonly().get_by_id(component_id)
}
/// Gets a [`MutUntyped`] of the component of the given [`ComponentId`] from the entity.
///
/// **You should prefer to use the typed API [`Self::get_mut`] where possible and only
/// use this in cases where the actual component types are not known at
/// compile time.**
///
/// Unlike [`EntityMutExcept::get_mut`], this returns a raw pointer to the component,
/// which is only valid while the [`EntityMutExcept`] is alive.
#[inline]
pub fn get_mut_by_id<F: DynamicComponentFetch>(
&mut self,
component_id: ComponentId,
) -> Option<MutUntyped<'_>> {
let components = self.entity.world().components();
(!bundle_contains_component::<B>(components, component_id))
.then(|| {
// SAFETY: We have write access
unsafe { self.entity.get_mut_by_id(component_id).ok() }
})
.flatten()
}
} }
impl<B: Bundle> PartialEq for EntityMutExcept<'_, B> { impl<B: Bundle> PartialEq for EntityMutExcept<'_, B> {