From da3a1fcab3cdf05d6aebdf333516def412a3fe63 Mon Sep 17 00:00:00 2001 From: Nicola Papale Date: Thu, 3 Aug 2023 12:21:24 +0200 Subject: [PATCH 01/23] Add the `AssetChanged` query filter --- crates/bevy_asset/src/asset_changed.rs | 429 +++++++++++++++++++++++++ crates/bevy_asset/src/assets.rs | 21 +- crates/bevy_asset/src/lib.rs | 4 + 3 files changed, 452 insertions(+), 2 deletions(-) create mode 100644 crates/bevy_asset/src/asset_changed.rs diff --git a/crates/bevy_asset/src/asset_changed.rs b/crates/bevy_asset/src/asset_changed.rs new file mode 100644 index 0000000000000..3c09ed2726561 --- /dev/null +++ b/crates/bevy_asset/src/asset_changed.rs @@ -0,0 +1,429 @@ +//! Filter query to limit iteration over [`Handle`]s which content was updated recently. +use std::marker::PhantomData; + +use bevy_ecs::{ + archetype::{Archetype, ArchetypeComponentId}, + component::{ComponentId, Tick}, + prelude::{Changed, Entity, Or, Resource, World}, + query::{Access, FilteredAccess, QueryItem, ReadFetch, WorldQuery, WorldQueryFilter}, + storage::{Table, TableRow}, + world::unsafe_world_cell::UnsafeWorldCell, +}; +use bevy_utils::{get_short_name, HashMap}; + +use crate::{Asset, AssetId, Handle}; + +#[doc(hidden)] +#[derive(Resource)] +pub struct AssetChanges { + change_ticks: HashMap, Tick>, + last_change_tick: Tick, +} + +impl AssetChanges { + pub(crate) fn insert(&mut self, asset_id: AssetId, tick: Tick) { + self.last_change_tick = tick; + self.change_ticks.insert(asset_id, tick); + } + pub(crate) fn remove(&mut self, asset_id: &AssetId) { + self.change_ticks.remove(asset_id); + } +} +impl Default for AssetChanges { + fn default() -> Self { + Self { + change_ticks: Default::default(), + last_change_tick: Tick::new(0), + } + } +} + +struct AssetChangeCheck<'w, A: Asset> { + handle_change_map: &'w HashMap, Tick>, + last_run: Tick, + this_run: Tick, +} +impl Clone for AssetChangeCheck<'_, A> { + fn clone(&self) -> Self { + *self + } +} +impl Copy for AssetChangeCheck<'_, A> {} +impl<'w, A: Asset> AssetChangeCheck<'w, A> { + fn new(changes: &'w AssetChanges, last_run: Tick, this_run: Tick) -> Self { + Self { + handle_change_map: &changes.change_ticks, + last_run, + this_run, + } + } + // TODO(perf): some sort of caching? Each check has two levels of indirection, + // which is not optimal. + fn has_changed(&self, handle: &Handle) -> bool { + let is_newer = |tick: &Tick| tick.is_newer_than(self.last_run, self.this_run); + let id = &handle.id(); + + self.handle_change_map.get(id).is_some_and(is_newer) + } +} + +/// A shortcut for the commonly used `Or<(Changed>, AssetChanged)>` +/// query filter. +/// +/// If you want to react to changes to `Handle`, you need to: +/// - Check if the `Handle` was changed through a `Query<&mut Handle>`. +/// - Check if the `A` in `Assets` pointed by `Handle` was changed through an +/// [`Assets::get_mut`]. +/// +/// To properly handle both cases, you need to combine the `Changed` and `AssetChanged` +/// filters. This query filter is exactly this. +/// +/// [`Assets::get_mut`]: crate::Assets::get_mut +pub type AssetOrHandleChanged = Or<(Changed>, AssetChanged)>; + +/// Filter that selects entities with a `Handle` for an asset that changed +/// after the system last ran. +/// +/// Unlike `Changed>`, this is true whenever the asset for the `Handle` +/// in `ResMut>` changed. For example, when a mesh changed through the +/// [`Assets::get_mut`] method, `AssetChanged` will iterate over all +/// entities with the `Handle` for that mesh. Meanwhile, `Changed>` +/// will iterate over no entities. +/// +/// You probably need to check for both asset and handle changes. If this is the case, +/// use [`AssetOrHandleChanged`] instead. +/// +/// # Quirks +/// +/// - Asset changes are registered in the [`AssetEvents`] schedule. +/// - Removed assets are not detected. +/// - Asset update tracking only starts after the first system with an +/// `AssetChanged` query parameter ran for the first time. +/// +/// This means that assets added before the system ran won't be detected +/// (for example in a `Startup` schedule). +/// +/// This is also true of systems gated by a `run_if` condition. +/// +/// The list of changed assets only gets updated in the +/// [`AssetEvents`] schedule, just after `PostUpdate`. Therefore, `AssetChanged` +/// will only pick up asset changes in schedules following `AssetEvents` or the +/// next frame. Consider adding the system in the `Last` schedule if you need +/// to react without frame delay to asset changes. +/// +/// # Performance +/// +/// When at least one `A` is updated, this will +/// read a hashmap once per entity with a `Handle` component. The +/// runtime of the query is proportional to how many entities with a `Handle` +/// it matches. +/// +/// If no `A` asset updated since the last time the system ran, then no lookups occur. +/// +/// [`AssetEvents`]: crate::AssetEvents +/// [`Assets::get_mut`]: crate::Assets::get_mut +pub struct AssetChanged(PhantomData); + +/// Fetch for [`AssetChanged`]. +#[doc(hidden)] +pub struct AssetChangedFetch<'w, A: Asset> { + inner: Option>>, + check: AssetChangeCheck<'w, A>, +} +impl<'w, A: Asset> Clone for AssetChangedFetch<'w, A> { + fn clone(&self) -> Self { + Self { + inner: self.inner, + check: self.check, + } + } +} + +/// State for [`AssetChanged`]. +#[doc(hidden)] +pub struct AssetChangedState { + handle_id: ComponentId, + resource_id: ComponentId, + archetype_id: ArchetypeComponentId, + _asset: PhantomData, +} + +// SAFETY: `ROQueryFetch` is the same as `QueryFetch` +unsafe impl WorldQuery for AssetChanged { + type Fetch<'w> = AssetChangedFetch<'w, A>; + type State = AssetChangedState; + + type Item<'w> = (); + + fn shrink<'wlong: 'wshort, 'wshort>(item: QueryItem<'wlong, Self>) -> QueryItem<'wshort, Self> { + item + } + + fn init_state(world: &mut World) -> AssetChangedState { + let resource_id = world.init_resource::>(); + let archetype_id = world.storages().resources.get(resource_id).unwrap().id(); + AssetChangedState { + handle_id: world.init_component::>(), + resource_id, + archetype_id, + _asset: PhantomData, + } + } + + fn matches_component_set( + state: &Self::State, + set_contains_id: &impl Fn(ComponentId) -> bool, + ) -> bool { + set_contains_id(state.handle_id) + } + unsafe fn init_fetch<'w>( + world: UnsafeWorldCell<'w>, + state: &Self::State, + last_run: Tick, + this_run: Tick, + ) -> Self::Fetch<'w> { + let err_msg = || { + panic!( + "AssetChanges<{ty}> resource was removed, please do not remove \ + AssetChanges<{ty}> when using the AssetChanged<{ty}> world query", + ty = get_short_name(std::any::type_name::()) + ) + }; + let changes: &AssetChanges<_> = world.get_resource().unwrap_or_else(err_msg); + let has_updates = changes.last_change_tick.is_newer_than(last_run, this_run); + AssetChangedFetch { + inner: has_updates + .then(|| <&_>::init_fetch(world, &state.handle_id, last_run, this_run)), + check: AssetChangeCheck::new(changes, last_run, this_run), + } + } + + const IS_DENSE: bool = <&Handle>::IS_DENSE; + + unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, table: &'w Table) { + if let Some(inner) = &mut fetch.inner { + <&Handle>::set_table(inner, &state.handle_id, table); + } + } + + unsafe fn set_archetype<'w>( + fetch: &mut Self::Fetch<'w>, + state: &Self::State, + archetype: &'w Archetype, + table: &'w Table, + ) { + if let Some(inner) = &mut fetch.inner { + <&Handle>::set_archetype(inner, &state.handle_id, archetype, table); + } + } + + unsafe fn fetch<'w>(_: &mut Self::Fetch<'w>, _: Entity, _: TableRow) -> Self::Item<'w> { + () + } + + #[inline] + fn update_component_access(state: &Self::State, access: &mut FilteredAccess) { + <&Handle>::update_component_access(&state.handle_id, access); + access.add_read(state.resource_id); + } + + #[inline] + fn update_archetype_component_access( + state: &Self::State, + archetype: &Archetype, + access: &mut Access, + ) { + access.add_read(state.archetype_id); + <&Handle>::update_archetype_component_access(&state.handle_id, archetype, access); + } +} + +/// SAFETY: read-only access +impl WorldQueryFilter for AssetChanged { + const IS_ARCHETYPAL: bool = false; + + #[inline] + unsafe fn filter_fetch( + fetch: &mut Self::Fetch<'_>, + entity: Entity, + table_row: TableRow, + ) -> bool { + fetch.inner.as_mut().map_or(false, |inner| { + let handle = <&Handle>::fetch(inner, entity, table_row); + fetch.check.has_changed(handle) + }) + } +} + +#[cfg(test)] +mod tests { + use crate::{self as bevy_asset, AssetPlugin}; + + use crate::{AssetApp, Assets}; + use bevy_app::{App, AppExit, Last, Startup, Update}; + use bevy_core::TaskPoolPlugin; + use bevy_ecs::{ + event::EventWriter, + system::{Commands, IntoSystem, Local, Query, Res, ResMut, Resource}, + }; + use bevy_reflect::TypePath; + + use super::*; + + #[derive(Asset, TypePath, Debug)] + struct MyAsset(usize, &'static str); + + fn run_app(system: impl IntoSystem<(), (), Marker>) { + let mut app = App::new(); + app.add_plugins((TaskPoolPlugin::default(), AssetPlugin::default())) + .init_asset::() + .add_systems(Update, system); + app.update(); + } + + // According to a comment in QueryState::new in bevy_ecs, components on filter + // position shouldn't conflict with components on query position. + #[test] + fn handle_filter_pos_ok() { + fn compatible_filter( + _query: Query<&mut Handle, AssetChanged>, + mut exit: EventWriter, + ) { + exit.send(AppExit); + } + run_app(compatible_filter); + } + + #[derive(Default, PartialEq, Debug, Resource)] + struct Counter(Vec); + + fn count_update( + mut counter: ResMut, + assets: Res>, + query: Query<&Handle, AssetChanged>, + ) { + for handle in query.iter() { + let asset = assets.get(handle).unwrap(); + counter.0[asset.0] += 1; + } + } + + fn update_some(mut assets: ResMut>, mut run_count: Local) { + let mut update_index = |i| { + let id = assets + .iter() + .find_map(|(h, a)| (a.0 == i).then_some(h)) + .unwrap(); + let asset = assets.get_mut(id).unwrap(); + println!("setting new value for {}", asset.0); + asset.1 = "new_value"; + }; + match *run_count { + 0 | 1 => update_index(0), + 2 => {} + 3 => { + update_index(0); + update_index(1); + } + 4.. => update_index(1), + }; + *run_count += 1; + } + fn add_some( + mut assets: ResMut>, + mut cmds: Commands, + mut run_count: Local, + ) { + match *run_count { + 1 => { + cmds.spawn(assets.add(MyAsset(0, "init"))); + } + 0 | 2 => {} + 3 => { + cmds.spawn(assets.add(MyAsset(1, "init"))); + cmds.spawn(assets.add(MyAsset(2, "init"))); + } + 4.. => { + cmds.spawn(assets.add(MyAsset(3, "init"))); + } + }; + *run_count += 1; + } + #[track_caller] + fn assert_counter(app: &App, assert: Counter) { + assert_eq!(&assert, app.world.resource::()); + } + + #[test] + fn added() { + let mut app = App::new(); + + app.add_plugins((TaskPoolPlugin::default(), AssetPlugin::default())) + .init_asset::() + .insert_resource(Counter(vec![0, 0, 0, 0])) + .add_systems(Update, add_some) + .add_systems(Last, count_update); + + // First run of the app, `add_systems(Startup…)` runs. + app.update(); // run_count == 0 + assert_counter(&app, Counter(vec![0, 0, 0, 0])); + app.update(); // run_count == 1 + assert_counter(&app, Counter(vec![1, 0, 0, 0])); + app.update(); // run_count == 2 + assert_counter(&app, Counter(vec![1, 0, 0, 0])); + app.update(); // run_count == 3 + assert_counter(&app, Counter(vec![1, 1, 1, 0])); + app.update(); // run_count == 4 + assert_counter(&app, Counter(vec![1, 1, 1, 1])); + } + + #[test] + fn changed() { + let mut app = App::new(); + + app.add_plugins((TaskPoolPlugin::default(), AssetPlugin::default())) + .init_asset::() + .insert_resource(Counter(vec![0, 0])) + .add_systems( + Startup, + |mut cmds: Commands, mut assets: ResMut>| { + let asset0 = assets.add(MyAsset(0, "init")); + let asset1 = assets.add(MyAsset(1, "init")); + cmds.spawn(asset0.clone()); + cmds.spawn(asset0); + cmds.spawn(asset1.clone()); + cmds.spawn(asset1.clone()); + cmds.spawn(asset1); + }, + ) + .add_systems(Update, update_some) + .add_systems(Last, count_update); + + // First run of the app, `add_systems(Startup…)` runs. + app.update(); // run_count == 0 + + // `AssetChanges` do not react to `Added` or `Modified` events that occured before + // the first run of `count_update`. This is why the counters are still 0 + assert_counter(&app, Counter(vec![0, 0])); + + // Second run: `update_once` updates the first asset, which is + // associated with two entities, so `count_update` picks up two updates + app.update(); // run_count == 1 + assert_counter(&app, Counter(vec![2, 0])); + + // Third run: `update_once` doesn't update anything, same values as last + app.update(); // run_count == 2 + assert_counter(&app, Counter(vec![2, 0])); + + // Fourth run: We update the two assets (asset 0: 2 entities, asset 1: 3) + app.update(); // run_count == 3 + assert_counter(&app, Counter(vec![4, 3])); + + // Fifth run: only update second asset + app.update(); // run_count == 4 + assert_counter(&app, Counter(vec![4, 6])); + // ibid + app.update(); // run_count == 5 + assert_counter(&app, Counter(vec![4, 9])); + } +} diff --git a/crates/bevy_asset/src/assets.rs b/crates/bevy_asset/src/assets.rs index 3c33aa43faf91..0a2154a1ee79b 100644 --- a/crates/bevy_asset/src/assets.rs +++ b/crates/bevy_asset/src/assets.rs @@ -5,7 +5,7 @@ use crate::{ use alloc::sync::Arc; use bevy_ecs::{ prelude::EventWriter, - system::{Res, ResMut, Resource}, + system::{Res, ResMut, Resource, SystemChangeTick}, }; use bevy_reflect::{Reflect, TypePath}; use bevy_utils::HashMap; @@ -559,7 +559,24 @@ impl Assets { /// A system that applies accumulated asset change events to the [`Events`] resource. /// /// [`Events`]: bevy_ecs::event::Events - pub fn asset_events(mut assets: ResMut, mut events: EventWriter>) { + pub fn asset_events( + mut assets: ResMut, + mut events: EventWriter>, + asset_changes: Option>>, + ticks: SystemChangeTick, + ) { + use AssetEvent::{Added, LoadedWithDependencies, Modified, Removed}; + + if let Some(mut asset_changes) = asset_changes { + for new_event in &assets.queued_events { + match new_event { + Removed { id } => asset_changes.remove(id), + Added { id } | Modified { id } | LoadedWithDependencies { id } => { + asset_changes.insert(*id, ticks.this_run()); + } + }; + } + } events.send_batch(assets.queued_events.drain(..)); } diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index d252946c3c186..65f5538f74483 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -159,6 +159,9 @@ pub mod transformer; /// /// This includes the most common types in this crate, re-exported for your convenience. pub mod prelude { + #[doc(hidden)] + pub use crate::asset_changed::{AssetChanged, AssetOrHandleChanged}; + #[doc(hidden)] pub use crate::{ Asset, AssetApp, AssetEvent, AssetId, AssetMode, AssetPlugin, AssetServer, Assets, @@ -166,6 +169,7 @@ pub mod prelude { }; } +mod asset_changed; mod assets; mod direct_access_ext; mod event; From ce19c2f08db71bb150116f9a25d7d27004d7c380 Mon Sep 17 00:00:00 2001 From: Nicola Papale Date: Wed, 6 Dec 2023 17:00:18 +0100 Subject: [PATCH 02/23] Implement djeedai feedbacks --- crates/bevy_asset/src/asset_changed.rs | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/crates/bevy_asset/src/asset_changed.rs b/crates/bevy_asset/src/asset_changed.rs index 3c09ed2726561..4a54845c8c2db 100644 --- a/crates/bevy_asset/src/asset_changed.rs +++ b/crates/bevy_asset/src/asset_changed.rs @@ -1,4 +1,6 @@ -//! Filter query to limit iteration over [`Handle`]s which content was updated recently. +//! Define the [`AssetChanged`] query filter. +//! +//! Like [`Changed`], but for [`Asset`]s. use std::marker::PhantomData; use bevy_ecs::{ @@ -29,6 +31,7 @@ impl AssetChanges { self.change_ticks.remove(asset_id); } } + impl Default for AssetChanges { fn default() -> Self { Self { @@ -39,20 +42,23 @@ impl Default for AssetChanges { } struct AssetChangeCheck<'w, A: Asset> { - handle_change_map: &'w HashMap, Tick>, + change_ticks: &'w HashMap, Tick>, last_run: Tick, this_run: Tick, } + impl Clone for AssetChangeCheck<'_, A> { fn clone(&self) -> Self { *self } } + impl Copy for AssetChangeCheck<'_, A> {} + impl<'w, A: Asset> AssetChangeCheck<'w, A> { fn new(changes: &'w AssetChanges, last_run: Tick, this_run: Tick) -> Self { Self { - handle_change_map: &changes.change_ticks, + change_ticks: &changes.change_ticks, last_run, this_run, } @@ -63,7 +69,7 @@ impl<'w, A: Asset> AssetChangeCheck<'w, A> { let is_newer = |tick: &Tick| tick.is_newer_than(self.last_run, self.this_run); let id = &handle.id(); - self.handle_change_map.get(id).is_some_and(is_newer) + self.change_ticks.get(id).is_some_and(is_newer) } } @@ -90,8 +96,9 @@ pub type AssetOrHandleChanged = Or<(Changed>, AssetChanged)>; /// entities with the `Handle` for that mesh. Meanwhile, `Changed>` /// will iterate over no entities. /// -/// You probably need to check for both asset and handle changes. If this is the case, -/// use [`AssetOrHandleChanged`] instead. +/// Swapping the actual `Handle` component is a common pattern. So you +/// should check for _both_ `AssetChanged` and `Changed>` with +/// [`AssetOrHandleChanged`] /// /// # Quirks /// @@ -130,6 +137,7 @@ pub struct AssetChangedFetch<'w, A: Asset> { inner: Option>>, check: AssetChangeCheck<'w, A>, } + impl<'w, A: Asset> Clone for AssetChangedFetch<'w, A> { fn clone(&self) -> Self { Self { @@ -176,6 +184,7 @@ unsafe impl WorldQuery for AssetChanged { ) -> bool { set_contains_id(state.handle_id) } + unsafe fn init_fetch<'w>( world: UnsafeWorldCell<'w>, state: &Self::State, @@ -329,6 +338,7 @@ mod tests { }; *run_count += 1; } + fn add_some( mut assets: ResMut>, mut cmds: Commands, @@ -349,6 +359,7 @@ mod tests { }; *run_count += 1; } + #[track_caller] fn assert_counter(app: &App, assert: Counter) { assert_eq!(&assert, app.world.resource::()); From 5dbfea9fc322c8515c65db2f7db5bdb7fc8af159 Mon Sep 17 00:00:00 2001 From: Nicola Papale Date: Wed, 6 Dec 2023 17:04:20 +0100 Subject: [PATCH 03/23] Fix clippy lints --- crates/bevy_asset/src/asset_changed.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/crates/bevy_asset/src/asset_changed.rs b/crates/bevy_asset/src/asset_changed.rs index 4a54845c8c2db..9d8d2811ba83d 100644 --- a/crates/bevy_asset/src/asset_changed.rs +++ b/crates/bevy_asset/src/asset_changed.rs @@ -163,9 +163,7 @@ unsafe impl WorldQuery for AssetChanged { type Item<'w> = (); - fn shrink<'wlong: 'wshort, 'wshort>(item: QueryItem<'wlong, Self>) -> QueryItem<'wshort, Self> { - item - } + fn shrink<'wlong: 'wshort, 'wshort>(_: QueryItem<'wlong, Self>) -> QueryItem<'wshort, Self> {} fn init_state(world: &mut World) -> AssetChangedState { let resource_id = world.init_resource::>(); @@ -226,9 +224,7 @@ unsafe impl WorldQuery for AssetChanged { } } - unsafe fn fetch<'w>(_: &mut Self::Fetch<'w>, _: Entity, _: TableRow) -> Self::Item<'w> { - () - } + unsafe fn fetch<'w>(_: &mut Self::Fetch<'w>, _: Entity, _: TableRow) -> Self::Item<'w> {} #[inline] fn update_component_access(state: &Self::State, access: &mut FilteredAccess) { From 03d4848e2be4e93d6ebab8a96a899e54c1795327 Mon Sep 17 00:00:00 2001 From: Nicola Papale Date: Wed, 6 Dec 2023 17:53:01 +0100 Subject: [PATCH 04/23] Rename handle_id --- crates/bevy_asset/src/asset_changed.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/bevy_asset/src/asset_changed.rs b/crates/bevy_asset/src/asset_changed.rs index 9d8d2811ba83d..d11a3d1644532 100644 --- a/crates/bevy_asset/src/asset_changed.rs +++ b/crates/bevy_asset/src/asset_changed.rs @@ -150,7 +150,7 @@ impl<'w, A: Asset> Clone for AssetChangedFetch<'w, A> { /// State for [`AssetChanged`]. #[doc(hidden)] pub struct AssetChangedState { - handle_id: ComponentId, + asset_id: ComponentId, resource_id: ComponentId, archetype_id: ArchetypeComponentId, _asset: PhantomData, @@ -169,7 +169,7 @@ unsafe impl WorldQuery for AssetChanged { let resource_id = world.init_resource::>(); let archetype_id = world.storages().resources.get(resource_id).unwrap().id(); AssetChangedState { - handle_id: world.init_component::>(), + asset_id: world.init_component::>(), resource_id, archetype_id, _asset: PhantomData, @@ -180,7 +180,7 @@ unsafe impl WorldQuery for AssetChanged { state: &Self::State, set_contains_id: &impl Fn(ComponentId) -> bool, ) -> bool { - set_contains_id(state.handle_id) + set_contains_id(state.asset_id) } unsafe fn init_fetch<'w>( @@ -200,7 +200,7 @@ unsafe impl WorldQuery for AssetChanged { let has_updates = changes.last_change_tick.is_newer_than(last_run, this_run); AssetChangedFetch { inner: has_updates - .then(|| <&_>::init_fetch(world, &state.handle_id, last_run, this_run)), + .then(|| <&_>::init_fetch(world, &state.asset_id, last_run, this_run)), check: AssetChangeCheck::new(changes, last_run, this_run), } } @@ -209,7 +209,7 @@ unsafe impl WorldQuery for AssetChanged { unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, table: &'w Table) { if let Some(inner) = &mut fetch.inner { - <&Handle>::set_table(inner, &state.handle_id, table); + <&Handle>::set_table(inner, &state.asset_id, table); } } @@ -220,7 +220,7 @@ unsafe impl WorldQuery for AssetChanged { table: &'w Table, ) { if let Some(inner) = &mut fetch.inner { - <&Handle>::set_archetype(inner, &state.handle_id, archetype, table); + <&Handle>::set_archetype(inner, &state.asset_id, archetype, table); } } @@ -228,7 +228,7 @@ unsafe impl WorldQuery for AssetChanged { #[inline] fn update_component_access(state: &Self::State, access: &mut FilteredAccess) { - <&Handle>::update_component_access(&state.handle_id, access); + <&Handle>::update_component_access(&state.asset_id, access); access.add_read(state.resource_id); } @@ -239,7 +239,7 @@ unsafe impl WorldQuery for AssetChanged { access: &mut Access, ) { access.add_read(state.archetype_id); - <&Handle>::update_archetype_component_access(&state.handle_id, archetype, access); + <&Handle>::update_archetype_component_access(&state.asset_id, archetype, access); } } From 5e9bd4aea5e267ba61be6b1caaeb07a76e56f9be Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Sun, 8 Dec 2024 22:31:41 -0800 Subject: [PATCH 05/23] Update to main. --- crates/bevy_asset/src/asset_changed.rs | 76 +++++++++++++------------- crates/bevy_asset/src/assets.rs | 3 +- 2 files changed, 40 insertions(+), 39 deletions(-) diff --git a/crates/bevy_asset/src/asset_changed.rs b/crates/bevy_asset/src/asset_changed.rs index d11a3d1644532..9a816aa6100e7 100644 --- a/crates/bevy_asset/src/asset_changed.rs +++ b/crates/bevy_asset/src/asset_changed.rs @@ -2,16 +2,17 @@ //! //! Like [`Changed`], but for [`Asset`]s. use std::marker::PhantomData; - +use disqualified::ShortName; use bevy_ecs::{ archetype::{Archetype, ArchetypeComponentId}, component::{ComponentId, Tick}, prelude::{Changed, Entity, Or, Resource, World}, - query::{Access, FilteredAccess, QueryItem, ReadFetch, WorldQuery, WorldQueryFilter}, + query::{Access, FilteredAccess, QueryItem, ReadFetch, WorldQuery, QueryFilter}, storage::{Table, TableRow}, world::unsafe_world_cell::UnsafeWorldCell, }; -use bevy_utils::{get_short_name, HashMap}; +use bevy_ecs::component::Components; +use bevy_utils::HashMap; use crate::{Asset, AssetId, Handle}; @@ -158,29 +159,15 @@ pub struct AssetChangedState { // SAFETY: `ROQueryFetch` is the same as `QueryFetch` unsafe impl WorldQuery for AssetChanged { + type Item<'w> = (); type Fetch<'w> = AssetChangedFetch<'w, A>; - type State = AssetChangedState; - type Item<'w> = (); + type State = AssetChangedState; fn shrink<'wlong: 'wshort, 'wshort>(_: QueryItem<'wlong, Self>) -> QueryItem<'wshort, Self> {} - fn init_state(world: &mut World) -> AssetChangedState { - let resource_id = world.init_resource::>(); - let archetype_id = world.storages().resources.get(resource_id).unwrap().id(); - AssetChangedState { - asset_id: world.init_component::>(), - resource_id, - archetype_id, - _asset: PhantomData, - } - } - - fn matches_component_set( - state: &Self::State, - set_contains_id: &impl Fn(ComponentId) -> bool, - ) -> bool { - set_contains_id(state.asset_id) + fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { + fetch } unsafe fn init_fetch<'w>( @@ -193,7 +180,7 @@ unsafe impl WorldQuery for AssetChanged { panic!( "AssetChanges<{ty}> resource was removed, please do not remove \ AssetChanges<{ty}> when using the AssetChanged<{ty}> world query", - ty = get_short_name(std::any::type_name::()) + ty = ShortName::of::() ) }; let changes: &AssetChanges<_> = world.get_resource().unwrap_or_else(err_msg); @@ -207,12 +194,6 @@ unsafe impl WorldQuery for AssetChanged { const IS_DENSE: bool = <&Handle>::IS_DENSE; - unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, table: &'w Table) { - if let Some(inner) = &mut fetch.inner { - <&Handle>::set_table(inner, &state.asset_id, table); - } - } - unsafe fn set_archetype<'w>( fetch: &mut Self::Fetch<'w>, state: &Self::State, @@ -224,6 +205,12 @@ unsafe impl WorldQuery for AssetChanged { } } + unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, table: &'w Table) { + if let Some(inner) = &mut fetch.inner { + <&Handle>::set_table(inner, &state.asset_id, table); + } + } + unsafe fn fetch<'w>(_: &mut Self::Fetch<'w>, _: Entity, _: TableRow) -> Self::Item<'w> {} #[inline] @@ -232,19 +219,31 @@ unsafe impl WorldQuery for AssetChanged { access.add_read(state.resource_id); } - #[inline] - fn update_archetype_component_access( + fn init_state(world: &mut World) -> AssetChangedState { + let resource_id = world.init_resource::>(); + let archetype_id = world.storages().resources.get(resource_id).unwrap().id(); + AssetChangedState { + asset_id: world.init_component::>(), + resource_id, + archetype_id, + _asset: PhantomData, + } + } + + fn get_state(components: &Components) -> Option { + todo!() + } + + fn matches_component_set( state: &Self::State, - archetype: &Archetype, - access: &mut Access, - ) { - access.add_read(state.archetype_id); - <&Handle>::update_archetype_component_access(&state.asset_id, archetype, access); + set_contains_id: &impl Fn(ComponentId) -> bool, + ) -> bool { + set_contains_id(state.asset_id) } } /// SAFETY: read-only access -impl WorldQueryFilter for AssetChanged { +unsafe impl QueryFilter for AssetChanged { const IS_ARCHETYPAL: bool = false; #[inline] @@ -262,6 +261,7 @@ impl WorldQueryFilter for AssetChanged { #[cfg(test)] mod tests { + use std::num::NonZero; use crate::{self as bevy_asset, AssetPlugin}; use crate::{AssetApp, Assets}; @@ -294,7 +294,7 @@ mod tests { _query: Query<&mut Handle, AssetChanged>, mut exit: EventWriter, ) { - exit.send(AppExit); + exit.send(AppExit::Error(NonZero::::MIN)); } run_app(compatible_filter); } @@ -358,7 +358,7 @@ mod tests { #[track_caller] fn assert_counter(app: &App, assert: Counter) { - assert_eq!(&assert, app.world.resource::()); + assert_eq!(&assert, app.world().resource::()); } #[test] diff --git a/crates/bevy_asset/src/assets.rs b/crates/bevy_asset/src/assets.rs index 0a2154a1ee79b..f1334d26da988 100644 --- a/crates/bevy_asset/src/assets.rs +++ b/crates/bevy_asset/src/assets.rs @@ -14,6 +14,7 @@ use crossbeam_channel::{Receiver, Sender}; use serde::{Deserialize, Serialize}; use thiserror::Error; use uuid::Uuid; +use crate::asset_changed::AssetChanges; /// A generational runtime-only identifier for a specific [`Asset`] stored in [`Assets`]. This is optimized for efficient runtime /// usage and is not suitable for identifying assets across app runs. @@ -570,7 +571,7 @@ impl Assets { if let Some(mut asset_changes) = asset_changes { for new_event in &assets.queued_events { match new_event { - Removed { id } => asset_changes.remove(id), + Removed { id } | AssetEvent::Unused { id } => asset_changes.remove(id), Added { id } | Modified { id } | LoadedWithDependencies { id } => { asset_changes.insert(*id, ticks.this_run()); } From 2092eb15ea11be0b91d10f819bbdaed2b7fcb183 Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Fri, 13 Dec 2024 11:52:39 -0800 Subject: [PATCH 06/23] Implement updates for AssetChanges. --- crates/bevy_asset/src/asset_changed.rs | 207 ++++++++++-------- crates/bevy_asset/src/assets.rs | 4 +- crates/bevy_asset/src/lib.rs | 14 +- crates/bevy_ecs/macros/src/world_query.rs | 5 +- crates/bevy_ecs/src/query/fetch.rs | 63 +++--- crates/bevy_ecs/src/query/filter.rs | 23 +- crates/bevy_ecs/src/query/state.rs | 12 +- crates/bevy_ecs/src/query/world_query.rs | 12 +- .../bevy_ecs/src/world/unsafe_world_cell.rs | 6 +- crates/bevy_render/src/sync_world.rs | 10 +- 10 files changed, 191 insertions(+), 165 deletions(-) diff --git a/crates/bevy_asset/src/asset_changed.rs b/crates/bevy_asset/src/asset_changed.rs index 9a816aa6100e7..fd8a0f3ed630d 100644 --- a/crates/bevy_asset/src/asset_changed.rs +++ b/crates/bevy_asset/src/asset_changed.rs @@ -1,24 +1,23 @@ //! Define the [`AssetChanged`] query filter. //! //! Like [`Changed`], but for [`Asset`]s. -use std::marker::PhantomData; -use disqualified::ShortName; + use bevy_ecs::{ - archetype::{Archetype, ArchetypeComponentId}, + archetype::Archetype, component::{ComponentId, Tick}, - prelude::{Changed, Entity, Or, Resource, World}, - query::{Access, FilteredAccess, QueryItem, ReadFetch, WorldQuery, QueryFilter}, + prelude::{Entity, Resource, World}, + query::{FilteredAccess, QueryFilter, QueryItem, ReadFetch, WorldQuery}, storage::{Table, TableRow}, world::unsafe_world_cell::UnsafeWorldCell, }; -use bevy_ecs::component::Components; use bevy_utils::HashMap; +use disqualified::ShortName; +use std::marker::PhantomData; -use crate::{Asset, AssetId, Handle}; +use crate::{AsAssetId, Asset, AssetId}; -#[doc(hidden)] #[derive(Resource)] -pub struct AssetChanges { +pub(crate) struct AssetChanges { change_ticks: HashMap, Tick>, last_change_tick: Tick, } @@ -42,22 +41,22 @@ impl Default for AssetChanges { } } -struct AssetChangeCheck<'w, A: Asset> { - change_ticks: &'w HashMap, Tick>, +struct AssetChangeCheck<'w, A: AsAssetId> { + change_ticks: &'w HashMap, Tick>, last_run: Tick, this_run: Tick, } -impl Clone for AssetChangeCheck<'_, A> { +impl Clone for AssetChangeCheck<'_, A> { fn clone(&self) -> Self { *self } } -impl Copy for AssetChangeCheck<'_, A> {} +impl Copy for AssetChangeCheck<'_, A> {} -impl<'w, A: Asset> AssetChangeCheck<'w, A> { - fn new(changes: &'w AssetChanges, last_run: Tick, this_run: Tick) -> Self { +impl<'w, A: AsAssetId> AssetChangeCheck<'w, A> { + fn new(changes: &'w AssetChanges, last_run: Tick, this_run: Tick) -> Self { Self { change_ticks: &changes.change_ticks, last_run, @@ -66,80 +65,60 @@ impl<'w, A: Asset> AssetChangeCheck<'w, A> { } // TODO(perf): some sort of caching? Each check has two levels of indirection, // which is not optimal. - fn has_changed(&self, handle: &Handle) -> bool { + fn has_changed(&self, handle: &A) -> bool { let is_newer = |tick: &Tick| tick.is_newer_than(self.last_run, self.this_run); - let id = &handle.id(); + let id = handle.as_asset_id(); - self.change_ticks.get(id).is_some_and(is_newer) + self.change_ticks.get(&id).is_some_and(is_newer) } } -/// A shortcut for the commonly used `Or<(Changed>, AssetChanged)>` -/// query filter. -/// -/// If you want to react to changes to `Handle`, you need to: -/// - Check if the `Handle` was changed through a `Query<&mut Handle>`. -/// - Check if the `A` in `Assets` pointed by `Handle` was changed through an -/// [`Assets::get_mut`]. -/// -/// To properly handle both cases, you need to combine the `Changed` and `AssetChanged` -/// filters. This query filter is exactly this. -/// -/// [`Assets::get_mut`]: crate::Assets::get_mut -pub type AssetOrHandleChanged = Or<(Changed>, AssetChanged)>; - -/// Filter that selects entities with a `Handle` for an asset that changed -/// after the system last ran. +/// Filter that selects entities with a `A` for an asset that changed +/// after the system last ran, where `A` is a component that implements +/// [`AsAssetId`]. /// -/// Unlike `Changed>`, this is true whenever the asset for the `Handle` +/// Unlike `Changed`, this is true whenever the asset for the `A` /// in `ResMut>` changed. For example, when a mesh changed through the /// [`Assets::get_mut`] method, `AssetChanged` will iterate over all /// entities with the `Handle` for that mesh. Meanwhile, `Changed>` /// will iterate over no entities. /// -/// Swapping the actual `Handle` component is a common pattern. So you -/// should check for _both_ `AssetChanged` and `Changed>` with -/// [`AssetOrHandleChanged`] +/// Swapping the actual `A` component is a common pattern. So you +/// should check for _both_ `AssetChanged` and `Changed` with +/// `Or<(Changed, AssetChanged)>`. /// /// # Quirks /// /// - Asset changes are registered in the [`AssetEvents`] schedule. /// - Removed assets are not detected. -/// - Asset update tracking only starts after the first system with an -/// `AssetChanged` query parameter ran for the first time. -/// -/// This means that assets added before the system ran won't be detected -/// (for example in a `Startup` schedule). -/// -/// This is also true of systems gated by a `run_if` condition. /// /// The list of changed assets only gets updated in the -/// [`AssetEvents`] schedule, just after `PostUpdate`. Therefore, `AssetChanged` +/// [`AssetEvents`] schedule which runs in `Last`. Therefore, `AssetChanged` /// will only pick up asset changes in schedules following `AssetEvents` or the -/// next frame. Consider adding the system in the `Last` schedule if you need +/// next frame. Consider adding the system in the `Last` schedule after [`AssetEvents`] if you need /// to react without frame delay to asset changes. /// /// # Performance /// /// When at least one `A` is updated, this will -/// read a hashmap once per entity with a `Handle` component. The -/// runtime of the query is proportional to how many entities with a `Handle` +/// read a hashmap once per entity with a `A` component. The +/// runtime of the query is proportional to how many entities with a `A` /// it matches. /// /// If no `A` asset updated since the last time the system ran, then no lookups occur. /// /// [`AssetEvents`]: crate::AssetEvents /// [`Assets::get_mut`]: crate::Assets::get_mut -pub struct AssetChanged(PhantomData); +pub struct AssetChanged(PhantomData); /// Fetch for [`AssetChanged`]. #[doc(hidden)] -pub struct AssetChangedFetch<'w, A: Asset> { - inner: Option>>, +pub struct AssetChangedFetch<'w, A: AsAssetId> { + inner: Option>, check: AssetChangeCheck<'w, A>, } -impl<'w, A: Asset> Clone for AssetChangedFetch<'w, A> { +impl<'w, A: AsAssetId> Clone for AssetChangedFetch<'w, A> { fn clone(&self) -> Self { Self { inner: self.inner, @@ -150,15 +129,14 @@ impl<'w, A: Asset> Clone for AssetChangedFetch<'w, A> { /// State for [`AssetChanged`]. #[doc(hidden)] -pub struct AssetChangedState { +pub struct AssetChangedState { asset_id: ComponentId, resource_id: ComponentId, - archetype_id: ArchetypeComponentId, _asset: PhantomData, } // SAFETY: `ROQueryFetch` is the same as `QueryFetch` -unsafe impl WorldQuery for AssetChanged { +unsafe impl WorldQuery for AssetChanged { type Item<'w> = (); type Fetch<'w> = AssetChangedFetch<'w, A>; @@ -183,16 +161,21 @@ unsafe impl WorldQuery for AssetChanged { ty = ShortName::of::() ) }; - let changes: &AssetChanges<_> = world.get_resource().unwrap_or_else(err_msg); + // SAFETY: `AssetChanges` is private and only accessed mutably in the `AssetEvents` schedule + let changes: &AssetChanges<_> = unsafe { world.get_resource().unwrap_or_else(err_msg) }; let has_updates = changes.last_change_tick.is_newer_than(last_run, this_run); + AssetChangedFetch { - inner: has_updates - .then(|| <&_>::init_fetch(world, &state.asset_id, last_run, this_run)), + inner: has_updates.then(|| + // SAFETY: We delegate to the inner `init_fetch` for `A` + unsafe { + <&A>::init_fetch(world, &state.asset_id, last_run, this_run) + }), check: AssetChangeCheck::new(changes, last_run, this_run), } } - const IS_DENSE: bool = <&Handle>::IS_DENSE; + const IS_DENSE: bool = <&A>::IS_DENSE; unsafe fn set_archetype<'w>( fetch: &mut Self::Fetch<'w>, @@ -201,13 +184,19 @@ unsafe impl WorldQuery for AssetChanged { table: &'w Table, ) { if let Some(inner) = &mut fetch.inner { - <&Handle>::set_archetype(inner, &state.asset_id, archetype, table); + // SAFETY: We delegate to the inner `set_archetype` for `A` + unsafe { + <&A>::set_archetype(inner, &state.asset_id, archetype, table); + } } } unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, table: &'w Table) { if let Some(inner) = &mut fetch.inner { - <&Handle>::set_table(inner, &state.asset_id, table); + // SAFETY: We delegate to the inner `set_table` for `A` + unsafe { + <&A>::set_table(inner, &state.asset_id, table); + } } } @@ -215,23 +204,33 @@ unsafe impl WorldQuery for AssetChanged { #[inline] fn update_component_access(state: &Self::State, access: &mut FilteredAccess) { - <&Handle>::update_component_access(&state.asset_id, access); - access.add_read(state.resource_id); + <&A>::update_component_access(&state.asset_id, access); + access.add_resource_read(state.resource_id); } fn init_state(world: &mut World) -> AssetChangedState { - let resource_id = world.init_resource::>(); - let archetype_id = world.storages().resources.get(resource_id).unwrap().id(); + let resource_id = world.init_resource::>(); + let asset_id = world.component_id::().unwrap(); AssetChangedState { - asset_id: world.init_component::>(), + asset_id, resource_id, - archetype_id, _asset: PhantomData, } } - fn get_state(components: &Components) -> Option { - todo!() + fn get_state<'w>(world: impl Into>) -> Option { + // SAFETY: + // - `world` is a valid world + // - we only access our private `AssetChanges` resource + let world = unsafe { world.into().world() }; + + let resource_id = world.resource_id::>()?; + let asset_id = world.component_id::()?; + Some(AssetChangedState { + asset_id, + resource_id, + _asset: PhantomData, + }) } fn matches_component_set( @@ -243,7 +242,7 @@ unsafe impl WorldQuery for AssetChanged { } /// SAFETY: read-only access -unsafe impl QueryFilter for AssetChanged { +unsafe impl QueryFilter for AssetChanged { const IS_ARCHETYPAL: bool = false; #[inline] @@ -253,21 +252,26 @@ unsafe impl QueryFilter for AssetChanged { table_row: TableRow, ) -> bool { fetch.inner.as_mut().map_or(false, |inner| { - let handle = <&Handle>::fetch(inner, entity, table_row); - fetch.check.has_changed(handle) + // SAFETY: We delegate to the inner `fetch` for `A` + unsafe { + let handle = <&A>::fetch(inner, entity, table_row); + fetch.check.has_changed(handle) + } }) } } #[cfg(test)] mod tests { + use crate::{self as bevy_asset, AssetEvents, AssetPlugin, Handle}; use std::num::NonZero; - use crate::{self as bevy_asset, AssetPlugin}; use crate::{AssetApp, Assets}; use bevy_app::{App, AppExit, Last, Startup, Update}; use bevy_core::TaskPoolPlugin; + use bevy_ecs::schedule::IntoSystemConfigs; use bevy_ecs::{ + component::Component, event::EventWriter, system::{Commands, IntoSystem, Local, Query, Res, ResMut, Resource}, }; @@ -278,6 +282,17 @@ mod tests { #[derive(Asset, TypePath, Debug)] struct MyAsset(usize, &'static str); + #[derive(Component)] + struct MyComponent(Handle); + + impl AsAssetId for MyComponent { + type Asset = MyAsset; + + fn as_asset_id(&self) -> AssetId { + self.0.id() + } + } + fn run_app(system: impl IntoSystem<(), (), Marker>) { let mut app = App::new(); app.add_plugins((TaskPoolPlugin::default(), AssetPlugin::default())) @@ -291,7 +306,7 @@ mod tests { #[test] fn handle_filter_pos_ok() { fn compatible_filter( - _query: Query<&mut Handle, AssetChanged>, + _query: Query<&mut MyComponent, AssetChanged>, mut exit: EventWriter, ) { exit.send(AppExit::Error(NonZero::::MIN)); @@ -305,10 +320,11 @@ mod tests { fn count_update( mut counter: ResMut, assets: Res>, - query: Query<&Handle, AssetChanged>, + query: Query<&MyComponent, AssetChanged>, ) { + println!("counting updates"); for handle in query.iter() { - let asset = assets.get(handle).unwrap(); + let asset = assets.get(&handle.0).unwrap(); counter.0[asset.0] += 1; } } @@ -342,15 +358,15 @@ mod tests { ) { match *run_count { 1 => { - cmds.spawn(assets.add(MyAsset(0, "init"))); + cmds.spawn(MyComponent(assets.add(MyAsset(0, "init")))); } 0 | 2 => {} 3 => { - cmds.spawn(assets.add(MyAsset(1, "init"))); - cmds.spawn(assets.add(MyAsset(2, "init"))); + cmds.spawn(MyComponent(assets.add(MyAsset(1, "init")))); + cmds.spawn(MyComponent(assets.add(MyAsset(2, "init")))); } 4.. => { - cmds.spawn(assets.add(MyAsset(3, "init"))); + cmds.spawn(MyComponent(assets.add(MyAsset(3, "init")))); } }; *run_count += 1; @@ -369,7 +385,7 @@ mod tests { .init_asset::() .insert_resource(Counter(vec![0, 0, 0, 0])) .add_systems(Update, add_some) - .add_systems(Last, count_update); + .add_systems(Last, count_update.after(AssetEvents)); // First run of the app, `add_systems(Startup…)` runs. app.update(); // run_count == 0 @@ -396,41 +412,40 @@ mod tests { |mut cmds: Commands, mut assets: ResMut>| { let asset0 = assets.add(MyAsset(0, "init")); let asset1 = assets.add(MyAsset(1, "init")); - cmds.spawn(asset0.clone()); - cmds.spawn(asset0); - cmds.spawn(asset1.clone()); - cmds.spawn(asset1.clone()); - cmds.spawn(asset1); + cmds.spawn(MyComponent(asset0.clone())); + cmds.spawn(MyComponent(asset0)); + cmds.spawn(MyComponent(asset1.clone())); + cmds.spawn(MyComponent(asset1.clone())); + cmds.spawn(MyComponent(asset1)); }, ) .add_systems(Update, update_some) - .add_systems(Last, count_update); + .add_systems(Last, count_update.after(AssetEvents)); // First run of the app, `add_systems(Startup…)` runs. app.update(); // run_count == 0 - // `AssetChanges` do not react to `Added` or `Modified` events that occured before - // the first run of `count_update`. This is why the counters are still 0 - assert_counter(&app, Counter(vec![0, 0])); + // First run: We count the entities that were added in the `Startup` schedule + assert_counter(&app, Counter(vec![2, 3])); // Second run: `update_once` updates the first asset, which is // associated with two entities, so `count_update` picks up two updates app.update(); // run_count == 1 - assert_counter(&app, Counter(vec![2, 0])); + assert_counter(&app, Counter(vec![4, 3])); // Third run: `update_once` doesn't update anything, same values as last app.update(); // run_count == 2 - assert_counter(&app, Counter(vec![2, 0])); + assert_counter(&app, Counter(vec![4, 3])); // Fourth run: We update the two assets (asset 0: 2 entities, asset 1: 3) app.update(); // run_count == 3 - assert_counter(&app, Counter(vec![4, 3])); + assert_counter(&app, Counter(vec![6, 6])); // Fifth run: only update second asset app.update(); // run_count == 4 - assert_counter(&app, Counter(vec![4, 6])); + assert_counter(&app, Counter(vec![6, 9])); // ibid app.update(); // run_count == 5 - assert_counter(&app, Counter(vec![4, 9])); + assert_counter(&app, Counter(vec![6, 12])); } } diff --git a/crates/bevy_asset/src/assets.rs b/crates/bevy_asset/src/assets.rs index f1334d26da988..6e3bf1b5cf51f 100644 --- a/crates/bevy_asset/src/assets.rs +++ b/crates/bevy_asset/src/assets.rs @@ -1,3 +1,4 @@ +use crate::asset_changed::AssetChanges; use crate::{ self as bevy_asset, Asset, AssetEvent, AssetHandleProvider, AssetId, AssetServer, Handle, UntypedHandle, @@ -14,7 +15,6 @@ use crossbeam_channel::{Receiver, Sender}; use serde::{Deserialize, Serialize}; use thiserror::Error; use uuid::Uuid; -use crate::asset_changed::AssetChanges; /// A generational runtime-only identifier for a specific [`Asset`] stored in [`Assets`]. This is optimized for efficient runtime /// usage and is not suitable for identifying assets across app runs. @@ -560,7 +560,7 @@ impl Assets { /// A system that applies accumulated asset change events to the [`Events`] resource. /// /// [`Events`]: bevy_ecs::event::Events - pub fn asset_events( + pub(crate) fn asset_events( mut assets: ResMut, mut events: EventWriter>, asset_changes: Option>>, diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index 65f5538f74483..78b6bc8a7ca44 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -141,6 +141,7 @@ // FIXME(3492): remove once docs are ready // FIXME(15321): solve CI failures, then replace with `#![expect()]`. #![allow(missing_docs, reason = "Not all docs are written yet, see #3492.")] +#![allow(unsafe_code)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![doc( html_logo_url = "https://bevyengine.org/assets/icon.png", @@ -148,6 +149,7 @@ )] extern crate alloc; +extern crate core; pub mod io; pub mod meta; @@ -160,7 +162,7 @@ pub mod transformer; /// This includes the most common types in this crate, re-exported for your convenience. pub mod prelude { #[doc(hidden)] - pub use crate::asset_changed::{AssetChanged, AssetOrHandleChanged}; + pub use crate::asset_changed::AssetChanged; #[doc(hidden)] pub use crate::{ @@ -209,6 +211,7 @@ use crate::{ }; use alloc::sync::Arc; use bevy_app::{App, Last, Plugin, PreUpdate}; +use bevy_ecs::prelude::Component; use bevy_ecs::{ reflect::AppTypeRegistry, schedule::{IntoSystemConfigs, IntoSystemSetConfigs, SystemSet}, @@ -404,6 +407,15 @@ impl Plugin for AssetPlugin { )] pub trait Asset: VisitAssetDependencies + TypePath + Send + Sync + 'static {} +/// A trait for newtypes that can be used as asset identifiers, i.e. handle wrappers. +pub trait AsAssetId: Component { + /// The underlying asset type. + type Asset: Asset; + + /// Converts this component to an asset id. + fn as_asset_id(&self) -> AssetId; +} + /// This trait defines how to visit the dependencies of an asset. /// For example, a 3D model might require both textures and meshes to be loaded. /// diff --git a/crates/bevy_ecs/macros/src/world_query.rs b/crates/bevy_ecs/macros/src/world_query.rs index 9d97f185dede9..ff6e4f99422aa 100644 --- a/crates/bevy_ecs/macros/src/world_query.rs +++ b/crates/bevy_ecs/macros/src/world_query.rs @@ -195,9 +195,10 @@ pub(crate) fn world_query_impl( } } - fn get_state(components: &#path::component::Components) -> Option<#state_struct_name #user_ty_generics> { + fn get_state<'w>(world: impl Into<#path::world::unsafe_world_cell::UnsafeWorldCell<'w>>) -> Option<#state_struct_name #user_ty_generics> { + let world = world.into(); Some(#state_struct_name { - #(#named_field_idents: <#field_types>::get_state(components)?,)* + #(#named_field_idents: <#field_types>::get_state(world)?,)* }) } diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index a56754d95b8f8..99dce8f535389 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -2,7 +2,7 @@ use crate::{ archetype::{Archetype, Archetypes}, bundle::Bundle, change_detection::{MaybeThinSlicePtrLocation, Ticks, TicksMut}, - component::{Component, ComponentId, Components, Mutable, StorageType, Tick}, + component::{Component, ComponentId, Mutable, StorageType, Tick}, entity::{Entities, Entity, EntityLocation}, query::{Access, DebugCheckedUnwrap, FilteredAccess, WorldQuery}, storage::{ComponentSparseSet, Table, TableRow}, @@ -342,7 +342,7 @@ unsafe impl WorldQuery for Entity { fn init_state(_world: &mut World) {} - fn get_state(_components: &Components) -> Option<()> { + fn get_state<'w>(_world: impl Into>) -> Option<()> { Some(()) } @@ -418,7 +418,7 @@ unsafe impl WorldQuery for EntityLocation { fn init_state(_world: &mut World) {} - fn get_state(_components: &Components) -> Option<()> { + fn get_state<'w>(_world: impl Into>) -> Option<()> { Some(()) } @@ -501,7 +501,7 @@ unsafe impl<'a> WorldQuery for EntityRef<'a> { fn init_state(_world: &mut World) {} - fn get_state(_components: &Components) -> Option<()> { + fn get_state<'w>(_world: impl Into>) -> Option<()> { Some(()) } @@ -581,7 +581,7 @@ unsafe impl<'a> WorldQuery for EntityMut<'a> { fn init_state(_world: &mut World) {} - fn get_state(_components: &Components) -> Option<()> { + fn get_state<'w>(_world: impl Into>) -> Option<()> { Some(()) } @@ -673,7 +673,7 @@ unsafe impl<'a> WorldQuery for FilteredEntityRef<'a> { FilteredAccess::default() } - fn get_state(_components: &Components) -> Option { + fn get_state<'w>(_world: impl Into>) -> Option { Some(FilteredAccess::default()) } @@ -767,7 +767,7 @@ unsafe impl<'a> WorldQuery for FilteredEntityMut<'a> { FilteredAccess::default() } - fn get_state(_components: &Components) -> Option { + fn get_state<'w>(_world: impl Into>) -> Option { Some(FilteredAccess::default()) } @@ -853,12 +853,12 @@ where } fn init_state(world: &mut World) -> Self::State { - Self::get_state(world.components()).unwrap() + Self::get_state(world).unwrap() } - fn get_state(components: &Components) -> Option { + fn get_state<'w>(world: impl Into>) -> Option { let mut ids = SmallVec::new(); - B::get_component_ids(components, &mut |maybe_id| { + B::get_component_ids(world.into().components(), &mut |maybe_id| { if let Some(id) = maybe_id { ids.push(id); } @@ -952,12 +952,12 @@ where } fn init_state(world: &mut World) -> Self::State { - Self::get_state(world.components()).unwrap() + Self::get_state(world).unwrap() } - fn get_state(components: &Components) -> Option { + fn get_state<'w>(world: impl Into>) -> Option { let mut ids = SmallVec::new(); - B::get_component_ids(components, &mut |maybe_id| { + B::get_component_ids(world.into().components(), &mut |maybe_id| { if let Some(id) = maybe_id { ids.push(id); } @@ -1038,7 +1038,7 @@ unsafe impl WorldQuery for &Archetype { fn init_state(_world: &mut World) {} - fn get_state(_components: &Components) -> Option<()> { + fn get_state<'w>(_world: impl Into>) -> Option<()> { Some(()) } @@ -1197,8 +1197,8 @@ unsafe impl WorldQuery for &T { world.register_component::() } - fn get_state(components: &Components) -> Option { - components.component_id::() + fn get_state<'w>(world: impl Into>) -> Option { + world.into().components().component_id::() } fn matches_component_set( @@ -1396,8 +1396,8 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { world.register_component::() } - fn get_state(components: &Components) -> Option { - components.component_id::() + fn get_state<'w>(world: impl Into>) -> Option { + world.into().components().component_id::() } fn matches_component_set( @@ -1595,8 +1595,8 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { world.register_component::() } - fn get_state(components: &Components) -> Option { - components.component_id::() + fn get_state<'w>(world: impl Into>) -> Option { + world.into().components().component_id::() } fn matches_component_set( @@ -1699,8 +1699,8 @@ unsafe impl<'__w, T: Component> WorldQuery for Mut<'__w, T> { } // Forwarded to `&mut T` - fn get_state(components: &Components) -> Option { - <&mut T as WorldQuery>::get_state(components) + fn get_state<'w>(world: impl Into>) -> Option { + <&mut T as WorldQuery>::get_state(world) } // Forwarded to `&mut T` @@ -1826,8 +1826,8 @@ unsafe impl WorldQuery for Option { T::init_state(world) } - fn get_state(components: &Components) -> Option { - T::get_state(components) + fn get_state<'w>(world: impl Into>) -> Option { + T::get_state(world) } fn matches_component_set( @@ -1985,8 +1985,8 @@ unsafe impl WorldQuery for Has { world.register_component::() } - fn get_state(components: &Components) -> Option { - components.component_id::() + fn get_state<'w>(world: impl Into>) -> Option { + world.into().components().component_id::() } fn matches_component_set( @@ -2144,8 +2144,9 @@ macro_rules! impl_anytuple_fetch { ($($name::init_state(world),)*) } #[allow(unused_variables)] - fn get_state(components: &Components) -> Option { - Some(($($name::get_state(components)?,)*)) + fn get_state<'w>(world: impl Into>) -> Option { + let world = world.into(); + Some(($($name::get_state(world)?,)*)) } fn matches_component_set(_state: &Self::State, _set_contains_id: &impl Fn(ComponentId) -> bool) -> bool { @@ -2239,8 +2240,8 @@ unsafe impl WorldQuery for NopWorldQuery { D::init_state(world) } - fn get_state(components: &Components) -> Option { - D::get_state(components) + fn get_state<'w>(world: impl Into>) -> Option { + D::get_state(world) } fn matches_component_set( @@ -2307,7 +2308,7 @@ unsafe impl WorldQuery for PhantomData { fn init_state(_world: &mut World) -> Self::State {} - fn get_state(_components: &Components) -> Option { + fn get_state<'w>(_world: impl Into>) -> Option { Some(()) } diff --git a/crates/bevy_ecs/src/query/filter.rs b/crates/bevy_ecs/src/query/filter.rs index b096e801f4cc2..26402ab51dfa3 100644 --- a/crates/bevy_ecs/src/query/filter.rs +++ b/crates/bevy_ecs/src/query/filter.rs @@ -1,6 +1,6 @@ use crate::{ archetype::Archetype, - component::{Component, ComponentId, Components, StorageType, Tick}, + component::{Component, ComponentId, StorageType, Tick}, entity::Entity, query::{DebugCheckedUnwrap, FilteredAccess, StorageSwitch, WorldQuery}, storage::{ComponentSparseSet, Table, TableRow}, @@ -194,8 +194,8 @@ unsafe impl WorldQuery for With { world.register_component::() } - fn get_state(components: &Components) -> Option { - components.component_id::() + fn get_state<'w>(world: impl Into>) -> Option { + world.into().components().component_id::() } fn matches_component_set( @@ -305,8 +305,8 @@ unsafe impl WorldQuery for Without { world.register_component::() } - fn get_state(components: &Components) -> Option { - components.component_id::() + fn get_state<'w>(world: impl Into>) -> Option { + world.into().components().component_id::() } fn matches_component_set( @@ -488,8 +488,9 @@ macro_rules! impl_or_query_filter { ($($filter::init_state(world),)*) } - fn get_state(components: &Components) -> Option { - Some(($($filter::get_state(components)?,)*)) + fn get_state<'w>(world: impl Into>) -> Option { + let world = world.into(); + Some(($($filter::get_state(world)?,)*)) } fn matches_component_set(_state: &Self::State, _set_contains_id: &impl Fn(ComponentId) -> bool) -> bool { @@ -763,8 +764,8 @@ unsafe impl WorldQuery for Added { world.register_component::() } - fn get_state(components: &Components) -> Option { - components.component_id::() + fn get_state<'w>(world: impl Into>) -> Option { + world.into().components().component_id::() } fn matches_component_set( @@ -996,8 +997,8 @@ unsafe impl WorldQuery for Changed { world.register_component::() } - fn get_state(components: &Components) -> Option { - components.component_id::() + fn get_state<'w>(world: impl Into>) -> Option { + world.into().components().component_id::() } fn matches_component_set( diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index f493ba7c97ef9..0a701114d4630 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -205,8 +205,8 @@ impl QueryState { /// `new_archetype` and its variants must be called on all of the World's archetypes before the /// state can return valid query results. fn try_new_uninitialized(world: &World) -> Option { - let fetch_state = D::get_state(world.components())?; - let filter_state = F::get_state(world.components())?; + let fetch_state = D::get_state(world)?; + let filter_state = F::get_state(world)?; Some(Self::from_states_uninitialized( world.id(), fetch_state, @@ -613,8 +613,8 @@ impl QueryState { self.validate_world(world.id()); let mut component_access = FilteredAccess::default(); - let mut fetch_state = NewD::get_state(world.components()).expect("Could not create fetch_state, Please initialize all referenced components before transmuting."); - let filter_state = NewF::get_state(world.components()).expect("Could not create filter_state, Please initialize all referenced components before transmuting."); + let mut fetch_state = NewD::get_state(world).expect("Could not create fetch_state, Please initialize all referenced components before transmuting."); + let filter_state = NewF::get_state(world).expect("Could not create filter_state, Please initialize all referenced components before transmuting."); NewD::set_access(&mut fetch_state, &self.component_access); NewD::update_component_access(&fetch_state, &mut component_access); @@ -701,9 +701,9 @@ impl QueryState { self.validate_world(world.id()); let mut component_access = FilteredAccess::default(); - let mut new_fetch_state = NewD::get_state(world.components()) + let mut new_fetch_state = NewD::get_state(world) .expect("Could not create fetch_state, Please initialize all referenced components before transmuting."); - let new_filter_state = NewF::get_state(world.components()) + let new_filter_state = NewF::get_state(world) .expect("Could not create filter_state, Please initialize all referenced components before transmuting."); NewD::set_access(&mut new_fetch_state, &self.component_access); diff --git a/crates/bevy_ecs/src/query/world_query.rs b/crates/bevy_ecs/src/query/world_query.rs index 47bb1be40d237..36b3b6ec0b309 100644 --- a/crates/bevy_ecs/src/query/world_query.rs +++ b/crates/bevy_ecs/src/query/world_query.rs @@ -1,6 +1,6 @@ use crate::{ archetype::Archetype, - component::{ComponentId, Components, Tick}, + component::{ComponentId, Tick}, entity::Entity, query::FilteredAccess, storage::{Table, TableRow}, @@ -132,9 +132,8 @@ pub unsafe trait WorldQuery { /// Creates and initializes a [`State`](WorldQuery::State) for this [`WorldQuery`] type. fn init_state(world: &mut World) -> Self::State; - /// Attempts to initialize a [`State`](WorldQuery::State) for this [`WorldQuery`] type using read-only - /// access to [`Components`]. - fn get_state(components: &Components) -> Option; + /// Attempts to initialize a [`State`](WorldQuery::State) for this [`WorldQuery`] type. + fn get_state<'w>(world: impl Into>) -> Option; /// Returns `true` if this query matches a set of components. Otherwise, returns `false`. /// @@ -228,8 +227,9 @@ macro_rules! impl_tuple_world_query { ($($name::init_state(world),)*) } #[allow(unused_variables)] - fn get_state(components: &Components) -> Option { - Some(($($name::get_state(components)?,)*)) + fn get_state<'w>(world: impl Into>) -> Option { + let world = world.into(); + Some(($($name::get_state(world)?,)*)) } fn matches_component_set(state: &Self::State, _set_contains_id: &impl Fn(ComponentId) -> bool) -> bool { diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index aed797c5551e5..592082892727e 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -910,11 +910,7 @@ impl<'w> UnsafeEntityCell<'w> { /// - the [`UnsafeEntityCell`] has permission to access the queried data immutably /// - no mutable references to the queried data exist at the same time pub(crate) unsafe fn get_components(&self) -> Option> { - // SAFETY: World is only used to access query data and initialize query state - let state = unsafe { - let world = self.world().world(); - Q::get_state(world.components())? - }; + let state = Q::get_state(self.world)?; let location = self.location(); // SAFETY: Location is guaranteed to exist let archetype = unsafe { diff --git a/crates/bevy_render/src/sync_world.rs b/crates/bevy_render/src/sync_world.rs index ebff81507a0fe..805aef5ab11e8 100644 --- a/crates/bevy_render/src/sync_world.rs +++ b/crates/bevy_render/src/sync_world.rs @@ -255,7 +255,7 @@ mod render_entities_world_query_impls { use bevy_ecs::{ archetype::Archetype, - component::{ComponentId, Components, Tick}, + component::{ComponentId, Tick}, entity::Entity, query::{FilteredAccess, QueryData, ReadOnlyQueryData, WorldQuery}, storage::{Table, TableRow}, @@ -340,8 +340,8 @@ mod render_entities_world_query_impls { <&RenderEntity as WorldQuery>::init_state(world) } - fn get_state(components: &Components) -> Option { - <&RenderEntity as WorldQuery>::get_state(components) + fn get_state<'w>(world: impl Into>) -> Option { + <&RenderEntity as WorldQuery>::get_state(world) } fn matches_component_set( @@ -438,8 +438,8 @@ mod render_entities_world_query_impls { <&MainEntity as WorldQuery>::init_state(world) } - fn get_state(components: &Components) -> Option { - <&MainEntity as WorldQuery>::get_state(components) + fn get_state<'w>(world: impl Into>) -> Option { + <&MainEntity as WorldQuery>::get_state(world) } fn matches_component_set( From 2f9529051d1b659c8c1bce63a6e787d7866210ea Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Fri, 13 Dec 2024 12:23:13 -0800 Subject: [PATCH 07/23] Ci. --- crates/bevy_asset/src/asset_changed.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_asset/src/asset_changed.rs b/crates/bevy_asset/src/asset_changed.rs index fd8a0f3ed630d..ae58228f89635 100644 --- a/crates/bevy_asset/src/asset_changed.rs +++ b/crates/bevy_asset/src/asset_changed.rs @@ -12,7 +12,7 @@ use bevy_ecs::{ }; use bevy_utils::HashMap; use disqualified::ShortName; -use std::marker::PhantomData; +use core::marker::PhantomData; use crate::{AsAssetId, Asset, AssetId}; From eae75cf7c2510a87eb10f9c69eca36591efc8033 Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Fri, 13 Dec 2024 12:26:23 -0800 Subject: [PATCH 08/23] Fmt. --- crates/bevy_asset/src/asset_changed.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_asset/src/asset_changed.rs b/crates/bevy_asset/src/asset_changed.rs index ae58228f89635..989a4556617d9 100644 --- a/crates/bevy_asset/src/asset_changed.rs +++ b/crates/bevy_asset/src/asset_changed.rs @@ -11,8 +11,8 @@ use bevy_ecs::{ world::unsafe_world_cell::UnsafeWorldCell, }; use bevy_utils::HashMap; -use disqualified::ShortName; use core::marker::PhantomData; +use disqualified::ShortName; use crate::{AsAssetId, Asset, AssetId}; From 4278615376f3332a7516755152f7b688634eddc6 Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Fri, 13 Dec 2024 12:51:49 -0800 Subject: [PATCH 09/23] Switch back to using &Components. --- crates/bevy_asset/src/asset_changed.rs | 15 ++--- crates/bevy_ecs/macros/src/world_query.rs | 5 +- crates/bevy_ecs/src/query/fetch.rs | 63 +++++++++---------- crates/bevy_ecs/src/query/filter.rs | 23 ++++--- crates/bevy_ecs/src/query/state.rs | 12 ++-- crates/bevy_ecs/src/query/world_query.rs | 12 ++-- .../bevy_ecs/src/world/unsafe_world_cell.rs | 6 +- crates/bevy_render/src/sync_world.rs | 10 +-- 8 files changed, 71 insertions(+), 75 deletions(-) diff --git a/crates/bevy_asset/src/asset_changed.rs b/crates/bevy_asset/src/asset_changed.rs index 989a4556617d9..8a8ecbc05ac93 100644 --- a/crates/bevy_asset/src/asset_changed.rs +++ b/crates/bevy_asset/src/asset_changed.rs @@ -2,6 +2,8 @@ //! //! Like [`Changed`], but for [`Asset`]s. +use crate::{AsAssetId, Asset, AssetId}; +use bevy_ecs::component::Components; use bevy_ecs::{ archetype::Archetype, component::{ComponentId, Tick}, @@ -14,8 +16,6 @@ use bevy_utils::HashMap; use core::marker::PhantomData; use disqualified::ShortName; -use crate::{AsAssetId, Asset, AssetId}; - #[derive(Resource)] pub(crate) struct AssetChanges { change_ticks: HashMap, Tick>, @@ -218,14 +218,9 @@ unsafe impl WorldQuery for AssetChanged { } } - fn get_state<'w>(world: impl Into>) -> Option { - // SAFETY: - // - `world` is a valid world - // - we only access our private `AssetChanges` resource - let world = unsafe { world.into().world() }; - - let resource_id = world.resource_id::>()?; - let asset_id = world.component_id::()?; + fn get_state(components: &Components) -> Option { + let resource_id = components.resource_id::>()?; + let asset_id = components.component_id::()?; Some(AssetChangedState { asset_id, resource_id, diff --git a/crates/bevy_ecs/macros/src/world_query.rs b/crates/bevy_ecs/macros/src/world_query.rs index ff6e4f99422aa..9d97f185dede9 100644 --- a/crates/bevy_ecs/macros/src/world_query.rs +++ b/crates/bevy_ecs/macros/src/world_query.rs @@ -195,10 +195,9 @@ pub(crate) fn world_query_impl( } } - fn get_state<'w>(world: impl Into<#path::world::unsafe_world_cell::UnsafeWorldCell<'w>>) -> Option<#state_struct_name #user_ty_generics> { - let world = world.into(); + fn get_state(components: &#path::component::Components) -> Option<#state_struct_name #user_ty_generics> { Some(#state_struct_name { - #(#named_field_idents: <#field_types>::get_state(world)?,)* + #(#named_field_idents: <#field_types>::get_state(components)?,)* }) } diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index 99dce8f535389..a56754d95b8f8 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -2,7 +2,7 @@ use crate::{ archetype::{Archetype, Archetypes}, bundle::Bundle, change_detection::{MaybeThinSlicePtrLocation, Ticks, TicksMut}, - component::{Component, ComponentId, Mutable, StorageType, Tick}, + component::{Component, ComponentId, Components, Mutable, StorageType, Tick}, entity::{Entities, Entity, EntityLocation}, query::{Access, DebugCheckedUnwrap, FilteredAccess, WorldQuery}, storage::{ComponentSparseSet, Table, TableRow}, @@ -342,7 +342,7 @@ unsafe impl WorldQuery for Entity { fn init_state(_world: &mut World) {} - fn get_state<'w>(_world: impl Into>) -> Option<()> { + fn get_state(_components: &Components) -> Option<()> { Some(()) } @@ -418,7 +418,7 @@ unsafe impl WorldQuery for EntityLocation { fn init_state(_world: &mut World) {} - fn get_state<'w>(_world: impl Into>) -> Option<()> { + fn get_state(_components: &Components) -> Option<()> { Some(()) } @@ -501,7 +501,7 @@ unsafe impl<'a> WorldQuery for EntityRef<'a> { fn init_state(_world: &mut World) {} - fn get_state<'w>(_world: impl Into>) -> Option<()> { + fn get_state(_components: &Components) -> Option<()> { Some(()) } @@ -581,7 +581,7 @@ unsafe impl<'a> WorldQuery for EntityMut<'a> { fn init_state(_world: &mut World) {} - fn get_state<'w>(_world: impl Into>) -> Option<()> { + fn get_state(_components: &Components) -> Option<()> { Some(()) } @@ -673,7 +673,7 @@ unsafe impl<'a> WorldQuery for FilteredEntityRef<'a> { FilteredAccess::default() } - fn get_state<'w>(_world: impl Into>) -> Option { + fn get_state(_components: &Components) -> Option { Some(FilteredAccess::default()) } @@ -767,7 +767,7 @@ unsafe impl<'a> WorldQuery for FilteredEntityMut<'a> { FilteredAccess::default() } - fn get_state<'w>(_world: impl Into>) -> Option { + fn get_state(_components: &Components) -> Option { Some(FilteredAccess::default()) } @@ -853,12 +853,12 @@ where } fn init_state(world: &mut World) -> Self::State { - Self::get_state(world).unwrap() + Self::get_state(world.components()).unwrap() } - fn get_state<'w>(world: impl Into>) -> Option { + fn get_state(components: &Components) -> Option { let mut ids = SmallVec::new(); - B::get_component_ids(world.into().components(), &mut |maybe_id| { + B::get_component_ids(components, &mut |maybe_id| { if let Some(id) = maybe_id { ids.push(id); } @@ -952,12 +952,12 @@ where } fn init_state(world: &mut World) -> Self::State { - Self::get_state(world).unwrap() + Self::get_state(world.components()).unwrap() } - fn get_state<'w>(world: impl Into>) -> Option { + fn get_state(components: &Components) -> Option { let mut ids = SmallVec::new(); - B::get_component_ids(world.into().components(), &mut |maybe_id| { + B::get_component_ids(components, &mut |maybe_id| { if let Some(id) = maybe_id { ids.push(id); } @@ -1038,7 +1038,7 @@ unsafe impl WorldQuery for &Archetype { fn init_state(_world: &mut World) {} - fn get_state<'w>(_world: impl Into>) -> Option<()> { + fn get_state(_components: &Components) -> Option<()> { Some(()) } @@ -1197,8 +1197,8 @@ unsafe impl WorldQuery for &T { world.register_component::() } - fn get_state<'w>(world: impl Into>) -> Option { - world.into().components().component_id::() + fn get_state(components: &Components) -> Option { + components.component_id::() } fn matches_component_set( @@ -1396,8 +1396,8 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { world.register_component::() } - fn get_state<'w>(world: impl Into>) -> Option { - world.into().components().component_id::() + fn get_state(components: &Components) -> Option { + components.component_id::() } fn matches_component_set( @@ -1595,8 +1595,8 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { world.register_component::() } - fn get_state<'w>(world: impl Into>) -> Option { - world.into().components().component_id::() + fn get_state(components: &Components) -> Option { + components.component_id::() } fn matches_component_set( @@ -1699,8 +1699,8 @@ unsafe impl<'__w, T: Component> WorldQuery for Mut<'__w, T> { } // Forwarded to `&mut T` - fn get_state<'w>(world: impl Into>) -> Option { - <&mut T as WorldQuery>::get_state(world) + fn get_state(components: &Components) -> Option { + <&mut T as WorldQuery>::get_state(components) } // Forwarded to `&mut T` @@ -1826,8 +1826,8 @@ unsafe impl WorldQuery for Option { T::init_state(world) } - fn get_state<'w>(world: impl Into>) -> Option { - T::get_state(world) + fn get_state(components: &Components) -> Option { + T::get_state(components) } fn matches_component_set( @@ -1985,8 +1985,8 @@ unsafe impl WorldQuery for Has { world.register_component::() } - fn get_state<'w>(world: impl Into>) -> Option { - world.into().components().component_id::() + fn get_state(components: &Components) -> Option { + components.component_id::() } fn matches_component_set( @@ -2144,9 +2144,8 @@ macro_rules! impl_anytuple_fetch { ($($name::init_state(world),)*) } #[allow(unused_variables)] - fn get_state<'w>(world: impl Into>) -> Option { - let world = world.into(); - Some(($($name::get_state(world)?,)*)) + fn get_state(components: &Components) -> Option { + Some(($($name::get_state(components)?,)*)) } fn matches_component_set(_state: &Self::State, _set_contains_id: &impl Fn(ComponentId) -> bool) -> bool { @@ -2240,8 +2239,8 @@ unsafe impl WorldQuery for NopWorldQuery { D::init_state(world) } - fn get_state<'w>(world: impl Into>) -> Option { - D::get_state(world) + fn get_state(components: &Components) -> Option { + D::get_state(components) } fn matches_component_set( @@ -2308,7 +2307,7 @@ unsafe impl WorldQuery for PhantomData { fn init_state(_world: &mut World) -> Self::State {} - fn get_state<'w>(_world: impl Into>) -> Option { + fn get_state(_components: &Components) -> Option { Some(()) } diff --git a/crates/bevy_ecs/src/query/filter.rs b/crates/bevy_ecs/src/query/filter.rs index 26402ab51dfa3..b096e801f4cc2 100644 --- a/crates/bevy_ecs/src/query/filter.rs +++ b/crates/bevy_ecs/src/query/filter.rs @@ -1,6 +1,6 @@ use crate::{ archetype::Archetype, - component::{Component, ComponentId, StorageType, Tick}, + component::{Component, ComponentId, Components, StorageType, Tick}, entity::Entity, query::{DebugCheckedUnwrap, FilteredAccess, StorageSwitch, WorldQuery}, storage::{ComponentSparseSet, Table, TableRow}, @@ -194,8 +194,8 @@ unsafe impl WorldQuery for With { world.register_component::() } - fn get_state<'w>(world: impl Into>) -> Option { - world.into().components().component_id::() + fn get_state(components: &Components) -> Option { + components.component_id::() } fn matches_component_set( @@ -305,8 +305,8 @@ unsafe impl WorldQuery for Without { world.register_component::() } - fn get_state<'w>(world: impl Into>) -> Option { - world.into().components().component_id::() + fn get_state(components: &Components) -> Option { + components.component_id::() } fn matches_component_set( @@ -488,9 +488,8 @@ macro_rules! impl_or_query_filter { ($($filter::init_state(world),)*) } - fn get_state<'w>(world: impl Into>) -> Option { - let world = world.into(); - Some(($($filter::get_state(world)?,)*)) + fn get_state(components: &Components) -> Option { + Some(($($filter::get_state(components)?,)*)) } fn matches_component_set(_state: &Self::State, _set_contains_id: &impl Fn(ComponentId) -> bool) -> bool { @@ -764,8 +763,8 @@ unsafe impl WorldQuery for Added { world.register_component::() } - fn get_state<'w>(world: impl Into>) -> Option { - world.into().components().component_id::() + fn get_state(components: &Components) -> Option { + components.component_id::() } fn matches_component_set( @@ -997,8 +996,8 @@ unsafe impl WorldQuery for Changed { world.register_component::() } - fn get_state<'w>(world: impl Into>) -> Option { - world.into().components().component_id::() + fn get_state(components: &Components) -> Option { + components.component_id::() } fn matches_component_set( diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 0a701114d4630..f493ba7c97ef9 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -205,8 +205,8 @@ impl QueryState { /// `new_archetype` and its variants must be called on all of the World's archetypes before the /// state can return valid query results. fn try_new_uninitialized(world: &World) -> Option { - let fetch_state = D::get_state(world)?; - let filter_state = F::get_state(world)?; + let fetch_state = D::get_state(world.components())?; + let filter_state = F::get_state(world.components())?; Some(Self::from_states_uninitialized( world.id(), fetch_state, @@ -613,8 +613,8 @@ impl QueryState { self.validate_world(world.id()); let mut component_access = FilteredAccess::default(); - let mut fetch_state = NewD::get_state(world).expect("Could not create fetch_state, Please initialize all referenced components before transmuting."); - let filter_state = NewF::get_state(world).expect("Could not create filter_state, Please initialize all referenced components before transmuting."); + let mut fetch_state = NewD::get_state(world.components()).expect("Could not create fetch_state, Please initialize all referenced components before transmuting."); + let filter_state = NewF::get_state(world.components()).expect("Could not create filter_state, Please initialize all referenced components before transmuting."); NewD::set_access(&mut fetch_state, &self.component_access); NewD::update_component_access(&fetch_state, &mut component_access); @@ -701,9 +701,9 @@ impl QueryState { self.validate_world(world.id()); let mut component_access = FilteredAccess::default(); - let mut new_fetch_state = NewD::get_state(world) + let mut new_fetch_state = NewD::get_state(world.components()) .expect("Could not create fetch_state, Please initialize all referenced components before transmuting."); - let new_filter_state = NewF::get_state(world) + let new_filter_state = NewF::get_state(world.components()) .expect("Could not create filter_state, Please initialize all referenced components before transmuting."); NewD::set_access(&mut new_fetch_state, &self.component_access); diff --git a/crates/bevy_ecs/src/query/world_query.rs b/crates/bevy_ecs/src/query/world_query.rs index 36b3b6ec0b309..47bb1be40d237 100644 --- a/crates/bevy_ecs/src/query/world_query.rs +++ b/crates/bevy_ecs/src/query/world_query.rs @@ -1,6 +1,6 @@ use crate::{ archetype::Archetype, - component::{ComponentId, Tick}, + component::{ComponentId, Components, Tick}, entity::Entity, query::FilteredAccess, storage::{Table, TableRow}, @@ -132,8 +132,9 @@ pub unsafe trait WorldQuery { /// Creates and initializes a [`State`](WorldQuery::State) for this [`WorldQuery`] type. fn init_state(world: &mut World) -> Self::State; - /// Attempts to initialize a [`State`](WorldQuery::State) for this [`WorldQuery`] type. - fn get_state<'w>(world: impl Into>) -> Option; + /// Attempts to initialize a [`State`](WorldQuery::State) for this [`WorldQuery`] type using read-only + /// access to [`Components`]. + fn get_state(components: &Components) -> Option; /// Returns `true` if this query matches a set of components. Otherwise, returns `false`. /// @@ -227,9 +228,8 @@ macro_rules! impl_tuple_world_query { ($($name::init_state(world),)*) } #[allow(unused_variables)] - fn get_state<'w>(world: impl Into>) -> Option { - let world = world.into(); - Some(($($name::get_state(world)?,)*)) + fn get_state(components: &Components) -> Option { + Some(($($name::get_state(components)?,)*)) } fn matches_component_set(state: &Self::State, _set_contains_id: &impl Fn(ComponentId) -> bool) -> bool { diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index 592082892727e..aed797c5551e5 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -910,7 +910,11 @@ impl<'w> UnsafeEntityCell<'w> { /// - the [`UnsafeEntityCell`] has permission to access the queried data immutably /// - no mutable references to the queried data exist at the same time pub(crate) unsafe fn get_components(&self) -> Option> { - let state = Q::get_state(self.world)?; + // SAFETY: World is only used to access query data and initialize query state + let state = unsafe { + let world = self.world().world(); + Q::get_state(world.components())? + }; let location = self.location(); // SAFETY: Location is guaranteed to exist let archetype = unsafe { diff --git a/crates/bevy_render/src/sync_world.rs b/crates/bevy_render/src/sync_world.rs index 805aef5ab11e8..ebff81507a0fe 100644 --- a/crates/bevy_render/src/sync_world.rs +++ b/crates/bevy_render/src/sync_world.rs @@ -255,7 +255,7 @@ mod render_entities_world_query_impls { use bevy_ecs::{ archetype::Archetype, - component::{ComponentId, Tick}, + component::{ComponentId, Components, Tick}, entity::Entity, query::{FilteredAccess, QueryData, ReadOnlyQueryData, WorldQuery}, storage::{Table, TableRow}, @@ -340,8 +340,8 @@ mod render_entities_world_query_impls { <&RenderEntity as WorldQuery>::init_state(world) } - fn get_state<'w>(world: impl Into>) -> Option { - <&RenderEntity as WorldQuery>::get_state(world) + fn get_state(components: &Components) -> Option { + <&RenderEntity as WorldQuery>::get_state(components) } fn matches_component_set( @@ -438,8 +438,8 @@ mod render_entities_world_query_impls { <&MainEntity as WorldQuery>::init_state(world) } - fn get_state<'w>(world: impl Into>) -> Option { - <&MainEntity as WorldQuery>::get_state(world) + fn get_state(components: &Components) -> Option { + <&MainEntity as WorldQuery>::get_state(components) } fn matches_component_set( From 1001e27d61708f5b7afceb9d659f26ffc7de8f71 Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Fri, 13 Dec 2024 12:55:39 -0800 Subject: [PATCH 10/23] Ci. --- crates/bevy_asset/src/asset_changed.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_asset/src/asset_changed.rs b/crates/bevy_asset/src/asset_changed.rs index 8a8ecbc05ac93..71cab91876ce8 100644 --- a/crates/bevy_asset/src/asset_changed.rs +++ b/crates/bevy_asset/src/asset_changed.rs @@ -259,7 +259,7 @@ unsafe impl QueryFilter for AssetChanged { #[cfg(test)] mod tests { use crate::{self as bevy_asset, AssetEvents, AssetPlugin, Handle}; - use std::num::NonZero; + use core::num::NonZero; use crate::{AssetApp, Assets}; use bevy_app::{App, AppExit, Last, Startup, Update}; From b6ac681856a7d5f9e69368e5884f4827fac2d9d1 Mon Sep 17 00:00:00 2001 From: charlotte Date: Fri, 13 Dec 2024 13:08:40 -0800 Subject: [PATCH 11/23] Update crates/bevy_asset/src/lib.rs Co-authored-by: Patrick Walton --- crates/bevy_asset/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index 78b6bc8a7ca44..b3bd3456ec389 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -412,7 +412,7 @@ pub trait AsAssetId: Component { /// The underlying asset type. type Asset: Asset; - /// Converts this component to an asset id. + /// Retrieves the asset id from this component. fn as_asset_id(&self) -> AssetId; } From ec0803c3585b396c1e533419a8b176db1f92383f Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Fri, 13 Dec 2024 13:28:36 -0800 Subject: [PATCH 12/23] Respond to feedback. --- crates/bevy_asset/src/asset_changed.rs | 19 ++++++++++++++----- crates/bevy_asset/src/lib.rs | 2 +- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/crates/bevy_asset/src/asset_changed.rs b/crates/bevy_asset/src/asset_changed.rs index 71cab91876ce8..6f2b57f10431d 100644 --- a/crates/bevy_asset/src/asset_changed.rs +++ b/crates/bevy_asset/src/asset_changed.rs @@ -12,6 +12,7 @@ use bevy_ecs::{ storage::{Table, TableRow}, world::unsafe_world_cell::UnsafeWorldCell, }; +use bevy_log::error; use bevy_utils::HashMap; use core::marker::PhantomData; use disqualified::ShortName; @@ -154,15 +155,23 @@ unsafe impl WorldQuery for AssetChanged { last_run: Tick, this_run: Tick, ) -> Self::Fetch<'w> { - let err_msg = || { - panic!( + // SAFETY: `AssetChanges` is private and only accessed mutably in the `AssetEvents` schedule + let Some(changes) = (unsafe { world.get_resource() }) else { + error!( "AssetChanges<{ty}> resource was removed, please do not remove \ AssetChanges<{ty}> when using the AssetChanged<{ty}> world query", ty = ShortName::of::() - ) + ); + + return AssetChangedFetch { + inner: None, + check: AssetChangeCheck { + change_ticks: &HashMap::default(), + last_run, + this_run, + }, + }; }; - // SAFETY: `AssetChanges` is private and only accessed mutably in the `AssetEvents` schedule - let changes: &AssetChanges<_> = unsafe { world.get_resource().unwrap_or_else(err_msg) }; let has_updates = changes.last_change_tick.is_newer_than(last_run, this_run); AssetChangedFetch { diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index b3bd3456ec389..b6af028994f84 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -407,7 +407,7 @@ impl Plugin for AssetPlugin { )] pub trait Asset: VisitAssetDependencies + TypePath + Send + Sync + 'static {} -/// A trait for newtypes that can be used as asset identifiers, i.e. handle wrappers. +/// A trait for components that can be used as asset identifiers, e.g. handle wrappers. pub trait AsAssetId: Component { /// The underlying asset type. type Asset: Asset; From b1640b522f77eee0e649d405ea88645fc4b2f76e Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Fri, 13 Dec 2024 13:30:25 -0800 Subject: [PATCH 13/23] Ci. --- crates/bevy_asset/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index b6af028994f84..838f252459211 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -21,7 +21,7 @@ //! Typically, you'll use the [`AssetServer::load`] method to load an asset from disk, which returns a [`Handle`]. //! Note that this method does not attempt to reload the asset if it has already been loaded: as long as at least one handle has not been dropped, //! calling [`AssetServer::load`] on the same path will return the same handle. -//! The handle that's returned can be used to instantiate various [`Component`](bevy_ecs::prelude::Component)s that require asset data to function, +//! The handle that's returned can be used to instantiate various [`Component`]s that require asset data to function, //! which will then be spawned into the world as part of an entity. //! //! To avoid assets "popping" into existence, you may want to check that all of the required assets are loaded before transitioning to a new scene. From 3015581dc9b00dd0a34b9f648e11738a87b63f5c Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Fri, 13 Dec 2024 13:31:07 -0800 Subject: [PATCH 14/23] Ci. --- crates/bevy_asset/src/asset_changed.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_asset/src/asset_changed.rs b/crates/bevy_asset/src/asset_changed.rs index 6f2b57f10431d..2a76d668c7b5f 100644 --- a/crates/bevy_asset/src/asset_changed.rs +++ b/crates/bevy_asset/src/asset_changed.rs @@ -1,6 +1,6 @@ //! Define the [`AssetChanged`] query filter. //! -//! Like [`Changed`], but for [`Asset`]s. +//! Like [`Changed`](bevy_ecs::prelude::Changed), but for [`Asset`]s. use crate::{AsAssetId, Asset, AssetId}; use bevy_ecs::component::Components; From d70303d9643da2b0a8219983d7ba54ef87732d93 Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Fri, 13 Dec 2024 13:36:10 -0800 Subject: [PATCH 15/23] Ci. --- crates/bevy_asset/src/asset_changed.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/crates/bevy_asset/src/asset_changed.rs b/crates/bevy_asset/src/asset_changed.rs index 2a76d668c7b5f..685bf625c052a 100644 --- a/crates/bevy_asset/src/asset_changed.rs +++ b/crates/bevy_asset/src/asset_changed.rs @@ -12,7 +12,7 @@ use bevy_ecs::{ storage::{Table, TableRow}, world::unsafe_world_cell::UnsafeWorldCell, }; -use bevy_log::error; +use bevy_utils::tracing::error; use bevy_utils::HashMap; use core::marker::PhantomData; use disqualified::ShortName; @@ -43,7 +43,9 @@ impl Default for AssetChanges { } struct AssetChangeCheck<'w, A: AsAssetId> { - change_ticks: &'w HashMap, Tick>, + // This should never be `None` in practice, but we need to handle the case + // where the `AssetChanges` resource was removed. + change_ticks: Option<&'w HashMap, Tick>>, last_run: Tick, this_run: Tick, } @@ -59,7 +61,7 @@ impl Copy for AssetChangeCheck<'_, A> {} impl<'w, A: AsAssetId> AssetChangeCheck<'w, A> { fn new(changes: &'w AssetChanges, last_run: Tick, this_run: Tick) -> Self { Self { - change_ticks: &changes.change_ticks, + change_ticks: Some(&changes.change_ticks), last_run, this_run, } @@ -70,7 +72,8 @@ impl<'w, A: AsAssetId> AssetChangeCheck<'w, A> { let is_newer = |tick: &Tick| tick.is_newer_than(self.last_run, self.this_run); let id = handle.as_asset_id(); - self.change_ticks.get(&id).is_some_and(is_newer) + self.change_ticks + .is_some_and(|change_ticks| change_ticks.get(&id).is_some_and(is_newer)) } } @@ -156,7 +159,7 @@ unsafe impl WorldQuery for AssetChanged { this_run: Tick, ) -> Self::Fetch<'w> { // SAFETY: `AssetChanges` is private and only accessed mutably in the `AssetEvents` schedule - let Some(changes) = (unsafe { world.get_resource() }) else { + let Some(changes) = (unsafe { world.get_resource::>() }) else { error!( "AssetChanges<{ty}> resource was removed, please do not remove \ AssetChanges<{ty}> when using the AssetChanged<{ty}> world query", @@ -166,7 +169,7 @@ unsafe impl WorldQuery for AssetChanged { return AssetChangedFetch { inner: None, check: AssetChangeCheck { - change_ticks: &HashMap::default(), + change_ticks: None, last_run, this_run, }, From 53eb8c855e327d9e47722228793c89312868bab1 Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Fri, 13 Dec 2024 15:04:09 -0800 Subject: [PATCH 16/23] Add docs. --- crates/bevy_asset/src/asset_changed.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/bevy_asset/src/asset_changed.rs b/crates/bevy_asset/src/asset_changed.rs index 685bf625c052a..97dd2bc3791b0 100644 --- a/crates/bevy_asset/src/asset_changed.rs +++ b/crates/bevy_asset/src/asset_changed.rs @@ -17,6 +17,13 @@ use bevy_utils::HashMap; use core::marker::PhantomData; use disqualified::ShortName; +/// A resource that stores the last tick an asset was changed. This is used by +/// the [`AssetChanged`] filter to determine if an asset has changed since the last time +/// a query ran. +/// +/// This resource is automatically managed by the [`AssetEvents`] schedule and should not be +/// exposed to the user in order to maintain safety guarantees. Any additional uses of this +/// resource should be carefully audited to ensure that they do not introduce any safety issues. #[derive(Resource)] pub(crate) struct AssetChanges { change_ticks: HashMap, Tick>, From d0130a34108ece47c77232d3b4a6e85874090547 Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Fri, 13 Dec 2024 19:25:47 -0800 Subject: [PATCH 17/23] Check resource reads and write in `update_archetype_component_access`. --- crates/bevy_ecs/src/query/state.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index f493ba7c97ef9..e066c844f70e4 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -583,6 +583,20 @@ impl QueryState { { access.add_component_write(archetype_component_id); } + if self + .component_access + .access + .has_resource_read(component_id) + { + access.add_resource_read(archetype_component_id); + } + if self + .component_access + .access + .has_resource_write(component_id) + { + access.add_resource_write(archetype_component_id); + } } } From 08a81ca1983d48ad9e1ea880519ae108675289fa Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Fri, 13 Dec 2024 19:35:57 -0800 Subject: [PATCH 18/23] Ci. --- crates/bevy_asset/src/asset_changed.rs | 7 ++++--- crates/bevy_ecs/src/query/state.rs | 6 +----- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/crates/bevy_asset/src/asset_changed.rs b/crates/bevy_asset/src/asset_changed.rs index 97dd2bc3791b0..03da2e2eb7b11 100644 --- a/crates/bevy_asset/src/asset_changed.rs +++ b/crates/bevy_asset/src/asset_changed.rs @@ -21,9 +21,10 @@ use disqualified::ShortName; /// the [`AssetChanged`] filter to determine if an asset has changed since the last time /// a query ran. /// -/// This resource is automatically managed by the [`AssetEvents`] schedule and should not be -/// exposed to the user in order to maintain safety guarantees. Any additional uses of this -/// resource should be carefully audited to ensure that they do not introduce any safety issues. +/// This resource is automatically managed by the [`AssetEvents`](crate::AssetEvents) schedule and +/// should not be exposed to the user in order to maintain safety guarantees. Any additional uses of +/// this resource should be carefully audited to ensure that they do not introduce any safety +/// issues. #[derive(Resource)] pub(crate) struct AssetChanges { change_ticks: HashMap, Tick>, diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index e066c844f70e4..35ed575c70a5d 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -583,11 +583,7 @@ impl QueryState { { access.add_component_write(archetype_component_id); } - if self - .component_access - .access - .has_resource_read(component_id) - { + if self.component_access.access.has_resource_read(component_id) { access.add_resource_read(archetype_component_id); } if self From 13ddde4557f50fa1cff0b849557bd588eaadaab9 Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Fri, 13 Dec 2024 20:28:10 -0800 Subject: [PATCH 19/23] `component_id` -> `register_component`. --- crates/bevy_asset/src/asset_changed.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/bevy_asset/src/asset_changed.rs b/crates/bevy_asset/src/asset_changed.rs index 03da2e2eb7b11..27a9eb513a821 100644 --- a/crates/bevy_asset/src/asset_changed.rs +++ b/crates/bevy_asset/src/asset_changed.rs @@ -230,7 +230,7 @@ unsafe impl WorldQuery for AssetChanged { fn init_state(world: &mut World) -> AssetChangedState { let resource_id = world.init_resource::>(); - let asset_id = world.component_id::().unwrap(); + let asset_id = world.register_component::(); AssetChangedState { asset_id, resource_id, @@ -337,7 +337,6 @@ mod tests { assets: Res>, query: Query<&MyComponent, AssetChanged>, ) { - println!("counting updates"); for handle in query.iter() { let asset = assets.get(&handle.0).unwrap(); counter.0[asset.0] += 1; From 1aee41394161cadbca17fc5e8c60a2d90f2317a5 Mon Sep 17 00:00:00 2001 From: charlotte Date: Fri, 13 Dec 2024 22:33:57 -0800 Subject: [PATCH 20/23] Apply suggestions from code review Co-authored-by: Alice Cecile --- crates/bevy_asset/src/asset_changed.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/bevy_asset/src/asset_changed.rs b/crates/bevy_asset/src/asset_changed.rs index 27a9eb513a821..3d4e875d088e8 100644 --- a/crates/bevy_asset/src/asset_changed.rs +++ b/crates/bevy_asset/src/asset_changed.rs @@ -1,6 +1,7 @@ -//! Define the [`AssetChanged`] query filter. +//! Defines the [`AssetChanged`] query filter. //! -//! Like [`Changed`](bevy_ecs::prelude::Changed), but for [`Asset`]s. +//! Like [`Changed`](bevy_ecs::prelude::Changed), but for [`Asset`]s, +//! and triggers whenever the handle or the underlying asset changes. use crate::{AsAssetId, Asset, AssetId}; use bevy_ecs::component::Components; @@ -123,7 +124,7 @@ impl<'w, A: AsAssetId> AssetChangeCheck<'w, A> { /// [`Assets::get_mut`]: crate::Assets::get_mut pub struct AssetChanged(PhantomData); -/// Fetch for [`AssetChanged`]. +/// [`WorldQuery`] fetch for [`AssetChanged`]. #[doc(hidden)] pub struct AssetChangedFetch<'w, A: AsAssetId> { inner: Option>, @@ -139,7 +140,7 @@ impl<'w, A: AsAssetId> Clone for AssetChangedFetch<'w, A> { } } -/// State for [`AssetChanged`]. +/// [`WorldQuery`] state for [`AssetChanged`]. #[doc(hidden)] pub struct AssetChangedState { asset_id: ComponentId, From 46dfc2ca58e811cd1bdc74d513b8e9b241a2f1a5 Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Sat, 14 Dec 2024 09:29:37 -0800 Subject: [PATCH 21/23] Move to local allows. --- crates/bevy_asset/src/asset_changed.rs | 2 ++ crates/bevy_asset/src/lib.rs | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/bevy_asset/src/asset_changed.rs b/crates/bevy_asset/src/asset_changed.rs index 3d4e875d088e8..0ed7dc9897c72 100644 --- a/crates/bevy_asset/src/asset_changed.rs +++ b/crates/bevy_asset/src/asset_changed.rs @@ -149,6 +149,7 @@ pub struct AssetChangedState { } // SAFETY: `ROQueryFetch` is the same as `QueryFetch` +#[allow(unsafe_code)] unsafe impl WorldQuery for AssetChanged { type Item<'w> = (); type Fetch<'w> = AssetChangedFetch<'w, A>; @@ -258,6 +259,7 @@ unsafe impl WorldQuery for AssetChanged { } /// SAFETY: read-only access +#[allow(unsafe_code)] unsafe impl QueryFilter for AssetChanged { const IS_ARCHETYPAL: bool = false; diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index 838f252459211..685c03e2fe7b4 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -141,7 +141,6 @@ // FIXME(3492): remove once docs are ready // FIXME(15321): solve CI failures, then replace with `#![expect()]`. #![allow(missing_docs, reason = "Not all docs are written yet, see #3492.")] -#![allow(unsafe_code)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![doc( html_logo_url = "https://bevyengine.org/assets/icon.png", From 53a8c67df5640b347524fc0333723d0ccd5f9915 Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Sat, 14 Dec 2024 09:33:54 -0800 Subject: [PATCH 22/23] Ci. --- crates/bevy_asset/src/asset_changed.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bevy_asset/src/asset_changed.rs b/crates/bevy_asset/src/asset_changed.rs index 0ed7dc9897c72..6cdb19666c519 100644 --- a/crates/bevy_asset/src/asset_changed.rs +++ b/crates/bevy_asset/src/asset_changed.rs @@ -79,7 +79,7 @@ impl<'w, A: AsAssetId> AssetChangeCheck<'w, A> { // which is not optimal. fn has_changed(&self, handle: &A) -> bool { let is_newer = |tick: &Tick| tick.is_newer_than(self.last_run, self.this_run); - let id = handle.as_asset_id(); + let ids = handle.as_asset_id(); self.change_ticks .is_some_and(|change_ticks| change_ticks.get(&id).is_some_and(is_newer)) @@ -148,8 +148,8 @@ pub struct AssetChangedState { _asset: PhantomData, } -// SAFETY: `ROQueryFetch` is the same as `QueryFetch` #[allow(unsafe_code)] +/// SAFETY: `ROQueryFetch` is the same as `QueryFetch` unsafe impl WorldQuery for AssetChanged { type Item<'w> = (); type Fetch<'w> = AssetChangedFetch<'w, A>; @@ -258,8 +258,8 @@ unsafe impl WorldQuery for AssetChanged { } } -/// SAFETY: read-only access #[allow(unsafe_code)] +/// SAFETY: read-only access unsafe impl QueryFilter for AssetChanged { const IS_ARCHETYPAL: bool = false; From 5a37dcad7d7aa14e3bb7648691291a3423a7f4d2 Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Sat, 14 Dec 2024 09:38:54 -0800 Subject: [PATCH 23/23] Ci. --- crates/bevy_asset/src/asset_changed.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_asset/src/asset_changed.rs b/crates/bevy_asset/src/asset_changed.rs index 6cdb19666c519..9a7a093f073d2 100644 --- a/crates/bevy_asset/src/asset_changed.rs +++ b/crates/bevy_asset/src/asset_changed.rs @@ -79,7 +79,7 @@ impl<'w, A: AsAssetId> AssetChangeCheck<'w, A> { // which is not optimal. fn has_changed(&self, handle: &A) -> bool { let is_newer = |tick: &Tick| tick.is_newer_than(self.last_run, self.this_run); - let ids = handle.as_asset_id(); + let id = handle.as_asset_id(); self.change_ticks .is_some_and(|change_ticks| change_ticks.get(&id).is_some_and(is_newer))