From 267a0d003cff08fb8f7c2de391f36fd4661fd7c3 Mon Sep 17 00:00:00 2001 From: Andreas Monitzer Date: Wed, 12 Feb 2025 19:34:35 +0100 Subject: [PATCH] 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). --- crates/bevy_ecs/src/world/entity_ref.rs | 195 ++++++++++++++++++++++++ 1 file changed, 195 insertions(+) diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index ef043d0acc..17367e56df 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -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<'_> { fn eq(&self, other: &Self) -> bool { 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<'_> { fn eq(&self, other: &Self) -> bool { self.entity() == other.entity() @@ -3737,6 +3773,93 @@ where pub fn spawned_by(&self) -> MaybeLocation { 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> { + let components = self.entity.world().components(); + (!bundle_contains_component::(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(&self) -> bool { + self.contains_type_id(TypeId::of::()) + } + + /// 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(&self) -> Option { + let component_id = self.entity.world().components().get_id(TypeId::of::())?; + let components = self.entity.world().components(); + (!bundle_contains_component::(components, component_id)) + .then(|| { + // SAFETY: We have read access + unsafe { self.entity.get_change_ticks::() } + }) + .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 { + let components = self.entity.world().components(); + (!bundle_contains_component::(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> @@ -3894,6 +4017,78 @@ where pub fn spawned_by(&self) -> MaybeLocation { 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(&self) -> bool { + self.contains_type_id(TypeId::of::()) + } + + /// 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> { + 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( + &mut self, + component_id: ComponentId, + ) -> Option> { + let components = self.entity.world().components(); + (!bundle_contains_component::(components, component_id)) + .then(|| { + // SAFETY: We have write access + unsafe { self.entity.get_mut_by_id(component_id).ok() } + }) + .flatten() + } } impl PartialEq for EntityMutExcept<'_, B> {