From 74d904060aa89227ada52bbcf777334f718338c0 Mon Sep 17 00:00:00 2001 From: re0312 Date: Sat, 31 May 2025 08:55:28 +0800 Subject: [PATCH 01/14] event --- crates/bevy_ecs/src/archetype.rs | 25 +++++---- crates/bevy_ecs/src/bundle.rs | 90 +++++++++++++++++++++++--------- 2 files changed, 82 insertions(+), 33 deletions(-) diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index f12cd03a69dbd..3fbe5fa369f8f 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -23,17 +23,23 @@ use crate::{ bundle::BundleId, component::{ComponentId, Components, RequiredComponentConstructor, StorageType}, entity::{Entity, EntityLocation}, + event::Event, observer::Observers, storage::{ImmutableSparseSet, SparseArray, SparseSet, TableId, TableRow}, }; use alloc::{boxed::Box, vec::Vec}; -use bevy_platform::collections::HashMap; +use bevy_platform::collections::{hash_map::Entry, HashMap}; use core::{ hash::Hash, ops::{Index, IndexMut, RangeFrom}, }; use nonmax::NonMaxU32; +#[derive(Event)] +#[allow(dead_code)] + +pub(crate) struct ArchetypeCreated(pub ArchetypeId); + /// An opaque location within a [`Archetype`]. /// /// This can be used in conjunction with [`ArchetypeId`] to find the exact location @@ -881,7 +887,7 @@ impl Archetypes { table_id: TableId, table_components: Vec, sparse_set_components: Vec, - ) -> ArchetypeId { + ) -> (ArchetypeId, bool) { let archetype_identity = ArchetypeComponents { sparse_set_components: sparse_set_components.into_boxed_slice(), table_components: table_components.into_boxed_slice(), @@ -889,14 +895,13 @@ impl Archetypes { let archetypes = &mut self.archetypes; let component_index = &mut self.by_component; - *self - .by_components - .entry(archetype_identity) - .or_insert_with_key(move |identity| { + match self.by_components.entry(archetype_identity) { + Entry::Occupied(occupied) => (*occupied.get(), false), + Entry::Vacant(vacant) => { let ArchetypeComponents { table_components, sparse_set_components, - } = identity; + } = vacant.key(); let id = ArchetypeId::new(archetypes.len()); archetypes.push(Archetype::new( components, @@ -907,8 +912,10 @@ impl Archetypes { table_components.iter().copied(), sparse_set_components.iter().copied(), )); - id - }) + vacant.insert(id); + (id, true) + } + } } /// Clears all entities from all archetypes. diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index e3e54c092f644..967fcaec69eb3 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -6,8 +6,8 @@ pub use bevy_ecs_macros::Bundle; use crate::{ archetype::{ - Archetype, ArchetypeAfterBundleInsert, ArchetypeId, Archetypes, BundleComponentStatus, - ComponentStatus, SpawnBundleStatus, + Archetype, ArchetypeAfterBundleInsert, ArchetypeCreated, ArchetypeId, Archetypes, + BundleComponentStatus, ComponentStatus, SpawnBundleStatus, }, change_detection::MaybeLocation, component::{ @@ -732,7 +732,7 @@ impl BundleInfo { } } - /// Inserts a bundle into the given archetype and returns the resulting archetype. + /// Inserts a bundle into the given archetype and returns the resulting archetype and whether a new archetype was created. /// This could be the same [`ArchetypeId`], in the event that inserting the given bundle /// does not result in an [`Archetype`] change. /// @@ -747,12 +747,12 @@ impl BundleInfo { components: &Components, observers: &Observers, archetype_id: ArchetypeId, - ) -> ArchetypeId { + ) -> (ArchetypeId, bool) { if let Some(archetype_after_insert_id) = archetypes[archetype_id] .edges() .get_archetype_after_bundle_insert(self.id) { - return archetype_after_insert_id; + return (archetype_after_insert_id, false); } let mut new_table_components = Vec::new(); let mut new_sparse_set_components = Vec::new(); @@ -806,7 +806,7 @@ impl BundleInfo { added, existing, ); - archetype_id + (archetype_id, false) } else { let table_id; let table_components; @@ -842,13 +842,14 @@ impl BundleInfo { }; }; // SAFETY: ids in self must be valid - let new_archetype_id = archetypes.get_id_or_insert( + let (new_archetype_id, is_new_created) = archetypes.get_id_or_insert( components, observers, table_id, table_components, sparse_set_components, ); + // Add an edge from the old archetype to the new archetype. archetypes[archetype_id] .edges_mut() @@ -860,11 +861,11 @@ impl BundleInfo { added, existing, ); - new_archetype_id + (new_archetype_id, is_new_created) } } - /// Removes a bundle from the given archetype and returns the resulting archetype + /// Removes a bundle from the given archetype and returns the resulting archetype and whether a new archetype was created. /// (or `None` if the removal was invalid). /// This could be the same [`ArchetypeId`], in the event that removing the given bundle /// does not result in an [`Archetype`] change. @@ -887,7 +888,7 @@ impl BundleInfo { observers: &Observers, archetype_id: ArchetypeId, intersection: bool, - ) -> Option { + ) -> (Option, bool) { // Check the archetype graph to see if the bundle has been // removed from this archetype in the past. let archetype_after_remove_result = { @@ -898,9 +899,9 @@ impl BundleInfo { edges.get_archetype_after_bundle_take(self.id()) } }; - let result = if let Some(result) = archetype_after_remove_result { + let (result, is_new_created) = if let Some(result) = archetype_after_remove_result { // This bundle removal result is cached. Just return that! - result + (result, false) } else { let mut next_table_components; let mut next_sparse_set_components; @@ -925,7 +926,7 @@ impl BundleInfo { current_archetype .edges_mut() .cache_archetype_after_bundle_take(self.id(), None); - return None; + return (None, false); } } @@ -953,14 +954,14 @@ impl BundleInfo { }; } - let new_archetype_id = archetypes.get_id_or_insert( + let (new_archetype_id, is_new_created) = archetypes.get_id_or_insert( components, observers, next_table_id, next_table_components, next_sparse_set_components, ); - Some(new_archetype_id) + (Some(new_archetype_id), is_new_created) }; let current_archetype = &mut archetypes[archetype_id]; // Cache the result in an edge. @@ -973,7 +974,7 @@ impl BundleInfo { .edges_mut() .cache_archetype_after_bundle_take(self.id(), result); } - result + (result, is_new_created) } } @@ -1034,15 +1035,21 @@ impl<'w> BundleInserter<'w> { change_tick: Tick, ) -> Self { // SAFETY: We will not make any accesses to the command queue, component or resource data of this world - let bundle_info = world.bundles.get_unchecked(bundle_id); + let mut bundle_info = world.bundles.get_unchecked(bundle_id); let bundle_id = bundle_info.id(); - let new_archetype_id = bundle_info.insert_bundle_into_archetype( + let (new_archetype_id, is_new_created) = bundle_info.insert_bundle_into_archetype( &mut world.archetypes, &mut world.storages, &world.components, &world.observers, archetype_id, ); + + if is_new_created { + world.trigger(ArchetypeCreated(new_archetype_id)); + bundle_info = world.bundles.get_unchecked(bundle_id); + } + if new_archetype_id == archetype_id { let archetype = &mut world.archetypes[archetype_id]; // SAFETY: The edge is assured to be initialized when we called insert_bundle_into_archetype @@ -1419,9 +1426,9 @@ impl<'w> BundleRemover<'w> { bundle_id: BundleId, require_all: bool, ) -> Option { - let bundle_info = world.bundles.get_unchecked(bundle_id); + let mut bundle_info = world.bundles.get_unchecked(bundle_id); // SAFETY: Caller ensures archetype and bundle ids are correct. - let new_archetype_id = unsafe { + let (new_archetype_id, is_new_created) = unsafe { bundle_info.remove_bundle_from_archetype( &mut world.archetypes, &mut world.storages, @@ -1429,11 +1436,19 @@ impl<'w> BundleRemover<'w> { &world.observers, archetype_id, !require_all, - )? + ) }; + let new_archetype_id = new_archetype_id?; + if new_archetype_id == archetype_id { return None; } + + if is_new_created { + world.trigger(ArchetypeCreated(new_archetype_id)); + bundle_info = world.bundles.get_unchecked(bundle_id); + } + let (old_archetype, new_archetype) = world.archetypes.get_2_mut(archetype_id, new_archetype_id); @@ -1674,14 +1689,20 @@ impl<'w> BundleSpawner<'w> { bundle_id: BundleId, change_tick: Tick, ) -> Self { - let bundle_info = world.bundles.get_unchecked(bundle_id); - let new_archetype_id = bundle_info.insert_bundle_into_archetype( + let mut bundle_info = world.bundles.get_unchecked(bundle_id); + let (new_archetype_id, is_new_created) = bundle_info.insert_bundle_into_archetype( &mut world.archetypes, &mut world.storages, &world.components, &world.observers, ArchetypeId::EMPTY, ); + + if is_new_created { + world.trigger(ArchetypeCreated(new_archetype_id)); + bundle_info = world.bundles.get_unchecked(bundle_id); + } + let archetype = &mut world.archetypes[new_archetype_id]; let table = &mut world.storages.tables[archetype.table_id()]; Self { @@ -2043,7 +2064,9 @@ fn sorted_remove(source: &mut Vec, remove: &[T]) { #[cfg(test)] mod tests { - use crate::{component::HookContext, prelude::*, world::DeferredWorld}; + use crate::{ + archetype::ArchetypeCreated, component::HookContext, prelude::*, world::DeferredWorld, + }; use alloc::vec; #[derive(Component)] @@ -2280,4 +2303,23 @@ mod tests { assert_eq!(a, vec![1]); } + + #[test] + fn new_archetype_creadted() { + let mut world = World::new(); + #[derive(Resource, Default)] + struct Count(u32); + world.init_resource::(); + world.add_observer(|_t: Trigger, mut count: ResMut| { + count.0 += 1; + }); + + let mut e = world.spawn((A, B)); + e.insert(C); + e.remove::(); + e.insert(A); + e.insert(A); + + assert_eq!(world.resource::().0, 3); + } } From f25aa9cb96d167695dfd08006ee1a8ec73515449 Mon Sep 17 00:00:00 2001 From: re0312 Date: Sat, 31 May 2025 11:27:10 +0800 Subject: [PATCH 02/14] state_entity --- crates/bevy_audio/src/audio_output.rs | 6 +- .../tests/ui/system_param_derive_readonly.rs | 5 +- crates/bevy_ecs/src/query/state.rs | 4 +- crates/bevy_ecs/src/system/builder.rs | 20 +++-- crates/bevy_ecs/src/system/system_param.rs | 75 ++++++++++--------- crates/bevy_input_focus/src/lib.rs | 6 +- crates/bevy_input_focus/src/tab_navigation.rs | 10 +-- .../src/mesh_picking/ray_cast/mod.rs | 4 +- crates/bevy_text/src/text2d.rs | 2 +- crates/bevy_text/src/text_access.rs | 14 ++-- crates/bevy_transform/src/helper.rs | 8 +- .../src/experimental/ghost_hierarchy.rs | 38 +++++----- crates/bevy_ui/src/render/mod.rs | 14 ++-- crates/bevy_ui/src/ui_node.rs | 10 +-- crates/bevy_ui/src/widget/text.rs | 2 +- 15 files changed, 114 insertions(+), 104 deletions(-) diff --git a/crates/bevy_audio/src/audio_output.rs b/crates/bevy_audio/src/audio_output.rs index 9fc757af443ff..70d0d453a0648 100644 --- a/crates/bevy_audio/src/audio_output.rs +++ b/crates/bevy_audio/src/audio_output.rs @@ -54,10 +54,10 @@ pub struct PlaybackDespawnMarker; pub struct PlaybackRemoveMarker; #[derive(SystemParam)] -pub(crate) struct EarPositions<'w, 's> { - pub(crate) query: Query<'w, 's, (Entity, &'static GlobalTransform, &'static SpatialListener)>, +pub(crate) struct EarPositions<'w> { + pub(crate) query: Query<'w, 'w, (Entity, &'static GlobalTransform, &'static SpatialListener)>, } -impl<'w, 's> EarPositions<'w, 's> { +impl<'w> EarPositions<'w> { /// Gets a set of transformed ear positions. /// /// If there are no listeners, use the default values. If a user has added multiple diff --git a/crates/bevy_ecs/compile_fail/tests/ui/system_param_derive_readonly.rs b/crates/bevy_ecs/compile_fail/tests/ui/system_param_derive_readonly.rs index 84691f7a2704a..3002184b9d4bc 100644 --- a/crates/bevy_ecs/compile_fail/tests/ui/system_param_derive_readonly.rs +++ b/crates/bevy_ecs/compile_fail/tests/ui/system_param_derive_readonly.rs @@ -5,12 +5,11 @@ use bevy_ecs::system::{ReadOnlySystemParam, SystemParam, SystemState}; struct Foo; #[derive(SystemParam)] -struct Mutable<'w, 's> { - a: Query<'w, 's, &'static mut Foo>, +struct Mutable<'w> { + a: Query<'w, &'static mut Foo>, } fn main() { - let mut world = World::default(); let state = SystemState::::new(&mut world); state.get(&world); diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 836fefbdfd837..bf888af7042ab 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -3,7 +3,7 @@ use crate::{ component::{ComponentId, Tick}, entity::{Entity, EntityEquivalent, EntitySet, UniqueEntityArray}, entity_disabling::DefaultQueryFilters, - prelude::FromWorld, + prelude::{Component, FromWorld}, query::{FilteredAccess, QueryCombinationIter, QueryIter, QueryParIter, WorldQuery}, storage::{SparseSetIndex, TableId}, system::Query, @@ -60,6 +60,8 @@ pub(super) union StorageId { /// [`Fetch`]: crate::query::world_query::WorldQuery::Fetch /// [`Table`]: crate::storage::Table #[repr(C)] +#[derive(Component)] +#[component(storage = "SparseSet")] // SAFETY NOTE: // Do not add any new fields that use the `D` or `F` generic parameters as this may // make `QueryState::as_transmuted_state` unsound if not done with care. diff --git a/crates/bevy_ecs/src/system/builder.rs b/crates/bevy_ecs/src/system/builder.rs index f9c96c628449f..9f1a93e5d03b5 100644 --- a/crates/bevy_ecs/src/system/builder.rs +++ b/crates/bevy_ecs/src/system/builder.rs @@ -3,6 +3,8 @@ use bevy_platform::cell::SyncCell; use variadics_please::all_tuples; use crate::{ + component::ComponentId, + entity::Entity, prelude::QueryBuilder, query::{QueryData, QueryFilter, QueryState}, resource::Resource, @@ -212,10 +214,12 @@ impl ParamBuilder { unsafe impl<'w, 's, D: QueryData + 'static, F: QueryFilter + 'static> SystemParamBuilder> for QueryState { - fn build(self, world: &mut World, system_meta: &mut SystemMeta) -> QueryState { + fn build(self, world: &mut World, system_meta: &mut SystemMeta) -> (Entity, ComponentId) { self.validate_world(world.id()); init_query_param(world, system_meta, &self); - self + let id = world.register_component::>(); + let e = world.spawn(self).id(); + (e, id) } } @@ -291,12 +295,14 @@ unsafe impl< T: FnOnce(&mut QueryBuilder), > SystemParamBuilder> for QueryParamBuilder { - fn build(self, world: &mut World, system_meta: &mut SystemMeta) -> QueryState { + fn build(self, world: &mut World, system_meta: &mut SystemMeta) -> (Entity, ComponentId) { let mut builder = QueryBuilder::new(world); (self.0)(&mut builder); let state = builder.build(); init_query_param(world, system_meta, &state); - state + let id = world.register_component::>(); + let e = world.spawn(state).id(); + (e, id) } } @@ -965,9 +971,9 @@ mod tests { #[derive(SystemParam)] #[system_param(builder)] - struct CustomParam<'w, 's> { - query: Query<'w, 's, ()>, - local: Local<'s, usize>, + struct CustomParam<'w> { + query: Query<'w, 'w, ()>, + local: Local, } #[test] diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index df14530f48be8..bb6b1ddbe8794 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -4,10 +4,10 @@ use crate::{ bundle::Bundles, change_detection::{MaybeLocation, Ticks, TicksMut}, component::{ComponentId, ComponentTicks, Components, Tick}, - entity::Entities, + entity::{Entities, Entity}, query::{ - Access, FilteredAccess, FilteredAccessSet, QueryData, QueryFilter, QuerySingleError, - QueryState, ReadOnlyQueryData, + Access, DebugCheckedUnwrap, FilteredAccess, FilteredAccessSet, QueryData, QueryFilter, + QuerySingleError, QueryState, ReadOnlyQueryData, }, resource::Resource, storage::ResourceData, @@ -65,7 +65,7 @@ use variadics_please::{all_tuples, all_tuples_enumerated}; /// # #[derive(SystemParam)] /// # struct ParamsExample<'w, 's> { /// # query: -/// Query<'w, 's, Entity>, +/// Query<'w, 'w, Entity>, /// # res: /// Res<'w, SomeResource>, /// # res_mut: @@ -171,7 +171,7 @@ use variadics_please::{all_tuples, all_tuples_enumerated}; /// #[derive(SystemParam)] /// #[system_param(builder)] /// pub struct CustomParam<'w, 's> { -/// query: Query<'w, 's, ()>, +/// query: Query<'w, 'w, ()>, /// local: Local<'s, usize>, /// } /// @@ -321,13 +321,15 @@ unsafe impl<'w, 's, D: ReadOnlyQueryData + 'static, F: QueryFilter + 'static> Re // SAFETY: Relevant query ComponentId access is applied to SystemMeta. If // this Query conflicts with any prior access, a panic will occur. unsafe impl SystemParam for Query<'_, '_, D, F> { - type State = QueryState; - type Item<'w, 's> = Query<'w, 's, D, F>; + type State = (Entity, ComponentId); + type Item<'w, 's> = Query<'w, 'w, D, F>; fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { - let state = QueryState::new(world); + let state: QueryState = QueryState::new(world); init_query_param(world, system_meta, &state); - state + let e = world.spawn(state).id(); + let id = world.register_component::>(); + (e, id) } #[inline] @@ -336,11 +338,21 @@ unsafe impl SystemParam for Qu system_meta: &SystemMeta, world: UnsafeWorldCell<'w>, change_tick: Tick, - ) -> Self::Item<'w, 's> { + ) -> Self::Item<'w, 'w> { + let state = world + .storages() + .sparse_sets + .get(state.1) + .debug_checked_unwrap() + .get(state.0) + .debug_checked_unwrap() + .assert_unique() + .deref_mut::>(); // SAFETY: We have registered all of the query's world accesses, // so the caller ensures that `world` has permission to access any // world data that the query needs. // The caller ensures the world matches the one used in init_state. + unsafe { state.query_unchecked_with_ticks(world, system_meta.last_run, change_tick) } } } @@ -386,11 +398,11 @@ fn assert_component_access_compatibility( // SAFETY: Relevant query ComponentId access is applied to SystemMeta. If // this Query conflicts with any prior access, a panic will occur. unsafe impl<'a, D: QueryData + 'static, F: QueryFilter + 'static> SystemParam for Single<'a, D, F> { - type State = QueryState; + type State = (Entity, ComponentId); type Item<'w, 's> = Single<'w, D, F>; fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { - Query::init_state(world, system_meta) + Query::::init_state(world, system_meta) } #[inline] @@ -400,10 +412,8 @@ unsafe impl<'a, D: QueryData + 'static, F: QueryFilter + 'static> SystemParam fo world: UnsafeWorldCell<'w>, change_tick: Tick, ) -> Self::Item<'w, 's> { - // SAFETY: State ensures that the components it accesses are not accessible somewhere elsewhere. - // The caller ensures the world matches the one used in init_state. - let query = - unsafe { state.query_unchecked_with_ticks(world, system_meta.last_run, change_tick) }; + let query: Query<'_, '_, D, F> = Query::get_param(state, system_meta, world, change_tick); + let single = query .single_inner() .expect("The query was expected to contain exactly one matching entity."); @@ -419,12 +429,9 @@ unsafe impl<'a, D: QueryData + 'static, F: QueryFilter + 'static> SystemParam fo system_meta: &SystemMeta, world: UnsafeWorldCell, ) -> Result<(), SystemParamValidationError> { - // SAFETY: State ensures that the components it accesses are not mutably accessible elsewhere - // and the query is read only. - // The caller ensures the world matches the one used in init_state. - let query = unsafe { - state.query_unchecked_with_ticks(world, system_meta.last_run, world.change_tick()) - }; + let query: Query<'_, '_, D, F> = + Query::get_param(state, system_meta, world, world.change_tick()); + match query.single_inner() { Ok(_) => Ok(()), Err(QuerySingleError::NoEntities(_)) => Err( @@ -448,11 +455,11 @@ unsafe impl<'a, D: ReadOnlyQueryData + 'static, F: QueryFilter + 'static> ReadOn unsafe impl SystemParam for Populated<'_, '_, D, F> { - type State = QueryState; - type Item<'w, 's> = Populated<'w, 's, D, F>; + type State = (Entity, ComponentId); + type Item<'w, 's> = Populated<'w, 'w, D, F>; fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { - Query::init_state(world, system_meta) + Query::::init_state(world, system_meta) } #[inline] @@ -461,7 +468,7 @@ unsafe impl SystemParam system_meta: &SystemMeta, world: UnsafeWorldCell<'w>, change_tick: Tick, - ) -> Self::Item<'w, 's> { + ) -> Self::Item<'w, 'w> { // SAFETY: Delegate to existing `SystemParam` implementations. let query = unsafe { Query::get_param(state, system_meta, world, change_tick) }; Populated(query) @@ -473,12 +480,9 @@ unsafe impl SystemParam system_meta: &SystemMeta, world: UnsafeWorldCell, ) -> Result<(), SystemParamValidationError> { - // SAFETY: - // - We have read-only access to the components accessed by query. - // - The caller ensures the world matches the one used in init_state. - let query = unsafe { - state.query_unchecked_with_ticks(world, system_meta.last_run, world.change_tick()) - }; + // SAFETY: Delegate to existing `SystemParam` implementations. + let query: Query<'_, '_, D, F> = + unsafe { Query::get_param(state, system_meta, world, world.change_tick()) }; if query.is_empty() { Err(SystemParamValidationError::skipped::( "No matching entities", @@ -2573,11 +2577,10 @@ mod tests { #[derive(SystemParam)] pub struct SpecialQuery< 'w, - 's, D: QueryData + Send + Sync + 'static, F: QueryFilter + Send + Sync + 'static = (), > { - _query: Query<'w, 's, D, F>, + _query: Query<'w, 'w, D, F>, } fn my_system(_: SpecialQuery<(), ()>) {} @@ -2706,11 +2709,11 @@ mod tests { #[test] fn system_param_where_clause() { #[derive(SystemParam)] - pub struct WhereParam<'w, 's, D> + pub struct WhereParam<'w, D> where D: 'static + QueryData, { - _q: Query<'w, 's, D, ()>, + _q: Query<'w, 'w, D, ()>, } fn my_system(_: WhereParam<()>) {} diff --git a/crates/bevy_input_focus/src/lib.rs b/crates/bevy_input_focus/src/lib.rs index 44ff0ef645895..d85f5ebcb8374 100644 --- a/crates/bevy_input_focus/src/lib.rs +++ b/crates/bevy_input_focus/src/lib.rs @@ -293,13 +293,13 @@ pub trait IsFocused { /// /// When working with the entire [`World`], consider using the [`IsFocused`] instead. #[derive(SystemParam)] -pub struct IsFocusedHelper<'w, 's> { - parent_query: Query<'w, 's, &'static ChildOf>, +pub struct IsFocusedHelper<'w> { + parent_query: Query<'w, 'w, &'static ChildOf>, input_focus: Option>, input_focus_visible: Option>, } -impl IsFocused for IsFocusedHelper<'_, '_> { +impl IsFocused for IsFocusedHelper<'_> { fn is_focused(&self, entity: Entity) -> bool { self.input_focus .as_deref() diff --git a/crates/bevy_input_focus/src/tab_navigation.rs b/crates/bevy_input_focus/src/tab_navigation.rs index 60df130ae0651..1f7180f8071d9 100644 --- a/crates/bevy_input_focus/src/tab_navigation.rs +++ b/crates/bevy_input_focus/src/tab_navigation.rs @@ -150,21 +150,21 @@ pub enum TabNavigationError { /// An injectable helper object that provides tab navigation functionality. #[doc(hidden)] #[derive(SystemParam)] -pub struct TabNavigation<'w, 's> { +pub struct TabNavigation<'w> { // Query for tab groups. - tabgroup_query: Query<'w, 's, (Entity, &'static TabGroup, &'static Children)>, + tabgroup_query: Query<'w, 'w, (Entity, &'static TabGroup, &'static Children)>, // Query for tab indices. tabindex_query: Query< 'w, - 's, + 'w, (Entity, Option<&'static TabIndex>, Option<&'static Children>), Without, >, // Query for parents. - parent_query: Query<'w, 's, &'static ChildOf>, + parent_query: Query<'w, 'w, &'static ChildOf>, } -impl TabNavigation<'_, '_> { +impl TabNavigation<'_> { /// Navigate to the desired focusable entity. /// /// Change the [`NavAction`] to navigate in a different direction. diff --git a/crates/bevy_picking/src/mesh_picking/ray_cast/mod.rs b/crates/bevy_picking/src/mesh_picking/ray_cast/mod.rs index c1f465b96a80a..73748f2fb95cb 100644 --- a/crates/bevy_picking/src/mesh_picking/ray_cast/mod.rs +++ b/crates/bevy_picking/src/mesh_picking/ray_cast/mod.rs @@ -179,7 +179,7 @@ pub struct MeshRayCast<'w, 's> { #[doc(hidden)] pub culling_query: Query< 'w, - 's, + 'w, ( Read, Read, @@ -192,7 +192,7 @@ pub struct MeshRayCast<'w, 's> { #[doc(hidden)] pub mesh_query: Query< 'w, - 's, + 'w, ( Option>, Option>, diff --git a/crates/bevy_text/src/text2d.rs b/crates/bevy_text/src/text2d.rs index 5069804df8672..a946a07b4ea4c 100644 --- a/crates/bevy_text/src/text2d.rs +++ b/crates/bevy_text/src/text2d.rs @@ -131,7 +131,7 @@ impl From for Text2d { pub type Text2dReader<'w, 's> = TextReader<'w, 's, Text2d>; /// 2d alias for [`TextWriter`]. -pub type Text2dWriter<'w, 's> = TextWriter<'w, 's, Text2d>; +pub type Text2dWriter<'w> = TextWriter<'w, Text2d>; /// This system extracts the sprites from the 2D text components and adds them to the /// "render world". diff --git a/crates/bevy_text/src/text_access.rs b/crates/bevy_text/src/text_access.rs index 7aafa28ef63e2..7d3b306bb6800 100644 --- a/crates/bevy_text/src/text_access.rs +++ b/crates/bevy_text/src/text_access.rs @@ -52,7 +52,7 @@ pub struct TextReader<'w, 's, R: TextRoot> { scratch: Local<'s, TextIterScratch>, roots: Query< 'w, - 's, + 'w, ( &'static R, &'static TextFont, @@ -62,7 +62,7 @@ pub struct TextReader<'w, 's, R: TextRoot> { >, spans: Query< 'w, - 's, + 'w, ( &'static TextSpan, &'static TextFont, @@ -222,12 +222,12 @@ impl<'a, R: TextRoot> Drop for TextSpanIter<'a, R> { /// /// `R` is the root text component, and `S` is the text span component on children. #[derive(SystemParam)] -pub struct TextWriter<'w, 's, R: TextRoot> { +pub struct TextWriter<'w, R: TextRoot> { // This is a resource because two TextWriters can't run in parallel. scratch: ResMut<'w, TextIterScratch>, roots: Query< 'w, - 's, + 'w, ( &'static mut R, &'static mut TextFont, @@ -237,7 +237,7 @@ pub struct TextWriter<'w, 's, R: TextRoot> { >, spans: Query< 'w, - 's, + 'w, ( &'static mut TextSpan, &'static mut TextFont, @@ -245,10 +245,10 @@ pub struct TextWriter<'w, 's, R: TextRoot> { ), Without, >, - children: Query<'w, 's, &'static Children>, + children: Query<'w, 'w, &'static Children>, } -impl<'w, 's, R: TextRoot> TextWriter<'w, 's, R> { +impl<'w, R: TextRoot> TextWriter<'w, R> { /// Gets a mutable reference to a text span within a text block at a specific index in the flattened span list. pub fn get( &mut self, diff --git a/crates/bevy_transform/src/helper.rs b/crates/bevy_transform/src/helper.rs index d13822847fc40..7c7ebcd800bca 100644 --- a/crates/bevy_transform/src/helper.rs +++ b/crates/bevy_transform/src/helper.rs @@ -17,12 +17,12 @@ use crate::components::{GlobalTransform, Transform}; /// a [`GlobalTransform`] that reflects the changes made to any [`Transform`]s since /// the last time the transform propagation systems ran. #[derive(SystemParam)] -pub struct TransformHelper<'w, 's> { - parent_query: Query<'w, 's, &'static ChildOf>, - transform_query: Query<'w, 's, &'static Transform>, +pub struct TransformHelper<'w> { + parent_query: Query<'w, 'w, &'static ChildOf>, + transform_query: Query<'w, 'w, &'static Transform>, } -impl<'w, 's> TransformHelper<'w, 's> { +impl<'w> TransformHelper<'w> { /// Computes the [`GlobalTransform`] of the given entity from the [`Transform`] component on it and its ancestors. pub fn compute_global_transform( &self, diff --git a/crates/bevy_ui/src/experimental/ghost_hierarchy.rs b/crates/bevy_ui/src/experimental/ghost_hierarchy.rs index 9134f5eebac75..e331b32f86782 100644 --- a/crates/bevy_ui/src/experimental/ghost_hierarchy.rs +++ b/crates/bevy_ui/src/experimental/ghost_hierarchy.rs @@ -70,14 +70,14 @@ pub struct UiChildren<'w, 's> { #[cfg(not(feature = "ghost_nodes"))] /// System param that gives access to UI children utilities. #[derive(SystemParam)] -pub struct UiChildren<'w, 's> { - ui_children_query: Query<'w, 's, Option<&'static Children>, With>, - changed_children_query: Query<'w, 's, Entity, Changed>, - parents_query: Query<'w, 's, &'static ChildOf>, +pub struct UiChildren<'w> { + ui_children_query: Query<'w, 'w, Option<&'static Children>, With>, + changed_children_query: Query<'w, 'w, Entity, Changed>, + parents_query: Query<'w, 'w, &'static ChildOf>, } #[cfg(feature = "ghost_nodes")] -impl<'w, 's> UiChildren<'w, 's> { +impl<'w> UiChildren<'w> { /// Iterates the children of `entity`, skipping over [`GhostNode`]. /// /// Traverses the hierarchy depth-first to ensure child order. @@ -85,7 +85,7 @@ impl<'w, 's> UiChildren<'w, 's> { /// # Performance /// /// This iterator allocates if the `entity` node has more than 8 children (including ghost nodes). - pub fn iter_ui_children(&'s self, entity: Entity) -> UiChildrenIter<'w, 's> { + pub fn iter_ui_children(&'w self, entity: Entity) -> UiChildrenIter<'w, 's> { UiChildrenIter { stack: self .ui_children_query @@ -98,14 +98,14 @@ impl<'w, 's> UiChildren<'w, 's> { } /// Returns the UI parent of the provided entity, skipping over [`GhostNode`]. - pub fn get_parent(&'s self, entity: Entity) -> Option { + pub fn get_parent(&self, entity: Entity) -> Option { self.parents_query .iter_ancestors(entity) .find(|entity| !self.ghost_nodes_query.contains(*entity)) } /// Iterates the [`GhostNode`]s between this entity and its UI children. - pub fn iter_ghost_nodes(&'s self, entity: Entity) -> Box + 's> { + pub fn iter_ghost_nodes(&'w self, entity: Entity) -> Box + 's> { Box::new( self.children_query .get(entity) @@ -121,7 +121,7 @@ impl<'w, 's> UiChildren<'w, 's> { } /// Given an entity in the UI hierarchy, check if its set of children has changed, e.g if children has been added/removed or if the order has changed. - pub fn is_changed(&'s self, entity: Entity) -> bool { + pub fn is_changed(&self, entity: Entity) -> bool { self.changed_children_query.contains(entity) || self .iter_ghost_nodes(entity) @@ -129,15 +129,15 @@ impl<'w, 's> UiChildren<'w, 's> { } /// Returns `true` if the given entity is either a [`Node`] or a [`GhostNode`]. - pub fn is_ui_node(&'s self, entity: Entity) -> bool { + pub fn is_ui_node(&self, entity: Entity) -> bool { self.ui_children_query.contains(entity) } } #[cfg(not(feature = "ghost_nodes"))] -impl<'w, 's> UiChildren<'w, 's> { +impl<'w> UiChildren<'w> { /// Iterates the children of `entity`. - pub fn iter_ui_children(&'s self, entity: Entity) -> impl Iterator + 's { + pub fn iter_ui_children(&'w self, entity: Entity) -> impl Iterator + 'w { self.ui_children_query .get(entity) .ok() @@ -149,34 +149,34 @@ impl<'w, 's> UiChildren<'w, 's> { } /// Returns the UI parent of the provided entity. - pub fn get_parent(&'s self, entity: Entity) -> Option { + pub fn get_parent(&self, entity: Entity) -> Option { self.parents_query.get(entity).ok().map(ChildOf::parent) } /// Given an entity in the UI hierarchy, check if its set of children has changed, e.g if children has been added/removed or if the order has changed. - pub fn is_changed(&'s self, entity: Entity) -> bool { + pub fn is_changed(&self, entity: Entity) -> bool { self.changed_children_query.contains(entity) } /// Returns `true` if the given entity is either a [`Node`] or a [`GhostNode`]. - pub fn is_ui_node(&'s self, entity: Entity) -> bool { + pub fn is_ui_node(&self, entity: Entity) -> bool { self.ui_children_query.contains(entity) } } #[cfg(feature = "ghost_nodes")] -pub struct UiChildrenIter<'w, 's> { +pub struct UiChildrenIter<'w> { stack: SmallVec<[Entity; 8]>, - query: &'s Query< + query: &'w Query< + 'w, 'w, - 's, (Option<&'static Children>, Has), Or<(With, With)>, >, } #[cfg(feature = "ghost_nodes")] -impl<'w, 's> Iterator for UiChildrenIter<'w, 's> { +impl<'w> Iterator for UiChildrenIter<'w> { type Item = Entity; fn next(&mut self) -> Option { loop { diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 83140d0f3b9cb..594662efcabb9 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -271,13 +271,13 @@ impl ExtractedUiNodes { } #[derive(SystemParam)] -pub struct UiCameraMap<'w, 's> { - mapping: Query<'w, 's, RenderEntity>, +pub struct UiCameraMap<'w> { + mapping: Query<'w, 'w, RenderEntity>, } -impl<'w, 's> UiCameraMap<'w, 's> { +impl<'w> UiCameraMap<'w> { /// Get the default camera and create the mapper - pub fn get_mapper(&'w self) -> UiCameraMapper<'w, 's> { + pub fn get_mapper(&'w self) -> UiCameraMapper<'w> { UiCameraMapper { mapping: &self.mapping, camera_entity: Entity::PLACEHOLDER, @@ -286,13 +286,13 @@ impl<'w, 's> UiCameraMap<'w, 's> { } } -pub struct UiCameraMapper<'w, 's> { - mapping: &'w Query<'w, 's, RenderEntity>, +pub struct UiCameraMapper<'w> { + mapping: &'w Query<'w, 'w, RenderEntity>, camera_entity: Entity, render_entity: Entity, } -impl<'w, 's> UiCameraMapper<'w, 's> { +impl<'w> UiCameraMapper<'w> { /// Returns the render entity corresponding to the given `UiTargetCamera` or the default camera if `None`. pub fn map(&mut self, computed_target: &ComputedNodeTarget) -> Option { let camera_entity = computed_target.camera; diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index 4592b091d9ce6..5ca4faf6208b8 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -2677,13 +2677,13 @@ impl UiTargetCamera { pub struct IsDefaultUiCamera; #[derive(SystemParam)] -pub struct DefaultUiCamera<'w, 's> { - cameras: Query<'w, 's, (Entity, &'static Camera)>, - default_cameras: Query<'w, 's, Entity, (With, With)>, - primary_window: Query<'w, 's, Entity, With>, +pub struct DefaultUiCamera<'w> { + cameras: Query<'w, 'w, (Entity, &'static Camera)>, + default_cameras: Query<'w, 'w, Entity, (With, With)>, + primary_window: Query<'w, 'w, Entity, With>, } -impl<'w, 's> DefaultUiCamera<'w, 's> { +impl<'w> DefaultUiCamera<'w> { pub fn get(&self) -> Option { self.default_cameras.single().ok().or_else(|| { // If there isn't a single camera and the query isn't empty, there is two or more cameras queried. diff --git a/crates/bevy_ui/src/widget/text.rs b/crates/bevy_ui/src/widget/text.rs index 785040c1e9057..5642e07cf0678 100644 --- a/crates/bevy_ui/src/widget/text.rs +++ b/crates/bevy_ui/src/widget/text.rs @@ -132,7 +132,7 @@ impl From for Text { pub type TextUiReader<'w, 's> = TextReader<'w, 's, Text>; /// UI alias for [`TextWriter`]. -pub type TextUiWriter<'w, 's> = TextWriter<'w, 's, Text>; +pub type TextUiWriter<'w> = TextWriter<'w, Text>; /// Text measurement for UI layout. See [`NodeMeasure`]. pub struct TextMeasure { From 5f736e75b6c3edcdf765aa25304143535eb189a7 Mon Sep 17 00:00:00 2001 From: re0312 Date: Sun, 1 Jun 2025 08:38:06 +0800 Subject: [PATCH 03/14] cleanup --- crates/bevy_ecs/src/bundle.rs | 51 ++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index a97433ff540dc..63ce356066261 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -1035,7 +1035,7 @@ impl<'w> BundleInserter<'w> { change_tick: Tick, ) -> Self { // SAFETY: We will not make any accesses to the command queue, component or resource data of this world - let mut bundle_info = world.bundles.get_unchecked(bundle_id); + let bundle_info = world.bundles.get_unchecked(bundle_id); let bundle_id = bundle_info.id(); let (new_archetype_id, is_new_created) = bundle_info.insert_bundle_into_archetype( &mut world.archetypes, @@ -1045,12 +1045,7 @@ impl<'w> BundleInserter<'w> { archetype_id, ); - if is_new_created { - world.trigger(ArchetypeCreated(new_archetype_id)); - bundle_info = world.bundles.get_unchecked(bundle_id); - } - - if new_archetype_id == archetype_id { + let inserter = if new_archetype_id == archetype_id { let archetype = &mut world.archetypes[archetype_id]; // SAFETY: The edge is assured to be initialized when we called insert_bundle_into_archetype let archetype_after_insert = unsafe { @@ -1110,7 +1105,15 @@ impl<'w> BundleInserter<'w> { world: world.as_unsafe_world_cell(), } } + }; + + if is_new_created { + inserter + .world + .into_deferred() + .trigger(ArchetypeCreated(new_archetype_id)); } + inserter } /// # Safety @@ -1426,7 +1429,7 @@ impl<'w> BundleRemover<'w> { bundle_id: BundleId, require_all: bool, ) -> Option { - let mut bundle_info = world.bundles.get_unchecked(bundle_id); + let bundle_info = world.bundles.get_unchecked(bundle_id); // SAFETY: Caller ensures archetype and bundle ids are correct. let (new_archetype_id, is_new_created) = unsafe { bundle_info.remove_bundle_from_archetype( @@ -1444,11 +1447,6 @@ impl<'w> BundleRemover<'w> { return None; } - if is_new_created { - world.trigger(ArchetypeCreated(new_archetype_id)); - bundle_info = world.bundles.get_unchecked(bundle_id); - } - let (old_archetype, new_archetype) = world.archetypes.get_2_mut(archetype_id, new_archetype_id); @@ -1462,13 +1460,20 @@ impl<'w> BundleRemover<'w> { Some((old.into(), new.into())) }; - Some(Self { + let remover = Self { bundle_info: bundle_info.into(), new_archetype: new_archetype.into(), old_archetype: old_archetype.into(), old_and_new_table: tables, world: world.as_unsafe_world_cell(), - }) + }; + if is_new_created { + remover + .world + .into_deferred() + .trigger(ArchetypeCreated(new_archetype_id)); + } + Some(remover) } /// This can be passed to [`remove`](Self::remove) as the `pre_remove` function if you don't want to do anything before removing. @@ -1689,7 +1694,7 @@ impl<'w> BundleSpawner<'w> { bundle_id: BundleId, change_tick: Tick, ) -> Self { - let mut bundle_info = world.bundles.get_unchecked(bundle_id); + let bundle_info = world.bundles.get_unchecked(bundle_id); let (new_archetype_id, is_new_created) = bundle_info.insert_bundle_into_archetype( &mut world.archetypes, &mut world.storages, @@ -1698,20 +1703,22 @@ impl<'w> BundleSpawner<'w> { ArchetypeId::EMPTY, ); - if is_new_created { - world.trigger(ArchetypeCreated(new_archetype_id)); - bundle_info = world.bundles.get_unchecked(bundle_id); - } - let archetype = &mut world.archetypes[new_archetype_id]; let table = &mut world.storages.tables[archetype.table_id()]; - Self { + let spawner = Self { bundle_info: bundle_info.into(), table: table.into(), archetype: archetype.into(), change_tick, world: world.as_unsafe_world_cell(), + }; + if is_new_created { + spawner + .world + .into_deferred() + .trigger(ArchetypeCreated(new_archetype_id)); } + spawner } #[inline] From f5f22e0db7060e4c6d306b18dd1b03f3e659b266 Mon Sep 17 00:00:00 2001 From: re0312 Date: Sun, 1 Jun 2025 10:44:34 +0800 Subject: [PATCH 04/14] reason --- crates/bevy_ecs/src/archetype.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index 3fbe5fa369f8f..ce5417a13c1fe 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -36,7 +36,7 @@ use core::{ use nonmax::NonMaxU32; #[derive(Event)] -#[allow(dead_code)] +#[allow(dead_code, reason = "Prepare for the upcoming Query as Entities")] pub(crate) struct ArchetypeCreated(pub ArchetypeId); From 7b530c9ee9c00743b54d9511b8bee7fec500a1f4 Mon Sep 17 00:00:00 2001 From: re0312 Date: Sun, 1 Jun 2025 10:55:42 +0800 Subject: [PATCH 05/14] fix cli --- crates/bevy_ecs/src/archetype.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index ce5417a13c1fe..a9e5c571ff633 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -36,8 +36,7 @@ use core::{ use nonmax::NonMaxU32; #[derive(Event)] -#[allow(dead_code, reason = "Prepare for the upcoming Query as Entities")] - +#[expect(dead_code, reason = "Prepare for the upcoming Query as Entities")] pub(crate) struct ArchetypeCreated(pub ArchetypeId); /// An opaque location within a [`Archetype`]. From 5901c9ea05780c9931d731394599831cc2a475be Mon Sep 17 00:00:00 2001 From: re0312 Date: Tue, 3 Jun 2025 08:53:51 +0800 Subject: [PATCH 06/14] cleanup --- crates/bevy_ecs/src/query/state.rs | 36 ++++++++++++++++++++-- crates/bevy_ecs/src/system/builder.rs | 12 +++----- crates/bevy_ecs/src/system/system_param.rs | 19 +++--------- 3 files changed, 42 insertions(+), 25 deletions(-) diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index bf888af7042ab..5860a008bed7a 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -14,7 +14,8 @@ use crate::{ use crate::entity::UniqueEntityEquivalentSlice; use alloc::vec::Vec; -use core::{fmt, ptr}; +use core::{fmt, ops::DerefMut, ptr}; +use derive_more::derive::{Deref, DerefMut}; use fixedbitset::FixedBitSet; use log::warn; #[cfg(feature = "trace")] @@ -25,6 +26,12 @@ use super::{ QueryManyUniqueIter, QuerySingleError, ROQueryItem, ReadOnlyQueryData, }; +#[repr(C)] +#[derive(Component, Deref, DerefMut)] +#[component(storage = "SparseSet")] +/// A Internal Wrapper of [`QueryState`] for safety reasons. +pub(crate) struct InternalQueryState(QueryState); + /// An ID for either a table or an archetype. Used for Query iteration. /// /// Query iteration is exclusively dense (over tables) or archetypal (over archetypes) based on whether @@ -60,8 +67,6 @@ pub(super) union StorageId { /// [`Fetch`]: crate::query::world_query::WorldQuery::Fetch /// [`Table`]: crate::storage::Table #[repr(C)] -#[derive(Component)] -#[component(storage = "SparseSet")] // SAFETY NOTE: // Do not add any new fields that use the `D` or `F` generic parameters as this may // make `QueryState::as_transmuted_state` unsound if not done with care. @@ -1753,6 +1758,31 @@ impl QueryState { } } +impl QueryState { + /// cache a [`QueryState`] into world. + pub(crate) fn cached(self, world: &mut World) -> (Entity, ComponentId) { + let id = world.register_component::>(); + let e = world.spawn(InternalQueryState(self)).id(); + (e, id) + } + + /// fetch a cached [`QueryState`] from world + pub(crate) unsafe fn fetch_mut_from_cached<'w>( + cached: (Entity, ComponentId), + w: UnsafeWorldCell<'w>, + ) -> Option<&'w mut QueryState> { + w.storages() + .sparse_sets + .get(cached.1) + .map(|s| s.get(cached.0)) + .flatten() + .map(|ptr| { + ptr.assert_unique() + .deref_mut::>() + .deref_mut() + }) + } +} impl From> for QueryState { fn from(mut value: QueryBuilder) -> Self { QueryState::from_builder(&mut value) diff --git a/crates/bevy_ecs/src/system/builder.rs b/crates/bevy_ecs/src/system/builder.rs index 9f1a93e5d03b5..d640e7d9ff0d3 100644 --- a/crates/bevy_ecs/src/system/builder.rs +++ b/crates/bevy_ecs/src/system/builder.rs @@ -217,9 +217,7 @@ unsafe impl<'w, 's, D: QueryData + 'static, F: QueryFilter + 'static> fn build(self, world: &mut World, system_meta: &mut SystemMeta) -> (Entity, ComponentId) { self.validate_world(world.id()); init_query_param(world, system_meta, &self); - let id = world.register_component::>(); - let e = world.spawn(self).id(); - (e, id) + self.cached(world) } } @@ -300,9 +298,7 @@ unsafe impl< (self.0)(&mut builder); let state = builder.build(); init_query_param(world, system_meta, &state); - let id = world.register_component::>(); - let e = world.spawn(state).id(); - (e, id) + state.cached(world) } } @@ -971,9 +967,9 @@ mod tests { #[derive(SystemParam)] #[system_param(builder)] - struct CustomParam<'w> { + struct CustomParam<'w, 's> { query: Query<'w, 'w, ()>, - local: Local, + local: Local<'s, usize>, } #[test] diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index bb6b1ddbe8794..f55488a2d8671 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -6,8 +6,8 @@ use crate::{ component::{ComponentId, ComponentTicks, Components, Tick}, entity::{Entities, Entity}, query::{ - Access, DebugCheckedUnwrap, FilteredAccess, FilteredAccessSet, QueryData, QueryFilter, - QuerySingleError, QueryState, ReadOnlyQueryData, + Access, FilteredAccess, FilteredAccessSet, QueryData, QueryFilter, QuerySingleError, + QueryState, ReadOnlyQueryData, }, resource::Resource, storage::ResourceData, @@ -327,9 +327,7 @@ unsafe impl SystemParam for Qu fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { let state: QueryState = QueryState::new(world); init_query_param(world, system_meta, &state); - let e = world.spawn(state).id(); - let id = world.register_component::>(); - (e, id) + state.cached(world) } #[inline] @@ -339,15 +337,8 @@ unsafe impl SystemParam for Qu world: UnsafeWorldCell<'w>, change_tick: Tick, ) -> Self::Item<'w, 'w> { - let state = world - .storages() - .sparse_sets - .get(state.1) - .debug_checked_unwrap() - .get(state.0) - .debug_checked_unwrap() - .assert_unique() - .deref_mut::>(); + let state = QueryState::fetch_mut_from_cached(*state, world) + .expect("cached querystate entity not found"); // SAFETY: We have registered all of the query's world accesses, // so the caller ensures that `world` has permission to access any // world data that the query needs. From f9f59b804827f6a9610b03b08ef4db18785f017a Mon Sep 17 00:00:00 2001 From: re0312 Date: Tue, 3 Jun 2025 09:52:36 +0800 Subject: [PATCH 07/14] migration --- crates/bevy_input_focus/src/tab_navigation.rs | 2 -- .../migration-guides/query_lifetime_change.md | 16 ++++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 release-content/migration-guides/query_lifetime_change.md diff --git a/crates/bevy_input_focus/src/tab_navigation.rs b/crates/bevy_input_focus/src/tab_navigation.rs index 1f7180f8071d9..1c1bd1c2320b8 100644 --- a/crates/bevy_input_focus/src/tab_navigation.rs +++ b/crates/bevy_input_focus/src/tab_navigation.rs @@ -395,7 +395,6 @@ mod tests { let mut system_state: SystemState = SystemState::new(world); let tab_navigation = system_state.get(world); assert_eq!(tab_navigation.tabgroup_query.iter().count(), 1); - assert_eq!(tab_navigation.tabindex_query.iter().count(), 2); let next_entity = tab_navigation.navigate(&InputFocus::from_entity(tab_entity_1), NavAction::Next); @@ -428,7 +427,6 @@ mod tests { let mut system_state: SystemState = SystemState::new(world); let tab_navigation = system_state.get(world); assert_eq!(tab_navigation.tabgroup_query.iter().count(), 2); - assert_eq!(tab_navigation.tabindex_query.iter().count(), 4); let next_entity = tab_navigation.navigate(&InputFocus::from_entity(tab_entity_1), NavAction::Next); diff --git a/release-content/migration-guides/query_lifetime_change.md b/release-content/migration-guides/query_lifetime_change.md new file mode 100644 index 0000000000000..58d9ae84c518f --- /dev/null +++ b/release-content/migration-guides/query_lifetime_change.md @@ -0,0 +1,16 @@ +--- +title: Query lifetime change +pull_requests: [18860] +--- +Query lifetimes have been change if you are deriving `SystemParam` with `Query` you will need to update. + +```diff +#[derive(SystemParam)] +-- struct MyParam<'w, 's> { +-- query: Query<'w, 's, Entity> +-- } +++ struct MyParam<'w> { +++ query: Query<'w, 'w, Entity> +++ } + +``` From 0f1c7c8dcc8a33aee26e19fdc5f9c7c7756539fb Mon Sep 17 00:00:00 2001 From: re0312 Date: Tue, 3 Jun 2025 12:26:29 +0800 Subject: [PATCH 08/14] cli --- crates/bevy_ecs/src/query/state.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 5860a008bed7a..ec2a8c9e834fb 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -1773,9 +1773,8 @@ impl QueryState { ) -> Option<&'w mut QueryState> { w.storages() .sparse_sets - .get(cached.1) - .map(|s| s.get(cached.0)) - .flatten() + .get(cached.1)? + .get(cached.0) .map(|ptr| { ptr.assert_unique() .deref_mut::>() From 04cdaf77dfbda9cc13f10ceb46cf0450321a2bad Mon Sep 17 00:00:00 2001 From: re0312 Date: Tue, 3 Jun 2025 12:47:17 +0800 Subject: [PATCH 09/14] fix_ghost_nodes --- .../src/experimental/ghost_hierarchy.rs | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/crates/bevy_ui/src/experimental/ghost_hierarchy.rs b/crates/bevy_ui/src/experimental/ghost_hierarchy.rs index e331b32f86782..b405f6c351934 100644 --- a/crates/bevy_ui/src/experimental/ghost_hierarchy.rs +++ b/crates/bevy_ui/src/experimental/ghost_hierarchy.rs @@ -29,19 +29,19 @@ pub struct GhostNode; /// /// A UI root node is either a [`Node`] without a [`ChildOf`], or with only [`GhostNode`] ancestors. #[derive(SystemParam)] -pub struct UiRootNodes<'w, 's> { - root_node_query: Query<'w, 's, Entity, (With, Without)>, - root_ghost_node_query: Query<'w, 's, Entity, (With, Without)>, - all_nodes_query: Query<'w, 's, Entity, With>, - ui_children: UiChildren<'w, 's>, +pub struct UiRootNodes<'w> { + root_node_query: Query<'w, 'w, Entity, (With, Without)>, + root_ghost_node_query: Query<'w, 'w, Entity, (With, Without)>, + all_nodes_query: Query<'w, 'w, Entity, With>, + ui_children: UiChildren<'w>, } #[cfg(not(feature = "ghost_nodes"))] -pub type UiRootNodes<'w, 's> = Query<'w, 's, Entity, (With, Without)>; +pub type UiRootNodes<'w> = Query<'w, 'w, Entity, (With, Without)>; #[cfg(feature = "ghost_nodes")] -impl<'w, 's> UiRootNodes<'w, 's> { - pub fn iter(&'s self) -> impl Iterator + 's { +impl<'w> UiRootNodes<'w> { + pub fn iter(&'w self) -> impl Iterator + 'w { self.root_node_query .iter() .chain(self.root_ghost_node_query.iter().flat_map(|root_ghost| { @@ -54,17 +54,17 @@ impl<'w, 's> UiRootNodes<'w, 's> { #[cfg(feature = "ghost_nodes")] /// System param that gives access to UI children utilities, skipping over [`GhostNode`]. #[derive(SystemParam)] -pub struct UiChildren<'w, 's> { +pub struct UiChildren<'w> { ui_children_query: Query< 'w, - 's, + 'w, (Option<&'static Children>, Has), Or<(With, With)>, >, - changed_children_query: Query<'w, 's, Entity, Changed>, - children_query: Query<'w, 's, &'static Children>, - ghost_nodes_query: Query<'w, 's, Entity, With>, - parents_query: Query<'w, 's, &'static ChildOf>, + changed_children_query: Query<'w, 'w, Entity, Changed>, + children_query: Query<'w, 'w, &'static Children>, + ghost_nodes_query: Query<'w, 'w, Entity, With>, + parents_query: Query<'w, 'w, &'static ChildOf>, } #[cfg(not(feature = "ghost_nodes"))] @@ -85,7 +85,7 @@ impl<'w> UiChildren<'w> { /// # Performance /// /// This iterator allocates if the `entity` node has more than 8 children (including ghost nodes). - pub fn iter_ui_children(&'w self, entity: Entity) -> UiChildrenIter<'w, 's> { + pub fn iter_ui_children(&'w self, entity: Entity) -> UiChildrenIter<'w> { UiChildrenIter { stack: self .ui_children_query @@ -105,7 +105,7 @@ impl<'w> UiChildren<'w> { } /// Iterates the [`GhostNode`]s between this entity and its UI children. - pub fn iter_ghost_nodes(&'w self, entity: Entity) -> Box + 's> { + pub fn iter_ghost_nodes(&'w self, entity: Entity) -> Box + 'w> { Box::new( self.children_query .get(entity) From 3890ea9a8e613bf27a630fafe2078679019e40da Mon Sep 17 00:00:00 2001 From: re0312 Date: Tue, 3 Jun 2025 13:44:29 +0800 Subject: [PATCH 10/14] fix_document --- crates/bevy_ecs/src/world/entity_ref.rs | 12 ++++++------ examples/ecs/system_param.rs | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 9842ee54e365d..18d56dd776822 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -5238,9 +5238,9 @@ mod tests { fn system(_: Query<&mut TestComponent>, mut query: Query>) { for mut entity_mut in query.iter_mut() { - assert!(entity_mut - .get_mut::() - .is_some_and(|component| component.0 == 0)); + if let Some(c) = entity_mut.get_mut::() { + assert!(c.0 == 0); + } } } } @@ -5256,9 +5256,9 @@ mod tests { fn system(_: Query<&mut TestComponent>, mut query: Query>) { for mut entity_mut in query.iter_mut() { - assert!(entity_mut - .get_mut::() - .is_some_and(|component| component.0 == 0)); + if let Some(c) = entity_mut.get_mut::() { + assert!(c.0 == 0); + } } } } diff --git a/examples/ecs/system_param.rs b/examples/ecs/system_param.rs index f32a783f72ca4..62735e1bf52f1 100644 --- a/examples/ecs/system_param.rs +++ b/examples/ecs/system_param.rs @@ -21,12 +21,12 @@ struct PlayerCount(usize); /// /// In this example, it includes a query and a mutable resource. #[derive(SystemParam)] -struct PlayerCounter<'w, 's> { - players: Query<'w, 's, &'static Player>, +struct PlayerCounter<'w> { + players: Query<'w, 'w, &'static Player>, count: ResMut<'w, PlayerCount>, } -impl<'w, 's> PlayerCounter<'w, 's> { +impl<'w> PlayerCounter<'w> { fn count(&mut self) { self.count.0 = self.players.iter().len(); } From c0520540910f944b65e6470cfe974ec60d525753 Mon Sep 17 00:00:00 2001 From: re0312 Date: Tue, 3 Jun 2025 14:06:26 +0800 Subject: [PATCH 11/14] fix test --- crates/bevy_ecs/src/world/entity_ref.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 18d56dd776822..6bec47e269589 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -5174,10 +5174,9 @@ mod tests { fn system(_: Query<&mut TestComponent>, query: Query>) { for entity_ref in query.iter() { - assert!(matches!( - entity_ref.get::(), - Some(TestComponent2(0)) - )); + if let Some(c) = entity_ref.get::() { + assert!(c.0 == 0); + } } } } @@ -5197,10 +5196,9 @@ mod tests { assert!(entity_mut.get::().is_none()); assert!(entity_mut.get_ref::().is_none()); assert!(entity_mut.get_mut::().is_none()); - assert!(matches!( - entity_mut.get::(), - Some(TestComponent2(0)) - )); + if let Some(c) = entity_mut.get::() { + assert!(c.0 == 0); + } } assert!(found); From e16b155bc26af0c394758a886af91ab59450c100 Mon Sep 17 00:00:00 2001 From: re0312 Date: Tue, 3 Jun 2025 14:32:00 +0800 Subject: [PATCH 12/14] fix test --- .../compile_fail/tests/ui/system_param_derive_readonly.rs | 2 +- crates/bevy_ecs/src/system/mod.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ecs/compile_fail/tests/ui/system_param_derive_readonly.rs b/crates/bevy_ecs/compile_fail/tests/ui/system_param_derive_readonly.rs index 3002184b9d4bc..408c80ec5a0e0 100644 --- a/crates/bevy_ecs/compile_fail/tests/ui/system_param_derive_readonly.rs +++ b/crates/bevy_ecs/compile_fail/tests/ui/system_param_derive_readonly.rs @@ -6,7 +6,7 @@ struct Foo; #[derive(SystemParam)] struct Mutable<'w> { - a: Query<'w, &'static mut Foo>, + a: Query<'w, 'w, &'static mut Foo>, } fn main() { diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index c3448fb8190b3..f85223bf71db9 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -1329,10 +1329,10 @@ mod tests { #[test] fn system_state_spawned() { let mut world = World::default(); - world.spawn_empty(); + world.spawn(A); let spawn_tick = world.change_tick(); - let mut system_state: SystemState>> = + let mut system_state: SystemState)>>> = SystemState::new(&mut world); { let query = system_state.get(&world); From 09a7cfc306ed083fd5cfc73d4ae4302d61f9a0f0 Mon Sep 17 00:00:00 2001 From: re0312 Date: Tue, 3 Jun 2025 16:11:44 +0800 Subject: [PATCH 13/14] immutable --- crates/bevy_ecs/src/query/state.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index ec2a8c9e834fb..e68d59e4388cb 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -28,7 +28,7 @@ use super::{ #[repr(C)] #[derive(Component, Deref, DerefMut)] -#[component(storage = "SparseSet")] +#[component(storage = "SparseSet", immutable)] /// A Internal Wrapper of [`QueryState`] for safety reasons. pub(crate) struct InternalQueryState(QueryState); From 533bcb10df696a1e85646f2962ac3977e314baa9 Mon Sep 17 00:00:00 2001 From: re0312 <45868716+re0312@users.noreply.github.com> Date: Wed, 4 Jun 2025 07:11:06 +0800 Subject: [PATCH 14/14] Update crates/bevy_ecs/src/query/state.rs Co-authored-by: Mike --- crates/bevy_ecs/src/query/state.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index e68d59e4388cb..8c851c15f5e68 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -1767,6 +1767,9 @@ impl QueryState { } /// fetch a cached [`QueryState`] from world + /// + /// Safety: + /// - Must not mutably alias the returned [`QueryState`]. pub(crate) unsafe fn fetch_mut_from_cached<'w>( cached: (Entity, ComponentId), w: UnsafeWorldCell<'w>,