Skip to content

Commit b30ee2d

Browse files
authored
Disallow requesting write resource access in Queries (#17116)
Related to #16843 Since `WorldQuery::Fetch` is `Clone`, it can't store mutable references to resources, so it doesn't make sense to mutably access resources. In that sense, it is hard to find usecases of mutably accessing resources and to clearly define, what mutably accessing resources would mean, so it's been decided to disallow write resource access. Also changed documentation of safety requirements of `WorldQuery::init_fetch` and `WorldQuery::fetch` to clearly state to the caller, what safety invariants they need to uphold.
1 parent 7dd5686 commit b30ee2d

File tree

4 files changed

+15
-131
lines changed

4 files changed

+15
-131
lines changed

crates/bevy_asset/src/asset_changed.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -233,11 +233,6 @@ unsafe impl<A: AsAssetId> WorldQuery for AssetChanged<A> {
233233
#[inline]
234234
fn update_component_access(state: &Self::State, access: &mut FilteredAccess<ComponentId>) {
235235
<&A>::update_component_access(&state.asset_id, access);
236-
assert!(
237-
!access.access().has_resource_write(state.resource_id),
238-
"AssetChanged<{ty}> requires read-only access to AssetChanges<{ty}>",
239-
ty = ShortName::of::<A>()
240-
);
241236
access.add_resource_read(state.resource_id);
242237
}
243238

crates/bevy_ecs/src/query/mod.rs

Lines changed: 1 addition & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -805,9 +805,6 @@ mod tests {
805805
/// `QueryData` that performs read access on R to test that resource access is tracked
806806
struct ReadsRData;
807807

808-
/// `QueryData` that performs write access on R to test that resource access is tracked
809-
struct WritesRData;
810-
811808
/// SAFETY:
812809
/// `update_component_access` adds resource read access for `R`.
813810
/// `update_archetype_component_access` does nothing, as this accesses no components.
@@ -890,124 +887,20 @@ mod tests {
890887
/// SAFETY: access is read only
891888
unsafe impl ReadOnlyQueryData for ReadsRData {}
892889

893-
/// SAFETY:
894-
/// `update_component_access` adds resource read access for `R`.
895-
/// `update_archetype_component_access` does nothing, as this accesses no components.
896-
unsafe impl WorldQuery for WritesRData {
897-
type Item<'w> = ();
898-
type Fetch<'w> = ();
899-
type State = ComponentId;
900-
901-
fn shrink<'wlong: 'wshort, 'wshort>(_item: Self::Item<'wlong>) -> Self::Item<'wshort> {}
902-
903-
fn shrink_fetch<'wlong: 'wshort, 'wshort>(_: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {}
904-
905-
unsafe fn init_fetch<'w>(
906-
_world: UnsafeWorldCell<'w>,
907-
_state: &Self::State,
908-
_last_run: Tick,
909-
_this_run: Tick,
910-
) -> Self::Fetch<'w> {
911-
}
912-
913-
const IS_DENSE: bool = true;
914-
915-
#[inline]
916-
unsafe fn set_archetype<'w>(
917-
_fetch: &mut Self::Fetch<'w>,
918-
_state: &Self::State,
919-
_archetype: &'w Archetype,
920-
_table: &Table,
921-
) {
922-
}
923-
924-
#[inline]
925-
unsafe fn set_table<'w>(
926-
_fetch: &mut Self::Fetch<'w>,
927-
_state: &Self::State,
928-
_table: &'w Table,
929-
) {
930-
}
931-
932-
#[inline(always)]
933-
unsafe fn fetch<'w>(
934-
_fetch: &mut Self::Fetch<'w>,
935-
_entity: Entity,
936-
_table_row: TableRow,
937-
) -> Self::Item<'w> {
938-
}
939-
940-
fn update_component_access(
941-
&component_id: &Self::State,
942-
access: &mut FilteredAccess<ComponentId>,
943-
) {
944-
assert!(
945-
!access.access().has_resource_read(component_id),
946-
"WritesRData conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.",
947-
);
948-
access.add_resource_write(component_id);
949-
}
950-
951-
fn init_state(world: &mut World) -> Self::State {
952-
world.components.register_resource::<R>()
953-
}
954-
955-
fn get_state(components: &Components) -> Option<Self::State> {
956-
components.resource_id::<R>()
957-
}
958-
959-
fn matches_component_set(
960-
_state: &Self::State,
961-
_set_contains_id: &impl Fn(ComponentId) -> bool,
962-
) -> bool {
963-
true
964-
}
965-
}
966-
967-
/// SAFETY: `Self` is the same as `Self::ReadOnly`
968-
unsafe impl QueryData for WritesRData {
969-
type ReadOnly = ReadsRData;
970-
}
971-
972890
#[test]
973891
fn read_res_read_res_no_conflict() {
974892
fn system(_q1: Query<ReadsRData, With<A>>, _q2: Query<ReadsRData, Without<A>>) {}
975893
assert_is_system(system);
976894
}
977895

978896
#[test]
979-
#[should_panic]
980-
fn read_res_write_res_conflict() {
981-
fn system(_q1: Query<ReadsRData, With<A>>, _q2: Query<WritesRData, Without<A>>) {}
982-
assert_is_system(system);
983-
}
984-
985-
#[test]
986-
#[should_panic]
987-
fn write_res_read_res_conflict() {
988-
fn system(_q1: Query<WritesRData, With<A>>, _q2: Query<ReadsRData, Without<A>>) {}
989-
assert_is_system(system);
990-
}
991-
992-
#[test]
993-
#[should_panic]
994-
fn write_res_write_res_conflict() {
995-
fn system(_q1: Query<WritesRData, With<A>>, _q2: Query<WritesRData, Without<A>>) {}
996-
assert_is_system(system);
997-
}
998-
999-
#[test]
1000-
fn read_write_res_sets_archetype_component_access() {
897+
fn read_res_sets_archetype_component_access() {
1001898
let mut world = World::new();
1002899

1003900
fn read_query(_q: Query<ReadsRData, With<A>>) {}
1004901
let mut read_query = IntoSystem::into_system(read_query);
1005902
read_query.initialize(&mut world);
1006903

1007-
fn write_query(_q: Query<WritesRData, With<A>>) {}
1008-
let mut write_query = IntoSystem::into_system(write_query);
1009-
write_query.initialize(&mut world);
1010-
1011904
fn read_res(_r: Res<R>) {}
1012905
let mut read_res = IntoSystem::into_system(read_res);
1013906
read_res.initialize(&mut world);
@@ -1019,14 +912,8 @@ mod tests {
1019912
assert!(read_query
1020913
.archetype_component_access()
1021914
.is_compatible(read_res.archetype_component_access()));
1022-
assert!(!write_query
1023-
.archetype_component_access()
1024-
.is_compatible(read_res.archetype_component_access()));
1025915
assert!(!read_query
1026916
.archetype_component_access()
1027917
.is_compatible(write_res.archetype_component_access()));
1028-
assert!(!write_query
1029-
.archetype_component_access()
1030-
.is_compatible(write_res.archetype_component_access()));
1031918
}
1032919
}

crates/bevy_ecs/src/query/state.rs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -199,13 +199,10 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
199199
}
200200
}
201201

202-
if state.component_access.access().has_write_all_resources() {
203-
access.write_all_resources();
204-
} else {
205-
for component_id in state.component_access.access().resource_writes() {
206-
access.add_resource_write(world.initialize_resource_internal(component_id).id());
207-
}
208-
}
202+
debug_assert!(
203+
!state.component_access.access().has_any_resource_write(),
204+
"Mutable resource access in queries is not allowed"
205+
);
209206

210207
state
211208
}

crates/bevy_ecs/src/query/world_query.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use variadics_please::all_tuples;
1414
/// # Safety
1515
///
1616
/// Implementor must ensure that
17-
/// [`update_component_access`], [`matches_component_set`], and [`fetch`]
17+
/// [`update_component_access`], [`matches_component_set`], [`fetch`] and [`init_fetch`]
1818
/// obey the following:
1919
///
2020
/// - For each component mutably accessed by [`fetch`], [`update_component_access`] should add write access unless read or write access has already been added, in which case it should panic.
@@ -26,8 +26,8 @@ use variadics_please::all_tuples;
2626
/// - [`matches_component_set`] must be a disjunction of the element's implementations
2727
/// - [`update_component_access`] must replace the filters with a disjunction of filters
2828
/// - Each filter in that disjunction must be a conjunction of the corresponding element's filter with the previous `access`
29-
/// - For each resource mutably accessed by [`init_fetch`], [`update_component_access`] should add write access unless read or write access has already been added, in which case it should panic.
30-
/// - For each resource readonly accessed by [`init_fetch`], [`update_component_access`] should add read access unless write access has already been added, in which case it should panic.
29+
/// - For each resource readonly accessed by [`init_fetch`], [`update_component_access`] should add read access.
30+
/// - Mutable resource access is not allowed.
3131
///
3232
/// When implementing [`update_component_access`], note that `add_read` and `add_write` both also add a `With` filter, whereas `extend_access` does not change the filters.
3333
///
@@ -60,11 +60,14 @@ pub unsafe trait WorldQuery {
6060
fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort>;
6161

6262
/// Creates a new instance of this fetch.
63+
/// Readonly accesses resources registered in [`WorldQuery::update_component_access`].
6364
///
6465
/// # Safety
6566
///
6667
/// - `state` must have been initialized (via [`WorldQuery::init_state`]) using the same `world` passed
6768
/// in to this function.
69+
/// - `world` must have the **right** to access any access registered in `update_component_access`.
70+
/// - There must not be simultaneous resource access conflicting with readonly resource access registered in [`WorldQuery::update_component_access`].
6871
unsafe fn init_fetch<'w>(
6972
world: UnsafeWorldCell<'w>,
7073
state: &Self::State,
@@ -114,11 +117,13 @@ pub unsafe trait WorldQuery {
114117
/// or for the given `entity` in the current [`Archetype`]. This must always be called after
115118
/// [`WorldQuery::set_table`] with a `table_row` in the range of the current [`Table`] or after
116119
/// [`WorldQuery::set_archetype`] with a `entity` in the current archetype.
120+
/// Accesses components registered in [`WorldQuery::update_component_access`].
117121
///
118122
/// # Safety
119123
///
120-
/// Must always be called _after_ [`WorldQuery::set_table`] or [`WorldQuery::set_archetype`]. `entity` and
121-
/// `table_row` must be in the range of the current table and archetype.
124+
/// - Must always be called _after_ [`WorldQuery::set_table`] or [`WorldQuery::set_archetype`]. `entity` and
125+
/// `table_row` must be in the range of the current table and archetype.
126+
/// - There must not be simultaneous conflicting component access registered in `update_component_access`.
122127
unsafe fn fetch<'w>(
123128
fetch: &mut Self::Fetch<'w>,
124129
entity: Entity,

0 commit comments

Comments
 (0)