Skip to content

Commit 70f91b2

Browse files
james7132Carter0jakobhellermanncart
authored
Implement WorldQuery for EntityRef (#6960)
# Objective Partially address #5504. Fix #4278. Provide "whole entity" access in queries. This can be useful when you don't know at compile time what you're accessing (i.e. reflection via `ReflectComponent`). ## Solution Implement `WorldQuery` for `EntityRef`. - This provides read-only access to the entire entity, and supports anything that `EntityRef` can normally do. - It matches all archetypes and tables and will densely iterate when possible. - It marks all of the ArchetypeComponentIds of a matched archetype as read. - Adding it to a query will cause it to panic if used in conjunction with any other mutable access. - Expanded the docs on Query to advertise this feature. - Added tests to ensure the panics were working as intended. - Added `EntityRef` to the ECS prelude. To make this safe, `EntityRef::world` was removed as it gave potential `UnsafeCell`-like access to other parts of the `World` including aliased mutable access to the components it would otherwise read safely. ## Performance Not great beyond the additional parallelization opportunity over exclusive systems. The `EntityRef` is fetched from `Entities` like any other call to `World::entity`, which can be very random access heavy. This could be simplified if `ArchetypeRow` is available in `WorldQuery::fetch`'s arguments, but that's likely not something we should optimize for. ## Future work An equivalent API where it gives mutable access to all components on a entity can be done with a scoped version of `EntityMut` where it does not provide `&mut World` access nor allow for structural changes to the entity is feasible as well. This could be done as a safe alternative to exclusive system when structural mutation isn't required or the target set of entities is scoped. --- ## Changelog Added: `Access::has_any_write` Added: `EntityRef` now implements `WorldQuery`. Allows read-only access to the entire entity, incompatible with any other mutable access, can be mixed with `With`/`Without` filters for more targeted use. Added: `EntityRef` to `bevy::ecs::prelude`. Removed: `EntityRef::world` ## Migration Guide TODO --------- Co-authored-by: Carter Weinberg <[email protected]> Co-authored-by: Jakob Hellermann <[email protected]> Co-authored-by: Carter Anderson <[email protected]>
1 parent 724e69b commit 70f91b2

File tree

5 files changed

+236
-68
lines changed

5 files changed

+236
-68
lines changed

crates/bevy_ecs/src/lib.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ pub mod prelude {
5050
Commands, Deferred, In, IntoSystem, Local, NonSend, NonSendMut, ParallelCommands,
5151
ParamSet, Query, ReadOnlySystem, Res, ResMut, Resource, System, SystemParamFunction,
5252
},
53-
world::{FromWorld, World},
53+
world::{EntityRef, FromWorld, World},
5454
};
5555
}
5656

@@ -70,7 +70,7 @@ mod tests {
7070
entity::Entity,
7171
query::{Added, Changed, FilteredAccess, ReadOnlyWorldQuery, With, Without},
7272
system::Resource,
73-
world::{Mut, World},
73+
world::{EntityRef, Mut, World},
7474
};
7575
use bevy_tasks::{ComputeTaskPool, TaskPool};
7676
use std::{
@@ -1323,13 +1323,27 @@ mod tests {
13231323
world.query::<(&A, &mut A)>();
13241324
}
13251325

1326+
#[test]
1327+
#[should_panic]
1328+
fn entity_ref_and_mut_query_panic() {
1329+
let mut world = World::new();
1330+
world.query::<(EntityRef, &mut A)>();
1331+
}
1332+
13261333
#[test]
13271334
#[should_panic]
13281335
fn mut_and_ref_query_panic() {
13291336
let mut world = World::new();
13301337
world.query::<(&mut A, &A)>();
13311338
}
13321339

1340+
#[test]
1341+
#[should_panic]
1342+
fn mut_and_entity_ref_query_panic() {
1343+
let mut world = World::new();
1344+
world.query::<(&mut A, EntityRef)>();
1345+
}
1346+
13331347
#[test]
13341348
#[should_panic]
13351349
fn mut_and_mut_query_panic() {

crates/bevy_ecs/src/query/access.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,11 @@ impl<T: SparseSetIndex> Access<T> {
121121
self.writes.contains(index.sparse_set_index())
122122
}
123123

124+
/// Returns `true` if this accesses anything mutably.
125+
pub fn has_any_write(&self) -> bool {
126+
!self.writes.is_clear()
127+
}
128+
124129
/// Sets this as having access to all indexed elements (i.e. `&World`).
125130
pub fn read_all(&mut self) {
126131
self.reads_all = true;

crates/bevy_ecs/src/query/fetch.rs

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::{
55
entity::Entity,
66
query::{Access, DebugCheckedUnwrap, FilteredAccess},
77
storage::{ComponentSparseSet, Table, TableRow},
8-
world::{unsafe_world_cell::UnsafeWorldCell, Mut, Ref, World},
8+
world::{unsafe_world_cell::UnsafeWorldCell, EntityRef, Mut, Ref, World},
99
};
1010
pub use bevy_ecs_macros::WorldQuery;
1111
use bevy_ptr::{ThinSlicePtr, UnsafeCellDeref};
@@ -536,6 +536,89 @@ unsafe impl WorldQuery for Entity {
536536
/// SAFETY: access is read only
537537
unsafe impl ReadOnlyWorldQuery for Entity {}
538538

539+
/// SAFETY: `Self` is the same as `Self::ReadOnly`
540+
unsafe impl<'a> WorldQuery for EntityRef<'a> {
541+
type Fetch<'w> = &'w World;
542+
type Item<'w> = EntityRef<'w>;
543+
type ReadOnly = Self;
544+
type State = ();
545+
546+
fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> {
547+
item
548+
}
549+
550+
const IS_DENSE: bool = true;
551+
552+
const IS_ARCHETYPAL: bool = true;
553+
554+
unsafe fn init_fetch<'w>(
555+
world: UnsafeWorldCell<'w>,
556+
_state: &Self::State,
557+
_last_run: Tick,
558+
_this_run: Tick,
559+
) -> Self::Fetch<'w> {
560+
// SAFE: EntityRef has permission to access the whole world immutably thanks to update_component_access and update_archetype_component_access
561+
world.world()
562+
}
563+
564+
unsafe fn clone_fetch<'w>(world: &Self::Fetch<'w>) -> Self::Fetch<'w> {
565+
world
566+
}
567+
568+
#[inline]
569+
unsafe fn set_archetype<'w>(
570+
_fetch: &mut Self::Fetch<'w>,
571+
_state: &Self::State,
572+
_archetype: &'w Archetype,
573+
_table: &Table,
574+
) {
575+
}
576+
577+
#[inline]
578+
unsafe fn set_table<'w>(_fetch: &mut Self::Fetch<'w>, _state: &Self::State, _table: &'w Table) {
579+
}
580+
581+
#[inline(always)]
582+
unsafe fn fetch<'w>(
583+
world: &mut Self::Fetch<'w>,
584+
entity: Entity,
585+
_table_row: TableRow,
586+
) -> Self::Item<'w> {
587+
// SAFETY: `fetch` must be called with an entity that exists in the world
588+
unsafe { world.get_entity(entity).debug_checked_unwrap() }
589+
}
590+
591+
fn update_component_access(_state: &Self::State, access: &mut FilteredAccess<ComponentId>) {
592+
assert!(
593+
!access.access().has_any_write(),
594+
"EntityRef conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.",
595+
);
596+
access.read_all();
597+
}
598+
599+
fn update_archetype_component_access(
600+
_state: &Self::State,
601+
archetype: &Archetype,
602+
access: &mut Access<ArchetypeComponentId>,
603+
) {
604+
for component_id in archetype.components() {
605+
access.add_read(archetype.get_archetype_component_id(component_id).unwrap());
606+
}
607+
}
608+
609+
fn init_state(_world: &mut World) {}
610+
611+
fn matches_component_set(
612+
_state: &Self::State,
613+
_set_contains_id: &impl Fn(ComponentId) -> bool,
614+
) -> bool {
615+
true
616+
}
617+
}
618+
619+
/// SAFETY: access is read only
620+
unsafe impl<'a> ReadOnlyWorldQuery for EntityRef<'a> {}
621+
539622
#[doc(hidden)]
540623
pub struct ReadFetch<'w, T> {
541624
// T::Storage = TableStorage

crates/bevy_ecs/src/system/mod.rs

Lines changed: 84 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -125,71 +125,6 @@ pub use system_param::*;
125125

126126
use crate::world::World;
127127

128-
/// Ensure that a given function is a [system](System).
129-
///
130-
/// This should be used when writing doc examples,
131-
/// to confirm that systems used in an example are
132-
/// valid systems.
133-
///
134-
/// # Examples
135-
///
136-
/// The following example will panic when run since the
137-
/// system's parameters mutably access the same component
138-
/// multiple times.
139-
///
140-
/// ```should_panic
141-
/// # use bevy_ecs::{prelude::*, system::assert_is_system};
142-
/// #
143-
/// # #[derive(Component)]
144-
/// # struct Transform;
145-
/// #
146-
/// fn my_system(query1: Query<&mut Transform>, query2: Query<&mut Transform>) {
147-
/// // ...
148-
/// }
149-
///
150-
/// assert_is_system(my_system);
151-
/// ```
152-
pub fn assert_is_system<In: 'static, Out: 'static, Marker>(
153-
system: impl IntoSystem<In, Out, Marker>,
154-
) {
155-
let mut system = IntoSystem::into_system(system);
156-
157-
// Initialize the system, which will panic if the system has access conflicts.
158-
let mut world = World::new();
159-
system.initialize(&mut world);
160-
}
161-
162-
/// Ensure that a given function is a [read-only system](ReadOnlySystem).
163-
///
164-
/// This should be used when writing doc examples,
165-
/// to confirm that systems used in an example are
166-
/// valid systems.
167-
///
168-
/// # Examples
169-
///
170-
/// The following example will fail to compile
171-
/// since the system accesses a component mutably.
172-
///
173-
/// ```compile_fail
174-
/// # use bevy_ecs::{prelude::*, system::assert_is_read_only_system};
175-
/// #
176-
/// # #[derive(Component)]
177-
/// # struct Transform;
178-
/// #
179-
/// fn my_system(query: Query<&mut Transform>) {
180-
/// // ...
181-
/// }
182-
///
183-
/// assert_is_read_only_system(my_system);
184-
/// ```
185-
pub fn assert_is_read_only_system<In: 'static, Out: 'static, Marker, S>(system: S)
186-
where
187-
S: IntoSystem<In, Out, Marker>,
188-
S::System: ReadOnlySystem,
189-
{
190-
assert_is_system(system);
191-
}
192-
193128
/// Conversion trait to turn something into a [`System`].
194129
///
195130
/// Use this to get a system from a function. Also note that every system implements this trait as
@@ -477,6 +412,83 @@ pub mod adapter {
477412
pub fn ignore<T>(In(_): In<T>) {}
478413
}
479414

415+
/// Ensure that a given function is a [system](System).
416+
///
417+
/// This should be used when writing doc examples,
418+
/// to confirm that systems used in an example are
419+
/// valid systems.
420+
///
421+
/// # Examples
422+
///
423+
/// The following example will panic when run since the
424+
/// system's parameters mutably access the same component
425+
/// multiple times.
426+
///
427+
/// ```should_panic
428+
/// # use bevy_ecs::{prelude::*, system::assert_is_system};
429+
/// #
430+
/// # #[derive(Component)]
431+
/// # struct Transform;
432+
/// #
433+
/// fn my_system(query1: Query<&mut Transform>, query2: Query<&mut Transform>) {
434+
/// // ...
435+
/// }
436+
///
437+
/// assert_is_system(my_system);
438+
/// ```
439+
pub fn assert_is_system<In: 'static, Out: 'static, Marker>(
440+
system: impl IntoSystem<In, Out, Marker>,
441+
) {
442+
let mut system = IntoSystem::into_system(system);
443+
444+
// Initialize the system, which will panic if the system has access conflicts.
445+
let mut world = World::new();
446+
system.initialize(&mut world);
447+
}
448+
449+
/// Ensure that a given function is a [read-only system](ReadOnlySystem).
450+
///
451+
/// This should be used when writing doc examples,
452+
/// to confirm that systems used in an example are
453+
/// valid systems.
454+
///
455+
/// # Examples
456+
///
457+
/// The following example will fail to compile
458+
/// since the system accesses a component mutably.
459+
///
460+
/// ```compile_fail
461+
/// # use bevy_ecs::{prelude::*, system::assert_is_read_only_system};
462+
/// #
463+
/// # #[derive(Component)]
464+
/// # struct Transform;
465+
/// #
466+
/// fn my_system(query: Query<&mut Transform>) {
467+
/// // ...
468+
/// }
469+
///
470+
/// assert_is_read_only_system(my_system);
471+
/// ```
472+
pub fn assert_is_read_only_system<In: 'static, Out: 'static, Marker, S>(system: S)
473+
where
474+
S: IntoSystem<In, Out, Marker>,
475+
S::System: ReadOnlySystem,
476+
{
477+
assert_is_system(system);
478+
}
479+
480+
/// Ensures that the provided system doesn't with itself.
481+
///
482+
/// This function will panic if the provided system conflict with itself.
483+
///
484+
/// Note: this will run the system on an empty world.
485+
pub fn assert_system_does_not_conflict<Out, Params, S: IntoSystem<(), Out, Params>>(sys: S) {
486+
let mut world = World::new();
487+
let mut system = IntoSystem::into_system(sys);
488+
system.initialize(&mut world);
489+
system.run((), &mut world);
490+
}
491+
480492
#[cfg(test)]
481493
mod tests {
482494
use std::any::TypeId;
@@ -1739,6 +1751,13 @@ mod tests {
17391751
query.iter();
17401752
}
17411753

1754+
#[test]
1755+
#[should_panic]
1756+
fn assert_system_does_not_conflict() {
1757+
fn system(_query: Query<(&mut W<u32>, &mut W<u32>)>) {}
1758+
super::assert_system_does_not_conflict(system);
1759+
}
1760+
17421761
#[test]
17431762
#[should_panic]
17441763
fn panic_inside_system() {

crates/bevy_ecs/src/system/query.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,52 @@ use std::{any::TypeId, borrow::Borrow, fmt::Debug};
183183
///
184184
/// An alternative to this idiom is to wrap the conflicting queries into a [`ParamSet`](super::ParamSet).
185185
///
186+
/// ## Whole Entity Access
187+
///
188+
/// [`EntityRef`]s can be fetched from a query. This will give read-only access to any component on the entity,
189+
/// and can be use to dynamically fetch any component without baking it into the query type. Due to this global
190+
/// access to the entity, this will block any other system from parallelizing with it. As such these queries
191+
/// should be sparingly used.
192+
///
193+
/// ```
194+
/// # use bevy_ecs::prelude::*;
195+
/// # #[derive(Component)]
196+
/// # struct ComponentA;
197+
/// # fn system(
198+
/// query: Query<(EntityRef, &ComponentA)>
199+
/// # ) {}
200+
/// # bevy_ecs::system::assert_is_system(system);
201+
/// ```
202+
///
203+
/// As `EntityRef` can read any component on an entity, a query using it will conflict with *any* mutable
204+
/// access. It is strongly advised to couple `EntityRef` queries with the use of either `With`/`Without`
205+
/// filters or `ParamSets`. This also limits the scope of the query, which will improve iteration performance
206+
/// and also allows it to parallelize with other non-conflicting systems.
207+
///
208+
/// ```should_panic
209+
/// # use bevy_ecs::prelude::*;
210+
/// # #[derive(Component)]
211+
/// # struct ComponentA;
212+
/// # fn system(
213+
/// // This will panic!
214+
/// query: Query<(EntityRef, &mut ComponentA)>
215+
/// # ) {}
216+
/// # bevy_ecs::system::assert_system_does_not_conflict(system);
217+
/// ```
218+
/// ```
219+
/// # use bevy_ecs::prelude::*;
220+
/// # #[derive(Component)]
221+
/// # struct ComponentA;
222+
/// # #[derive(Component)]
223+
/// # struct ComponentB;
224+
/// # fn system(
225+
/// // This will not panic.
226+
/// query_a: Query<EntityRef, With<ComponentA>>,
227+
/// query_b: Query<&mut ComponentB, Without<ComponentA>>,
228+
/// # ) {}
229+
/// # bevy_ecs::system::assert_system_does_not_conflict(system);
230+
/// ```
231+
///
186232
/// # Accessing query items
187233
///
188234
/// The following table summarizes the behavior of the safe methods that can be used to get query items.
@@ -248,6 +294,7 @@ use std::{any::TypeId, borrow::Borrow, fmt::Debug};
248294
/// [`Changed`]: crate::query::Changed
249295
/// [components]: crate::component::Component
250296
/// [entity identifiers]: crate::entity::Entity
297+
/// [`EntityRef`]: crate::world::EntityRef
251298
/// [`for_each`]: Self::for_each
252299
/// [`for_each_mut`]: Self::for_each_mut
253300
/// [`get`]: Self::get

0 commit comments

Comments
 (0)