Have Entity(Ref|Mut)Except look up the component ids on initialization and store them in an Access that can be borrowed by each row.

This commit is contained in:
Chris Russell 2025-07-12 10:15:27 -04:00
parent af0cc64d98
commit 4bb6066a90
5 changed files with 109 additions and 81 deletions

View File

@ -408,10 +408,7 @@ impl<A: Animatable> AnimationCurveEvaluator for AnimatableCurveEvaluator<A> {
self.evaluator.push_blend_register(weight, graph_node)
}
fn commit<'a>(
&mut self,
mut entity: AnimationEntityMut<'a>,
) -> Result<(), AnimationEvaluationError> {
fn commit(&mut self, mut entity: AnimationEntityMut) -> Result<(), AnimationEvaluationError> {
let property = self.property.get_mut(&mut entity)?;
*property = self
.evaluator
@ -596,10 +593,7 @@ impl AnimationCurveEvaluator for WeightsCurveEvaluator {
Ok(())
}
fn commit<'a>(
&mut self,
mut entity: AnimationEntityMut<'a>,
) -> Result<(), AnimationEvaluationError> {
fn commit(&mut self, mut entity: AnimationEntityMut) -> Result<(), AnimationEvaluationError> {
if self.stack_morph_target_weights.is_empty() {
return Ok(());
}
@ -905,10 +899,7 @@ pub trait AnimationCurveEvaluator: Downcast + Send + Sync + 'static {
///
/// The property on the component must be overwritten with the value from
/// the stack, not blended with it.
fn commit<'a>(
&mut self,
entity: AnimationEntityMut<'a>,
) -> Result<(), AnimationEvaluationError>;
fn commit(&mut self, entity: AnimationEntityMut) -> Result<(), AnimationEvaluationError>;
}
impl_downcast!(AnimationCurveEvaluator);

View File

@ -1021,8 +1021,8 @@ pub fn advance_animations(
}
/// A type alias for [`EntityMutExcept`] as used in animation.
pub type AnimationEntityMut<'w> =
EntityMutExcept<'w, (AnimationTarget, AnimationPlayer, AnimationGraphHandle)>;
pub type AnimationEntityMut<'w, 's> =
EntityMutExcept<'w, 's, (AnimationTarget, AnimationPlayer, AnimationGraphHandle)>;
/// A system that modifies animation targets (e.g. bones in a skinned mesh)
/// according to the currently-playing animations.

View File

@ -14,7 +14,6 @@ use crate::{
use bevy_ptr::{ThinSlicePtr, UnsafeCellDeref};
use bevy_utils::prelude::DebugName;
use core::{cell::UnsafeCell, marker::PhantomData, panic::Location};
use smallvec::SmallVec;
use variadics_please::all_tuples;
/// Types that can be fetched from a [`World`] using a [`Query`].
@ -1160,12 +1159,12 @@ unsafe impl<'a, 'b> QueryData for FilteredEntityMut<'a, 'b> {
/// SAFETY: `EntityRefExcept` guards access to all components in the bundle `B`
/// and populates `Access` values so that queries that conflict with this access
/// are rejected.
unsafe impl<'a, B> WorldQuery for EntityRefExcept<'a, B>
unsafe impl<'a, 'b, B> WorldQuery for EntityRefExcept<'a, 'b, B>
where
B: Bundle,
{
type Fetch<'w> = EntityFetch<'w>;
type State = SmallVec<[ComponentId; 4]>;
type State = Access<ComponentId>;
fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {
fetch
@ -1197,15 +1196,9 @@ where
unsafe fn set_table<'w, 's>(_: &mut Self::Fetch<'w>, _: &'s Self::State, _: &'w Table) {}
fn update_component_access(
state: &Self::State,
my_access: &Self::State,
filtered_access: &mut FilteredAccess<ComponentId>,
) {
let mut my_access = Access::new();
my_access.read_all_components();
for id in state {
my_access.remove_component_read(*id);
}
let access = filtered_access.access_mut();
assert!(
access.is_compatible(&my_access),
@ -1216,17 +1209,29 @@ where
}
fn init_state(world: &mut World) -> Self::State {
Self::get_state(world.components()).unwrap()
let mut access = Access::new();
access.read_all_components();
B::component_ids(&mut world.components_registrator(), &mut |id| {
access.remove_component_read(id);
});
access
}
fn get_state(components: &Components) -> Option<Self::State> {
let mut ids = SmallVec::new();
let mut access = Access::new();
access.read_all_components();
B::get_component_ids(components, &mut |maybe_id| {
// If the component isn't registered, we don't have a `ComponentId`
// to use to exclude its access.
// Rather than fail, just try to take additional access.
// This is sound because access checks will run on the resulting access.
// Since the component isn't registered, there are no entities with that
// component, and the extra access will usually have no effect.
if let Some(id) = maybe_id {
ids.push(id);
access.remove_component_read(id);
}
});
Some(ids)
Some(access)
}
fn matches_component_set(_: &Self::State, _: &impl Fn(ComponentId) -> bool) -> bool {
@ -1235,13 +1240,13 @@ where
}
/// SAFETY: `Self` is the same as `Self::ReadOnly`.
unsafe impl<'a, B> QueryData for EntityRefExcept<'a, B>
unsafe impl<'a, 'b, B> QueryData for EntityRefExcept<'a, 'b, B>
where
B: Bundle,
{
const IS_READ_ONLY: bool = true;
type ReadOnly = Self;
type Item<'w, 's> = EntityRefExcept<'w, B>;
type Item<'w, 's> = EntityRefExcept<'w, 's, B>;
fn shrink<'wlong: 'wshort, 'wshort, 's>(
item: Self::Item<'wlong, 's>,
@ -1250,7 +1255,7 @@ where
}
unsafe fn fetch<'w, 's>(
_state: &'s Self::State,
access: &'s Self::State,
fetch: &mut Self::Fetch<'w>,
entity: Entity,
_: TableRow,
@ -1259,23 +1264,23 @@ where
.world
.get_entity_with_ticks(entity, fetch.last_run, fetch.this_run)
.unwrap();
EntityRefExcept::new(cell)
EntityRefExcept::new(cell, access)
}
}
/// SAFETY: `EntityRefExcept` enforces read-only access to its contained
/// components.
unsafe impl<'a, B> ReadOnlyQueryData for EntityRefExcept<'a, B> where B: Bundle {}
unsafe impl<B> ReadOnlyQueryData for EntityRefExcept<'_, '_, B> where B: Bundle {}
/// SAFETY: `EntityMutExcept` guards access to all components in the bundle `B`
/// and populates `Access` values so that queries that conflict with this access
/// are rejected.
unsafe impl<'a, B> WorldQuery for EntityMutExcept<'a, B>
unsafe impl<'a, 'b, B> WorldQuery for EntityMutExcept<'a, 'b, B>
where
B: Bundle,
{
type Fetch<'w> = EntityFetch<'w>;
type State = SmallVec<[ComponentId; 4]>;
type State = Access<ComponentId>;
fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {
fetch
@ -1307,15 +1312,9 @@ where
unsafe fn set_table<'w, 's>(_: &mut Self::Fetch<'w>, _: &'s Self::State, _: &'w Table) {}
fn update_component_access(
state: &Self::State,
my_access: &Self::State,
filtered_access: &mut FilteredAccess<ComponentId>,
) {
let mut my_access = Access::new();
my_access.write_all_components();
for id in state {
my_access.remove_component_read(*id);
}
let access = filtered_access.access_mut();
assert!(
access.is_compatible(&my_access),
@ -1326,17 +1325,29 @@ where
}
fn init_state(world: &mut World) -> Self::State {
Self::get_state(world.components()).unwrap()
let mut access = Access::new();
access.write_all_components();
B::component_ids(&mut world.components_registrator(), &mut |id| {
access.remove_component_read(id);
});
access
}
fn get_state(components: &Components) -> Option<Self::State> {
let mut ids = SmallVec::new();
let mut access = Access::new();
access.write_all_components();
B::get_component_ids(components, &mut |maybe_id| {
// If the component isn't registered, we don't have a `ComponentId`
// to use to exclude its access.
// Rather than fail, just try to take additional access.
// This is sound because access checks will run on the resulting access.
// Since the component isn't registered, there are no entities with that
// component, and the extra access will usually have no effect.
if let Some(id) = maybe_id {
ids.push(id);
access.remove_component_read(id);
}
});
Some(ids)
Some(access)
}
fn matches_component_set(_: &Self::State, _: &impl Fn(ComponentId) -> bool) -> bool {
@ -1346,13 +1357,13 @@ where
/// SAFETY: All accesses that `EntityRefExcept` provides are also accesses that
/// `EntityMutExcept` provides.
unsafe impl<'a, B> QueryData for EntityMutExcept<'a, B>
unsafe impl<'a, 'b, B> QueryData for EntityMutExcept<'a, 'b, B>
where
B: Bundle,
{
const IS_READ_ONLY: bool = false;
type ReadOnly = EntityRefExcept<'a, B>;
type Item<'w, 's> = EntityMutExcept<'w, B>;
type ReadOnly = EntityRefExcept<'a, 'b, B>;
type Item<'w, 's> = EntityMutExcept<'w, 's, B>;
fn shrink<'wlong: 'wshort, 'wshort, 's>(
item: Self::Item<'wlong, 's>,
@ -1361,7 +1372,7 @@ where
}
unsafe fn fetch<'w, 's>(
_state: &'s Self::State,
access: &'s Self::State,
fetch: &mut Self::Fetch<'w>,
entity: Entity,
_: TableRow,
@ -1370,7 +1381,7 @@ where
.world
.get_entity_with_ticks(entity, fetch.last_run, fetch.this_run)
.unwrap();
EntityMutExcept::new(cell)
EntityMutExcept::new(cell, access)
}
}

View File

@ -970,13 +970,13 @@ unsafe impl<'w, 's, F: QueryFilter> EntitySetIterator
// SAFETY: [`QueryIter`] is guaranteed to return every matching entity once and only once.
unsafe impl<'w, 's, F: QueryFilter, B: Bundle> EntitySetIterator
for QueryIter<'w, 's, EntityRefExcept<'_, B>, F>
for QueryIter<'w, 's, EntityRefExcept<'_, '_, B>, F>
{
}
// SAFETY: [`QueryIter`] is guaranteed to return every matching entity once and only once.
unsafe impl<'w, 's, F: QueryFilter, B: Bundle> EntitySetIterator
for QueryIter<'w, 's, EntityMutExcept<'_, B>, F>
for QueryIter<'w, 's, EntityMutExcept<'_, '_, B>, F>
{
}

View File

@ -3561,6 +3561,14 @@ impl<'a> From<&'a EntityWorldMut<'_>> for FilteredEntityRef<'a, 'static> {
}
}
impl<'w, 's, B: Bundle> From<&'w EntityRefExcept<'_, 's, B>> for FilteredEntityRef<'w, 's> {
fn from(value: &'w EntityRefExcept<'_, 's, B>) -> Self {
// SAFETY:
// - The FilteredEntityRef has the same component access as the given EntityRefExcept.
unsafe { FilteredEntityRef::new(value.entity, value.access) }
}
}
impl PartialEq for FilteredEntityRef<'_, '_> {
fn eq(&self, other: &Self) -> bool {
self.entity() == other.entity()
@ -3884,6 +3892,14 @@ impl<'a> From<&'a mut EntityWorldMut<'_>> for FilteredEntityMut<'a, 'static> {
}
}
impl<'w, 's, B: Bundle> From<&'w EntityMutExcept<'_, 's, B>> for FilteredEntityMut<'w, 's> {
fn from(value: &'w EntityMutExcept<'_, 's, B>) -> Self {
// SAFETY:
// - The FilteredEntityMut has the same component access as the given EntityMutExcept.
unsafe { FilteredEntityMut::new(value.entity, value.access) }
}
}
impl PartialEq for FilteredEntityMut<'_, '_> {
fn eq(&self, other: &Self) -> bool {
self.entity() == other.entity()
@ -3938,23 +3954,28 @@ pub enum TryFromFilteredError {
/// Provides read-only access to a single entity and all its components, save
/// for an explicitly-enumerated set.
pub struct EntityRefExcept<'w, B>
pub struct EntityRefExcept<'w, 's, B>
where
B: Bundle,
{
entity: UnsafeEntityCell<'w>,
access: &'s Access<ComponentId>,
phantom: PhantomData<B>,
}
impl<'w, B> EntityRefExcept<'w, B>
impl<'w, 's, B> EntityRefExcept<'w, 's, B>
where
B: Bundle,
{
/// # Safety
/// Other users of `UnsafeEntityCell` must only have mutable access to the components in `B`.
pub(crate) unsafe fn new(entity: UnsafeEntityCell<'w>) -> Self {
pub(crate) unsafe fn new(
entity: UnsafeEntityCell<'w>,
access: &'s Access<ComponentId>,
) -> Self {
Self {
entity,
access,
phantom: PhantomData,
}
}
@ -4107,34 +4128,34 @@ where
}
}
impl<'a, B> From<&'a EntityMutExcept<'_, B>> for EntityRefExcept<'a, B>
impl<'w, 's, B> From<&'w EntityMutExcept<'_, 's, B>> for EntityRefExcept<'w, 's, B>
where
B: Bundle,
{
fn from(entity: &'a EntityMutExcept<'_, B>) -> Self {
fn from(entity: &'w EntityMutExcept<'_, 's, B>) -> Self {
// SAFETY: All accesses that `EntityRefExcept` provides are also
// accesses that `EntityMutExcept` provides.
unsafe { EntityRefExcept::new(entity.entity) }
unsafe { EntityRefExcept::new(entity.entity, entity.access) }
}
}
impl<B: Bundle> Clone for EntityRefExcept<'_, B> {
impl<B: Bundle> Clone for EntityRefExcept<'_, '_, B> {
fn clone(&self) -> Self {
*self
}
}
impl<B: Bundle> Copy for EntityRefExcept<'_, B> {}
impl<B: Bundle> Copy for EntityRefExcept<'_, '_, B> {}
impl<B: Bundle> PartialEq for EntityRefExcept<'_, B> {
impl<B: Bundle> PartialEq for EntityRefExcept<'_, '_, B> {
fn eq(&self, other: &Self) -> bool {
self.entity() == other.entity()
}
}
impl<B: Bundle> Eq for EntityRefExcept<'_, B> {}
impl<B: Bundle> Eq for EntityRefExcept<'_, '_, B> {}
impl<B: Bundle> PartialOrd for EntityRefExcept<'_, B> {
impl<B: Bundle> PartialOrd for EntityRefExcept<'_, '_, B> {
/// [`EntityRefExcept`]'s comparison trait implementations match the underlying [`Entity`],
/// and cannot discern between different worlds.
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
@ -4142,26 +4163,26 @@ impl<B: Bundle> PartialOrd for EntityRefExcept<'_, B> {
}
}
impl<B: Bundle> Ord for EntityRefExcept<'_, B> {
impl<B: Bundle> Ord for EntityRefExcept<'_, '_, B> {
fn cmp(&self, other: &Self) -> Ordering {
self.entity().cmp(&other.entity())
}
}
impl<B: Bundle> Hash for EntityRefExcept<'_, B> {
impl<B: Bundle> Hash for EntityRefExcept<'_, '_, B> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.entity().hash(state);
}
}
impl<B: Bundle> ContainsEntity for EntityRefExcept<'_, B> {
impl<B: Bundle> ContainsEntity for EntityRefExcept<'_, '_, B> {
fn entity(&self) -> Entity {
self.id()
}
}
// SAFETY: This type represents one Entity. We implement the comparison traits based on that Entity.
unsafe impl<B: Bundle> EntityEquivalent for EntityRefExcept<'_, B> {}
unsafe impl<B: Bundle> EntityEquivalent for EntityRefExcept<'_, '_, B> {}
/// Provides mutable access to all components of an entity, with the exception
/// of an explicit set.
@ -4171,23 +4192,28 @@ unsafe impl<B: Bundle> EntityEquivalent for EntityRefExcept<'_, B> {}
/// queries that might match entities that this query also matches. If you don't
/// need access to all components, prefer a standard query with a
/// [`crate::query::Without`] filter.
pub struct EntityMutExcept<'w, B>
pub struct EntityMutExcept<'w, 's, B>
where
B: Bundle,
{
entity: UnsafeEntityCell<'w>,
access: &'s Access<ComponentId>,
phantom: PhantomData<B>,
}
impl<'w, B> EntityMutExcept<'w, B>
impl<'w, 's, B> EntityMutExcept<'w, 's, B>
where
B: Bundle,
{
/// # Safety
/// Other users of `UnsafeEntityCell` must not have access to any components not in `B`.
pub(crate) unsafe fn new(entity: UnsafeEntityCell<'w>) -> Self {
pub(crate) unsafe fn new(
entity: UnsafeEntityCell<'w>,
access: &'s Access<ComponentId>,
) -> Self {
Self {
entity,
access,
phantom: PhantomData,
}
}
@ -4203,16 +4229,16 @@ where
///
/// This is useful if you have `&mut EntityMutExcept`, but you need
/// `EntityMutExcept`.
pub fn reborrow(&mut self) -> EntityMutExcept<'_, B> {
pub fn reborrow(&mut self) -> EntityMutExcept<'_, 's, B> {
// SAFETY: We have exclusive access to the entire entity and the
// applicable components.
unsafe { Self::new(self.entity) }
unsafe { Self::new(self.entity, self.access) }
}
/// Gets read-only access to all of the entity's components, except for the
/// ones in `CL`.
#[inline]
pub fn as_readonly(&self) -> EntityRefExcept<'_, B> {
pub fn as_readonly(&self) -> EntityRefExcept<'_, 's, B> {
EntityRefExcept::from(self)
}
@ -4341,15 +4367,15 @@ where
}
}
impl<B: Bundle> PartialEq for EntityMutExcept<'_, B> {
impl<B: Bundle> PartialEq for EntityMutExcept<'_, '_, B> {
fn eq(&self, other: &Self) -> bool {
self.entity() == other.entity()
}
}
impl<B: Bundle> Eq for EntityMutExcept<'_, B> {}
impl<B: Bundle> Eq for EntityMutExcept<'_, '_, B> {}
impl<B: Bundle> PartialOrd for EntityMutExcept<'_, B> {
impl<B: Bundle> PartialOrd for EntityMutExcept<'_, '_, B> {
/// [`EntityMutExcept`]'s comparison trait implementations match the underlying [`Entity`],
/// and cannot discern between different worlds.
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
@ -4357,26 +4383,26 @@ impl<B: Bundle> PartialOrd for EntityMutExcept<'_, B> {
}
}
impl<B: Bundle> Ord for EntityMutExcept<'_, B> {
impl<B: Bundle> Ord for EntityMutExcept<'_, '_, B> {
fn cmp(&self, other: &Self) -> Ordering {
self.entity().cmp(&other.entity())
}
}
impl<B: Bundle> Hash for EntityMutExcept<'_, B> {
impl<B: Bundle> Hash for EntityMutExcept<'_, '_, B> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.entity().hash(state);
}
}
impl<B: Bundle> ContainsEntity for EntityMutExcept<'_, B> {
impl<B: Bundle> ContainsEntity for EntityMutExcept<'_, '_, B> {
fn entity(&self) -> Entity {
self.id()
}
}
// SAFETY: This type represents one Entity. We implement the comparison traits based on that Entity.
unsafe impl<B: Bundle> EntityEquivalent for EntityMutExcept<'_, B> {}
unsafe impl<B: Bundle> EntityEquivalent for EntityMutExcept<'_, '_, B> {}
fn bundle_contains_component<B>(components: &Components, query_id: ComponentId) -> bool
where