Skip to content

Commit 3c41586

Browse files
authored
Add EntityRefExcept and EntityMutExcept world queries, in preparation for generalized animation. (#15207)
This commit adds two new `WorldQuery` types: `EntityRefExcept` and `EntityMutExcept`. These types work just like `EntityRef` and `EntityMut`, but they prevent access to a statically-specified list of components. For example, `EntityMutExcept<(AnimationPlayer, Handle<AnimationGraph>)>` provides mutable access to all components except for `AnimationPlayer` and `Handle<AnimationGraph>`. These types are useful when you need to be able to process arbitrary queries while iterating over the results of another `EntityMut` query. The motivating use case is *generalized animation*, which is an upcoming feature that allows animation of any component property, not just rotation, translation, scaling, or morph weights. To implement this, we must change the current `AnyOf<(&mut Transform, &mut MorphWeights)>` to instead be `EntityMutExcept<(AnimationPlayer, Handle<AnimationGraph>)>`. It's possible to use `FilteredEntityMut` in conjunction with a dynamically-generated system instead, but `FilteredEntityMut` isn't optimized for the use case of a large number of allowed components coupled with a small set of disallowed components. No amount of optimization of `FilteredEntityMut` produced acceptable performance on the `many_foxes` benchmark. `Query<EntityMut, Without<AnimationPlayer>>` will not suffice either, as it's legal and idiomatic for an `AnimationTarget` and an `AnimationPlayer` to coexist on the same entity. An alternate proposal was to implement a somewhat-more-general `Except<Q, CL>` feature, where Q is a `WorldQuery` and CL is a `ComponentList`. I wasn't able to implement that proposal in a reasonable way, because of the fact that methods like `EntityMut::get_mut` and `EntityRef::get` are inherent methods instead of methods on `WorldQuery`, and therefore there was no way to delegate methods like `get` and `get_mut` to the inner query in a generic way. Refactoring those methods into a trait would probably be possible. However, I didn't see a use case for a hypothetical `Except` with arbitrary queries: `Query<Except<(&Transform, &Visibility), Visibility>>` would just be a complicated equivalent to `Query<&Transform>`, for instance. So, out of a desire for simplicity, I omitted a generic `Except` mechanism. I've tested the performance of generalized animation on `many_foxes` and found that, with this patch, `animate_targets` has a 7.4% slowdown over `main`. With `FilteredEntityMut` optimized to use `Arc<Access>`, the slowdown is 75.6%, due to contention on the reference count. Without `Arc<Access>`, the slowdown is even worse, over 2x. ## Testing New tests have been added that check that `EntityRefExcept` and `EntityMutExcept` allow and disallow access to components properly and that the query engine can correctly reject conflicting queries involving those types. A Tracy profile of `many_foxes` with 10,000 foxes showing generalized animation using `FilteredEntityMut` (red) vs. main (yellow) is as follows: ![Screenshot 2024-09-12 225914](https://github.com/user-attachments/assets/2993d74c-a513-4ba4-85bd-225672e7170a) A Tracy profile of `many_foxes` with 10,000 foxes showing generalized animation using this `EntityMutExcept` (yellow) vs. main (red) is as follows: ![Screenshot 2024-09-14 205831](https://github.com/user-attachments/assets/4241015e-0c5d-44ef-835b-43f78a24e604)
1 parent b45d83e commit 3c41586

File tree

10 files changed

+930
-214
lines changed

10 files changed

+930
-214
lines changed

crates/bevy_ecs/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ serde = { version = "1", optional = true, default-features = false }
3434
thiserror = "1.0"
3535
nonmax = "0.5"
3636
arrayvec = { version = "0.7.4", optional = true }
37+
smallvec = "1"
3738

3839
[dev-dependencies]
3940
rand = "0.8"

crates/bevy_ecs/src/archetype.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,16 @@ impl Archetype {
499499
self.components.len()
500500
}
501501

502+
/// Gets an iterator of all of the components in the archetype, along with
503+
/// their archetype component ID.
504+
pub(crate) fn components_with_archetype_component_id(
505+
&self,
506+
) -> impl Iterator<Item = (ComponentId, ArchetypeComponentId)> + '_ {
507+
self.components
508+
.iter()
509+
.map(|(component_id, info)| (*component_id, info.archetype_component_id))
510+
}
511+
502512
/// Fetches an immutable reference to the archetype's [`Edges`], a cache of
503513
/// archetypal relationships.
504514
#[inline]

crates/bevy_ecs/src/query/access.rs

Lines changed: 319 additions & 122 deletions
Large diffs are not rendered by default.

crates/bevy_ecs/src/query/builder.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,14 @@ impl<'w, D: QueryData, F: QueryFilter> QueryBuilder<'w, D, F> {
7979
.map_or(false, |info| info.storage_type() == StorageType::Table)
8080
};
8181

82-
self.access
83-
.access()
84-
.component_reads_and_writes()
85-
.all(is_dense)
82+
#[allow(deprecated)]
83+
let (mut component_reads_and_writes, component_reads_and_writes_inverted) =
84+
self.access.access().component_reads_and_writes();
85+
if component_reads_and_writes_inverted {
86+
return false;
87+
}
88+
89+
component_reads_and_writes.all(is_dense)
8690
&& self.access.access().archetypal().all(is_dense)
8791
&& !self.access.access().has_read_all_components()
8892
&& self.access.with_filters().all(is_dense)

crates/bevy_ecs/src/query/fetch.rs

Lines changed: 207 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
use crate::{
22
archetype::{Archetype, Archetypes},
3+
bundle::Bundle,
34
change_detection::{MaybeThinSlicePtrLocation, Ticks, TicksMut},
45
component::{Component, ComponentId, Components, StorageType, Tick},
56
entity::{Entities, Entity, EntityLocation},
67
query::{Access, DebugCheckedUnwrap, FilteredAccess, WorldQuery},
78
storage::{ComponentSparseSet, Table, TableRow},
89
world::{
9-
unsafe_world_cell::UnsafeWorldCell, EntityMut, EntityRef, FilteredEntityMut,
10-
FilteredEntityRef, Mut, Ref, World,
10+
unsafe_world_cell::UnsafeWorldCell, EntityMut, EntityMutExcept, EntityRef, EntityRefExcept,
11+
FilteredEntityMut, FilteredEntityRef, Mut, Ref, World,
1112
},
1213
};
1314
use bevy_ptr::{ThinSlicePtr, UnsafeCellDeref};
1415
use bevy_utils::all_tuples;
16+
use smallvec::SmallVec;
1517
use std::{cell::UnsafeCell, marker::PhantomData};
1618

1719
/// Types that can be fetched from a [`World`] using a [`Query`].
@@ -626,27 +628,15 @@ unsafe impl<'a> WorldQuery for FilteredEntityRef<'a> {
626628
unsafe fn set_archetype<'w>(
627629
fetch: &mut Self::Fetch<'w>,
628630
state: &Self::State,
629-
archetype: &'w Archetype,
631+
_: &'w Archetype,
630632
_table: &Table,
631633
) {
632-
let mut access = Access::default();
633-
state.access.component_reads().for_each(|id| {
634-
if archetype.contains(id) {
635-
access.add_component_read(id);
636-
}
637-
});
638-
fetch.1 = access;
634+
fetch.1.clone_from(&state.access);
639635
}
640636

641637
#[inline]
642-
unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, table: &'w Table) {
643-
let mut access = Access::default();
644-
state.access.component_reads().for_each(|id| {
645-
if table.has_column(id) {
646-
access.add_component_read(id);
647-
}
648-
});
649-
fetch.1 = access;
638+
unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, _: &'w Table) {
639+
fetch.1.clone_from(&state.access);
650640
}
651641

652642
#[inline]
@@ -733,37 +723,15 @@ unsafe impl<'a> WorldQuery for FilteredEntityMut<'a> {
733723
unsafe fn set_archetype<'w>(
734724
fetch: &mut Self::Fetch<'w>,
735725
state: &Self::State,
736-
archetype: &'w Archetype,
726+
_: &'w Archetype,
737727
_table: &Table,
738728
) {
739-
let mut access = Access::default();
740-
state.access.component_reads().for_each(|id| {
741-
if archetype.contains(id) {
742-
access.add_component_read(id);
743-
}
744-
});
745-
state.access.component_writes().for_each(|id| {
746-
if archetype.contains(id) {
747-
access.add_component_write(id);
748-
}
749-
});
750-
fetch.1 = access;
729+
fetch.1.clone_from(&state.access);
751730
}
752731

753732
#[inline]
754-
unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, table: &'w Table) {
755-
let mut access = Access::default();
756-
state.access.component_reads().for_each(|id| {
757-
if table.has_column(id) {
758-
access.add_component_read(id);
759-
}
760-
});
761-
state.access.component_writes().for_each(|id| {
762-
if table.has_column(id) {
763-
access.add_component_write(id);
764-
}
765-
});
766-
fetch.1 = access;
733+
unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, _: &'w Table) {
734+
fetch.1.clone_from(&state.access);
767735
}
768736

769737
#[inline]
@@ -815,6 +783,201 @@ unsafe impl<'a> QueryData for FilteredEntityMut<'a> {
815783
type ReadOnly = FilteredEntityRef<'a>;
816784
}
817785

786+
/// SAFETY: `EntityRefExcept` guards access to all components in the bundle `B`
787+
/// and populates `Access` values so that queries that conflict with this access
788+
/// are rejected.
789+
unsafe impl<'a, B> WorldQuery for EntityRefExcept<'a, B>
790+
where
791+
B: Bundle,
792+
{
793+
type Fetch<'w> = UnsafeWorldCell<'w>;
794+
type Item<'w> = EntityRefExcept<'w, B>;
795+
type State = SmallVec<[ComponentId; 4]>;
796+
797+
fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> {
798+
item
799+
}
800+
801+
fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {
802+
fetch
803+
}
804+
805+
unsafe fn init_fetch<'w>(
806+
world: UnsafeWorldCell<'w>,
807+
_: &Self::State,
808+
_: Tick,
809+
_: Tick,
810+
) -> Self::Fetch<'w> {
811+
world
812+
}
813+
814+
const IS_DENSE: bool = true;
815+
816+
unsafe fn set_archetype<'w>(
817+
_: &mut Self::Fetch<'w>,
818+
_: &Self::State,
819+
_: &'w Archetype,
820+
_: &'w Table,
821+
) {
822+
}
823+
824+
unsafe fn set_table<'w>(_: &mut Self::Fetch<'w>, _: &Self::State, _: &'w Table) {}
825+
826+
unsafe fn fetch<'w>(
827+
world: &mut Self::Fetch<'w>,
828+
entity: Entity,
829+
_: TableRow,
830+
) -> Self::Item<'w> {
831+
let cell = world.get_entity(entity).unwrap();
832+
EntityRefExcept::new(cell)
833+
}
834+
835+
fn update_component_access(
836+
state: &Self::State,
837+
filtered_access: &mut FilteredAccess<ComponentId>,
838+
) {
839+
let mut my_access = Access::new();
840+
my_access.read_all_components();
841+
for id in state {
842+
my_access.remove_component_read(*id);
843+
}
844+
845+
let access = filtered_access.access_mut();
846+
assert!(
847+
access.is_compatible(&my_access),
848+
"`EntityRefExcept<{}>` conflicts with a previous access in this query.",
849+
std::any::type_name::<B>(),
850+
);
851+
access.extend(&my_access);
852+
}
853+
854+
fn init_state(world: &mut World) -> Self::State {
855+
Self::get_state(world.components()).unwrap()
856+
}
857+
858+
fn get_state(components: &Components) -> Option<Self::State> {
859+
let mut ids = SmallVec::new();
860+
B::get_component_ids(components, &mut |maybe_id| {
861+
if let Some(id) = maybe_id {
862+
ids.push(id);
863+
}
864+
});
865+
Some(ids)
866+
}
867+
868+
fn matches_component_set(_: &Self::State, _: &impl Fn(ComponentId) -> bool) -> bool {
869+
true
870+
}
871+
}
872+
873+
/// SAFETY: `Self` is the same as `Self::ReadOnly`.
874+
unsafe impl<'a, B> QueryData for EntityRefExcept<'a, B>
875+
where
876+
B: Bundle,
877+
{
878+
type ReadOnly = Self;
879+
}
880+
881+
/// SAFETY: `EntityRefExcept` enforces read-only access to its contained
882+
/// components.
883+
unsafe impl<'a, B> ReadOnlyQueryData for EntityRefExcept<'a, B> where B: Bundle {}
884+
885+
/// SAFETY: `EntityMutExcept` guards access to all components in the bundle `B`
886+
/// and populates `Access` values so that queries that conflict with this access
887+
/// are rejected.
888+
unsafe impl<'a, B> WorldQuery for EntityMutExcept<'a, B>
889+
where
890+
B: Bundle,
891+
{
892+
type Fetch<'w> = UnsafeWorldCell<'w>;
893+
type Item<'w> = EntityMutExcept<'w, B>;
894+
type State = SmallVec<[ComponentId; 4]>;
895+
896+
fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> {
897+
item
898+
}
899+
900+
fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {
901+
fetch
902+
}
903+
904+
unsafe fn init_fetch<'w>(
905+
world: UnsafeWorldCell<'w>,
906+
_: &Self::State,
907+
_: Tick,
908+
_: Tick,
909+
) -> Self::Fetch<'w> {
910+
world
911+
}
912+
913+
const IS_DENSE: bool = true;
914+
915+
unsafe fn set_archetype<'w>(
916+
_: &mut Self::Fetch<'w>,
917+
_: &Self::State,
918+
_: &'w Archetype,
919+
_: &'w Table,
920+
) {
921+
}
922+
923+
unsafe fn set_table<'w>(_: &mut Self::Fetch<'w>, _: &Self::State, _: &'w Table) {}
924+
925+
unsafe fn fetch<'w>(
926+
world: &mut Self::Fetch<'w>,
927+
entity: Entity,
928+
_: TableRow,
929+
) -> Self::Item<'w> {
930+
let cell = world.get_entity(entity).unwrap();
931+
EntityMutExcept::new(cell)
932+
}
933+
934+
fn update_component_access(
935+
state: &Self::State,
936+
filtered_access: &mut FilteredAccess<ComponentId>,
937+
) {
938+
let mut my_access = Access::new();
939+
my_access.write_all_components();
940+
for id in state {
941+
my_access.remove_component_read(*id);
942+
}
943+
944+
let access = filtered_access.access_mut();
945+
assert!(
946+
access.is_compatible(&my_access),
947+
"`EntityMutExcept<{}>` conflicts with a previous access in this query.",
948+
std::any::type_name::<B>()
949+
);
950+
access.extend(&my_access);
951+
}
952+
953+
fn init_state(world: &mut World) -> Self::State {
954+
Self::get_state(world.components()).unwrap()
955+
}
956+
957+
fn get_state(components: &Components) -> Option<Self::State> {
958+
let mut ids = SmallVec::new();
959+
B::get_component_ids(components, &mut |maybe_id| {
960+
if let Some(id) = maybe_id {
961+
ids.push(id);
962+
}
963+
});
964+
Some(ids)
965+
}
966+
967+
fn matches_component_set(_: &Self::State, _: &impl Fn(ComponentId) -> bool) -> bool {
968+
true
969+
}
970+
}
971+
972+
/// SAFETY: All accesses that `EntityRefExcept` provides are also accesses that
973+
/// `EntityMutExcept` provides.
974+
unsafe impl<'a, B> QueryData for EntityMutExcept<'a, B>
975+
where
976+
B: Bundle,
977+
{
978+
type ReadOnly = EntityRefExcept<'a, B>;
979+
}
980+
818981
/// SAFETY:
819982
/// `update_component_access` and `update_archetype_component_access` do nothing.
820983
/// This is sound because `fetch` does not access components.

crates/bevy_ecs/src/query/state.rs

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -508,22 +508,46 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
508508
archetype: &Archetype,
509509
access: &mut Access<ArchetypeComponentId>,
510510
) {
511-
self.component_access
512-
.access
513-
.component_reads()
514-
.for_each(|id| {
511+
// As a fast path, we can iterate directly over the components involved
512+
// if the `access` isn't inverted.
513+
#[allow(deprecated)]
514+
let (component_reads_and_writes, component_reads_and_writes_inverted) =
515+
self.component_access.access.component_reads_and_writes();
516+
let (component_writes, component_writes_inverted) =
517+
self.component_access.access.component_writes();
518+
519+
if !component_reads_and_writes_inverted && !component_writes_inverted {
520+
component_reads_and_writes.for_each(|id| {
515521
if let Some(id) = archetype.get_archetype_component_id(id) {
516522
access.add_component_read(id);
517523
}
518524
});
519-
self.component_access
520-
.access
521-
.component_writes()
522-
.for_each(|id| {
525+
component_writes.for_each(|id| {
523526
if let Some(id) = archetype.get_archetype_component_id(id) {
524527
access.add_component_write(id);
525528
}
526529
});
530+
return;
531+
}
532+
533+
for (component_id, archetype_component_id) in
534+
archetype.components_with_archetype_component_id()
535+
{
536+
if self
537+
.component_access
538+
.access
539+
.has_component_read(component_id)
540+
{
541+
access.add_component_read(archetype_component_id);
542+
}
543+
if self
544+
.component_access
545+
.access
546+
.has_component_write(component_id)
547+
{
548+
access.add_component_write(archetype_component_id);
549+
}
550+
}
527551
}
528552

529553
/// Use this to transform a [`QueryState`] into a more generic [`QueryState`].

0 commit comments

Comments
 (0)