From 2469aa5f2aa42530bd782bd498d32cd31a885359 Mon Sep 17 00:00:00 2001 From: Elliott Pierce Date: Fri, 23 Jan 2026 11:55:52 -0500 Subject: [PATCH 01/17] Only use `&World` This required a little bundle work too. --- crates/bevy_asset/src/asset_changed.rs | 7 +-- crates/bevy_ecs/macros/src/lib.rs | 6 +-- crates/bevy_ecs/macros/src/world_query.rs | 2 +- crates/bevy_ecs/src/bundle/impls.rs | 12 ++--- crates/bevy_ecs/src/bundle/mod.rs | 10 ++-- crates/bevy_ecs/src/component/register.rs | 21 ++++++++ .../src/observer/distributed_storage.rs | 8 ++- crates/bevy_ecs/src/query/builder.rs | 9 +--- crates/bevy_ecs/src/query/fetch.rs | 52 +++++++++---------- crates/bevy_ecs/src/query/filter.rs | 24 ++++----- crates/bevy_ecs/src/query/mod.rs | 4 +- crates/bevy_ecs/src/query/state.rs | 6 +-- crates/bevy_ecs/src/query/world_query.rs | 4 +- crates/bevy_ecs/src/spawn.rs | 12 ++--- crates/bevy_render/src/sync_world.rs | 4 +- 15 files changed, 101 insertions(+), 80 deletions(-) diff --git a/crates/bevy_asset/src/asset_changed.rs b/crates/bevy_asset/src/asset_changed.rs index 555861dd9809f..7e314892fabde 100644 --- a/crates/bevy_asset/src/asset_changed.rs +++ b/crates/bevy_asset/src/asset_changed.rs @@ -235,9 +235,10 @@ unsafe impl WorldQuery for AssetChanged { access.add_resource_read(state.resource_id); } - fn init_state(world: &mut World) -> AssetChangedState { - let resource_id = world.init_resource::>(); - let asset_id = world.register_component::(); + fn init_state(world: &World) -> AssetChangedState { + let components = world.components_queue(); + let resource_id = components.queue_register_resource::>(); + let asset_id = components.queue_register_component::(); AssetChangedState { asset_id, resource_id, diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index 2e9889698919b..78b8666d54e3a 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -134,9 +134,9 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { // - `Bundle::get_components` is exactly once for each member. Rely's on the Component -> Bundle implementation to properly pass // the correct `StorageType` into the callback. unsafe impl #impl_generics #ecs_path::bundle::Bundle for #struct_name #ty_generics #where_clause { - fn component_ids( - components: &mut #ecs_path::component::ComponentsRegistrator, - ) -> impl Iterator + use<#(#generics_ty_list,)*> { + fn component_ids( + components: &mut Components, + ) -> impl Iterator + use<#(#generics_ty_list,)* Components> { core::iter::empty()#(.chain(<#active_field_types as #ecs_path::bundle::Bundle>::component_ids(components)))* } diff --git a/crates/bevy_ecs/macros/src/world_query.rs b/crates/bevy_ecs/macros/src/world_query.rs index 39feecf3798b8..5b9cf7e963d79 100644 --- a/crates/bevy_ecs/macros/src/world_query.rs +++ b/crates/bevy_ecs/macros/src/world_query.rs @@ -156,7 +156,7 @@ pub(crate) fn world_query_impl( #( <#field_types>::update_component_access(&state.#field_aliases, _access); )* } - fn init_state(world: &mut #path::world::World) -> #state_struct_name #user_ty_generics { + fn init_state(world: &#path::world::World) -> #state_struct_name #user_ty_generics { #state_struct_name { #(#field_aliases: <#field_types>::init_state(world),)* } diff --git a/crates/bevy_ecs/src/bundle/impls.rs b/crates/bevy_ecs/src/bundle/impls.rs index 4083ae916b7d2..f7f642967a2a4 100644 --- a/crates/bevy_ecs/src/bundle/impls.rs +++ b/crates/bevy_ecs/src/bundle/impls.rs @@ -6,7 +6,7 @@ use variadics_please::all_tuples_enumerated; use crate::{ bundle::{Bundle, BundleFromComponents, DynamicBundle, NoBundleEffect}, - component::{Component, ComponentId, Components, ComponentsRegistrator, StorageType}, + component::{Component, ComponentId, ComponentIdDictator, Components, StorageType}, world::EntityWorldMut, }; @@ -14,10 +14,10 @@ use crate::{ // - `Bundle::component_ids` calls `ids` for C's component id (and nothing else) // - `Bundle::get_components` is called exactly once for C and passes the component's storage type based on its associated constant. unsafe impl Bundle for C { - fn component_ids( - components: &mut ComponentsRegistrator, - ) -> impl Iterator + use { - iter::once(components.register_component::()) + fn component_ids( + components: &mut Components, + ) -> impl Iterator + use { + iter::once(components.determine_component_id_of::()) } fn get_component_ids(components: &Components) -> impl Iterator> { @@ -73,7 +73,7 @@ macro_rules! tuple_impl { // - `Bundle::get_components` is called exactly once for each member. Relies on the above implementation to pass the correct // `StorageType` into the callback. unsafe impl<$($name: Bundle),*> Bundle for ($($name,)*) { - fn component_ids<'a>(components: &'a mut ComponentsRegistrator) -> impl Iterator + use<$($name,)*> { + fn component_ids<'a, Components: ComponentIdDictator>(components: &'a mut Components) -> impl Iterator + use<$($name,)* Components> { iter::empty()$(.chain(<$name as Bundle>::component_ids(components)))* } diff --git a/crates/bevy_ecs/src/bundle/mod.rs b/crates/bevy_ecs/src/bundle/mod.rs index f7cd9996b2853..41a4882f2946f 100644 --- a/crates/bevy_ecs/src/bundle/mod.rs +++ b/crates/bevy_ecs/src/bundle/mod.rs @@ -77,7 +77,7 @@ pub use info::*; pub use bevy_ecs_macros::Bundle; use crate::{ - component::{ComponentId, Components, ComponentsRegistrator, StorageType}, + component::{ComponentId, ComponentIdDictator, Components, StorageType}, world::EntityWorldMut, }; use bevy_ptr::OwningPtr; @@ -204,11 +204,11 @@ use bevy_ptr::OwningPtr; )] pub unsafe trait Bundle: DynamicBundle + Send + Sync + 'static { /// Gets this [`Bundle`]'s component ids, in the order of this bundle's [`Component`]s - /// This will register the component if it doesn't exist. + /// This may register the component if it doesn't exist, depending on which [`ComponentIdDictator`] is used. #[doc(hidden)] - fn component_ids( - components: &mut ComponentsRegistrator, - ) -> impl Iterator + use; + fn component_ids( + components: &mut Components, + ) -> impl Iterator + use; /// Return a iterator over this [`Bundle`]'s component ids. This will be [`None`] if the component has not been registered. fn get_component_ids(components: &Components) -> impl Iterator>; diff --git a/crates/bevy_ecs/src/component/register.rs b/crates/bevy_ecs/src/component/register.rs index 5d8ac3ee6e98b..67445085a7f97 100644 --- a/crates/bevy_ecs/src/component/register.rs +++ b/crates/bevy_ecs/src/component/register.rs @@ -698,3 +698,24 @@ impl<'w> ComponentsQueuedRegistrator<'w> { }) } } + +/// Represents a way to get the id of component types. +pub trait ComponentIdDictator { + /// Determines the [`ComponentId`] of `T`. + /// This makes no promises of whether or not `T` will be full registered; it just gets its id. + fn determine_component_id_of(&mut self) -> ComponentId; +} + +impl ComponentIdDictator for ComponentsRegistrator<'_> { + #[inline] + fn determine_component_id_of(&mut self) -> ComponentId { + self.register_component::() + } +} + +impl ComponentIdDictator for ComponentsQueuedRegistrator<'_> { + #[inline] + fn determine_component_id_of(&mut self) -> ComponentId { + self.queue_register_component::() + } +} diff --git a/crates/bevy_ecs/src/observer/distributed_storage.rs b/crates/bevy_ecs/src/observer/distributed_storage.rs index 6f1ebebbc8cdc..db37b336a753a 100644 --- a/crates/bevy_ecs/src/observer/distributed_storage.rs +++ b/crates/bevy_ecs/src/observer/distributed_storage.rs @@ -433,11 +433,15 @@ fn hook_on_add>( ) { world.commands().queue(move |world: &mut World| { let event_key = world.register_event_key::(); - let components = B::component_ids(&mut world.components_registrator()); + let components = B::component_ids(&mut world.components_registrator()) + .collect::>(); if let Some(mut observer) = world.get_mut::(entity) { observer.descriptor.event_keys.push(event_key); - observer.descriptor.components.extend(components); + observer + .descriptor + .components + .extend_from_slice(&components); let system: &mut dyn Any = observer.system.as_mut(); let system: *mut dyn ObserverSystem = system.downcast_mut::().unwrap(); diff --git a/crates/bevy_ecs/src/query/builder.rs b/crates/bevy_ecs/src/query/builder.rs index 2c4b29e06ea66..cf0940222a8fb 100644 --- a/crates/bevy_ecs/src/query/builder.rs +++ b/crates/bevy_ecs/src/query/builder.rs @@ -37,7 +37,7 @@ use super::{FilteredAccess, QueryData, QueryFilter}; /// ``` pub struct QueryBuilder<'w, D: QueryData = (), F: QueryFilter = ()> { access: FilteredAccess, - world: &'w mut World, + world: &'w World, or: bool, first: bool, _marker: PhantomData<(D, F)>, @@ -45,7 +45,7 @@ pub struct QueryBuilder<'w, D: QueryData = (), F: QueryFilter = ()> { impl<'w, D: QueryData, F: QueryFilter> QueryBuilder<'w, D, F> { /// Creates a new builder with the accesses required for `Q` and `F` - pub fn new(world: &'w mut World) -> Self { + pub fn new(world: &'w World) -> Self { let fetch_state = D::init_state(world); let filter_state = F::init_state(world); @@ -101,11 +101,6 @@ impl<'w, D: QueryData, F: QueryFilter> QueryBuilder<'w, D, F> { self.world } - /// Returns a mutable reference to the world passed to [`Self::new`]. - pub fn world_mut(&mut self) -> &mut World { - self.world - } - /// Adds access to self's underlying [`FilteredAccess`] respecting [`Self::or`] and [`Self::and`] pub fn extend_access(&mut self, mut access: FilteredAccess) { if self.or { diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index d0b191513e8ed..afda5bf90d690 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -421,7 +421,7 @@ unsafe impl WorldQuery for Entity { fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess) {} - fn init_state(_world: &mut World) {} + fn init_state(_world: &World) {} fn get_state(_components: &Components) -> Option<()> { Some(()) @@ -518,7 +518,7 @@ unsafe impl WorldQuery for EntityLocation { fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess) {} - fn init_state(_world: &mut World) {} + fn init_state(_world: &World) {} fn get_state(_components: &Components) -> Option<()> { Some(()) @@ -686,7 +686,7 @@ unsafe impl WorldQuery for SpawnDetails { fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess) {} - fn init_state(_world: &mut World) {} + fn init_state(_world: &World) {} fn get_state(_components: &Components) -> Option<()> { Some(()) @@ -814,7 +814,7 @@ unsafe impl<'a> WorldQuery for EntityRef<'a> { access.read_all_components(); } - fn init_state(_world: &mut World) {} + fn init_state(_world: &World) {} fn get_state(_components: &Components) -> Option<()> { Some(()) @@ -924,7 +924,7 @@ unsafe impl<'a> WorldQuery for EntityMut<'a> { access.write_all_components(); } - fn init_state(_world: &mut World) {} + fn init_state(_world: &World) {} fn get_state(_components: &Components) -> Option<()> { Some(()) @@ -1031,7 +1031,7 @@ unsafe impl WorldQuery for FilteredEntityRef<'_, '_> { filtered_access.access.extend(state); } - fn init_state(_world: &mut World) -> Self::State { + fn init_state(_world: &World) -> Self::State { Access::default() } @@ -1156,7 +1156,7 @@ unsafe impl WorldQuery for FilteredEntityMut<'_, '_> { filtered_access.access.extend(state); } - fn init_state(_world: &mut World) -> Self::State { + fn init_state(_world: &World) -> Self::State { Access::default() } @@ -1276,10 +1276,10 @@ where access.extend(state); } - fn init_state(world: &mut World) -> Self::State { + fn init_state(world: &World) -> Self::State { let mut access = Access::new(); access.read_all_components(); - for id in B::component_ids(&mut world.components_registrator()) { + for id in B::component_ids(&mut world.components_queue()) { access.remove_component_read(id); } access @@ -1394,10 +1394,10 @@ where access.extend(state); } - fn init_state(world: &mut World) -> Self::State { + fn init_state(world: &World) -> Self::State { let mut access = Access::new(); access.write_all_components(); - for id in B::component_ids(&mut world.components_registrator()) { + for id in B::component_ids(&mut world.components_queue()) { access.remove_component_read(id); } access @@ -1503,7 +1503,7 @@ unsafe impl WorldQuery for &Archetype { fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess) {} - fn init_state(_world: &mut World) {} + fn init_state(_world: &World) {} fn get_state(_components: &Components) -> Option<()> { Some(()) @@ -1660,8 +1660,8 @@ unsafe impl WorldQuery for &T { access.add_component_read(component_id); } - fn init_state(world: &mut World) -> ComponentId { - world.register_component::() + fn init_state(world: &World) -> ComponentId { + world.components_queue().queue_register_component::() } fn get_state(components: &Components) -> Option { @@ -1850,8 +1850,8 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { access.add_component_read(component_id); } - fn init_state(world: &mut World) -> ComponentId { - world.register_component::() + fn init_state(world: &World) -> ComponentId { + world.components_queue().queue_register_component::() } fn get_state(components: &Components) -> Option { @@ -2067,8 +2067,8 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { access.add_component_write(component_id); } - fn init_state(world: &mut World) -> ComponentId { - world.register_component::() + fn init_state(world: &World) -> ComponentId { + world.components_queue().queue_register_component::() } fn get_state(components: &Components) -> Option { @@ -2225,7 +2225,7 @@ unsafe impl<'__w, T: Component> WorldQuery for Mut<'__w, T> { } // Forwarded to `&mut T` - fn init_state(world: &mut World) -> ComponentId { + fn init_state(world: &World) -> ComponentId { <&mut T as WorldQuery>::init_state(world) } @@ -2375,7 +2375,7 @@ unsafe impl WorldQuery for Option { access.extend_access(&intermediate); } - fn init_state(world: &mut World) -> T::State { + fn init_state(world: &World) -> T::State { T::init_state(world) } @@ -2562,8 +2562,8 @@ unsafe impl WorldQuery for Has { access.access_mut().add_archetypal(component_id); } - fn init_state(world: &mut World) -> ComponentId { - world.register_component::() + fn init_state(world: &World) -> ComponentId { + world.components_queue().queue_register_component::() } fn get_state(components: &Components) -> Option { @@ -2812,7 +2812,7 @@ macro_rules! impl_anytuple_fetch { <($(Option<$name>,)*)>::update_component_access(state, access); } - fn init_state(world: &mut World) -> Self::State { + fn init_state(world: &World) -> Self::State { ($($name::init_state(world),)*) } fn get_state(components: &Components) -> Option { @@ -2971,7 +2971,7 @@ unsafe impl WorldQuery for NopWorldQuery { fn update_component_access(_state: &D::State, _access: &mut FilteredAccess) {} - fn init_state(world: &mut World) -> Self::State { + fn init_state(world: &World) -> Self::State { D::init_state(world) } @@ -3063,7 +3063,7 @@ unsafe impl WorldQuery for PhantomData { fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess) {} - fn init_state(_world: &mut World) -> Self::State {} + fn init_state(_world: &World) -> Self::State {} fn get_state(_components: &Components) -> Option { Some(()) @@ -3273,7 +3273,7 @@ mod tests { fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess) {} - fn init_state(_world: &mut World) {} + fn init_state(_world: &World) {} 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 f8578d5d21705..50ffe2a8d78be 100644 --- a/crates/bevy_ecs/src/query/filter.rs +++ b/crates/bevy_ecs/src/query/filter.rs @@ -185,8 +185,8 @@ unsafe impl WorldQuery for With { access.and_with(id); } - fn init_state(world: &mut World) -> ComponentId { - world.register_component::() + fn init_state(world: &World) -> ComponentId { + world.components_queue().queue_register_component::() } fn get_state(components: &Components) -> Option { @@ -286,8 +286,8 @@ unsafe impl WorldQuery for Without { access.and_without(id); } - fn init_state(world: &mut World) -> ComponentId { - world.register_component::() + fn init_state(world: &World) -> ComponentId { + world.components_queue().queue_register_component::() } fn get_state(components: &Components) -> Option { @@ -477,7 +477,7 @@ macro_rules! impl_or_query_filter { *access = new_access; } - fn init_state(world: &mut World) -> Self::State { + fn init_state(world: &World) -> Self::State { ($($filter::init_state(world),)*) } @@ -617,8 +617,8 @@ unsafe impl WorldQuery for Allow { access.access_mut().add_archetypal(id); } - fn init_state(world: &mut World) -> ComponentId { - world.register_component::() + fn init_state(world: &World) -> ComponentId { + world.components_queue().queue_register_component::() } fn get_state(components: &Components) -> Option { @@ -818,8 +818,8 @@ unsafe impl WorldQuery for Added { access.add_component_read(id); } - fn init_state(world: &mut World) -> ComponentId { - world.register_component::() + fn init_state(world: &World) -> ComponentId { + world.components_queue().queue_register_component::() } fn get_state(components: &Components) -> Option { @@ -1045,8 +1045,8 @@ unsafe impl WorldQuery for Changed { access.add_component_read(id); } - fn init_state(world: &mut World) -> ComponentId { - world.register_component::() + fn init_state(world: &World) -> ComponentId { + world.components_queue().queue_register_component::() } fn get_state(components: &Components) -> Option { @@ -1205,7 +1205,7 @@ unsafe impl WorldQuery for Spawned { #[inline] fn update_component_access(_state: &(), _access: &mut FilteredAccess) {} - fn init_state(_world: &mut World) {} + fn init_state(_world: &World) {} fn get_state(_components: &Components) -> Option<()> { Some(()) diff --git a/crates/bevy_ecs/src/query/mod.rs b/crates/bevy_ecs/src/query/mod.rs index 065eadd24db88..bcc4c3a8ed3b6 100644 --- a/crates/bevy_ecs/src/query/mod.rs +++ b/crates/bevy_ecs/src/query/mod.rs @@ -867,8 +867,8 @@ mod tests { access.add_resource_read(component_id); } - fn init_state(world: &mut World) -> Self::State { - world.components_registrator().register_resource::() + fn init_state(world: &World) -> Self::State { + world.components_queue().queue_register_resource::() } fn get_state(components: &Components) -> Option { diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 448335db8ed0f..f33aba4e12b7f 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -181,7 +181,7 @@ 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 new_uninitialized(world: &mut World) -> Self { + fn new_uninitialized(world: &World) -> Self { let fetch_state = D::init_state(world); let filter_state = F::init_state(world); Self::from_states_uninitialized(world, fetch_state, filter_state) @@ -253,8 +253,8 @@ impl QueryState { /// Creates a new [`QueryState`] from a given [`QueryBuilder`] and inherits its [`FilteredAccess`]. pub fn from_builder(builder: &mut QueryBuilder) -> Self { - let mut fetch_state = D::init_state(builder.world_mut()); - let filter_state = F::init_state(builder.world_mut()); + let mut fetch_state = D::init_state(builder.world()); + let filter_state = F::init_state(builder.world()); let mut component_access = FilteredAccess::default(); D::update_component_access(&fetch_state, &mut component_access); diff --git a/crates/bevy_ecs/src/query/world_query.rs b/crates/bevy_ecs/src/query/world_query.rs index fef5b0257f167..86a94577e494f 100644 --- a/crates/bevy_ecs/src/query/world_query.rs +++ b/crates/bevy_ecs/src/query/world_query.rs @@ -116,7 +116,7 @@ pub unsafe trait WorldQuery { fn update_component_access(state: &Self::State, access: &mut FilteredAccess); /// Creates and initializes a [`State`](WorldQuery::State) for this [`WorldQuery`] type. - fn init_state(world: &mut World) -> Self::State; + fn init_state(world: &World) -> Self::State; /// Attempts to initialize a [`State`](WorldQuery::State) for this [`WorldQuery`] type using read-only /// access to [`Components`]. @@ -205,7 +205,7 @@ macro_rules! impl_tuple_world_query { let ($($name,)*) = state; $($name::update_component_access($name, access);)* } - fn init_state(world: &mut World) -> Self::State { + fn init_state(world: &World) -> Self::State { ($($name::init_state(world),)*) } fn get_state(components: &Components) -> Option { diff --git a/crates/bevy_ecs/src/spawn.rs b/crates/bevy_ecs/src/spawn.rs index 7f14c08e11a0c..6bef0b0248724 100644 --- a/crates/bevy_ecs/src/spawn.rs +++ b/crates/bevy_ecs/src/spawn.rs @@ -298,9 +298,9 @@ pub struct SpawnRelatedBundle> { unsafe impl + Send + Sync + 'static> Bundle for SpawnRelatedBundle { - fn component_ids( - components: &mut crate::component::ComponentsRegistrator, - ) -> impl Iterator + use { + fn component_ids( + components: &mut Components, + ) -> impl Iterator + use { ::component_ids(components) } @@ -388,9 +388,9 @@ impl DynamicBundle for SpawnOneRelated { // SAFETY: This internally relies on the RelationshipTarget's Bundle implementation, which is sound. unsafe impl Bundle for SpawnOneRelated { - fn component_ids( - components: &mut crate::component::ComponentsRegistrator, - ) -> impl Iterator + use { + fn component_ids( + components: &mut Components, + ) -> impl Iterator + use { ::component_ids(components) } diff --git a/crates/bevy_render/src/sync_world.rs b/crates/bevy_render/src/sync_world.rs index 6a1a1fa813a21..df71f21b06787 100644 --- a/crates/bevy_render/src/sync_world.rs +++ b/crates/bevy_render/src/sync_world.rs @@ -343,7 +343,7 @@ mod render_entities_world_query_impls { <&RenderEntity as WorldQuery>::update_component_access(&component_id, access); } - fn init_state(world: &mut World) -> ComponentId { + fn init_state(world: &World) -> ComponentId { <&RenderEntity as WorldQuery>::init_state(world) } @@ -458,7 +458,7 @@ mod render_entities_world_query_impls { <&MainEntity as WorldQuery>::update_component_access(&component_id, access); } - fn init_state(world: &mut World) -> ComponentId { + fn init_state(world: &World) -> ComponentId { <&MainEntity as WorldQuery>::init_state(world) } From bbd7033b938a53dfd9d7b42f78ffa3d05c911946 Mon Sep 17 00:00:00 2001 From: Elliott Pierce Date: Fri, 23 Jan 2026 11:58:24 -0500 Subject: [PATCH 02/17] compiles --- crates/bevy_ecs/src/query/builder.rs | 62 ++++++++++++--------------- crates/bevy_ecs/src/system/builder.rs | 2 +- crates/bevy_ui_widgets/src/observe.rs | 6 +-- 3 files changed, 32 insertions(+), 38 deletions(-) diff --git a/crates/bevy_ecs/src/query/builder.rs b/crates/bevy_ecs/src/query/builder.rs index cf0940222a8fb..fceb6cf37fe55 100644 --- a/crates/bevy_ecs/src/query/builder.rs +++ b/crates/bevy_ecs/src/query/builder.rs @@ -294,13 +294,13 @@ mod tests { let entity_a = world.spawn((A(0), B(0))).id(); let entity_b = world.spawn((A(0), C(0))).id(); - let mut query_a = QueryBuilder::::new(&mut world) + let mut query_a = QueryBuilder::::new(&world) .with::() .without::() .build(); assert_eq!(entity_a, query_a.single(&world).unwrap()); - let mut query_b = QueryBuilder::::new(&mut world) + let mut query_b = QueryBuilder::::new(&world) .with::() .without::() .build(); @@ -316,13 +316,13 @@ mod tests { let component_id_b = world.register_component::(); let component_id_c = world.register_component::(); - let mut query_a = QueryBuilder::::new(&mut world) + let mut query_a = QueryBuilder::::new(&world) .with_id(component_id_a) .without_id(component_id_c) .build(); assert_eq!(entity_a, query_a.single(&world).unwrap()); - let mut query_b = QueryBuilder::::new(&mut world) + let mut query_b = QueryBuilder::::new(&world) .with_id(component_id_a) .without_id(component_id_b) .build(); @@ -336,7 +336,7 @@ mod tests { world.spawn((B(0), D)); world.spawn((C(0), D)); - let mut query_a = QueryBuilder::<&D>::new(&mut world) + let mut query_a = QueryBuilder::<&D>::new(&world) .or(|builder| { builder.with::(); builder.with::(); @@ -344,7 +344,7 @@ mod tests { .build(); assert_eq!(2, query_a.iter(&world).count()); - let mut query_b = QueryBuilder::<&D>::new(&mut world) + let mut query_b = QueryBuilder::<&D>::new(&world) .or(|builder| { builder.with::(); builder.without::(); @@ -353,7 +353,7 @@ mod tests { dbg!(&query_b.component_access); assert_eq!(2, query_b.iter(&world).count()); - let mut query_c = QueryBuilder::<&D>::new(&mut world) + let mut query_c = QueryBuilder::<&D>::new(&world) .or(|builder| { builder.with::(); builder.with::(); @@ -368,7 +368,7 @@ mod tests { let mut world = World::new(); world.spawn(A(0)); world.spawn((A(1), B(0))); - let mut query = QueryBuilder::<()>::new(&mut world) + let mut query = QueryBuilder::<()>::new(&world) .with::() .transmute::<&A>() .build(); @@ -381,7 +381,7 @@ mod tests { let mut world = World::new(); let entity = world.spawn((A(0), B(1))).id(); - let mut query = QueryBuilder::::new(&mut world) + let mut query = QueryBuilder::::new(&world) .data::<&A>() .data::<&B>() .build(); @@ -404,7 +404,7 @@ mod tests { let component_id_a = world.register_component::(); let component_id_b = world.register_component::(); - let mut query = QueryBuilder::::new(&mut world) + let mut query = QueryBuilder::::new(&world) .ref_id(component_id_a) .ref_id(component_id_b) .build(); @@ -429,12 +429,10 @@ mod tests { world.spawn((A(0), B(1), D)); let mut query = - QueryBuilder::<(Entity, FilteredEntityRef, FilteredEntityMut), With>::new( - &mut world, - ) - .data::<&mut A>() - .data::<&B>() - .build(); + QueryBuilder::<(Entity, FilteredEntityRef, FilteredEntityMut), With>::new(&world) + .data::<&mut A>() + .data::<&B>() + .build(); // The `FilteredEntityRef` only has read access, so the `FilteredEntityMut` can have read access without conflicts let (_entity, entity_ref_1, mut entity_ref_2) = query.single_mut(&mut world).unwrap(); @@ -446,12 +444,10 @@ mod tests { assert!(entity_ref_2.get_mut::().is_none()); let mut query = - QueryBuilder::<(Entity, FilteredEntityMut, FilteredEntityMut), With>::new( - &mut world, - ) - .data::<&mut A>() - .data::<&B>() - .build(); + QueryBuilder::<(Entity, FilteredEntityMut, FilteredEntityMut), With>::new(&world) + .data::<&mut A>() + .data::<&B>() + .build(); // The first `FilteredEntityMut` has write access to A, so the second one cannot have write access let (_entity, mut entity_ref_1, mut entity_ref_2) = query.single_mut(&mut world).unwrap(); @@ -464,7 +460,7 @@ mod tests { assert!(entity_ref_2.get::().is_some()); assert!(entity_ref_2.get_mut::().is_none()); - let mut query = QueryBuilder::<(FilteredEntityMut, &mut A, &B), With>::new(&mut world) + let mut query = QueryBuilder::<(FilteredEntityMut, &mut A, &B), With>::new(&world) .data::<&mut A>() .data::<&mut B>() .build(); @@ -476,7 +472,7 @@ mod tests { assert!(entity_ref.get::().is_some()); assert!(entity_ref.get_mut::().is_none()); - let mut query = QueryBuilder::<(FilteredEntityMut, &mut A, &B), With>::new(&mut world) + let mut query = QueryBuilder::<(FilteredEntityMut, &mut A, &B), With>::new(&world) .data::() .build(); @@ -488,7 +484,7 @@ mod tests { assert!(entity_ref.get_mut::().is_none()); let mut query = - QueryBuilder::<(FilteredEntityMut, EntityMutExcept), With>::new(&mut world) + QueryBuilder::<(FilteredEntityMut, EntityMutExcept), With>::new(&world) .data::() .build(); @@ -500,7 +496,7 @@ mod tests { assert!(entity_ref_1.get_mut::().is_none()); let mut query = - QueryBuilder::<(FilteredEntityMut, EntityRefExcept), With>::new(&mut world) + QueryBuilder::<(FilteredEntityMut, EntityRefExcept), With>::new(&world) .data::() .build(); @@ -527,9 +523,7 @@ mod tests { world.spawn(Dense); world.spawn((Dense, Sparse)); - let mut query = QueryBuilder::<&Dense>::new(&mut world) - .with::() - .build(); + let mut query = QueryBuilder::<&Dense>::new(&world).with::().build(); let matched = query.iter(&world).count(); assert_eq!(matched, 1); @@ -541,23 +535,23 @@ mod tests { #[component(storage = "SparseSet")] struct Sparse; - let mut world = World::new(); + let world = World::new(); // FilteredEntityRef and FilteredEntityMut are dense by default - let query = QueryBuilder::::new(&mut world).build(); + let query = QueryBuilder::::new(&world).build(); assert!(query.is_dense); - let query = QueryBuilder::::new(&mut world).build(); + let query = QueryBuilder::::new(&world).build(); assert!(query.is_dense); // Adding a required sparse term makes the query sparse - let query = QueryBuilder::::new(&mut world) + let query = QueryBuilder::::new(&world) .data::<&Sparse>() .build(); assert!(!query.is_dense); // Adding an optional sparse term lets it remain dense - let query = QueryBuilder::::new(&mut world) + let query = QueryBuilder::::new(&world) .data::>() .build(); assert!(query.is_dense); diff --git a/crates/bevy_ecs/src/system/builder.rs b/crates/bevy_ecs/src/system/builder.rs index 937911ca834c1..199b9eab40930 100644 --- a/crates/bevy_ecs/src/system/builder.rs +++ b/crates/bevy_ecs/src/system/builder.rs @@ -735,7 +735,7 @@ mod tests { world.spawn(A); world.spawn_empty(); - let state = QueryBuilder::new(&mut world).with::().build(); + let state = QueryBuilder::new(&world).with::().build(); let system = (state,).build_state(&mut world).build_system(query_system); diff --git a/crates/bevy_ui_widgets/src/observe.rs b/crates/bevy_ui_widgets/src/observe.rs index 9443cb5898711..6346bdd7e0ddf 100644 --- a/crates/bevy_ui_widgets/src/observe.rs +++ b/crates/bevy_ui_widgets/src/observe.rs @@ -25,9 +25,9 @@ unsafe impl< > Bundle for AddObserver { #[inline] - fn component_ids( - _components: &mut bevy_ecs::component::ComponentsRegistrator, - ) -> impl Iterator + use { + fn component_ids( + _components: &mut Components, + ) -> impl Iterator + use { // SAFETY: Empty iterator core::iter::empty() } From e4adb466a040f940fe98cfe79315edda3fb645a5 Mon Sep 17 00:00:00 2001 From: Elliott Pierce Date: Fri, 23 Jan 2026 12:28:27 -0500 Subject: [PATCH 03/17] fix-ish asset change detection --- crates/bevy_asset/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index 3c79c97124aa5..82bb6dff85502 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -205,6 +205,7 @@ pub use server::*; pub use uuid; use crate::{ + asset_changed::AssetChanges, io::{embedded::EmbeddedAssetRegistry, AssetSourceBuilder, AssetSourceBuilders, AssetSourceId}, processor::{AssetProcessor, Process}, }; @@ -650,6 +651,7 @@ impl AssetApp for App { )); } self.insert_resource(assets) + .init_resource::>() .allow_ambiguous_resource::>() .add_message::>() .add_message::>() From 05ebb696a8c9a17b87030cc5f7e12e1058c4ab77 Mon Sep 17 00:00:00 2001 From: Elliott Pierce Date: Fri, 23 Jan 2026 12:31:55 -0500 Subject: [PATCH 04/17] final touches --- crates/bevy_ecs/src/entity/entity_set.rs | 2 +- crates/bevy_ecs/src/query/state.rs | 75 +++++++++--------------- 2 files changed, 30 insertions(+), 47 deletions(-) diff --git a/crates/bevy_ecs/src/entity/entity_set.rs b/crates/bevy_ecs/src/entity/entity_set.rs index e4860685fe07f..e914f8dcb4986 100644 --- a/crates/bevy_ecs/src/entity/entity_set.rs +++ b/crates/bevy_ecs/src/entity/entity_set.rs @@ -500,7 +500,7 @@ mod tests { fn preserving_uniqueness() { let mut world = World::new(); - let mut query = QueryState::<&mut Thing>::new(&mut world); + let mut query = QueryState::<&mut Thing>::new(&world); let spawn_batch: Vec = world.spawn_batch(vec![Thing; 1000]).collect(); diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index f33aba4e12b7f..857d3331b1124 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -161,7 +161,7 @@ impl QueryState { } /// Creates a new [`QueryState`] from a given [`World`] and inherits the result of `world.id()`. - pub fn new(world: &mut World) -> Self { + pub fn new(world: &World) -> Self { let mut state = Self::new_uninitialized(world); state.update_archetypes(world); state @@ -172,9 +172,7 @@ impl QueryState { /// This function may fail if, for example, /// the components that make up this query have not been registered into the world. pub fn try_new(world: &World) -> Option { - let mut state = Self::try_new_uninitialized(world)?; - state.update_archetypes(world); - Some(state) + Some(Self::new(world)) } /// Creates a new [`QueryState`] but does not populate it with the matched results from the World yet @@ -187,20 +185,6 @@ impl QueryState { Self::from_states_uninitialized(world, fetch_state, filter_state) } - /// Creates a new [`QueryState`] but does not populate it with the matched results from the World yet - /// - /// `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())?; - Some(Self::from_states_uninitialized( - world, - fetch_state, - filter_state, - )) - } - /// Creates a new [`QueryState`] but does not populate it with the matched results from the World yet /// /// `new_archetype` and its variants must be called on all of the World's archetypes before the @@ -1974,7 +1958,7 @@ mod tests { fn can_transmute_filtered_entity() { let mut world = World::new(); let entity = world.spawn((A(0), B(1))).id(); - let query = QueryState::<(Entity, &A, &B)>::new(&mut world) + let query = QueryState::<(Entity, &A, &B)>::new(&world) .transmute::<(Entity, FilteredEntityRef)>(&world); let mut query = query; @@ -1991,7 +1975,7 @@ mod tests { let mut world = World::new(); let entity_a = world.spawn(A(0)).id(); - let mut query = QueryState::<(Entity, &A, Has)>::new(&mut world) + let mut query = QueryState::<(Entity, &A, Has)>::new(&world) .transmute_filtered::<(Entity, Has), Added>(&world); assert_eq!((entity_a, false), query.single(&world).unwrap()); @@ -2011,10 +1995,10 @@ mod tests { let mut world = World::new(); let entity_a = world.spawn(A(0)).id(); - let mut detection_query = QueryState::<(Entity, &A)>::new(&mut world) + let mut detection_query = QueryState::<(Entity, &A)>::new(&world) .transmute_filtered::>(&world); - let mut change_query = QueryState::<&mut A>::new(&mut world); + let mut change_query = QueryState::<&mut A>::new(&world); assert_eq!(entity_a, detection_query.single(&world).unwrap()); world.clear_trackers(); @@ -2032,7 +2016,7 @@ mod tests { let mut world = World::new(); world.register_component::(); world.register_component::(); - let query = QueryState::<&A>::new(&mut world); + let query = QueryState::<&A>::new(&world); let _new_query = query.transmute_filtered::>(&world); } @@ -2146,8 +2130,7 @@ mod tests { let mut world = World::new(); world.spawn(Sparse); - let mut query = - QueryState::::new(&mut world).transmute::>(&world); + let mut query = QueryState::::new(&world).transmute::>(&world); // EntityRef always performs dense iteration // But `Option<&Sparse>` will incorrectly report a component as never being present when doing dense iteration // See https://github.com/bevyengine/bevy/issues/16397 @@ -2155,7 +2138,7 @@ mod tests { let matched = query.iter(&world).filter(Option::is_some).count(); assert_eq!(matched, 0); - let mut query = QueryState::::new(&mut world).transmute::>(&world); + let mut query = QueryState::::new(&world).transmute::>(&world); // EntityRef always performs dense iteration // But `Has` will incorrectly report a component as never being present when doing dense iteration // See https://github.com/bevyengine/bevy/issues/16397 @@ -2172,8 +2155,8 @@ mod tests { let entity_ab = world.spawn((A(2), B(3))).id(); world.spawn((A(4), B(5), C(6))); - let query_1 = QueryState::<&A, Without>::new(&mut world); - let query_2 = QueryState::<&B, Without>::new(&mut world); + let query_1 = QueryState::<&A, Without>::new(&world); + let query_2 = QueryState::<&B, Without>::new(&world); let mut new_query: QueryState = query_1.join_filtered(&world, &query_2); assert_eq!(new_query.single(&world).unwrap(), entity_ab); @@ -2187,8 +2170,8 @@ mod tests { let entity_ab = world.spawn((A(2), B(3))).id(); let entity_abc = world.spawn((A(4), B(5), C(6))).id(); - let query_1 = QueryState::<&A>::new(&mut world); - let query_2 = QueryState::<&B, Without>::new(&mut world); + let query_1 = QueryState::<&A>::new(&world); + let query_2 = QueryState::<&B, Without>::new(&world); let mut new_query: QueryState = query_1.join_filtered(&world, &query_2); assert!(new_query.get(&world, entity_ab).is_ok()); @@ -2201,17 +2184,17 @@ mod tests { fn cannot_join_wrong_fetch() { let mut world = World::new(); world.register_component::(); - let query_1 = QueryState::<&A>::new(&mut world); - let query_2 = QueryState::<&B>::new(&mut world); + let query_1 = QueryState::<&A>::new(&world); + let query_2 = QueryState::<&B>::new(&world); let _query: QueryState<&C> = query_1.join(&world, &query_2); } #[test] #[should_panic] fn cannot_join_wrong_filter() { - let mut world = World::new(); - let query_1 = QueryState::<&A, Without>::new(&mut world); - let query_2 = QueryState::<&B, Without>::new(&mut world); + let world = World::new(); + let query_1 = QueryState::<&A, Without>::new(&world); + let query_2 = QueryState::<&B, Without>::new(&world); let _: QueryState> = query_1.join_filtered(&world, &query_2); } @@ -2235,8 +2218,8 @@ mod tests { let mut world = World::new(); world.spawn((A(2), B(3))); - let query_1 = QueryState::<&mut A>::new(&mut world); - let query_2 = QueryState::<&mut B>::new(&mut world); + let query_1 = QueryState::<&mut A>::new(&world); + let query_2 = QueryState::<&mut B>::new(&world); let mut new_query: QueryState<(Entity, FilteredEntityMut)> = query_1.join(&world, &query_2); let (_entity, mut entity_mut) = new_query.single_mut(&mut world).unwrap(); @@ -2254,23 +2237,23 @@ mod tests { world.register_disabling_component::(); // Without only matches the first entity - let mut query = QueryState::<&D>::new(&mut world); + let mut query = QueryState::<&D>::new(&world); assert_eq!(1, query.iter(&world).count()); // With matches the last two entities - let mut query = QueryState::<&D, With>::new(&mut world); + let mut query = QueryState::<&D, With>::new(&world); assert_eq!(2, query.iter(&world).count()); // Has should bypass the filter entirely - let mut query = QueryState::<(&D, Has)>::new(&mut world); + let mut query = QueryState::<(&D, Has)>::new(&world); assert_eq!(3, query.iter(&world).count()); // Allow should bypass the filter entirely - let mut query = QueryState::<&D, Allow>::new(&mut world); + let mut query = QueryState::<&D, Allow>::new(&world); assert_eq!(3, query.iter(&world).count()); // Other filters should still be respected - let mut query = QueryState::<(&D, Has), Without>::new(&mut world); + let mut query = QueryState::<(&D, Has), Without>::new(&world); assert_eq!(1, query.iter(&world).count()); } @@ -2291,14 +2274,14 @@ mod tests { world.spawn((Dummy, Table)); world.spawn((Dummy, Sparse)); - let mut query = QueryState::<&Dummy>::new(&mut world); + let mut query = QueryState::<&Dummy>::new(&world); // There are no sparse components involved thus the query is dense assert!(query.is_dense); assert_eq!(3, query.query(&world).count()); world.register_disabling_component::(); - let mut query = QueryState::<&Dummy>::new(&mut world); + let mut query = QueryState::<&Dummy>::new(&world); // The query doesn't ask for sparse components, but the default filters adds // a sparse component thus it is NOT dense assert!(!query.is_dense); @@ -2308,12 +2291,12 @@ mod tests { df.register_disabling_component(world.register_component::()); world.insert_resource(df); - let mut query = QueryState::<&Dummy>::new(&mut world); + let mut query = QueryState::<&Dummy>::new(&world); // If the filter is instead a table components, the query can still be dense assert!(query.is_dense); assert_eq!(1, query.query(&world).count()); - let mut query = QueryState::<&Sparse>::new(&mut world); + let mut query = QueryState::<&Sparse>::new(&world); // But only if the original query was dense assert!(!query.is_dense); assert_eq!(1, query.query(&world).count()); From 083e678367987cb9eb8493871ea4611b08fd7c00 Mon Sep 17 00:00:00 2001 From: Elliott Pierce Date: Fri, 23 Jan 2026 12:34:32 -0500 Subject: [PATCH 05/17] Quick doc improvement --- crates/bevy_asset/src/asset_changed.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/bevy_asset/src/asset_changed.rs b/crates/bevy_asset/src/asset_changed.rs index 7e314892fabde..6b826fdf7c6a9 100644 --- a/crates/bevy_asset/src/asset_changed.rs +++ b/crates/bevy_asset/src/asset_changed.rs @@ -105,6 +105,7 @@ impl<'w, A: AsAssetId> AssetChangeCheck<'w, A> { /// /// - Asset changes are registered in the [`AssetEventSystems`] system set. /// - Removed assets are not detected. +/// - The asset must be initialized ([`App::init_asset`](crate::AssetApp::init_asset)). /// /// The list of changed assets only gets updated in the [`AssetEventSystems`] system set, /// which runs in `PostUpdate`. Therefore, `AssetChanged` will only pick up asset changes in schedules From 3c7ae5d727e5660b6fc3a4e14614563cd0f036a4 Mon Sep 17 00:00:00 2001 From: Elliott Pierce Date: Fri, 23 Jan 2026 12:47:54 -0500 Subject: [PATCH 06/17] Keep `try_new` unchanged for now --- crates/bevy_ecs/src/query/state.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 857d3331b1124..708c127362a4c 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -172,7 +172,9 @@ impl QueryState { /// This function may fail if, for example, /// the components that make up this query have not been registered into the world. pub fn try_new(world: &World) -> Option { - Some(Self::new(world)) + let mut state = Self::try_new_uninitialized(world)?; + state.update_archetypes(world); + Some(state) } /// Creates a new [`QueryState`] but does not populate it with the matched results from the World yet @@ -185,6 +187,20 @@ impl QueryState { Self::from_states_uninitialized(world, fetch_state, filter_state) } + /// Creates a new [`QueryState`] but does not populate it with the matched results from the World yet + /// + /// `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())?; + Some(Self::from_states_uninitialized( + world, + fetch_state, + filter_state, + )) + } + /// Creates a new [`QueryState`] but does not populate it with the matched results from the World yet /// /// `new_archetype` and its variants must be called on all of the World's archetypes before the From 817dcf5429cfe27f5c305e06be48b2c0461de6bb Mon Sep 17 00:00:00 2001 From: Elliott Pierce Date: Fri, 23 Jan 2026 12:50:12 -0500 Subject: [PATCH 07/17] Fix Ci hopefully --- examples/ecs/dynamic.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/ecs/dynamic.rs b/examples/ecs/dynamic.rs index e5dd3c40b50a2..4385db6e5042f 100644 --- a/examples/ecs/dynamic.rs +++ b/examples/ecs/dynamic.rs @@ -152,7 +152,7 @@ fn main() { println!("Entity spawned with id: {}", entity.id()); } "q" => { - let mut builder = QueryBuilder::::new(&mut world); + let mut builder = QueryBuilder::::new(&world); parse_query(rest, &mut builder, &component_names); let mut query = builder.build(); query.iter_mut(&mut world).for_each(|filtered_entity| { From b71036fcaae893a7c6e2fd2b99c097aeb4e45aeb Mon Sep 17 00:00:00 2001 From: Elliott Pierce Date: Fri, 23 Jan 2026 13:24:05 -0500 Subject: [PATCH 08/17] a few conveniences --- benches/benches/bevy_ecs/world/world_get.rs | 16 ++++++------- .../src/fullscreen_material.rs | 2 +- crates/bevy_ecs/src/lib.rs | 24 +++++++++---------- crates/bevy_ecs/src/query/iter.rs | 2 +- crates/bevy_ecs/src/query/mod.rs | 2 +- crates/bevy_ecs/src/query/state.rs | 6 ++--- crates/bevy_ecs/src/world/mod.rs | 24 ++++++++++++++++--- 7 files changed, 47 insertions(+), 29 deletions(-) diff --git a/benches/benches/bevy_ecs/world/world_get.rs b/benches/benches/bevy_ecs/world/world_get.rs index 64a45662d9d26..fe014875b4457 100644 --- a/benches/benches/bevy_ecs/world/world_get.rs +++ b/benches/benches/bevy_ecs/world/world_get.rs @@ -106,7 +106,7 @@ pub fn world_query_get(criterion: &mut Criterion) { for entity_count in RANGE.map(|i| i * 10_000) { group.bench_function(format!("{entity_count}_entities_table"), |bencher| { - let (mut world, entities) = setup::
(entity_count); + let (world, entities) = setup::
(entity_count); let mut query = world.query::<&Table>(); bencher.iter(|| { @@ -116,7 +116,7 @@ pub fn world_query_get(criterion: &mut Criterion) { }); }); group.bench_function(format!("{entity_count}_entities_table_wide"), |bencher| { - let (mut world, entities) = setup_wide::<( + let (world, entities) = setup_wide::<( WideTable<0>, WideTable<1>, WideTable<2>, @@ -140,7 +140,7 @@ pub fn world_query_get(criterion: &mut Criterion) { }); }); group.bench_function(format!("{entity_count}_entities_sparse"), |bencher| { - let (mut world, entities) = setup::(entity_count); + let (world, entities) = setup::(entity_count); let mut query = world.query::<&Sparse>(); bencher.iter(|| { @@ -150,7 +150,7 @@ pub fn world_query_get(criterion: &mut Criterion) { }); }); group.bench_function(format!("{entity_count}_entities_sparse_wide"), |bencher| { - let (mut world, entities) = setup_wide::<( + let (world, entities) = setup_wide::<( WideSparse<0>, WideSparse<1>, WideSparse<2>, @@ -185,7 +185,7 @@ pub fn world_query_iter(criterion: &mut Criterion) { for entity_count in RANGE.map(|i| i * 10_000) { group.bench_function(format!("{entity_count}_entities_table"), |bencher| { - let (mut world, _) = setup::
(entity_count); + let (world, _) = setup::
(entity_count); let mut query = world.query::<&Table>(); bencher.iter(|| { @@ -199,7 +199,7 @@ pub fn world_query_iter(criterion: &mut Criterion) { }); }); group.bench_function(format!("{entity_count}_entities_sparse"), |bencher| { - let (mut world, _) = setup::(entity_count); + let (world, _) = setup::(entity_count); let mut query = world.query::<&Sparse>(); bencher.iter(|| { @@ -224,7 +224,7 @@ pub fn world_query_for_each(criterion: &mut Criterion) { for entity_count in RANGE.map(|i| i * 10_000) { group.bench_function(format!("{entity_count}_entities_table"), |bencher| { - let (mut world, _) = setup::
(entity_count); + let (world, _) = setup::
(entity_count); let mut query = world.query::<&Table>(); bencher.iter(|| { @@ -238,7 +238,7 @@ pub fn world_query_for_each(criterion: &mut Criterion) { }); }); group.bench_function(format!("{entity_count}_entities_sparse"), |bencher| { - let (mut world, _) = setup::(entity_count); + let (world, _) = setup::(entity_count); let mut query = world.query::<&Sparse>(); bencher.iter(|| { diff --git a/crates/bevy_core_pipeline/src/fullscreen_material.rs b/crates/bevy_core_pipeline/src/fullscreen_material.rs index 48b16b3055d3a..f45a803642a2e 100644 --- a/crates/bevy_core_pipeline/src/fullscreen_material.rs +++ b/crates/bevy_core_pipeline/src/fullscreen_material.rs @@ -99,7 +99,7 @@ impl Plugin for FullscreenMaterialPlugin { } fn extract_on_add(world: &mut World) { - world.resource_scope::(|world, mut main_world| { + world.resource_scope::(|world, main_world| { // Extract the material from the main world let mut query = main_world.query_filtered::<(Entity, Has, Has), Added>(); diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 6074af00ff021..74f20c81b8b5a 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -1466,62 +1466,62 @@ mod tests { #[test] #[should_panic] fn ref_and_mut_query_panic() { - let mut world = World::new(); + let world = World::new(); world.query::<(&A, &mut A)>(); } #[test] #[should_panic] fn entity_ref_and_mut_query_panic() { - let mut world = World::new(); + let world = World::new(); world.query::<(EntityRef, &mut A)>(); } #[test] #[should_panic] fn mut_and_ref_query_panic() { - let mut world = World::new(); + let world = World::new(); world.query::<(&mut A, &A)>(); } #[test] #[should_panic] fn mut_and_entity_ref_query_panic() { - let mut world = World::new(); + let world = World::new(); world.query::<(&mut A, EntityRef)>(); } #[test] #[should_panic] fn entity_ref_and_entity_mut_query_panic() { - let mut world = World::new(); + let world = World::new(); world.query::<(EntityRef, EntityMut)>(); } #[test] #[should_panic] fn entity_mut_and_entity_mut_query_panic() { - let mut world = World::new(); + let world = World::new(); world.query::<(EntityMut, EntityMut)>(); } #[test] fn entity_ref_and_entity_ref_query_no_panic() { - let mut world = World::new(); + let world = World::new(); world.query::<(EntityRef, EntityRef)>(); } #[test] #[should_panic] fn mut_and_mut_query_panic() { - let mut world = World::new(); + let world = World::new(); world.query::<(&mut A, &mut A)>(); } #[test] #[should_panic] fn multiple_worlds_same_query_iter() { - let mut world_a = World::new(); + let world_a = World::new(); let world_b = World::new(); let mut query = world_a.query::<&A>(); query.iter(&world_a); @@ -1530,7 +1530,7 @@ mod tests { #[test] fn query_filters_dont_collide_with_fetches() { - let mut world = World::new(); + let world = World::new(); world.query_filtered::<&mut A, Changed>(); } @@ -1555,7 +1555,7 @@ mod tests { #[test] #[should_panic] fn multiple_worlds_same_query_get() { - let mut world_a = World::new(); + let world_a = World::new(); let world_b = World::new(); let mut query = world_a.query::<&A>(); let _ = query.get(&world_a, Entity::from_raw_u32(0).unwrap()); @@ -1565,7 +1565,7 @@ mod tests { #[test] #[should_panic] fn multiple_worlds_same_query_for_each() { - let mut world_a = World::new(); + let world_a = World::new(); let world_b = World::new(); let mut query = world_a.query::<&A>(); query.iter(&world_a).for_each(|_| {}); diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index 0bd178ba03727..c65650d414655 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -2839,7 +2839,7 @@ mod tests { #[test] fn empty_query_iter_sort_after_next_does_not_panic() { - let mut world = World::new(); + let world = World::new(); { let mut query = world.query::<(&A, &Sparse)>(); let mut iter = query.iter(&world); diff --git a/crates/bevy_ecs/src/query/mod.rs b/crates/bevy_ecs/src/query/mod.rs index bcc4c3a8ed3b6..8ee319fbcf0f1 100644 --- a/crates/bevy_ecs/src/query/mod.rs +++ b/crates/bevy_ecs/src/query/mod.rs @@ -524,7 +524,7 @@ mod tests { b: &'static mut A, } - let mut world = World::new(); + let world = World::new(); world.query::(); } diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 708c127362a4c..4d03a7f0d0e45 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -1807,7 +1807,7 @@ mod tests { #[test] #[should_panic] fn right_world_get() { - let mut world_1 = World::new(); + let world_1 = World::new(); let world_2 = World::new(); let mut query_state = world_1.query::(); @@ -1817,7 +1817,7 @@ mod tests { #[test] #[should_panic] fn right_world_get_many() { - let mut world_1 = World::new(); + let world_1 = World::new(); let world_2 = World::new(); let mut query_state = world_1.query::(); @@ -1827,7 +1827,7 @@ mod tests { #[test] #[should_panic] fn right_world_get_many_mut() { - let mut world_1 = World::new(); + let world_1 = World::new(); let mut world_2 = World::new(); let mut query_state = world_1.query::(); diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 2493827e12f0d..4586c6249664a 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -60,7 +60,7 @@ use crate::{ resource::Resource, schedule::{Schedule, ScheduleLabel, Schedules}, storage::{ResourceData, Storages}, - system::Commands, + system::{Commands, Query}, world::{ command_queue::RawCommandQueue, error::{ @@ -1671,7 +1671,7 @@ impl World { /// ]); /// ``` #[inline] - pub fn query(&mut self) -> QueryState { + pub fn query(&self) -> QueryState { self.query_filtered::() } @@ -1695,7 +1695,7 @@ impl World { /// assert_eq!(matching_entities, vec![e2]); /// ``` #[inline] - pub fn query_filtered(&mut self) -> QueryState { + pub fn query_filtered(&self) -> QueryState { QueryState::new(self) } @@ -1777,6 +1777,24 @@ impl World { QueryState::try_new(self) } + /// Creates a readonly [`Query`] for `D` and `F` and runs `scope`. + pub fn query_filtered_readonly_scope<'a, D: QueryData, F: QueryFilter, O>( + &'a self, + scope: impl FnOnce(Query<'a, '_, D::ReadOnly, F>, &'a Self) -> O, + ) -> O { + let query = self.query_filtered::(); + let query = query.as_readonly().query_manual(self); + scope(query, self) + } + + /// Creates a readonly [`Query`] for `D` and runs `scope`. + pub fn query_readonly_scope<'a, D: QueryData, O>( + &'a self, + scope: impl FnOnce(Query<'a, '_, D::ReadOnly>, &'a Self) -> O, + ) -> O { + self.query_filtered_readonly_scope::<'a, D, (), O>(scope) + } + /// Returns an iterator of entities that had components of type `T` removed /// since the last call to [`World::clear_trackers`]. pub fn removed(&self) -> impl Iterator + '_ { From 256d34a1716cb90ed94ac714d1a3002d00769cd2 Mon Sep 17 00:00:00 2001 From: Elliott Pierce Date: Sun, 25 Jan 2026 22:51:06 -0500 Subject: [PATCH 09/17] deprecate `try_query` --- crates/bevy_ecs/src/query/state.rs | 1 + crates/bevy_ecs/src/world/mod.rs | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 4d03a7f0d0e45..7cec9a2e70154 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -171,6 +171,7 @@ impl QueryState { /// /// This function may fail if, for example, /// the components that make up this query have not been registered into the world. + #[deprecated(since = "0.19.0", note = "Use `init_state` instead.")] pub fn try_new(world: &World) -> Option { let mut state = Self::try_new_uninitialized(world)?; state.update_archetypes(world); diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 4586c6249664a..2796ef81a53f1 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1746,6 +1746,8 @@ impl World { /// assert!(some_query.is_some()); /// ``` #[inline] + #[deprecated(since = "0.19.0", note = "Use `init_state` instead.")] + #[expect(deprecated, reason = "also deprecated")] pub fn try_query(&self) -> Option> { self.try_query_filtered::() } @@ -1773,6 +1775,8 @@ impl World { /// Requires only an immutable world reference, but may fail if, for example, /// the components that make up this query have not been registered into the world. #[inline] + #[deprecated(since = "0.19.0", note = "Use `init_state` instead.")] + #[expect(deprecated, reason = "also deprecated")] pub fn try_query_filtered(&self) -> Option> { QueryState::try_new(self) } From 211774c45dd6d775745eb0e42fbaaa150759212d Mon Sep 17 00:00:00 2001 From: Elliott Pierce Date: Sun, 25 Jan 2026 22:58:36 -0500 Subject: [PATCH 10/17] remove uses of `try_query` --- crates/bevy_ui_render/src/lib.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/bevy_ui_render/src/lib.rs b/crates/bevy_ui_render/src/lib.rs index c5289eb2c79e7..55dc6d44fe8d9 100644 --- a/crates/bevy_ui_render/src/lib.rs +++ b/crates/bevy_ui_render/src/lib.rs @@ -418,9 +418,7 @@ impl RenderGraphNode for RunUiSubgraphOnUiViewNode { world: &'w World, ) -> Result<(), NodeRunError> { // Fetch the UI view. - let Some(mut render_views) = world.try_query::<&UiCameraView>() else { - return Ok(()); - }; + let mut render_views = world.query::<&UiCameraView>(); let Ok(ui_camera_view) = render_views.get(world, graph.view_entity()) else { return Ok(()); }; From af6488436b578b2c4a8cf28accc6739e3815e3a7 Mon Sep 17 00:00:00 2001 From: Elliott Pierce Date: Sun, 25 Jan 2026 23:00:16 -0500 Subject: [PATCH 11/17] docs for `ComponentIdDictator`. --- crates/bevy_ecs/src/component/register.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/bevy_ecs/src/component/register.rs b/crates/bevy_ecs/src/component/register.rs index 67445085a7f97..26e6d8f45e0d8 100644 --- a/crates/bevy_ecs/src/component/register.rs +++ b/crates/bevy_ecs/src/component/register.rs @@ -700,6 +700,9 @@ impl<'w> ComponentsQueuedRegistrator<'w> { } /// Represents a way to get the id of component types. +/// +/// This exists to encapsulate shared behavior of [`ComponentsRegistrator`] and [`ComponentsQueuedRegistrator`]. +/// If you know which one you are working with, prefer their direct methods. pub trait ComponentIdDictator { /// Determines the [`ComponentId`] of `T`. /// This makes no promises of whether or not `T` will be full registered; it just gets its id. From f701804eec003fcfc0cb100f0fb73df48d0a98c9 Mon Sep 17 00:00:00 2001 From: Elliott Pierce Date: Sun, 25 Jan 2026 23:37:34 -0500 Subject: [PATCH 12/17] release content --- .../migration-guides/bundle_traits.md | 10 ++++++ .../migration-guides/query_interfaces.md | 14 ++++++++ .../release-notes/readonly_query_states.md | 33 +++++++++++++++++++ 3 files changed, 57 insertions(+) create mode 100644 release-content/migration-guides/bundle_traits.md create mode 100644 release-content/migration-guides/query_interfaces.md create mode 100644 release-content/release-notes/readonly_query_states.md diff --git a/release-content/migration-guides/bundle_traits.md b/release-content/migration-guides/bundle_traits.md new file mode 100644 index 0000000000000..789bbd44c7e0e --- /dev/null +++ b/release-content/migration-guides/bundle_traits.md @@ -0,0 +1,10 @@ +--- +title: Bundle Traits +pull_requests: [22670] +--- + +The function `Bundle::component_ids` is now generic over `Components: ComponentIdDictator`. +The `ComponentIdDictator` is implemented for the old `ComponentsRegistrator` and `ComponentsQueuedRegistrator`. +Additionally, because [this rust rfc](https://github.com/rust-lang/rust/issues/130043) is not yet stable, the `use` had to be extended to `use`. +This may cause some lifetime annoyances but nothing a `SmallVec` can't fix. +This was done to allow bundles to work with queued component registration. diff --git a/release-content/migration-guides/query_interfaces.md b/release-content/migration-guides/query_interfaces.md new file mode 100644 index 0000000000000..732bc1cf23266 --- /dev/null +++ b/release-content/migration-guides/query_interfaces.md @@ -0,0 +1,14 @@ +--- +title: Query Interfaces +pull_requests: [22670] +--- + +The function `WorldQuery::init_state` now takes `&World` instead of `&mut World`. +Callers have no change here. +For implementers, you are no longer allowed to mutate the world during query registration. +If you were mutating it, consider instead doing some manual registration work before creating the query. +While this is annoying for some cases, query states were never intended to be a way to change the state of a world. +Prefer explicit world mutations. + +The `try_query` interface, including `QueryState::try_new`, `World::try_query`, and `World::try_query_filtered` have all been deprecated. +These can be done infallibility through `QueryState::new`, `World::query`, and `World::query_filtered`, which now only require `&World`. diff --git a/release-content/release-notes/readonly_query_states.md b/release-content/release-notes/readonly_query_states.md new file mode 100644 index 0000000000000..48e470121b80f --- /dev/null +++ b/release-content/release-notes/readonly_query_states.md @@ -0,0 +1,33 @@ +--- +title: Readonly Query State Creation +authors: ["@Eagster"] +pull_requests: [18173, 22670] +--- + +It has long been an annoyance in Bevy that getting immutable data from a world through a query often required mutable world access. +Previously, `QueryState` required `&mut World` to be created. +This meant you had to either make a `QueryState` using `&mut World` and keep track of it somewhere, later making a readonly query, +or use the fallible `QueryState::try_new`/`World::try_query` functions after manually registering components and other information. + +This pain point is now resolved! +Today, `QueryState::new`, `World::query`, and the like all only require `&World`. +For example, it is now possible to do the following: + +```rust + +#[derive(Resource)] +struct MySubWorld(World); + +fn read_sub_world(sub_world: Res) { + let query = sub_world.0.query::<&MyComponent>(); + let query = query.iter(&sub_world.0); + for entity in query.iter() { + // TADA! + } +} + +``` + +Note that using `World::query` or `QueryState::new` each time results in an uncached query. +The query state and matching tables must be recalculated each time. +As a result, for queries that are running very frequently, caching the `QueryState` (through system parameters, for example) is still a good idea. From 98596ba7f82f5afaf2a989cda597e49175313ed6 Mon Sep 17 00:00:00 2001 From: Elliott Pierce Date: Sun, 25 Jan 2026 23:56:45 -0500 Subject: [PATCH 13/17] fix markdown lint --- release-content/migration-guides/bundle_traits.md | 2 +- release-content/release-notes/readonly_query_states.md | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/release-content/migration-guides/bundle_traits.md b/release-content/migration-guides/bundle_traits.md index 789bbd44c7e0e..0f437710d6347 100644 --- a/release-content/migration-guides/bundle_traits.md +++ b/release-content/migration-guides/bundle_traits.md @@ -3,7 +3,7 @@ title: Bundle Traits pull_requests: [22670] --- -The function `Bundle::component_ids` is now generic over `Components: ComponentIdDictator`. +The function `Bundle::component_ids` is now generic over `Components: ComponentIdDictator`. The `ComponentIdDictator` is implemented for the old `ComponentsRegistrator` and `ComponentsQueuedRegistrator`. Additionally, because [this rust rfc](https://github.com/rust-lang/rust/issues/130043) is not yet stable, the `use` had to be extended to `use`. This may cause some lifetime annoyances but nothing a `SmallVec` can't fix. diff --git a/release-content/release-notes/readonly_query_states.md b/release-content/release-notes/readonly_query_states.md index 48e470121b80f..15b8f799b7ddb 100644 --- a/release-content/release-notes/readonly_query_states.md +++ b/release-content/release-notes/readonly_query_states.md @@ -6,11 +6,12 @@ pull_requests: [18173, 22670] It has long been an annoyance in Bevy that getting immutable data from a world through a query often required mutable world access. Previously, `QueryState` required `&mut World` to be created. -This meant you had to either make a `QueryState` using `&mut World` and keep track of it somewhere, later making a readonly query, -or use the fallible `QueryState::try_new`/`World::try_query` functions after manually registering components and other information. +This meant you had to either make a `QueryState` using `&mut World` and keep track of it somewhere, +or use the fallible `QueryState::try_new`/`World::try_query` functions after manually registering components. This pain point is now resolved! Today, `QueryState::new`, `World::query`, and the like all only require `&World`. +This will cause some breaking changes (see the migration guide for those), but it is well worth it for the possibilities it opens up. For example, it is now possible to do the following: ```rust From a2d8cd2e964405862d879e536c07b3adce97a134 Mon Sep 17 00:00:00 2001 From: Elliott Pierce Date: Mon, 26 Jan 2026 00:11:42 -0500 Subject: [PATCH 14/17] fix markdown lint --- release-content/release-notes/readonly_query_states.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release-content/release-notes/readonly_query_states.md b/release-content/release-notes/readonly_query_states.md index 15b8f799b7ddb..c8c57e8ffd51a 100644 --- a/release-content/release-notes/readonly_query_states.md +++ b/release-content/release-notes/readonly_query_states.md @@ -6,7 +6,7 @@ pull_requests: [18173, 22670] It has long been an annoyance in Bevy that getting immutable data from a world through a query often required mutable world access. Previously, `QueryState` required `&mut World` to be created. -This meant you had to either make a `QueryState` using `&mut World` and keep track of it somewhere, +This meant you had to either make a `QueryState` using `&mut World` and keep track of it somewhere, or use the fallible `QueryState::try_new`/`World::try_query` functions after manually registering components. This pain point is now resolved! From a039b5fcde163c7215c6269be3aa50159c28c217 Mon Sep 17 00:00:00 2001 From: Eagster <79881080+ElliottjPierce@users.noreply.github.com> Date: Mon, 26 Jan 2026 16:00:24 -0500 Subject: [PATCH 15/17] Apply suggestions from code review Co-authored-by: Chris Russell <8494645+chescock@users.noreply.github.com> --- crates/bevy_ecs/src/query/state.rs | 2 +- crates/bevy_ecs/src/world/mod.rs | 4 ++-- release-content/release-notes/readonly_query_states.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 7cec9a2e70154..ef3a6b5a59caa 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -171,7 +171,7 @@ impl QueryState { /// /// This function may fail if, for example, /// the components that make up this query have not been registered into the world. - #[deprecated(since = "0.19.0", note = "Use `init_state` instead.")] + #[deprecated(since = "0.19.0", note = "Use `new` instead.")] pub fn try_new(world: &World) -> Option { let mut state = Self::try_new_uninitialized(world)?; state.update_archetypes(world); diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 5e19a67c8f9f2..0f4d6028f95de 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1746,7 +1746,7 @@ impl World { /// assert!(some_query.is_some()); /// ``` #[inline] - #[deprecated(since = "0.19.0", note = "Use `init_state` instead.")] + #[deprecated(since = "0.19.0", note = "Use `query` instead.")] #[expect(deprecated, reason = "also deprecated")] pub fn try_query(&self) -> Option> { self.try_query_filtered::() @@ -1775,7 +1775,7 @@ impl World { /// Requires only an immutable world reference, but may fail if, for example, /// the components that make up this query have not been registered into the world. #[inline] - #[deprecated(since = "0.19.0", note = "Use `init_state` instead.")] + #[deprecated(since = "0.19.0", note = "Use `query_filtered` instead.")] #[expect(deprecated, reason = "also deprecated")] pub fn try_query_filtered(&self) -> Option> { QueryState::try_new(self) diff --git a/release-content/release-notes/readonly_query_states.md b/release-content/release-notes/readonly_query_states.md index c8c57e8ffd51a..5670dec31eb1a 100644 --- a/release-content/release-notes/readonly_query_states.md +++ b/release-content/release-notes/readonly_query_states.md @@ -29,6 +29,6 @@ fn read_sub_world(sub_world: Res) { ``` -Note that using `World::query` or `QueryState::new` each time results in an uncached query. +Note that using `World::query` or `QueryState::new` initializes a new query cache each time. The query state and matching tables must be recalculated each time. As a result, for queries that are running very frequently, caching the `QueryState` (through system parameters, for example) is still a good idea. From 972b8a330f30f94cf6d5bc396a323c48976d663e Mon Sep 17 00:00:00 2001 From: Elliott Pierce Date: Mon, 26 Jan 2026 16:01:31 -0500 Subject: [PATCH 16/17] remove query scopes --- crates/bevy_ecs/src/world/mod.rs | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 0f4d6028f95de..2f7d5e992fc0a 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -60,7 +60,7 @@ use crate::{ resource::Resource, schedule::{Schedule, ScheduleLabel, Schedules}, storage::{ResourceData, Storages}, - system::{Commands, Query}, + system::Commands, world::{ command_queue::RawCommandQueue, error::{ @@ -1781,24 +1781,6 @@ impl World { QueryState::try_new(self) } - /// Creates a readonly [`Query`] for `D` and `F` and runs `scope`. - pub fn query_filtered_readonly_scope<'a, D: QueryData, F: QueryFilter, O>( - &'a self, - scope: impl FnOnce(Query<'a, '_, D::ReadOnly, F>, &'a Self) -> O, - ) -> O { - let query = self.query_filtered::(); - let query = query.as_readonly().query_manual(self); - scope(query, self) - } - - /// Creates a readonly [`Query`] for `D` and runs `scope`. - pub fn query_readonly_scope<'a, D: QueryData, O>( - &'a self, - scope: impl FnOnce(Query<'a, '_, D::ReadOnly>, &'a Self) -> O, - ) -> O { - self.query_filtered_readonly_scope::<'a, D, (), O>(scope) - } - /// Returns an iterator of entities that had components of type `T` removed /// since the last call to [`World::clear_trackers`]. pub fn removed(&self) -> impl Iterator + '_ { From c6cb619b2f1899fb35266e084fc6e57a3bb3d605 Mon Sep 17 00:00:00 2001 From: Elliott Pierce Date: Mon, 26 Jan 2026 16:03:45 -0500 Subject: [PATCH 17/17] rename `ComponentIdDictator` --- crates/bevy_ecs/macros/src/lib.rs | 2 +- crates/bevy_ecs/src/bundle/impls.rs | 6 +++--- crates/bevy_ecs/src/bundle/mod.rs | 6 +++--- crates/bevy_ecs/src/component/register.rs | 6 +++--- crates/bevy_ecs/src/spawn.rs | 4 ++-- crates/bevy_ui_widgets/src/observe.rs | 2 +- release-content/migration-guides/bundle_traits.md | 4 ++-- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index 78b8666d54e3a..5028eb5ef3d8a 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -134,7 +134,7 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { // - `Bundle::get_components` is exactly once for each member. Rely's on the Component -> Bundle implementation to properly pass // the correct `StorageType` into the callback. unsafe impl #impl_generics #ecs_path::bundle::Bundle for #struct_name #ty_generics #where_clause { - fn component_ids( + fn component_ids( components: &mut Components, ) -> impl Iterator + use<#(#generics_ty_list,)* Components> { core::iter::empty()#(.chain(<#active_field_types as #ecs_path::bundle::Bundle>::component_ids(components)))* diff --git a/crates/bevy_ecs/src/bundle/impls.rs b/crates/bevy_ecs/src/bundle/impls.rs index f7f642967a2a4..6fd1a82d57bb4 100644 --- a/crates/bevy_ecs/src/bundle/impls.rs +++ b/crates/bevy_ecs/src/bundle/impls.rs @@ -6,7 +6,7 @@ use variadics_please::all_tuples_enumerated; use crate::{ bundle::{Bundle, BundleFromComponents, DynamicBundle, NoBundleEffect}, - component::{Component, ComponentId, ComponentIdDictator, Components, StorageType}, + component::{Component, ComponentId, Components, DetermineComponentIds, StorageType}, world::EntityWorldMut, }; @@ -14,7 +14,7 @@ use crate::{ // - `Bundle::component_ids` calls `ids` for C's component id (and nothing else) // - `Bundle::get_components` is called exactly once for C and passes the component's storage type based on its associated constant. unsafe impl Bundle for C { - fn component_ids( + fn component_ids( components: &mut Components, ) -> impl Iterator + use { iter::once(components.determine_component_id_of::()) @@ -73,7 +73,7 @@ macro_rules! tuple_impl { // - `Bundle::get_components` is called exactly once for each member. Relies on the above implementation to pass the correct // `StorageType` into the callback. unsafe impl<$($name: Bundle),*> Bundle for ($($name,)*) { - fn component_ids<'a, Components: ComponentIdDictator>(components: &'a mut Components) -> impl Iterator + use<$($name,)* Components> { + fn component_ids<'a, Components: DetermineComponentIds>(components: &'a mut Components) -> impl Iterator + use<$($name,)* Components> { iter::empty()$(.chain(<$name as Bundle>::component_ids(components)))* } diff --git a/crates/bevy_ecs/src/bundle/mod.rs b/crates/bevy_ecs/src/bundle/mod.rs index 41a4882f2946f..292bc7f46f545 100644 --- a/crates/bevy_ecs/src/bundle/mod.rs +++ b/crates/bevy_ecs/src/bundle/mod.rs @@ -77,7 +77,7 @@ pub use info::*; pub use bevy_ecs_macros::Bundle; use crate::{ - component::{ComponentId, ComponentIdDictator, Components, StorageType}, + component::{ComponentId, Components, DetermineComponentIds, StorageType}, world::EntityWorldMut, }; use bevy_ptr::OwningPtr; @@ -204,9 +204,9 @@ use bevy_ptr::OwningPtr; )] pub unsafe trait Bundle: DynamicBundle + Send + Sync + 'static { /// Gets this [`Bundle`]'s component ids, in the order of this bundle's [`Component`]s - /// This may register the component if it doesn't exist, depending on which [`ComponentIdDictator`] is used. + /// This may register the component if it doesn't exist, depending on which [`DetermineComponentIds`] is used. #[doc(hidden)] - fn component_ids( + fn component_ids( components: &mut Components, ) -> impl Iterator + use; diff --git a/crates/bevy_ecs/src/component/register.rs b/crates/bevy_ecs/src/component/register.rs index 26e6d8f45e0d8..238b7450974df 100644 --- a/crates/bevy_ecs/src/component/register.rs +++ b/crates/bevy_ecs/src/component/register.rs @@ -703,20 +703,20 @@ impl<'w> ComponentsQueuedRegistrator<'w> { /// /// This exists to encapsulate shared behavior of [`ComponentsRegistrator`] and [`ComponentsQueuedRegistrator`]. /// If you know which one you are working with, prefer their direct methods. -pub trait ComponentIdDictator { +pub trait DetermineComponentIds { /// Determines the [`ComponentId`] of `T`. /// This makes no promises of whether or not `T` will be full registered; it just gets its id. fn determine_component_id_of(&mut self) -> ComponentId; } -impl ComponentIdDictator for ComponentsRegistrator<'_> { +impl DetermineComponentIds for ComponentsRegistrator<'_> { #[inline] fn determine_component_id_of(&mut self) -> ComponentId { self.register_component::() } } -impl ComponentIdDictator for ComponentsQueuedRegistrator<'_> { +impl DetermineComponentIds for ComponentsQueuedRegistrator<'_> { #[inline] fn determine_component_id_of(&mut self) -> ComponentId { self.queue_register_component::() diff --git a/crates/bevy_ecs/src/spawn.rs b/crates/bevy_ecs/src/spawn.rs index 6bef0b0248724..8f416d15d1c0b 100644 --- a/crates/bevy_ecs/src/spawn.rs +++ b/crates/bevy_ecs/src/spawn.rs @@ -298,7 +298,7 @@ pub struct SpawnRelatedBundle> { unsafe impl + Send + Sync + 'static> Bundle for SpawnRelatedBundle { - fn component_ids( + fn component_ids( components: &mut Components, ) -> impl Iterator + use { ::component_ids(components) @@ -388,7 +388,7 @@ impl DynamicBundle for SpawnOneRelated { // SAFETY: This internally relies on the RelationshipTarget's Bundle implementation, which is sound. unsafe impl Bundle for SpawnOneRelated { - fn component_ids( + fn component_ids( components: &mut Components, ) -> impl Iterator + use { ::component_ids(components) diff --git a/crates/bevy_ui_widgets/src/observe.rs b/crates/bevy_ui_widgets/src/observe.rs index 6346bdd7e0ddf..95fbc3441ba20 100644 --- a/crates/bevy_ui_widgets/src/observe.rs +++ b/crates/bevy_ui_widgets/src/observe.rs @@ -25,7 +25,7 @@ unsafe impl< > Bundle for AddObserver { #[inline] - fn component_ids( + fn component_ids( _components: &mut Components, ) -> impl Iterator + use { // SAFETY: Empty iterator diff --git a/release-content/migration-guides/bundle_traits.md b/release-content/migration-guides/bundle_traits.md index 0f437710d6347..aa3b964875a61 100644 --- a/release-content/migration-guides/bundle_traits.md +++ b/release-content/migration-guides/bundle_traits.md @@ -3,8 +3,8 @@ title: Bundle Traits pull_requests: [22670] --- -The function `Bundle::component_ids` is now generic over `Components: ComponentIdDictator`. -The `ComponentIdDictator` is implemented for the old `ComponentsRegistrator` and `ComponentsQueuedRegistrator`. +The function `Bundle::component_ids` is now generic over `Components: DetermineComponentIds`. +The `DetermineComponentIds` is implemented for the old `ComponentsRegistrator` and `ComponentsQueuedRegistrator`. Additionally, because [this rust rfc](https://github.com/rust-lang/rust/issues/130043) is not yet stable, the `use` had to be extended to `use`. This may cause some lifetime annoyances but nothing a `SmallVec` can't fix. This was done to allow bundles to work with queued component registration.