From 2f73107fa64061b9abb082d3d796b6fdacafaba7 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Wed, 25 Feb 2026 03:13:40 +0100 Subject: [PATCH 1/4] add failing test --- crates/bevy_asset/src/asset_changed.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/crates/bevy_asset/src/asset_changed.rs b/crates/bevy_asset/src/asset_changed.rs index 0562a857d047d..d1ee39885655a 100644 --- a/crates/bevy_asset/src/asset_changed.rs +++ b/crates/bevy_asset/src/asset_changed.rs @@ -304,7 +304,7 @@ mod tests { component::Component, message::MessageWriter, resource::Resource, - system::{Commands, IntoSystem, Local, Query, Res, ResMut}, + system::{assert_is_system, Commands, IntoSystem, Local, Query, Res, ResMut}, }; use bevy_reflect::TypePath; @@ -324,6 +324,21 @@ mod tests { } } + #[test] + #[should_panic] + fn should_conflict() { + #[derive(Component)] + struct Foo; + + // should conflict, doesn't. + fn system( + _: Query<&Foo, AssetChanged>, + _: Query<&mut AssetChanges, bevy_ecs::query::Without>, + ) { + } + assert_is_system(system); + } + fn run_app(system: impl IntoSystem<(), (), Marker>) { let mut app = create_app().0; app.init_asset::().add_systems(Update, system); From c8343fe5f0fbf57ef0a802c7adc4cf49a11af929 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Wed, 25 Feb 2026 03:29:33 +0100 Subject: [PATCH 2/4] fixed the broken test --- crates/bevy_asset/src/asset_changed.rs | 37 ++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/crates/bevy_asset/src/asset_changed.rs b/crates/bevy_asset/src/asset_changed.rs index d1ee39885655a..76a9f908ce193 100644 --- a/crates/bevy_asset/src/asset_changed.rs +++ b/crates/bevy_asset/src/asset_changed.rs @@ -10,11 +10,13 @@ use bevy_ecs::{ change_detection::Tick, component::ComponentId, prelude::{Entity, Resource, World}, - query::{FilteredAccess, QueryData, QueryFilter, ReadFetch, WorldQuery}, + query::{FilteredAccess, FilteredAccessSet, QueryData, QueryFilter, ReadFetch, WorldQuery}, + resource::IS_RESOURCE, storage::{Table, TableRow}, world::unsafe_world_cell::UnsafeWorldCell, }; use bevy_platform::collections::HashMap; +use bevy_utils::prelude::DebugName; use core::marker::PhantomData; use disqualified::ShortName; use tracing::error; @@ -235,7 +237,38 @@ unsafe impl WorldQuery for AssetChanged { #[inline] fn update_component_access(state: &Self::State, access: &mut FilteredAccess) { <&A>::update_component_access(&state.asset_id, access); - access.add_resource_read(state.resource_id); + } + + // ChangedAsset accesses both the asset and the AssetChanges resource. + // In order to access two different entities we implement init_nested_access. + fn init_nested_access( + state: &Self::State, + system_name: Option<&str>, + component_access_set: &mut FilteredAccessSet, + _world: UnsafeWorldCell, + ) { + let combined_access = component_access_set.combined_access(); + assert!( + !combined_access.has_resource_write(state.resource_id), + "error[B0002]: AssetChanged<{}> in system {:?} conflicts with a previous system param. Consider removing the duplicate access. See: https://bevy.org/learn/errors/b0002", + DebugName::type_name::(), + system_name, + ); + + let mut filter = FilteredAccess::default(); + filter.add_component_read(state.resource_id); + filter.add_resource_read(state.resource_id); + filter.and_with(IS_RESOURCE); + + assert!(component_access_set + .get_conflicts_single(&filter) + .is_empty(), + "error[B0002]: AssetChanged<{}> in system {:?} conflicts with a previous system param. Consider removing the duplicate access. See: https://bevy.org/learn/errors/b0002", + DebugName::type_name::(), + system_name, + ); + + component_access_set.add(filter); } fn init_state(world: &mut World) -> AssetChangedState { From 3e3d8e55ae23344a30d5bcb247f0c30637f8bd32 Mon Sep 17 00:00:00 2001 From: Trashtalk217 Date: Thu, 26 Feb 2026 16:13:13 +0100 Subject: [PATCH 3/4] Update crates/bevy_asset/src/asset_changed.rs Co-authored-by: Chris Russell <8494645+chescock@users.noreply.github.com> --- crates/bevy_asset/src/asset_changed.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/bevy_asset/src/asset_changed.rs b/crates/bevy_asset/src/asset_changed.rs index 76a9f908ce193..0dbc3f47d5b2c 100644 --- a/crates/bevy_asset/src/asset_changed.rs +++ b/crates/bevy_asset/src/asset_changed.rs @@ -363,7 +363,6 @@ mod tests { #[derive(Component)] struct Foo; - // should conflict, doesn't. fn system( _: Query<&Foo, AssetChanged>, _: Query<&mut AssetChanges, bevy_ecs::query::Without>, From 0016c0c6b4e6c231990b3bf7f3380b0f8ec4c7b0 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Thu, 26 Feb 2026 16:17:43 +0100 Subject: [PATCH 4/4] fixed comment --- crates/bevy_asset/src/asset_changed.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/bevy_asset/src/asset_changed.rs b/crates/bevy_asset/src/asset_changed.rs index 0dbc3f47d5b2c..b1d10425711e7 100644 --- a/crates/bevy_asset/src/asset_changed.rs +++ b/crates/bevy_asset/src/asset_changed.rs @@ -170,9 +170,8 @@ unsafe impl WorldQuery for AssetChanged { // SAFETY: // - `state.resource_id` was obtained from `world.init_resource::>()`, // so the untyped pointer returned by `get_resource_by_id` can safely be dereferenced into that type. - // - `update_component_access` declares a read on `state.resource_id`, so it is safe to - // read that resource here (see trait-level safety comments on `WorldQuery`, regarding - // readonly resource access in `init_fetch`) + // - `init_nested_access` declares a read on `state.resource_id`, so it is safe to + // read that resource here (see trait-level safety comments on `WorldQuery`) let Some(changes) = (unsafe { world .get_resource_by_id(state.resource_id)