From f7e112a3c959fb196939eb6b8b37af21cf773314 Mon Sep 17 00:00:00 2001 From: Chris Russell <8494645+chescock@users.noreply.github.com> Date: Mon, 16 Jun 2025 17:05:41 -0400 Subject: [PATCH 01/25] Let query items borrow from query state to avoid needing to clone (#15396) # Objective Improve the performance of `FilteredEntity(Ref|Mut)` and `Entity(Ref|Mut)Except`. `FilteredEntityRef` needs an `Access` to determine what components it can access. There is one stored in the query state, but query items cannot borrow from the state, so it has to `clone()` the access for each row. Cloning the access involves memory allocations and can be expensive. ## Solution Let query items borrow from their query state. Add an `'s` lifetime to `WorldQuery::Item` and `WorldQuery::Fetch`, similar to the one in `SystemParam`, and provide `&'s Self::State` to the fetch so that it can borrow from the state. Unfortunately, there are a few cases where we currently return query items from temporary query states: the sorted iteration methods create a temporary state to query the sort keys, and the `EntityRef::components()` methods create a temporary state for their query. To allow these to continue to work with most `QueryData` implementations, introduce a new subtrait `ReleaseStateQueryData` that converts a `QueryItem<'w, 's>` to `QueryItem<'w, 'static>`, and is implemented for everything except `FilteredEntity(Ref|Mut)` and `Entity(Ref|Mut)Except`. `#[derive(QueryData)]` will generate `ReleaseStateQueryData` implementations that apply when all of the subqueries implement `ReleaseStateQueryData`. This PR does not actually change the implementation of `FilteredEntity(Ref|Mut)` or `Entity(Ref|Mut)Except`! That will be done as a follow-up PR so that the changes are easier to review. I have pushed the changes as chescock/bevy#5. ## Testing I ran performance traces of many_foxes, both against main and against chescock/bevy#5, both including #15282. These changes do appear to make generalized animation a bit faster: (Red is main, yellow is chescock/bevy#5) ![image](https://github.com/user-attachments/assets/de900117-0c6a-431d-ab62-c013834f97a9) ## Migration Guide The `WorldQuery::Item` and `WorldQuery::Fetch` associated types and the `QueryItem` and `ROQueryItem` type aliases now have an additional lifetime parameter corresponding to the `'s` lifetime in `Query`. Manual implementations of `WorldQuery` will need to update the method signatures to include the new lifetimes. Other uses of the types will need to be updated to include a lifetime parameter, although it can usually be passed as `'_`. In particular, `ROQueryItem` is used when implementing `RenderCommand`. Before: ```rust fn render<'w>( item: &P, view: ROQueryItem<'w, Self::ViewQuery>, entity: Option>, param: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult; ``` After: ```rust fn render<'w>( item: &P, view: ROQueryItem<'w, '_, Self::ViewQuery>, entity: Option>, param: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult; ``` --- Methods on `QueryState` that take `&mut self` may now result in conflicting borrows if the query items capture the lifetime of the mutable reference. This affects `get()`, `iter()`, and others. To fix the errors, first call `QueryState::update_archetypes()`, and then replace a call `state.foo(world, param)` with `state.query_manual(world).foo_inner(param)`. Alternately, you may be able to restructure the code to call `state.query(world)` once and then make multiple calls using the `Query`. Before: ```rust let mut state: QueryState<_, _> = ...; let d1 = state.get(world, e1); let d2 = state.get(world, e2); // Error: cannot borrow `state` as mutable more than once at a time println!("{d1:?}"); println!("{d2:?}"); ``` After: ```rust let mut state: QueryState<_, _> = ...; state.update_archetypes(world); let d1 = state.get_manual(world, e1); let d2 = state.get_manual(world, e2); // OR state.update_archetypes(world); let d1 = state.query(world).get_inner(e1); let d2 = state.query(world).get_inner(e2); // OR let query = state.query(world); let d1 = query.get_inner(e1); let d1 = query.get_inner(e2); println!("{d1:?}"); println!("{d2:?}"); ``` --- crates/bevy_anti_aliasing/src/smaa/mod.rs | 2 +- crates/bevy_asset/src/asset_changed.rs | 26 +- crates/bevy_core_pipeline/src/bloom/mod.rs | 2 +- .../bevy_core_pipeline/src/bloom/settings.rs | 2 +- .../src/core_2d/main_opaque_pass_2d_node.rs | 2 +- .../core_2d/main_transparent_pass_2d_node.rs | 2 +- .../src/core_3d/main_opaque_pass_3d_node.rs | 2 +- .../bevy_core_pipeline/src/deferred/node.rs | 5 +- crates/bevy_core_pipeline/src/dof/mod.rs | 2 +- .../bevy_core_pipeline/src/msaa_writeback.rs | 2 +- .../src/post_process/mod.rs | 4 +- crates/bevy_core_pipeline/src/prepass/node.rs | 6 +- crates/bevy_core_pipeline/src/skybox/mod.rs | 4 +- crates/bevy_ecs/macros/src/query_data.rs | 53 +- crates/bevy_ecs/macros/src/query_filter.rs | 5 +- crates/bevy_ecs/macros/src/world_query.rs | 40 +- crates/bevy_ecs/src/name.rs | 4 +- crates/bevy_ecs/src/observer/mod.rs | 2 +- crates/bevy_ecs/src/query/fetch.rs | 867 ++++++++++++------ crates/bevy_ecs/src/query/filter.rs | 136 +-- crates/bevy_ecs/src/query/iter.rs | 100 +- crates/bevy_ecs/src/query/mod.rs | 38 +- crates/bevy_ecs/src/query/par_iter.rs | 12 +- crates/bevy_ecs/src/query/state.rs | 55 +- crates/bevy_ecs/src/query/world_query.rs | 38 +- .../src/relationship/relationship_query.rs | 36 +- crates/bevy_ecs/src/system/query.rs | 63 +- crates/bevy_ecs/src/system/system_param.rs | 10 +- crates/bevy_ecs/src/traversal.rs | 14 +- crates/bevy_ecs/src/world/entity_ref.rs | 20 +- .../bevy_ecs/src/world/unsafe_world_cell.rs | 9 +- crates/bevy_gizmos/src/lib.rs | 12 +- crates/bevy_input_focus/src/lib.rs | 2 +- crates/bevy_pbr/src/atmosphere/mod.rs | 4 +- crates/bevy_pbr/src/atmosphere/node.rs | 2 +- .../src/light_probe/environment_map.rs | 2 +- crates/bevy_pbr/src/render/mesh.rs | 4 +- crates/bevy_pbr/src/ssr/mod.rs | 4 +- crates/bevy_pbr/src/volumetric_fog/render.rs | 2 +- crates/bevy_pbr/src/wireframe.rs | 2 +- crates/bevy_picking/src/events.rs | 2 +- .../macros/src/extract_component.rs | 2 +- crates/bevy_render/src/extract_component.rs | 2 +- crates/bevy_render/src/extract_instances.rs | 2 +- crates/bevy_render/src/render_graph/node.rs | 2 +- crates/bevy_render/src/render_phase/draw.rs | 8 +- crates/bevy_render/src/sync_world.rs | 88 +- crates/bevy_sprite/src/mesh2d/mesh.rs | 2 +- crates/bevy_sprite/src/mesh2d/wireframe2d.rs | 2 +- crates/bevy_sprite/src/render/mod.rs | 6 +- .../src/render/ui_material_pipeline.rs | 2 +- examples/ecs/custom_query_param.rs | 4 +- examples/shader/custom_phase_item.rs | 4 +- examples/shader/custom_render_phase.rs | 2 +- examples/shader/custom_shader_instancing.rs | 2 +- .../query_items_borrow_from_query_state.md | 65 ++ 56 files changed, 1166 insertions(+), 626 deletions(-) create mode 100644 release-content/migration-guides/query_items_borrow_from_query_state.md diff --git a/crates/bevy_anti_aliasing/src/smaa/mod.rs b/crates/bevy_anti_aliasing/src/smaa/mod.rs index 0947abc5f8..bb082c5a01 100644 --- a/crates/bevy_anti_aliasing/src/smaa/mod.rs +++ b/crates/bevy_anti_aliasing/src/smaa/mod.rs @@ -847,7 +847,7 @@ impl ViewNode for SmaaNode { view_smaa_uniform_offset, smaa_textures, view_smaa_bind_groups, - ): QueryItem<'w, Self::ViewQuery>, + ): QueryItem<'w, '_, Self::ViewQuery>, world: &'w World, ) -> Result<(), NodeRunError> { let pipeline_cache = world.resource::(); diff --git a/crates/bevy_asset/src/asset_changed.rs b/crates/bevy_asset/src/asset_changed.rs index d314fa3fd6..db97950179 100644 --- a/crates/bevy_asset/src/asset_changed.rs +++ b/crates/bevy_asset/src/asset_changed.rs @@ -150,20 +150,22 @@ pub struct AssetChangedState { #[expect(unsafe_code, reason = "WorldQuery is an unsafe trait.")] /// SAFETY: `ROQueryFetch` is the same as `QueryFetch` unsafe impl WorldQuery for AssetChanged { - type Fetch<'w> = AssetChangedFetch<'w, A>; + type Fetch<'w, 's> = AssetChangedFetch<'w, A>; type State = AssetChangedState; - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { + fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( + fetch: Self::Fetch<'wlong, 's>, + ) -> Self::Fetch<'wshort, 's> { fetch } - unsafe fn init_fetch<'w>( + unsafe fn init_fetch<'w, 's>( world: UnsafeWorldCell<'w>, - state: &Self::State, + state: &'s Self::State, last_run: Tick, this_run: Tick, - ) -> Self::Fetch<'w> { + ) -> Self::Fetch<'w, 's> { // SAFETY: // - `AssetChanges` is private and only accessed mutably in the `AssetEventSystems` system set. // - `resource_id` was obtained from the type ID of `AssetChanges`. @@ -201,9 +203,9 @@ unsafe impl WorldQuery for AssetChanged { const IS_DENSE: bool = <&A>::IS_DENSE; - unsafe fn set_archetype<'w>( - fetch: &mut Self::Fetch<'w>, - state: &Self::State, + unsafe fn set_archetype<'w, 's>( + fetch: &mut Self::Fetch<'w, 's>, + state: &'s Self::State, archetype: &'w Archetype, table: &'w Table, ) { @@ -215,7 +217,11 @@ unsafe impl WorldQuery for AssetChanged { } } - unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, table: &'w Table) { + unsafe fn set_table<'w, 's>( + fetch: &mut Self::Fetch<'w, 's>, + state: &Self::State, + table: &'w Table, + ) { if let Some(inner) = &mut fetch.inner { // SAFETY: We delegate to the inner `set_table` for `A` unsafe { @@ -265,7 +271,7 @@ unsafe impl QueryFilter for AssetChanged { #[inline] unsafe fn filter_fetch( - fetch: &mut Self::Fetch<'_>, + fetch: &mut Self::Fetch<'_, '_>, entity: Entity, table_row: TableRow, ) -> bool { diff --git a/crates/bevy_core_pipeline/src/bloom/mod.rs b/crates/bevy_core_pipeline/src/bloom/mod.rs index 10ffdf9c63..65e51c8472 100644 --- a/crates/bevy_core_pipeline/src/bloom/mod.rs +++ b/crates/bevy_core_pipeline/src/bloom/mod.rs @@ -121,7 +121,7 @@ impl ViewNode for BloomNode { bloom_settings, upsampling_pipeline_ids, downsampling_pipeline_ids, - ): QueryItem<'w, Self::ViewQuery>, + ): QueryItem<'w, '_, Self::ViewQuery>, world: &'w World, ) -> Result<(), NodeRunError> { if bloom_settings.intensity == 0.0 { diff --git a/crates/bevy_core_pipeline/src/bloom/settings.rs b/crates/bevy_core_pipeline/src/bloom/settings.rs index 195c2eb4c0..435ed037b5 100644 --- a/crates/bevy_core_pipeline/src/bloom/settings.rs +++ b/crates/bevy_core_pipeline/src/bloom/settings.rs @@ -227,7 +227,7 @@ impl ExtractComponent for Bloom { type QueryFilter = With; type Out = (Self, BloomUniforms); - fn extract_component((bloom, camera): QueryItem<'_, Self::QueryData>) -> Option { + fn extract_component((bloom, camera): QueryItem<'_, '_, Self::QueryData>) -> Option { match ( camera.physical_viewport_rect(), camera.physical_viewport_size(), diff --git a/crates/bevy_core_pipeline/src/core_2d/main_opaque_pass_2d_node.rs b/crates/bevy_core_pipeline/src/core_2d/main_opaque_pass_2d_node.rs index 60f355c115..e8cd0c65c6 100644 --- a/crates/bevy_core_pipeline/src/core_2d/main_opaque_pass_2d_node.rs +++ b/crates/bevy_core_pipeline/src/core_2d/main_opaque_pass_2d_node.rs @@ -31,7 +31,7 @@ impl ViewNode for MainOpaquePass2dNode { &self, graph: &mut RenderGraphContext, render_context: &mut RenderContext<'w>, - (camera, view, target, depth): QueryItem<'w, Self::ViewQuery>, + (camera, view, target, depth): QueryItem<'w, '_, Self::ViewQuery>, world: &'w World, ) -> Result<(), NodeRunError> { let (Some(opaque_phases), Some(alpha_mask_phases)) = ( diff --git a/crates/bevy_core_pipeline/src/core_2d/main_transparent_pass_2d_node.rs b/crates/bevy_core_pipeline/src/core_2d/main_transparent_pass_2d_node.rs index 494d4d0f89..4054283a57 100644 --- a/crates/bevy_core_pipeline/src/core_2d/main_transparent_pass_2d_node.rs +++ b/crates/bevy_core_pipeline/src/core_2d/main_transparent_pass_2d_node.rs @@ -28,7 +28,7 @@ impl ViewNode for MainTransparentPass2dNode { &self, graph: &mut RenderGraphContext, render_context: &mut RenderContext<'w>, - (camera, view, target, depth): bevy_ecs::query::QueryItem<'w, Self::ViewQuery>, + (camera, view, target, depth): bevy_ecs::query::QueryItem<'w, '_, Self::ViewQuery>, world: &'w World, ) -> Result<(), NodeRunError> { let Some(transparent_phases) = diff --git a/crates/bevy_core_pipeline/src/core_3d/main_opaque_pass_3d_node.rs b/crates/bevy_core_pipeline/src/core_3d/main_opaque_pass_3d_node.rs index 3b1bc96c90..b19268ac1f 100644 --- a/crates/bevy_core_pipeline/src/core_3d/main_opaque_pass_3d_node.rs +++ b/crates/bevy_core_pipeline/src/core_3d/main_opaque_pass_3d_node.rs @@ -45,7 +45,7 @@ impl ViewNode for MainOpaquePass3dNode { skybox_pipeline, skybox_bind_group, view_uniform_offset, - ): QueryItem<'w, Self::ViewQuery>, + ): QueryItem<'w, '_, Self::ViewQuery>, world: &'w World, ) -> Result<(), NodeRunError> { let (Some(opaque_phases), Some(alpha_mask_phases)) = ( diff --git a/crates/bevy_core_pipeline/src/deferred/node.rs b/crates/bevy_core_pipeline/src/deferred/node.rs index ffac1eec6d..e786d2a222 100644 --- a/crates/bevy_core_pipeline/src/deferred/node.rs +++ b/crates/bevy_core_pipeline/src/deferred/node.rs @@ -36,7 +36,7 @@ impl ViewNode for EarlyDeferredGBufferPrepassNode { &self, graph: &mut RenderGraphContext, render_context: &mut RenderContext<'w>, - view_query: QueryItem<'w, Self::ViewQuery>, + view_query: QueryItem<'w, '_, Self::ViewQuery>, world: &'w World, ) -> Result<(), NodeRunError> { run_deferred_prepass( @@ -74,7 +74,7 @@ impl ViewNode for LateDeferredGBufferPrepassNode { &self, graph: &mut RenderGraphContext, render_context: &mut RenderContext<'w>, - view_query: QueryItem<'w, Self::ViewQuery>, + view_query: QueryItem<'w, '_, Self::ViewQuery>, world: &'w World, ) -> Result<(), NodeRunError> { let (_, _, _, _, occlusion_culling, no_indirect_drawing) = view_query; @@ -107,6 +107,7 @@ fn run_deferred_prepass<'w>( render_context: &mut RenderContext<'w>, (camera, extracted_view, view_depth_texture, view_prepass_textures, _, _): QueryItem< 'w, + '_, ::ViewQuery, >, is_late: bool, diff --git a/crates/bevy_core_pipeline/src/dof/mod.rs b/crates/bevy_core_pipeline/src/dof/mod.rs index 38f5e1e796..c27d81180d 100644 --- a/crates/bevy_core_pipeline/src/dof/mod.rs +++ b/crates/bevy_core_pipeline/src/dof/mod.rs @@ -352,7 +352,7 @@ impl ViewNode for DepthOfFieldNode { view_bind_group_layouts, depth_of_field_uniform_index, auxiliary_dof_texture, - ): QueryItem<'w, Self::ViewQuery>, + ): QueryItem<'w, '_, Self::ViewQuery>, world: &'w World, ) -> Result<(), NodeRunError> { let pipeline_cache = world.resource::(); diff --git a/crates/bevy_core_pipeline/src/msaa_writeback.rs b/crates/bevy_core_pipeline/src/msaa_writeback.rs index 8dc51e4ed5..5f82e10599 100644 --- a/crates/bevy_core_pipeline/src/msaa_writeback.rs +++ b/crates/bevy_core_pipeline/src/msaa_writeback.rs @@ -61,7 +61,7 @@ impl ViewNode for MsaaWritebackNode { &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext<'w>, - (target, blit_pipeline_id, msaa): QueryItem<'w, Self::ViewQuery>, + (target, blit_pipeline_id, msaa): QueryItem<'w, '_, Self::ViewQuery>, world: &'w World, ) -> Result<(), NodeRunError> { if *msaa == Msaa::Off { diff --git a/crates/bevy_core_pipeline/src/post_process/mod.rs b/crates/bevy_core_pipeline/src/post_process/mod.rs index 1ab03c5dfa..0f188d1d73 100644 --- a/crates/bevy_core_pipeline/src/post_process/mod.rs +++ b/crates/bevy_core_pipeline/src/post_process/mod.rs @@ -352,7 +352,7 @@ impl ViewNode for PostProcessingNode { &self, _: &mut RenderGraphContext, render_context: &mut RenderContext<'w>, - (view_target, pipeline_id, chromatic_aberration, post_processing_uniform_buffer_offsets): QueryItem<'w, Self::ViewQuery>, + (view_target, pipeline_id, chromatic_aberration, post_processing_uniform_buffer_offsets): QueryItem<'w, '_, Self::ViewQuery>, world: &'w World, ) -> Result<(), NodeRunError> { let pipeline_cache = world.resource::(); @@ -485,7 +485,7 @@ impl ExtractComponent for ChromaticAberration { type Out = ChromaticAberration; fn extract_component( - chromatic_aberration: QueryItem<'_, Self::QueryData>, + chromatic_aberration: QueryItem<'_, '_, Self::QueryData>, ) -> Option { // Skip the postprocessing phase entirely if the intensity is zero. if chromatic_aberration.intensity > 0.0 { diff --git a/crates/bevy_core_pipeline/src/prepass/node.rs b/crates/bevy_core_pipeline/src/prepass/node.rs index 04cc1890b0..500cc0a42b 100644 --- a/crates/bevy_core_pipeline/src/prepass/node.rs +++ b/crates/bevy_core_pipeline/src/prepass/node.rs @@ -36,7 +36,7 @@ impl ViewNode for EarlyPrepassNode { &self, graph: &mut RenderGraphContext, render_context: &mut RenderContext<'w>, - view_query: QueryItem<'w, Self::ViewQuery>, + view_query: QueryItem<'w, '_, Self::ViewQuery>, world: &'w World, ) -> Result<(), NodeRunError> { run_prepass(graph, render_context, view_query, world, "early prepass") @@ -73,7 +73,7 @@ impl ViewNode for LatePrepassNode { &self, graph: &mut RenderGraphContext, render_context: &mut RenderContext<'w>, - query: QueryItem<'w, Self::ViewQuery>, + query: QueryItem<'w, '_, Self::ViewQuery>, world: &'w World, ) -> Result<(), NodeRunError> { // We only need a late prepass if we have occlusion culling and indirect @@ -112,7 +112,7 @@ fn run_prepass<'w>( _, _, has_deferred, - ): QueryItem<'w, ::ViewQuery>, + ): QueryItem<'w, '_, ::ViewQuery>, world: &'w World, label: &'static str, ) -> Result<(), NodeRunError> { diff --git a/crates/bevy_core_pipeline/src/skybox/mod.rs b/crates/bevy_core_pipeline/src/skybox/mod.rs index cb75df2053..81744f74a5 100644 --- a/crates/bevy_core_pipeline/src/skybox/mod.rs +++ b/crates/bevy_core_pipeline/src/skybox/mod.rs @@ -113,7 +113,9 @@ impl ExtractComponent for Skybox { type QueryFilter = (); type Out = (Self, SkyboxUniforms); - fn extract_component((skybox, exposure): QueryItem<'_, Self::QueryData>) -> Option { + fn extract_component( + (skybox, exposure): QueryItem<'_, '_, Self::QueryData>, + ) -> Option { let exposure = exposure .map(Exposure::exposure) .unwrap_or_else(|| Exposure::default().exposure()); diff --git a/crates/bevy_ecs/macros/src/query_data.rs b/crates/bevy_ecs/macros/src/query_data.rs index 4e4529e631..44021f27a7 100644 --- a/crates/bevy_ecs/macros/src/query_data.rs +++ b/crates/bevy_ecs/macros/src/query_data.rs @@ -76,6 +76,7 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream { let user_generics_with_world = { let mut generics = ast.generics; generics.params.insert(0, parse_quote!('__w)); + generics.params.insert(0, parse_quote!('__s)); generics }; let (user_impl_generics_with_world, user_ty_generics_with_world, user_where_clauses_with_world) = @@ -256,11 +257,11 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream { for #read_only_struct_name #user_ty_generics #user_where_clauses { const IS_READ_ONLY: bool = true; type ReadOnly = #read_only_struct_name #user_ty_generics; - type Item<'__w> = #read_only_item_struct_name #user_ty_generics_with_world; + type Item<'__w, '__s> = #read_only_item_struct_name #user_ty_generics_with_world; - fn shrink<'__wlong: '__wshort, '__wshort>( - item: Self::Item<'__wlong> - ) -> Self::Item<'__wshort> { + fn shrink<'__wlong: '__wshort, '__wshort, '__s>( + item: Self::Item<'__wlong, '__s> + ) -> Self::Item<'__wshort, '__s> { #read_only_item_struct_name { #( #field_idents: <#read_only_field_types>::shrink(item.#field_idents), @@ -278,16 +279,28 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream { /// SAFETY: we call `fetch` for each member that implements `Fetch`. #[inline(always)] - unsafe fn fetch<'__w>( - _fetch: &mut ::Fetch<'__w>, + unsafe fn fetch<'__w, '__s>( + _fetch: &mut ::Fetch<'__w, '__s>, _entity: #path::entity::Entity, _table_row: #path::storage::TableRow, - ) -> Self::Item<'__w> { + ) -> Self::Item<'__w, '__s> { Self::Item { #(#field_idents: <#read_only_field_types>::fetch(&mut _fetch.#named_field_idents, _entity, _table_row),)* } } } + + impl #user_impl_generics #path::query::ReleaseStateQueryData + for #read_only_struct_name #user_ty_generics #user_where_clauses + // Make these HRTBs with an unused lifetime parameter to allow trivial constraints + // See https://github.com/rust-lang/rust/issues/48214 + where #(for<'__a> #field_types: #path::query::QueryData,)* { + fn release_state<'__w>(_item: Self::Item<'__w, '_>) -> Self::Item<'__w, 'static> { + Self::Item { + #(#field_idents: <#read_only_field_types>::release_state(_item.#field_idents),)* + } + } + } } } else { quote! {} @@ -301,11 +314,11 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream { for #struct_name #user_ty_generics #user_where_clauses { const IS_READ_ONLY: bool = #is_read_only; type ReadOnly = #read_only_struct_name #user_ty_generics; - type Item<'__w> = #item_struct_name #user_ty_generics_with_world; + type Item<'__w, '__s> = #item_struct_name #user_ty_generics_with_world; - fn shrink<'__wlong: '__wshort, '__wshort>( - item: Self::Item<'__wlong> - ) -> Self::Item<'__wshort> { + fn shrink<'__wlong: '__wshort, '__wshort, '__s>( + item: Self::Item<'__wlong, '__s> + ) -> Self::Item<'__wshort, '__s> { #item_struct_name { #( #field_idents: <#field_types>::shrink(item.#field_idents), @@ -323,17 +336,29 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream { /// SAFETY: we call `fetch` for each member that implements `Fetch`. #[inline(always)] - unsafe fn fetch<'__w>( - _fetch: &mut ::Fetch<'__w>, + unsafe fn fetch<'__w, '__s>( + _fetch: &mut ::Fetch<'__w, '__s>, _entity: #path::entity::Entity, _table_row: #path::storage::TableRow, - ) -> Self::Item<'__w> { + ) -> Self::Item<'__w, '__s> { Self::Item { #(#field_idents: <#field_types>::fetch(&mut _fetch.#named_field_idents, _entity, _table_row),)* } } } + impl #user_impl_generics #path::query::ReleaseStateQueryData + for #struct_name #user_ty_generics #user_where_clauses + // Make these HRTBs with an unused lifetime parameter to allow trivial constraints + // See https://github.com/rust-lang/rust/issues/48214 + where #(for<'__a> #field_types: #path::query::ReleaseStateQueryData,)* { + fn release_state<'__w>(_item: Self::Item<'__w, '_>) -> Self::Item<'__w, 'static> { + Self::Item { + #(#field_idents: <#field_types>::release_state(_item.#field_idents),)* + } + } + } + #read_only_data_impl } }; diff --git a/crates/bevy_ecs/macros/src/query_filter.rs b/crates/bevy_ecs/macros/src/query_filter.rs index c7ddb9cc83..acc5c3d41b 100644 --- a/crates/bevy_ecs/macros/src/query_filter.rs +++ b/crates/bevy_ecs/macros/src/query_filter.rs @@ -23,6 +23,7 @@ pub fn derive_query_filter_impl(input: TokenStream) -> TokenStream { let user_generics_with_world = { let mut generics = ast.generics; generics.params.insert(0, parse_quote!('__w)); + generics.params.insert(0, parse_quote!('__s)); generics }; let (user_impl_generics_with_world, user_ty_generics_with_world, user_where_clauses_with_world) = @@ -101,8 +102,8 @@ pub fn derive_query_filter_impl(input: TokenStream) -> TokenStream { #[allow(unused_variables)] #[inline(always)] - unsafe fn filter_fetch<'__w>( - _fetch: &mut ::Fetch<'__w>, + unsafe fn filter_fetch<'__w, '__s>( + _fetch: &mut ::Fetch<'__w, '__s>, _entity: #path::entity::Entity, _table_row: #path::storage::TableRow, ) -> bool { diff --git a/crates/bevy_ecs/macros/src/world_query.rs b/crates/bevy_ecs/macros/src/world_query.rs index 5c4c0bff01..c064b05b97 100644 --- a/crates/bevy_ecs/macros/src/world_query.rs +++ b/crates/bevy_ecs/macros/src/world_query.rs @@ -34,14 +34,14 @@ pub(crate) fn item_struct( #derive_macro_call #item_attrs #visibility struct #item_struct_name #user_impl_generics_with_world #user_where_clauses_with_world { - #(#(#field_attrs)* #field_visibilities #field_idents: <#field_types as #path::query::QueryData>::Item<'__w>,)* + #(#(#field_attrs)* #field_visibilities #field_idents: <#field_types as #path::query::QueryData>::Item<'__w, '__s>,)* } }, Fields::Unnamed(_) => quote! { #derive_macro_call #item_attrs #visibility struct #item_struct_name #user_impl_generics_with_world #user_where_clauses_with_world( - #( #field_visibilities <#field_types as #path::query::QueryData>::Item<'__w>, )* + #( #field_visibilities <#field_types as #path::query::QueryData>::Item<'__w, '__s>, )* ); }, Fields::Unit => quote! { @@ -78,8 +78,8 @@ pub(crate) fn world_query_impl( )] #[automatically_derived] #visibility struct #fetch_struct_name #user_impl_generics_with_world #user_where_clauses_with_world { - #(#named_field_idents: <#field_types as #path::query::WorldQuery>::Fetch<'__w>,)* - #marker_name: &'__w (), + #(#named_field_idents: <#field_types as #path::query::WorldQuery>::Fetch<'__w, '__s>,)* + #marker_name: (&'__w(), &'__s()), } impl #user_impl_generics_with_world Clone for #fetch_struct_name #user_ty_generics_with_world @@ -87,7 +87,7 @@ pub(crate) fn world_query_impl( fn clone(&self) -> Self { Self { #(#named_field_idents: self.#named_field_idents.clone(),)* - #marker_name: &(), + #marker_name: (&(), &()), } } } @@ -96,26 +96,26 @@ pub(crate) fn world_query_impl( unsafe impl #user_impl_generics #path::query::WorldQuery for #struct_name #user_ty_generics #user_where_clauses { - type Fetch<'__w> = #fetch_struct_name #user_ty_generics_with_world; + type Fetch<'__w, '__s> = #fetch_struct_name #user_ty_generics_with_world; type State = #state_struct_name #user_ty_generics; - fn shrink_fetch<'__wlong: '__wshort, '__wshort>( - fetch: <#struct_name #user_ty_generics as #path::query::WorldQuery>::Fetch<'__wlong> - ) -> <#struct_name #user_ty_generics as #path::query::WorldQuery>::Fetch<'__wshort> { + fn shrink_fetch<'__wlong: '__wshort, '__wshort, '__s>( + fetch: <#struct_name #user_ty_generics as #path::query::WorldQuery>::Fetch<'__wlong, '__s> + ) -> <#struct_name #user_ty_generics as #path::query::WorldQuery>::Fetch<'__wshort, '__s> { #fetch_struct_name { #( #named_field_idents: <#field_types>::shrink_fetch(fetch.#named_field_idents), )* - #marker_name: &(), + #marker_name: (&(), &()), } } - unsafe fn init_fetch<'__w>( + unsafe fn init_fetch<'__w, '__s>( _world: #path::world::unsafe_world_cell::UnsafeWorldCell<'__w>, - state: &Self::State, + state: &'__s Self::State, _last_run: #path::component::Tick, _this_run: #path::component::Tick, - ) -> ::Fetch<'__w> { + ) -> ::Fetch<'__w, '__s> { #fetch_struct_name { #(#named_field_idents: <#field_types>::init_fetch( @@ -125,7 +125,7 @@ pub(crate) fn world_query_impl( _this_run, ), )* - #marker_name: &(), + #marker_name: (&(), &()), } } @@ -133,9 +133,9 @@ pub(crate) fn world_query_impl( /// SAFETY: we call `set_archetype` for each member that implements `Fetch` #[inline] - unsafe fn set_archetype<'__w>( - _fetch: &mut ::Fetch<'__w>, - _state: &Self::State, + unsafe fn set_archetype<'__w, '__s>( + _fetch: &mut ::Fetch<'__w, '__s>, + _state: &'__s Self::State, _archetype: &'__w #path::archetype::Archetype, _table: &'__w #path::storage::Table ) { @@ -144,9 +144,9 @@ pub(crate) fn world_query_impl( /// SAFETY: we call `set_table` for each member that implements `Fetch` #[inline] - unsafe fn set_table<'__w>( - _fetch: &mut ::Fetch<'__w>, - _state: &Self::State, + unsafe fn set_table<'__w, '__s>( + _fetch: &mut ::Fetch<'__w, '__s>, + _state: &'__s Self::State, _table: &'__w #path::storage::Table ) { #(<#field_types>::set_table(&mut _fetch.#named_field_idents, &_state.#named_field_idents, _table);)* diff --git a/crates/bevy_ecs/src/name.rs b/crates/bevy_ecs/src/name.rs index cd2e946678..67719ca18d 100644 --- a/crates/bevy_ecs/src/name.rs +++ b/crates/bevy_ecs/src/name.rs @@ -141,7 +141,7 @@ pub struct NameOrEntity { pub entity: Entity, } -impl<'a> core::fmt::Display for NameOrEntityItem<'a> { +impl<'w, 's> core::fmt::Display for NameOrEntityItem<'w, 's> { #[inline(always)] fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { match self.name { @@ -274,9 +274,9 @@ mod tests { let e2 = world.spawn(name.clone()).id(); let mut query = world.query::(); let d1 = query.get(&world, e1).unwrap(); - let d2 = query.get(&world, e2).unwrap(); // NameOrEntity Display for entities without a Name should be {index}v{generation} assert_eq!(d1.to_string(), "0v0"); + let d2 = query.get(&world, e2).unwrap(); // NameOrEntity Display for entities with a Name should be the Name assert_eq!(d2.to_string(), "MyName"); } diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index ef8b0b6042..4db65b888d 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -1078,7 +1078,7 @@ mod tests { struct ChildOf(Entity); impl Traversal for &'_ ChildOf { - fn traverse(item: Self::Item<'_>, _: &D) -> Option { + fn traverse(item: Self::Item<'_, '_>, _: &D) -> Option { Some(item.0) } } diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index 55fa42c41e..ba1a85cec3 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -163,7 +163,7 @@ use variadics_please::all_tuples; /// } /// /// // `HealthQueryItem` is only available when accessing the query with mutable methods. -/// impl<'w> HealthQueryItem<'w> { +/// impl<'w, 's> HealthQueryItem<'w, 's> { /// fn damage(&mut self, value: f32) { /// self.health.0 -= value; /// } @@ -174,7 +174,7 @@ use variadics_please::all_tuples; /// } /// /// // `HealthQueryReadOnlyItem` is only available when accessing the query with immutable methods. -/// impl<'w> HealthQueryReadOnlyItem<'w> { +/// impl<'w, 's> HealthQueryReadOnlyItem<'w, 's> { /// fn total(&self) -> f32 { /// self.health.0 + self.buff.map_or(0.0, |Buff(buff)| *buff) /// } @@ -290,10 +290,12 @@ pub unsafe trait QueryData: WorldQuery { /// The item returned by this [`WorldQuery`] /// This will be the data retrieved by the query, /// and is visible to the end user when calling e.g. `Query::get`. - type Item<'a>; + type Item<'w, 's>; /// This function manually implements subtyping for the query items. - fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort>; + fn shrink<'wlong: 'wshort, 'wshort, 's>( + item: Self::Item<'wlong, 's>, + ) -> Self::Item<'wshort, 's>; /// Offers additional access above what we requested in `update_component_access`. /// Implementations may add additional access that is a subset of `available_access` @@ -322,11 +324,11 @@ pub unsafe trait QueryData: WorldQuery { /// - Must always be called _after_ [`WorldQuery::set_table`] or [`WorldQuery::set_archetype`]. `entity` and /// `table_row` must be in the range of the current table and archetype. /// - There must not be simultaneous conflicting component access registered in `update_component_access`. - unsafe fn fetch<'w>( - fetch: &mut Self::Fetch<'w>, + unsafe fn fetch<'w, 's>( + fetch: &mut Self::Fetch<'w, 's>, entity: Entity, table_row: TableRow, - ) -> Self::Item<'w>; + ) -> Self::Item<'w, 's>; } /// A [`QueryData`] that is read only. @@ -337,40 +339,58 @@ pub unsafe trait QueryData: WorldQuery { pub unsafe trait ReadOnlyQueryData: QueryData {} /// The item type returned when a [`WorldQuery`] is iterated over -pub type QueryItem<'w, Q> = ::Item<'w>; +pub type QueryItem<'w, 's, Q> = ::Item<'w, 's>; /// The read-only variant of the item type returned when a [`QueryData`] is iterated over immutably -pub type ROQueryItem<'w, D> = QueryItem<'w, ::ReadOnly>; +pub type ROQueryItem<'w, 's, D> = QueryItem<'w, 's, ::ReadOnly>; + +/// A [`QueryData`] that does not borrow from its [`QueryState`](crate::query::QueryState). +/// +/// This is implemented by most `QueryData` types. +/// The main exceptions are [`FilteredEntityRef`], [`FilteredEntityMut`], [`EntityRefExcept`], and [`EntityMutExcept`], +/// which borrow an access list from their query state. +/// Consider using a full [`EntityRef`] or [`EntityMut`] if you would need those. +pub trait ReleaseStateQueryData: QueryData { + /// Releases the borrow from the query state by converting an item to have a `'static` state lifetime. + fn release_state<'w>(item: Self::Item<'w, '_>) -> Self::Item<'w, 'static>; +} /// SAFETY: /// `update_component_access` does nothing. /// This is sound because `fetch` does not access components. unsafe impl WorldQuery for Entity { - type Fetch<'w> = (); + type Fetch<'w, 's> = (); type State = (); - fn shrink_fetch<'wlong: 'wshort, 'wshort>(_: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {} + fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( + _: Self::Fetch<'wlong, 's>, + ) -> Self::Fetch<'wshort, 's> { + } - unsafe fn init_fetch<'w>( + unsafe fn init_fetch<'w, 's>( _world: UnsafeWorldCell<'w>, - _state: &Self::State, + _state: &'s Self::State, _last_run: Tick, _this_run: Tick, - ) -> Self::Fetch<'w> { + ) -> Self::Fetch<'w, 's> { } const IS_DENSE: bool = true; #[inline] - unsafe fn set_archetype<'w>( - _fetch: &mut Self::Fetch<'w>, - _state: &Self::State, + unsafe fn set_archetype<'w, 's>( + _fetch: &mut Self::Fetch<'w, 's>, + _state: &'s Self::State, _archetype: &'w Archetype, _table: &Table, ) { } #[inline] - unsafe fn set_table<'w>(_fetch: &mut Self::Fetch<'w>, _state: &Self::State, _table: &'w Table) { + unsafe fn set_table<'w, 's>( + _fetch: &mut Self::Fetch<'w, 's>, + _state: &'s Self::State, + _table: &'w Table, + ) { } fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess) {} @@ -394,18 +414,20 @@ unsafe impl QueryData for Entity { const IS_READ_ONLY: bool = true; type ReadOnly = Self; - type Item<'w> = Entity; + type Item<'w, 's> = Entity; - fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { + fn shrink<'wlong: 'wshort, 'wshort, 's>( + item: Self::Item<'wlong, 's>, + ) -> Self::Item<'wshort, 's> { item } #[inline(always)] - unsafe fn fetch<'w>( - _fetch: &mut Self::Fetch<'w>, + unsafe fn fetch<'w, 's>( + _fetch: &mut Self::Fetch<'w, 's>, entity: Entity, _table_row: TableRow, - ) -> Self::Item<'w> { + ) -> Self::Item<'w, 's> { entity } } @@ -413,23 +435,31 @@ unsafe impl QueryData for Entity { /// SAFETY: access is read only unsafe impl ReadOnlyQueryData for Entity {} +impl ReleaseStateQueryData for Entity { + fn release_state<'w>(item: Self::Item<'w, '_>) -> Self::Item<'w, 'static> { + item + } +} + /// SAFETY: /// `update_component_access` does nothing. /// This is sound because `fetch` does not access components. unsafe impl WorldQuery for EntityLocation { - type Fetch<'w> = &'w Entities; + type Fetch<'w, 's> = &'w Entities; type State = (); - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { + fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( + fetch: Self::Fetch<'wlong, 's>, + ) -> Self::Fetch<'wshort, 's> { fetch } - unsafe fn init_fetch<'w>( + unsafe fn init_fetch<'w, 's>( world: UnsafeWorldCell<'w>, - _state: &Self::State, + _state: &'s Self::State, _last_run: Tick, _this_run: Tick, - ) -> Self::Fetch<'w> { + ) -> Self::Fetch<'w, 's> { world.entities() } @@ -438,16 +468,20 @@ unsafe impl WorldQuery for EntityLocation { const IS_DENSE: bool = true; #[inline] - unsafe fn set_archetype<'w>( - _fetch: &mut Self::Fetch<'w>, - _state: &Self::State, + unsafe fn set_archetype<'w, 's>( + _fetch: &mut Self::Fetch<'w, 's>, + _state: &'s Self::State, _archetype: &'w Archetype, _table: &Table, ) { } #[inline] - unsafe fn set_table<'w>(_fetch: &mut Self::Fetch<'w>, _state: &Self::State, _table: &'w Table) { + unsafe fn set_table<'w, 's>( + _fetch: &mut Self::Fetch<'w, 's>, + _state: &'s Self::State, + _table: &'w Table, + ) { } fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess) {} @@ -470,18 +504,20 @@ unsafe impl WorldQuery for EntityLocation { unsafe impl QueryData for EntityLocation { const IS_READ_ONLY: bool = true; type ReadOnly = Self; - type Item<'w> = EntityLocation; + type Item<'w, 's> = EntityLocation; - fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { + fn shrink<'wlong: 'wshort, 'wshort, 's>( + item: Self::Item<'wlong, 's>, + ) -> Self::Item<'wshort, 's> { item } #[inline(always)] - unsafe fn fetch<'w>( - fetch: &mut Self::Fetch<'w>, + unsafe fn fetch<'w, 's>( + fetch: &mut Self::Fetch<'w, 's>, entity: Entity, _table_row: TableRow, - ) -> Self::Item<'w> { + ) -> Self::Item<'w, 's> { // SAFETY: `fetch` must be called with an entity that exists in the world unsafe { fetch.get(entity).debug_checked_unwrap() } } @@ -490,6 +526,12 @@ unsafe impl QueryData for EntityLocation { /// SAFETY: access is read only unsafe impl ReadOnlyQueryData for EntityLocation {} +impl ReleaseStateQueryData for EntityLocation { + fn release_state<'w>(item: Self::Item<'w, '_>) -> Self::Item<'w, 'static> { + item + } +} + /// The `SpawnDetails` query parameter fetches the [`Tick`] the entity was spawned at. /// /// To evaluate whether the spawn happened since the last time the system ran, the system @@ -563,19 +605,21 @@ pub struct SpawnDetailsFetch<'w> { // SAFETY: // No components are accessed. unsafe impl WorldQuery for SpawnDetails { - type Fetch<'w> = SpawnDetailsFetch<'w>; + type Fetch<'w, 's> = SpawnDetailsFetch<'w>; type State = (); - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { + fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( + fetch: Self::Fetch<'wlong, 's>, + ) -> Self::Fetch<'wshort, 's> { fetch } - unsafe fn init_fetch<'w>( + unsafe fn init_fetch<'w, 's>( world: UnsafeWorldCell<'w>, - _state: &Self::State, + _state: &'s Self::State, last_run: Tick, this_run: Tick, - ) -> Self::Fetch<'w> { + ) -> Self::Fetch<'w, 's> { SpawnDetailsFetch { entities: world.entities(), last_run, @@ -586,16 +630,20 @@ unsafe impl WorldQuery for SpawnDetails { const IS_DENSE: bool = true; #[inline] - unsafe fn set_archetype<'w>( - _fetch: &mut Self::Fetch<'w>, - _state: &Self::State, + unsafe fn set_archetype<'w, 's>( + _fetch: &mut Self::Fetch<'w, 's>, + _state: &'s Self::State, _archetype: &'w Archetype, _table: &'w Table, ) { } #[inline] - unsafe fn set_table<'w>(_fetch: &mut Self::Fetch<'w>, _state: &Self::State, _table: &'w Table) { + unsafe fn set_table<'w, 's>( + _fetch: &mut Self::Fetch<'w, 's>, + _state: &'s Self::State, + _table: &'w Table, + ) { } fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess) {} @@ -620,18 +668,20 @@ unsafe impl WorldQuery for SpawnDetails { unsafe impl QueryData for SpawnDetails { const IS_READ_ONLY: bool = true; type ReadOnly = Self; - type Item<'w> = Self; + type Item<'w, 's> = Self; - fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { + fn shrink<'wlong: 'wshort, 'wshort, 's>( + item: Self::Item<'wlong, 's>, + ) -> Self::Item<'wshort, 's> { item } #[inline(always)] - unsafe fn fetch<'w>( - fetch: &mut Self::Fetch<'w>, + unsafe fn fetch<'w, 's>( + fetch: &mut Self::Fetch<'w, 's>, entity: Entity, _table_row: TableRow, - ) -> Self::Item<'w> { + ) -> Self::Item<'w, 's> { // SAFETY: only living entities are queried let (spawned_by, spawned_at) = unsafe { fetch @@ -650,6 +700,12 @@ unsafe impl QueryData for SpawnDetails { /// SAFETY: access is read only unsafe impl ReadOnlyQueryData for SpawnDetails {} +impl ReleaseStateQueryData for SpawnDetails { + fn release_state<'w>(item: Self::Item<'w, '_>) -> Self::Item<'w, 'static> { + item + } +} + /// The [`WorldQuery::Fetch`] type for WorldQueries that can fetch multiple components from an entity /// ([`EntityRef`], [`EntityMut`], etc.) #[derive(Copy, Clone)] @@ -665,19 +721,21 @@ pub struct EntityFetch<'w> { /// This is sound because `update_component_access` sets read access for all components and panic when appropriate. /// Filters are unchanged. unsafe impl<'a> WorldQuery for EntityRef<'a> { - type Fetch<'w> = EntityFetch<'w>; + type Fetch<'w, 's> = EntityFetch<'w>; type State = (); - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { + fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( + fetch: Self::Fetch<'wlong, 's>, + ) -> Self::Fetch<'wshort, 's> { fetch } - unsafe fn init_fetch<'w>( + unsafe fn init_fetch<'w, 's>( world: UnsafeWorldCell<'w>, - _state: &Self::State, + _state: &'s Self::State, last_run: Tick, this_run: Tick, - ) -> Self::Fetch<'w> { + ) -> Self::Fetch<'w, 's> { EntityFetch { world, last_run, @@ -688,16 +746,20 @@ unsafe impl<'a> WorldQuery for EntityRef<'a> { const IS_DENSE: bool = true; #[inline] - unsafe fn set_archetype<'w>( - _fetch: &mut Self::Fetch<'w>, - _state: &Self::State, + unsafe fn set_archetype<'w, 's>( + _fetch: &mut Self::Fetch<'w, 's>, + _state: &'s Self::State, _archetype: &'w Archetype, _table: &Table, ) { } #[inline] - unsafe fn set_table<'w>(_fetch: &mut Self::Fetch<'w>, _state: &Self::State, _table: &'w Table) { + unsafe fn set_table<'w, 's>( + _fetch: &mut Self::Fetch<'w, 's>, + _state: &'s Self::State, + _table: &'w Table, + ) { } fn update_component_access(_state: &Self::State, access: &mut FilteredAccess) { @@ -726,18 +788,20 @@ unsafe impl<'a> WorldQuery for EntityRef<'a> { unsafe impl<'a> QueryData for EntityRef<'a> { const IS_READ_ONLY: bool = true; type ReadOnly = Self; - type Item<'w> = EntityRef<'w>; + type Item<'w, 's> = EntityRef<'w>; - fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { + fn shrink<'wlong: 'wshort, 'wshort, 's>( + item: Self::Item<'wlong, 's>, + ) -> Self::Item<'wshort, 's> { item } #[inline(always)] - unsafe fn fetch<'w>( - fetch: &mut Self::Fetch<'w>, + unsafe fn fetch<'w, 's>( + fetch: &mut Self::Fetch<'w, 's>, entity: Entity, _table_row: TableRow, - ) -> Self::Item<'w> { + ) -> Self::Item<'w, 's> { // SAFETY: `fetch` must be called with an entity that exists in the world let cell = unsafe { fetch @@ -753,21 +817,29 @@ unsafe impl<'a> QueryData for EntityRef<'a> { /// SAFETY: access is read only unsafe impl ReadOnlyQueryData for EntityRef<'_> {} +impl ReleaseStateQueryData for EntityRef<'_> { + fn release_state<'w>(item: Self::Item<'w, '_>) -> Self::Item<'w, 'static> { + item + } +} + /// SAFETY: The accesses of `Self::ReadOnly` are a subset of the accesses of `Self` unsafe impl<'a> WorldQuery for EntityMut<'a> { - type Fetch<'w> = EntityFetch<'w>; + type Fetch<'w, 's> = EntityFetch<'w>; type State = (); - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { + fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( + fetch: Self::Fetch<'wlong, 's>, + ) -> Self::Fetch<'wshort, 's> { fetch } - unsafe fn init_fetch<'w>( + unsafe fn init_fetch<'w, 's>( world: UnsafeWorldCell<'w>, - _state: &Self::State, + _state: &'s Self::State, last_run: Tick, this_run: Tick, - ) -> Self::Fetch<'w> { + ) -> Self::Fetch<'w, 's> { EntityFetch { world, last_run, @@ -778,16 +850,20 @@ unsafe impl<'a> WorldQuery for EntityMut<'a> { const IS_DENSE: bool = true; #[inline] - unsafe fn set_archetype<'w>( - _fetch: &mut Self::Fetch<'w>, - _state: &Self::State, + unsafe fn set_archetype<'w, 's>( + _fetch: &mut Self::Fetch<'w, 's>, + _state: &'s Self::State, _archetype: &'w Archetype, _table: &Table, ) { } #[inline] - unsafe fn set_table<'w>(_fetch: &mut Self::Fetch<'w>, _state: &Self::State, _table: &'w Table) { + unsafe fn set_table<'w, 's>( + _fetch: &mut Self::Fetch<'w, 's>, + _state: &'s Self::State, + _table: &'w Table, + ) { } fn update_component_access(_state: &Self::State, access: &mut FilteredAccess) { @@ -816,18 +892,20 @@ unsafe impl<'a> WorldQuery for EntityMut<'a> { unsafe impl<'a> QueryData for EntityMut<'a> { const IS_READ_ONLY: bool = false; type ReadOnly = EntityRef<'a>; - type Item<'w> = EntityMut<'w>; + type Item<'w, 's> = EntityMut<'w>; - fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { + fn shrink<'wlong: 'wshort, 'wshort, 's>( + item: Self::Item<'wlong, 's>, + ) -> Self::Item<'wshort, 's> { item } #[inline(always)] - unsafe fn fetch<'w>( - fetch: &mut Self::Fetch<'w>, + unsafe fn fetch<'w, 's>( + fetch: &mut Self::Fetch<'w, 's>, entity: Entity, _table_row: TableRow, - ) -> Self::Item<'w> { + ) -> Self::Item<'w, 's> { // SAFETY: `fetch` must be called with an entity that exists in the world let cell = unsafe { fetch @@ -840,23 +918,31 @@ unsafe impl<'a> QueryData for EntityMut<'a> { } } +impl ReleaseStateQueryData for EntityMut<'_> { + fn release_state<'w>(item: Self::Item<'w, '_>) -> Self::Item<'w, 'static> { + item + } +} + /// SAFETY: The accesses of `Self::ReadOnly` are a subset of the accesses of `Self` unsafe impl<'a> WorldQuery for FilteredEntityRef<'a> { - type Fetch<'w> = (EntityFetch<'w>, Access); + type Fetch<'w, 's> = (EntityFetch<'w>, Access); type State = Access; - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { + fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( + fetch: Self::Fetch<'wlong, 's>, + ) -> Self::Fetch<'wshort, 's> { fetch } const IS_DENSE: bool = false; - unsafe fn init_fetch<'w>( + unsafe fn init_fetch<'w, 's>( world: UnsafeWorldCell<'w>, - _state: &Self::State, + _state: &'s Self::State, last_run: Tick, this_run: Tick, - ) -> Self::Fetch<'w> { + ) -> Self::Fetch<'w, 's> { let mut access = Access::default(); access.read_all_components(); ( @@ -870,9 +956,9 @@ unsafe impl<'a> WorldQuery for FilteredEntityRef<'a> { } #[inline] - unsafe fn set_archetype<'w>( - fetch: &mut Self::Fetch<'w>, - state: &Self::State, + unsafe fn set_archetype<'w, 's>( + fetch: &mut Self::Fetch<'w, 's>, + state: &'s Self::State, _: &'w Archetype, _table: &Table, ) { @@ -880,7 +966,11 @@ unsafe impl<'a> WorldQuery for FilteredEntityRef<'a> { } #[inline] - unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, _: &'w Table) { + unsafe fn set_table<'w, 's>( + fetch: &mut Self::Fetch<'w, 's>, + state: &'s Self::State, + _: &'w Table, + ) { fetch.1.clone_from(state); } @@ -915,9 +1005,11 @@ unsafe impl<'a> WorldQuery for FilteredEntityRef<'a> { unsafe impl<'a> QueryData for FilteredEntityRef<'a> { const IS_READ_ONLY: bool = true; type ReadOnly = Self; - type Item<'w> = FilteredEntityRef<'w>; + type Item<'w, 's> = FilteredEntityRef<'w>; - fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { + fn shrink<'wlong: 'wshort, 'wshort, 's>( + item: Self::Item<'wlong, 's>, + ) -> Self::Item<'wshort, 's> { item } @@ -941,11 +1033,11 @@ unsafe impl<'a> QueryData for FilteredEntityRef<'a> { } #[inline(always)] - unsafe fn fetch<'w>( - (fetch, access): &mut Self::Fetch<'w>, + unsafe fn fetch<'w, 's>( + (fetch, access): &mut Self::Fetch<'w, 's>, entity: Entity, _table_row: TableRow, - ) -> Self::Item<'w> { + ) -> Self::Item<'w, 's> { // SAFETY: `fetch` must be called with an entity that exists in the world let cell = unsafe { fetch @@ -963,21 +1055,23 @@ unsafe impl ReadOnlyQueryData for FilteredEntityRef<'_> {} /// SAFETY: The accesses of `Self::ReadOnly` are a subset of the accesses of `Self` unsafe impl<'a> WorldQuery for FilteredEntityMut<'a> { - type Fetch<'w> = (EntityFetch<'w>, Access); + type Fetch<'w, 's> = (EntityFetch<'w>, Access); type State = Access; - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { + fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( + fetch: Self::Fetch<'wlong, 's>, + ) -> Self::Fetch<'wshort, 's> { fetch } const IS_DENSE: bool = false; - unsafe fn init_fetch<'w>( + unsafe fn init_fetch<'w, 's>( world: UnsafeWorldCell<'w>, - _state: &Self::State, + _state: &'s Self::State, last_run: Tick, this_run: Tick, - ) -> Self::Fetch<'w> { + ) -> Self::Fetch<'w, 's> { let mut access = Access::default(); access.write_all_components(); ( @@ -991,9 +1085,9 @@ unsafe impl<'a> WorldQuery for FilteredEntityMut<'a> { } #[inline] - unsafe fn set_archetype<'w>( - fetch: &mut Self::Fetch<'w>, - state: &Self::State, + unsafe fn set_archetype<'w, 's>( + fetch: &mut Self::Fetch<'w, 's>, + state: &'s Self::State, _: &'w Archetype, _table: &Table, ) { @@ -1001,7 +1095,11 @@ unsafe impl<'a> WorldQuery for FilteredEntityMut<'a> { } #[inline] - unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, _: &'w Table) { + unsafe fn set_table<'w, 's>( + fetch: &mut Self::Fetch<'w, 's>, + state: &'s Self::State, + _: &'w Table, + ) { fetch.1.clone_from(state); } @@ -1036,9 +1134,11 @@ unsafe impl<'a> WorldQuery for FilteredEntityMut<'a> { unsafe impl<'a> QueryData for FilteredEntityMut<'a> { const IS_READ_ONLY: bool = false; type ReadOnly = FilteredEntityRef<'a>; - type Item<'w> = FilteredEntityMut<'w>; + type Item<'w, 's> = FilteredEntityMut<'w>; - fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { + fn shrink<'wlong: 'wshort, 'wshort, 's>( + item: Self::Item<'wlong, 's>, + ) -> Self::Item<'wshort, 's> { item } @@ -1060,11 +1160,11 @@ unsafe impl<'a> QueryData for FilteredEntityMut<'a> { } #[inline(always)] - unsafe fn fetch<'w>( - (fetch, access): &mut Self::Fetch<'w>, + unsafe fn fetch<'w, 's>( + (fetch, access): &mut Self::Fetch<'w, 's>, entity: Entity, _table_row: TableRow, - ) -> Self::Item<'w> { + ) -> Self::Item<'w, 's> { // SAFETY: `fetch` must be called with an entity that exists in the world let cell = unsafe { fetch @@ -1084,19 +1184,21 @@ unsafe impl<'a, B> WorldQuery for EntityRefExcept<'a, B> where B: Bundle, { - type Fetch<'w> = EntityFetch<'w>; + type Fetch<'w, 's> = EntityFetch<'w>; type State = SmallVec<[ComponentId; 4]>; - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { + fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( + fetch: Self::Fetch<'wlong, 's>, + ) -> Self::Fetch<'wshort, 's> { fetch } - unsafe fn init_fetch<'w>( + unsafe fn init_fetch<'w, 's>( world: UnsafeWorldCell<'w>, - _: &Self::State, + _: &'s Self::State, last_run: Tick, this_run: Tick, - ) -> Self::Fetch<'w> { + ) -> Self::Fetch<'w, 's> { EntityFetch { world, last_run, @@ -1106,15 +1208,15 @@ where const IS_DENSE: bool = true; - unsafe fn set_archetype<'w>( - _: &mut Self::Fetch<'w>, - _: &Self::State, + unsafe fn set_archetype<'w, 's>( + _: &mut Self::Fetch<'w, 's>, + _: &'s Self::State, _: &'w Archetype, _: &'w Table, ) { } - unsafe fn set_table<'w>(_: &mut Self::Fetch<'w>, _: &Self::State, _: &'w Table) {} + unsafe fn set_table<'w, 's>(_: &mut Self::Fetch<'w, 's>, _: &'s Self::State, _: &'w Table) {} fn update_component_access( state: &Self::State, @@ -1161,17 +1263,19 @@ where { const IS_READ_ONLY: bool = true; type ReadOnly = Self; - type Item<'w> = EntityRefExcept<'w, B>; + type Item<'w, 's> = EntityRefExcept<'w, B>; - fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { + fn shrink<'wlong: 'wshort, 'wshort, 's>( + item: Self::Item<'wlong, 's>, + ) -> Self::Item<'wshort, 's> { item } - unsafe fn fetch<'w>( - fetch: &mut Self::Fetch<'w>, + unsafe fn fetch<'w, 's>( + fetch: &mut Self::Fetch<'w, 's>, entity: Entity, _: TableRow, - ) -> Self::Item<'w> { + ) -> Self::Item<'w, 's> { let cell = fetch .world .get_entity_with_ticks(entity, fetch.last_run, fetch.this_run) @@ -1191,19 +1295,21 @@ unsafe impl<'a, B> WorldQuery for EntityMutExcept<'a, B> where B: Bundle, { - type Fetch<'w> = EntityFetch<'w>; + type Fetch<'w, 's> = EntityFetch<'w>; type State = SmallVec<[ComponentId; 4]>; - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { + fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( + fetch: Self::Fetch<'wlong, 's>, + ) -> Self::Fetch<'wshort, 's> { fetch } - unsafe fn init_fetch<'w>( + unsafe fn init_fetch<'w, 's>( world: UnsafeWorldCell<'w>, - _: &Self::State, + _: &'s Self::State, last_run: Tick, this_run: Tick, - ) -> Self::Fetch<'w> { + ) -> Self::Fetch<'w, 's> { EntityFetch { world, last_run, @@ -1213,15 +1319,15 @@ where const IS_DENSE: bool = true; - unsafe fn set_archetype<'w>( - _: &mut Self::Fetch<'w>, - _: &Self::State, + unsafe fn set_archetype<'w, 's>( + _: &mut Self::Fetch<'w, 's>, + _: &'s Self::State, _: &'w Archetype, _: &'w Table, ) { } - unsafe fn set_table<'w>(_: &mut Self::Fetch<'w>, _: &Self::State, _: &'w Table) {} + unsafe fn set_table<'w, 's>(_: &mut Self::Fetch<'w, 's>, _: &'s Self::State, _: &'w Table) {} fn update_component_access( state: &Self::State, @@ -1269,17 +1375,19 @@ where { const IS_READ_ONLY: bool = false; type ReadOnly = EntityRefExcept<'a, B>; - type Item<'w> = EntityMutExcept<'w, B>; + type Item<'w, 's> = EntityMutExcept<'w, B>; - fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { + fn shrink<'wlong: 'wshort, 'wshort, 's>( + item: Self::Item<'wlong, 's>, + ) -> Self::Item<'wshort, 's> { item } - unsafe fn fetch<'w>( - fetch: &mut Self::Fetch<'w>, + unsafe fn fetch<'w, 's>( + fetch: &mut Self::Fetch<'w, 's>, entity: Entity, _: TableRow, - ) -> Self::Item<'w> { + ) -> Self::Item<'w, 's> { let cell = fetch .world .get_entity_with_ticks(entity, fetch.last_run, fetch.this_run) @@ -1292,19 +1400,21 @@ where /// `update_component_access` does nothing. /// This is sound because `fetch` does not access components. unsafe impl WorldQuery for &Archetype { - type Fetch<'w> = (&'w Entities, &'w Archetypes); + type Fetch<'w, 's> = (&'w Entities, &'w Archetypes); type State = (); - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { + fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( + fetch: Self::Fetch<'wlong, 's>, + ) -> Self::Fetch<'wshort, 's> { fetch } - unsafe fn init_fetch<'w>( + unsafe fn init_fetch<'w, 's>( world: UnsafeWorldCell<'w>, - _state: &Self::State, + _state: &'s Self::State, _last_run: Tick, _this_run: Tick, - ) -> Self::Fetch<'w> { + ) -> Self::Fetch<'w, 's> { (world.entities(), world.archetypes()) } @@ -1313,16 +1423,20 @@ unsafe impl WorldQuery for &Archetype { const IS_DENSE: bool = true; #[inline] - unsafe fn set_archetype<'w>( - _fetch: &mut Self::Fetch<'w>, - _state: &Self::State, + unsafe fn set_archetype<'w, 's>( + _fetch: &mut Self::Fetch<'w, 's>, + _state: &'s Self::State, _archetype: &'w Archetype, _table: &Table, ) { } #[inline] - unsafe fn set_table<'w>(_fetch: &mut Self::Fetch<'w>, _state: &Self::State, _table: &'w Table) { + unsafe fn set_table<'w, 's>( + _fetch: &mut Self::Fetch<'w, 's>, + _state: &'s Self::State, + _table: &'w Table, + ) { } fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess) {} @@ -1345,18 +1459,20 @@ unsafe impl WorldQuery for &Archetype { unsafe impl QueryData for &Archetype { const IS_READ_ONLY: bool = true; type ReadOnly = Self; - type Item<'w> = &'w Archetype; + type Item<'w, 's> = &'w Archetype; - fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { + fn shrink<'wlong: 'wshort, 'wshort, 's>( + item: Self::Item<'wlong, 's>, + ) -> Self::Item<'wshort, 's> { item } #[inline(always)] - unsafe fn fetch<'w>( - fetch: &mut Self::Fetch<'w>, + unsafe fn fetch<'w, 's>( + fetch: &mut Self::Fetch<'w, 's>, entity: Entity, _table_row: TableRow, - ) -> Self::Item<'w> { + ) -> Self::Item<'w, 's> { let (entities, archetypes) = *fetch; // SAFETY: `fetch` must be called with an entity that exists in the world let location = unsafe { entities.get(entity).debug_checked_unwrap() }; @@ -1368,6 +1484,12 @@ unsafe impl QueryData for &Archetype { /// SAFETY: access is read only unsafe impl ReadOnlyQueryData for &Archetype {} +impl ReleaseStateQueryData for &Archetype { + fn release_state<'w>(item: Self::Item<'w, '_>) -> Self::Item<'w, 'static> { + item + } +} + /// The [`WorldQuery::Fetch`] type for `& T`. pub struct ReadFetch<'w, T: Component> { components: StorageSwitch< @@ -1392,15 +1514,17 @@ impl Copy for ReadFetch<'_, T> {} /// `update_component_access` adds a `With` filter for a component. /// This is sound because `matches_component_set` returns whether the set contains that component. unsafe impl WorldQuery for &T { - type Fetch<'w> = ReadFetch<'w, T>; + type Fetch<'w, 's> = ReadFetch<'w, T>; type State = ComponentId; - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { + fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( + fetch: Self::Fetch<'wlong, 's>, + ) -> Self::Fetch<'wshort, 's> { fetch } #[inline] - unsafe fn init_fetch<'w>( + unsafe fn init_fetch<'w, 's>( world: UnsafeWorldCell<'w>, &component_id: &ComponentId, _last_run: Tick, @@ -1490,18 +1614,20 @@ unsafe impl WorldQuery for &T { unsafe impl QueryData for &T { const IS_READ_ONLY: bool = true; type ReadOnly = Self; - type Item<'w> = &'w T; + type Item<'w, 's> = &'w T; - fn shrink<'wlong: 'wshort, 'wshort>(item: &'wlong T) -> &'wshort T { + fn shrink<'wlong: 'wshort, 'wshort, 's>( + item: Self::Item<'wlong, 's>, + ) -> Self::Item<'wshort, 's> { item } #[inline(always)] - unsafe fn fetch<'w>( - fetch: &mut Self::Fetch<'w>, + unsafe fn fetch<'w, 's>( + fetch: &mut Self::Fetch<'w, 's>, entity: Entity, table_row: TableRow, - ) -> Self::Item<'w> { + ) -> Self::Item<'w, 's> { fetch.components.extract( |table| { // SAFETY: set_table was previously called @@ -1527,6 +1653,12 @@ unsafe impl QueryData for &T { /// SAFETY: access is read only unsafe impl ReadOnlyQueryData for &T {} +impl ReleaseStateQueryData for &T { + fn release_state<'w>(item: Self::Item<'w, '_>) -> Self::Item<'w, 'static> { + item + } +} + #[doc(hidden)] pub struct RefFetch<'w, T: Component> { components: StorageSwitch< @@ -1559,15 +1691,17 @@ impl Copy for RefFetch<'_, T> {} /// `update_component_access` adds a `With` filter for a component. /// This is sound because `matches_component_set` returns whether the set contains that component. unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { - type Fetch<'w> = RefFetch<'w, T>; + type Fetch<'w, 's> = RefFetch<'w, T>; type State = ComponentId; - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { + fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( + fetch: Self::Fetch<'wlong, 's>, + ) -> Self::Fetch<'wshort, 's> { fetch } #[inline] - unsafe fn init_fetch<'w>( + unsafe fn init_fetch<'w, 's>( world: UnsafeWorldCell<'w>, &component_id: &ComponentId, last_run: Tick, @@ -1666,18 +1800,20 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { unsafe impl<'__w, T: Component> QueryData for Ref<'__w, T> { const IS_READ_ONLY: bool = true; type ReadOnly = Self; - type Item<'w> = Ref<'w, T>; + type Item<'w, 's> = Ref<'w, T>; - fn shrink<'wlong: 'wshort, 'wshort>(item: Ref<'wlong, T>) -> Ref<'wshort, T> { + fn shrink<'wlong: 'wshort, 'wshort, 's>( + item: Self::Item<'wlong, 's>, + ) -> Self::Item<'wshort, 's> { item } #[inline(always)] - unsafe fn fetch<'w>( - fetch: &mut Self::Fetch<'w>, + unsafe fn fetch<'w, 's>( + fetch: &mut Self::Fetch<'w, 's>, entity: Entity, table_row: TableRow, - ) -> Self::Item<'w> { + ) -> Self::Item<'w, 's> { fetch.components.extract( |table| { // SAFETY: set_table was previously called @@ -1726,6 +1862,12 @@ unsafe impl<'__w, T: Component> QueryData for Ref<'__w, T> { /// SAFETY: access is read only unsafe impl<'__w, T: Component> ReadOnlyQueryData for Ref<'__w, T> {} +impl ReleaseStateQueryData for Ref<'_, T> { + fn release_state<'w>(item: Self::Item<'w, '_>) -> Self::Item<'w, 'static> { + item + } +} + /// The [`WorldQuery::Fetch`] type for `&mut T`. pub struct WriteFetch<'w, T: Component> { components: StorageSwitch< @@ -1758,15 +1900,17 @@ impl Copy for WriteFetch<'_, T> {} /// `update_component_access` adds a `With` filter for a component. /// This is sound because `matches_component_set` returns whether the set contains that component. unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { - type Fetch<'w> = WriteFetch<'w, T>; + type Fetch<'w, 's> = WriteFetch<'w, T>; type State = ComponentId; - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { + fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( + fetch: Self::Fetch<'wlong, 's>, + ) -> Self::Fetch<'wshort, 's> { fetch } #[inline] - unsafe fn init_fetch<'w>( + unsafe fn init_fetch<'w, 's>( world: UnsafeWorldCell<'w>, &component_id: &ComponentId, last_run: Tick, @@ -1865,18 +2009,20 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { unsafe impl<'__w, T: Component> QueryData for &'__w mut T { const IS_READ_ONLY: bool = false; type ReadOnly = &'__w T; - type Item<'w> = Mut<'w, T>; + type Item<'w, 's> = Mut<'w, T>; - fn shrink<'wlong: 'wshort, 'wshort>(item: Mut<'wlong, T>) -> Mut<'wshort, T> { + fn shrink<'wlong: 'wshort, 'wshort, 's>( + item: Self::Item<'wlong, 's>, + ) -> Self::Item<'wshort, 's> { item } #[inline(always)] - unsafe fn fetch<'w>( - fetch: &mut Self::Fetch<'w>, + unsafe fn fetch<'w, 's>( + fetch: &mut Self::Fetch<'w, 's>, entity: Entity, table_row: TableRow, - ) -> Self::Item<'w> { + ) -> Self::Item<'w, 's> { fetch.components.extract( |table| { // SAFETY: set_table was previously called @@ -1922,6 +2068,12 @@ unsafe impl<'__w, T: Component> QueryData for &'__w mut T } } +impl> ReleaseStateQueryData for &mut T { + fn release_state<'w>(item: Self::Item<'w, '_>) -> Self::Item<'w, 'static> { + item + } +} + /// When `Mut` is used in a query, it will be converted to `Ref` when transformed into its read-only form, providing access to change detection methods. /// /// By contrast `&mut T` will result in a `Mut` item in mutable form to record mutations, but result in a bare `&T` in read-only form. @@ -1932,16 +2084,18 @@ unsafe impl<'__w, T: Component> QueryData for &'__w mut T /// `update_component_access` adds a `With` filter for a component. /// This is sound because `matches_component_set` returns whether the set contains that component. unsafe impl<'__w, T: Component> WorldQuery for Mut<'__w, T> { - type Fetch<'w> = WriteFetch<'w, T>; + type Fetch<'w, 's> = WriteFetch<'w, T>; type State = ComponentId; - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { + fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( + fetch: Self::Fetch<'wlong, 's>, + ) -> Self::Fetch<'wshort, 's> { fetch } #[inline] // Forwarded to `&mut T` - unsafe fn init_fetch<'w>( + unsafe fn init_fetch<'w, 's>( world: UnsafeWorldCell<'w>, state: &ComponentId, last_run: Tick, @@ -2008,33 +2162,41 @@ unsafe impl<'__w, T: Component> WorldQuery for Mut<'__w, T> { unsafe impl<'__w, T: Component> QueryData for Mut<'__w, T> { const IS_READ_ONLY: bool = false; type ReadOnly = Ref<'__w, T>; - type Item<'w> = Mut<'w, T>; + type Item<'w, 's> = Mut<'w, T>; // Forwarded to `&mut T` - fn shrink<'wlong: 'wshort, 'wshort>(item: Mut<'wlong, T>) -> Mut<'wshort, T> { + fn shrink<'wlong: 'wshort, 'wshort, 's>( + item: Self::Item<'wlong, 's>, + ) -> Self::Item<'wshort, 's> { <&mut T as QueryData>::shrink(item) } #[inline(always)] // Forwarded to `&mut T` - unsafe fn fetch<'w>( + unsafe fn fetch<'w, 's>( // Rust complains about lifetime bounds not matching the trait if I directly use `WriteFetch<'w, T>` right here. // But it complains nowhere else in the entire trait implementation. - fetch: &mut Self::Fetch<'w>, + fetch: &mut Self::Fetch<'w, 's>, entity: Entity, table_row: TableRow, - ) -> Mut<'w, T> { + ) -> Self::Item<'w, 's> { <&mut T as QueryData>::fetch(fetch, entity, table_row) } } +impl> ReleaseStateQueryData for Mut<'_, T> { + fn release_state<'w>(item: Self::Item<'w, '_>) -> Self::Item<'w, 'static> { + item + } +} + #[doc(hidden)] -pub struct OptionFetch<'w, T: WorldQuery> { - fetch: T::Fetch<'w>, +pub struct OptionFetch<'w, 's, T: WorldQuery> { + fetch: T::Fetch<'w, 's>, matches: bool, } -impl Clone for OptionFetch<'_, T> { +impl Clone for OptionFetch<'_, '_, T> { fn clone(&self) -> Self { Self { fetch: self.fetch.clone(), @@ -2048,10 +2210,12 @@ impl Clone for OptionFetch<'_, T> { /// This is sound because `update_component_access` adds the same accesses as `T`. /// Filters are unchanged. unsafe impl WorldQuery for Option { - type Fetch<'w> = OptionFetch<'w, T>; + type Fetch<'w, 's> = OptionFetch<'w, 's, T>; type State = T::State; - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { + fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( + fetch: Self::Fetch<'wlong, 's>, + ) -> Self::Fetch<'wshort, 's> { OptionFetch { fetch: T::shrink_fetch(fetch.fetch), matches: fetch.matches, @@ -2059,12 +2223,12 @@ unsafe impl WorldQuery for Option { } #[inline] - unsafe fn init_fetch<'w>( + unsafe fn init_fetch<'w, 's>( world: UnsafeWorldCell<'w>, - state: &T::State, + state: &'s T::State, last_run: Tick, this_run: Tick, - ) -> OptionFetch<'w, T> { + ) -> OptionFetch<'w, 's, T> { OptionFetch { // SAFETY: The invariants are upheld by the caller. fetch: unsafe { T::init_fetch(world, state, last_run, this_run) }, @@ -2075,9 +2239,9 @@ unsafe impl WorldQuery for Option { const IS_DENSE: bool = T::IS_DENSE; #[inline] - unsafe fn set_archetype<'w>( - fetch: &mut OptionFetch<'w, T>, - state: &T::State, + unsafe fn set_archetype<'w, 's>( + fetch: &mut OptionFetch<'w, 's, T>, + state: &'s T::State, archetype: &'w Archetype, table: &'w Table, ) { @@ -2091,7 +2255,11 @@ unsafe impl WorldQuery for Option { } #[inline] - unsafe fn set_table<'w>(fetch: &mut OptionFetch<'w, T>, state: &T::State, table: &'w Table) { + unsafe fn set_table<'w, 's>( + fetch: &mut OptionFetch<'w, 's, T>, + state: &'s T::State, + table: &'w Table, + ) { fetch.matches = T::matches_component_set(state, &|id| table.has_column(id)); if fetch.matches { // SAFETY: The invariants are upheld by the caller. @@ -2136,18 +2304,20 @@ unsafe impl WorldQuery for Option { unsafe impl QueryData for Option { const IS_READ_ONLY: bool = T::IS_READ_ONLY; type ReadOnly = Option; - type Item<'w> = Option>; + type Item<'w, 's> = Option>; - fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { + fn shrink<'wlong: 'wshort, 'wshort, 's>( + item: Self::Item<'wlong, 's>, + ) -> Self::Item<'wshort, 's> { item.map(T::shrink) } #[inline(always)] - unsafe fn fetch<'w>( - fetch: &mut Self::Fetch<'w>, + unsafe fn fetch<'w, 's>( + fetch: &mut Self::Fetch<'w, 's>, entity: Entity, table_row: TableRow, - ) -> Self::Item<'w> { + ) -> Self::Item<'w, 's> { fetch .matches // SAFETY: The invariants are upheld by the caller. @@ -2158,6 +2328,12 @@ unsafe impl QueryData for Option { /// SAFETY: [`OptionFetch`] is read only because `T` is read only unsafe impl ReadOnlyQueryData for Option {} +impl ReleaseStateQueryData for Option { + fn release_state<'w>(item: Self::Item<'w, '_>) -> Self::Item<'w, 'static> { + item.map(T::release_state) + } +} + /// Returns a bool that describes if an entity has the component `T`. /// /// This can be used in a [`Query`](crate::system::Query) if you want to know whether or not entities @@ -2233,20 +2409,22 @@ impl core::fmt::Debug for Has { /// `update_component_access` does nothing. /// This is sound because `fetch` does not access components. unsafe impl WorldQuery for Has { - type Fetch<'w> = bool; + type Fetch<'w, 's> = bool; type State = ComponentId; - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { + fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( + fetch: Self::Fetch<'wlong, 's>, + ) -> Self::Fetch<'wshort, 's> { fetch } #[inline] - unsafe fn init_fetch<'w>( + unsafe fn init_fetch<'w, 's>( _world: UnsafeWorldCell<'w>, - _state: &Self::State, + _state: &'s Self::State, _last_run: Tick, _this_run: Tick, - ) -> Self::Fetch<'w> { + ) -> Self::Fetch<'w, 's> { false } @@ -2258,9 +2436,9 @@ unsafe impl WorldQuery for Has { }; #[inline] - unsafe fn set_archetype<'w>( - fetch: &mut Self::Fetch<'w>, - state: &Self::State, + unsafe fn set_archetype<'w, 's>( + fetch: &mut Self::Fetch<'w, 's>, + state: &'s Self::State, archetype: &'w Archetype, _table: &Table, ) { @@ -2268,7 +2446,11 @@ unsafe impl WorldQuery for Has { } #[inline] - unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, table: &'w Table) { + unsafe fn set_table<'w, 's>( + fetch: &mut Self::Fetch<'w, 's>, + state: &'s Self::State, + table: &'w Table, + ) { *fetch = table.has_column(*state); } @@ -2300,18 +2482,20 @@ unsafe impl WorldQuery for Has { unsafe impl QueryData for Has { const IS_READ_ONLY: bool = true; type ReadOnly = Self; - type Item<'w> = bool; + type Item<'w, 's> = bool; - fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { + fn shrink<'wlong: 'wshort, 'wshort, 's>( + item: Self::Item<'wlong, 's>, + ) -> Self::Item<'wshort, 's> { item } #[inline(always)] - unsafe fn fetch<'w>( - fetch: &mut Self::Fetch<'w>, + unsafe fn fetch<'w, 's>( + fetch: &mut Self::Fetch<'w, 's>, _entity: Entity, _table_row: TableRow, - ) -> Self::Item<'w> { + ) -> Self::Item<'w, 's> { *fetch } } @@ -2319,6 +2503,12 @@ unsafe impl QueryData for Has { /// SAFETY: [`Has`] is read only unsafe impl ReadOnlyQueryData for Has {} +impl ReleaseStateQueryData for Has { + fn release_state<'w>(item: Self::Item<'w, '_>) -> Self::Item<'w, 'static> { + item + } +} + /// The `AnyOf` query parameter fetches entities with any of the component types included in T. /// /// `Query>` is equivalent to `Query<(Option<&A>, Option<&B>, Option<&mut C>), Or<(With, With, With)>>`. @@ -2327,7 +2517,7 @@ unsafe impl ReadOnlyQueryData for Has {} pub struct AnyOf(PhantomData); macro_rules! impl_tuple_query_data { - ($(#[$meta:meta])* $(($name: ident, $state: ident)),*) => { + ($(#[$meta:meta])* $(($name: ident, $item: ident)),*) => { #[expect( clippy::allow_attributes, reason = "This is a tuple-related macro; as such the lints below may not always apply." @@ -2349,9 +2539,9 @@ macro_rules! impl_tuple_query_data { unsafe impl<$($name: QueryData),*> QueryData for ($($name,)*) { const IS_READ_ONLY: bool = true $(&& $name::IS_READ_ONLY)*; type ReadOnly = ($($name::ReadOnly,)*); - type Item<'w> = ($($name::Item<'w>,)*); + type Item<'w, 's> = ($($name::Item<'w, 's>,)*); - fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { + fn shrink<'wlong: 'wshort, 'wshort, 's>(item: Self::Item<'wlong, 's>) -> Self::Item<'wshort, 's> { let ($($name,)*) = item; ($( $name::shrink($name), @@ -2369,26 +2559,38 @@ macro_rules! impl_tuple_query_data { } #[inline(always)] - unsafe fn fetch<'w>( - fetch: &mut Self::Fetch<'w>, + unsafe fn fetch<'w, 's>( + fetch: &mut Self::Fetch<'w, 's>, entity: Entity, table_row: TableRow - ) -> Self::Item<'w> { + ) -> Self::Item<'w, 's> { let ($($name,)*) = fetch; // SAFETY: The invariants are upheld by the caller. ($(unsafe { $name::fetch($name, entity, table_row) },)*) } } - $(#[$meta])* /// SAFETY: each item in the tuple is read only unsafe impl<$($name: ReadOnlyQueryData),*> ReadOnlyQueryData for ($($name,)*) {} + #[expect( + clippy::allow_attributes, + reason = "This is a tuple-related macro; as such the lints below may not always apply." + )] + #[allow( + clippy::unused_unit, + reason = "Zero-length tuples will generate some function bodies equivalent to `()`; however, this macro is meant for all applicable tuples, and as such it makes no sense to rewrite it just for that case." + )] + impl<$($name: ReleaseStateQueryData),*> ReleaseStateQueryData for ($($name,)*) { + fn release_state<'w>(($($item,)*): Self::Item<'w, '_>) -> Self::Item<'w, 'static> { + ($($name::release_state($item),)*) + } + } }; } macro_rules! impl_anytuple_fetch { - ($(#[$meta:meta])* $(($name: ident, $state: ident)),*) => { + ($(#[$meta:meta])* $(($name: ident, $state: ident, $item: ident)),*) => { $(#[$meta])* #[expect( clippy::allow_attributes, @@ -2412,10 +2614,10 @@ macro_rules! impl_anytuple_fetch { /// `update_component_access` replaces the filters with a disjunction where every element is a conjunction of the previous filters and the filters of one of the subqueries. /// This is sound because `matches_component_set` returns a disjunction of the results of the subqueries' implementations. unsafe impl<$($name: WorldQuery),*> WorldQuery for AnyOf<($($name,)*)> { - type Fetch<'w> = ($(($name::Fetch<'w>, bool),)*); + type Fetch<'w, 's> = ($(($name::Fetch<'w, 's>, bool),)*); type State = ($($name::State,)*); - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { + fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>(fetch: Self::Fetch<'wlong, 's>) -> Self::Fetch<'wshort, 's> { let ($($name,)*) = fetch; ($( ($name::shrink_fetch($name.0), $name.1), @@ -2423,7 +2625,7 @@ macro_rules! impl_anytuple_fetch { } #[inline] - unsafe fn init_fetch<'w>(_world: UnsafeWorldCell<'w>, state: &Self::State, _last_run: Tick, _this_run: Tick) -> Self::Fetch<'w> { + unsafe fn init_fetch<'w, 's>(_world: UnsafeWorldCell<'w>, state: &'s Self::State, _last_run: Tick, _this_run: Tick) -> Self::Fetch<'w, 's> { let ($($name,)*) = state; // SAFETY: The invariants are upheld by the caller. ($(( unsafe { $name::init_fetch(_world, $name, _last_run, _this_run) }, false),)*) @@ -2432,9 +2634,9 @@ macro_rules! impl_anytuple_fetch { const IS_DENSE: bool = true $(&& $name::IS_DENSE)*; #[inline] - unsafe fn set_archetype<'w>( - _fetch: &mut Self::Fetch<'w>, - _state: &Self::State, + unsafe fn set_archetype<'w, 's>( + _fetch: &mut Self::Fetch<'w, 's>, + _state: &'s Self::State, _archetype: &'w Archetype, _table: &'w Table ) { @@ -2450,7 +2652,7 @@ macro_rules! impl_anytuple_fetch { } #[inline] - unsafe fn set_table<'w>(_fetch: &mut Self::Fetch<'w>, _state: &Self::State, _table: &'w Table) { + unsafe fn set_table<'w, 's>(_fetch: &mut Self::Fetch<'w, 's>, _state: &'s Self::State, _table: &'w Table) { let ($($name,)*) = _fetch; let ($($state,)*) = _state; $( @@ -2522,9 +2724,9 @@ macro_rules! impl_anytuple_fetch { unsafe impl<$($name: QueryData),*> QueryData for AnyOf<($($name,)*)> { const IS_READ_ONLY: bool = true $(&& $name::IS_READ_ONLY)*; type ReadOnly = AnyOf<($($name::ReadOnly,)*)>; - type Item<'w> = ($(Option<$name::Item<'w>>,)*); + type Item<'w, 's> = ($(Option<$name::Item<'w, 's>>,)*); - fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { + fn shrink<'wlong: 'wshort, 'wshort, 's>(item: Self::Item<'wlong, 's>) -> Self::Item<'wshort, 's> { let ($($name,)*) = item; ($( $name.map($name::shrink), @@ -2532,11 +2734,11 @@ macro_rules! impl_anytuple_fetch { } #[inline(always)] - unsafe fn fetch<'w>( - _fetch: &mut Self::Fetch<'w>, + unsafe fn fetch<'w, 's>( + _fetch: &mut Self::Fetch<'w, 's>, _entity: Entity, _table_row: TableRow - ) -> Self::Item<'w> { + ) -> Self::Item<'w, 's> { let ($($name,)*) = _fetch; ($( // SAFETY: The invariants are required to be upheld by the caller. @@ -2548,6 +2750,20 @@ macro_rules! impl_anytuple_fetch { $(#[$meta])* /// SAFETY: each item in the tuple is read only unsafe impl<$($name: ReadOnlyQueryData),*> ReadOnlyQueryData for AnyOf<($($name,)*)> {} + + #[expect( + clippy::allow_attributes, + reason = "This is a tuple-related macro; as such the lints below may not always apply." + )] + #[allow( + clippy::unused_unit, + reason = "Zero-length tuples will generate some function bodies equivalent to `()`; however, this macro is meant for all applicable tuples, and as such it makes no sense to rewrite it just for that case." + )] + impl<$($name: ReleaseStateQueryData),*> ReleaseStateQueryData for AnyOf<($($name,)*)> { + fn release_state<'w>(($($item,)*): Self::Item<'w, '_>) -> Self::Item<'w, 'static> { + ($($item.map(|$item| $name::release_state($item)),)*) + } + } }; } @@ -2557,7 +2773,7 @@ all_tuples!( 0, 15, F, - S + i ); all_tuples!( #[doc(fake_variadic)] @@ -2565,7 +2781,8 @@ all_tuples!( 0, 15, F, - S + S, + i ); /// [`WorldQuery`] used to nullify queries by turning `Query` into `Query>` @@ -2577,10 +2794,13 @@ pub(crate) struct NopWorldQuery(PhantomData); /// `update_component_access` does nothing. /// This is sound because `fetch` does not access components. unsafe impl WorldQuery for NopWorldQuery { - type Fetch<'w> = (); + type Fetch<'w, 's> = (); type State = D::State; - fn shrink_fetch<'wlong: 'wshort, 'wshort>(_: ()) {} + fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( + _fetch: Self::Fetch<'wlong, 's>, + ) -> Self::Fetch<'wshort, 's> { + } #[inline(always)] unsafe fn init_fetch( @@ -2627,54 +2847,67 @@ unsafe impl WorldQuery for NopWorldQuery { unsafe impl QueryData for NopWorldQuery { const IS_READ_ONLY: bool = true; type ReadOnly = Self; - type Item<'w> = (); + type Item<'w, 's> = (); - fn shrink<'wlong: 'wshort, 'wshort>(_: ()) {} + fn shrink<'wlong: 'wshort, 'wshort, 's>( + _item: Self::Item<'wlong, 's>, + ) -> Self::Item<'wshort, 's> { + } #[inline(always)] - unsafe fn fetch<'w>( - _fetch: &mut Self::Fetch<'w>, + unsafe fn fetch<'w, 's>( + _fetch: &mut Self::Fetch<'w, 's>, _entity: Entity, _table_row: TableRow, - ) -> Self::Item<'w> { + ) -> Self::Item<'w, 's> { } } /// SAFETY: `NopFetch` never accesses any data unsafe impl ReadOnlyQueryData for NopWorldQuery {} +impl ReleaseStateQueryData for NopWorldQuery { + fn release_state<'w>(_item: Self::Item<'w, '_>) -> Self::Item<'w, 'static> {} +} + /// SAFETY: /// `update_component_access` does nothing. /// This is sound because `fetch` does not access components. unsafe impl WorldQuery for PhantomData { - type Fetch<'a> = (); + type Fetch<'w, 's> = (); type State = (); - fn shrink_fetch<'wlong: 'wshort, 'wshort>(_fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { + fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( + _fetch: Self::Fetch<'wlong, 's>, + ) -> Self::Fetch<'wshort, 's> { } - unsafe fn init_fetch<'w>( + unsafe fn init_fetch<'w, 's>( _world: UnsafeWorldCell<'w>, - _state: &Self::State, + _state: &'s Self::State, _last_run: Tick, _this_run: Tick, - ) -> Self::Fetch<'w> { + ) -> Self::Fetch<'w, 's> { } // `PhantomData` does not match any components, so all components it matches // are stored in a Table (vacuous truth). const IS_DENSE: bool = true; - unsafe fn set_archetype<'w>( - _fetch: &mut Self::Fetch<'w>, - _state: &Self::State, + unsafe fn set_archetype<'w, 's>( + _fetch: &mut Self::Fetch<'w, 's>, + _state: &'s Self::State, _archetype: &'w Archetype, _table: &'w Table, ) { } - unsafe fn set_table<'w>(_fetch: &mut Self::Fetch<'w>, _state: &Self::State, _table: &'w Table) { + unsafe fn set_table<'w, 's>( + _fetch: &mut Self::Fetch<'w, 's>, + _state: &'s Self::State, + _table: &'w Table, + ) { } fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess) {} @@ -2697,21 +2930,28 @@ unsafe impl WorldQuery for PhantomData { unsafe impl QueryData for PhantomData { const IS_READ_ONLY: bool = true; type ReadOnly = Self; - type Item<'a> = (); + type Item<'w, 's> = (); - fn shrink<'wlong: 'wshort, 'wshort>(_item: Self::Item<'wlong>) -> Self::Item<'wshort> {} + fn shrink<'wlong: 'wshort, 'wshort, 's>( + _item: Self::Item<'wlong, 's>, + ) -> Self::Item<'wshort, 's> { + } - unsafe fn fetch<'w>( - _fetch: &mut Self::Fetch<'w>, + unsafe fn fetch<'w, 's>( + _fetch: &mut Self::Fetch<'w, 's>, _entity: Entity, _table_row: TableRow, - ) -> Self::Item<'w> { + ) -> Self::Item<'w, 's> { } } /// SAFETY: `PhantomData` never accesses any world data. unsafe impl ReadOnlyQueryData for PhantomData {} +impl ReleaseStateQueryData for PhantomData { + fn release_state<'w>(_item: Self::Item<'w, '_>) -> Self::Item<'w, 'static> {} +} + /// A compile-time checked union of two different types that differs based on the /// [`StorageType`] of a given component. pub(super) union StorageSwitch { @@ -2828,6 +3068,123 @@ mod tests { assert_is_system(ignored_system); } + #[test] + fn derive_release_state() { + struct NonReleaseQueryData; + + /// SAFETY: + /// `update_component_access` and `update_archetype_component_access` do nothing. + /// This is sound because `fetch` does not access components. + unsafe impl WorldQuery for NonReleaseQueryData { + type Fetch<'w, 's> = (); + type State = (); + + fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( + _: Self::Fetch<'wlong, 's>, + ) -> Self::Fetch<'wshort, 's> { + } + + unsafe fn init_fetch<'w, 's>( + _world: UnsafeWorldCell<'w>, + _state: &'s Self::State, + _last_run: Tick, + _this_run: Tick, + ) -> Self::Fetch<'w, 's> { + } + + const IS_DENSE: bool = true; + + #[inline] + unsafe fn set_archetype<'w, 's>( + _fetch: &mut Self::Fetch<'w, 's>, + _state: &'s Self::State, + _archetype: &'w Archetype, + _table: &Table, + ) { + } + + #[inline] + unsafe fn set_table<'w, 's>( + _fetch: &mut Self::Fetch<'w, 's>, + _state: &'s Self::State, + _table: &'w Table, + ) { + } + + fn update_component_access( + _state: &Self::State, + _access: &mut FilteredAccess, + ) { + } + + fn init_state(_world: &mut World) {} + + fn get_state(_components: &Components) -> Option<()> { + Some(()) + } + + fn matches_component_set( + _state: &Self::State, + _set_contains_id: &impl Fn(ComponentId) -> bool, + ) -> bool { + true + } + } + + /// SAFETY: `Self` is the same as `Self::ReadOnly` + unsafe impl QueryData for NonReleaseQueryData { + type ReadOnly = Self; + const IS_READ_ONLY: bool = true; + + type Item<'w, 's> = (); + + fn shrink<'wlong: 'wshort, 'wshort, 's>( + _item: Self::Item<'wlong, 's>, + ) -> Self::Item<'wshort, 's> { + } + + #[inline(always)] + unsafe fn fetch<'w, 's>( + _fetch: &mut Self::Fetch<'w, 's>, + _entity: Entity, + _table_row: TableRow, + ) -> Self::Item<'w, 's> { + } + } + + /// SAFETY: access is read only + unsafe impl ReadOnlyQueryData for NonReleaseQueryData {} + + #[derive(QueryData)] + pub struct DerivedNonReleaseRead { + non_release: NonReleaseQueryData, + a: &'static A, + } + + #[derive(QueryData)] + #[query_data(mutable)] + pub struct DerivedNonReleaseMutable { + non_release: NonReleaseQueryData, + a: &'static mut A, + } + + #[derive(QueryData)] + pub struct DerivedReleaseRead { + a: &'static A, + } + + #[derive(QueryData)] + #[query_data(mutable)] + pub struct DerivedReleaseMutable { + a: &'static mut A, + } + + fn assert_is_release_state() {} + + assert_is_release_state::(); + assert_is_release_state::(); + } + // Ensures that each field of a `WorldQuery` struct's read-only variant // has the same visibility as its corresponding mutable field. #[test] diff --git a/crates/bevy_ecs/src/query/filter.rs b/crates/bevy_ecs/src/query/filter.rs index eccc819ca7..a75acf3e97 100644 --- a/crates/bevy_ecs/src/query/filter.rs +++ b/crates/bevy_ecs/src/query/filter.rs @@ -103,7 +103,7 @@ pub unsafe trait QueryFilter: WorldQuery { /// Must always be called _after_ [`WorldQuery::set_table`] or [`WorldQuery::set_archetype`]. `entity` and /// `table_row` must be in the range of the current table and archetype. unsafe fn filter_fetch( - fetch: &mut Self::Fetch<'_>, + fetch: &mut Self::Fetch<'_, '_>, entity: Entity, table_row: TableRow, ) -> bool; @@ -144,10 +144,13 @@ pub struct With(PhantomData); /// `update_component_access` adds a `With` filter for `T`. /// This is sound because `matches_component_set` returns whether the set contains the component. unsafe impl WorldQuery for With { - type Fetch<'w> = (); + type Fetch<'w, 's> = (); type State = ComponentId; - fn shrink_fetch<'wlong: 'wshort, 'wshort>(_: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {} + fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( + _: Self::Fetch<'wlong, 's>, + ) -> Self::Fetch<'wshort, 's> { + } #[inline] unsafe fn init_fetch( @@ -204,7 +207,7 @@ unsafe impl QueryFilter for With { #[inline(always)] unsafe fn filter_fetch( - _fetch: &mut Self::Fetch<'_>, + _fetch: &mut Self::Fetch<'_, '_>, _entity: Entity, _table_row: TableRow, ) -> bool { @@ -244,10 +247,13 @@ pub struct Without(PhantomData); /// `update_component_access` adds a `Without` filter for `T`. /// This is sound because `matches_component_set` returns whether the set does not contain the component. unsafe impl WorldQuery for Without { - type Fetch<'w> = (); + type Fetch<'w, 's> = (); type State = ComponentId; - fn shrink_fetch<'wlong: 'wshort, 'wshort>(_: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {} + fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( + _: Self::Fetch<'wlong, 's>, + ) -> Self::Fetch<'wshort, 's> { + } #[inline] unsafe fn init_fetch( @@ -304,7 +310,7 @@ unsafe impl QueryFilter for Without { #[inline(always)] unsafe fn filter_fetch( - _fetch: &mut Self::Fetch<'_>, + _fetch: &mut Self::Fetch<'_, '_>, _entity: Entity, _table_row: TableRow, ) -> bool { @@ -345,12 +351,12 @@ unsafe impl QueryFilter for Without { pub struct Or(PhantomData); #[doc(hidden)] -pub struct OrFetch<'w, T: WorldQuery> { - fetch: T::Fetch<'w>, +pub struct OrFetch<'w, 's, T: WorldQuery> { + fetch: T::Fetch<'w, 's>, matches: bool, } -impl Clone for OrFetch<'_, T> { +impl Clone for OrFetch<'_, '_, T> { fn clone(&self) -> Self { Self { fetch: self.fetch.clone(), @@ -384,10 +390,10 @@ macro_rules! impl_or_query_filter { /// `update_component_access` replace the filters with a disjunction where every element is a conjunction of the previous filters and the filters of one of the subqueries. /// This is sound because `matches_component_set` returns a disjunction of the results of the subqueries' implementations. unsafe impl<$($filter: QueryFilter),*> WorldQuery for Or<($($filter,)*)> { - type Fetch<'w> = ($(OrFetch<'w, $filter>,)*); + type Fetch<'w, 's> = ($(OrFetch<'w, 's, $filter>,)*); type State = ($($filter::State,)*); - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { + fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>(fetch: Self::Fetch<'wlong, 's>) -> Self::Fetch<'wshort, 's> { let ($($filter,)*) = fetch; ($( OrFetch { @@ -400,7 +406,7 @@ macro_rules! impl_or_query_filter { const IS_DENSE: bool = true $(&& $filter::IS_DENSE)*; #[inline] - unsafe fn init_fetch<'w>(world: UnsafeWorldCell<'w>, state: &Self::State, last_run: Tick, this_run: Tick) -> Self::Fetch<'w> { + unsafe fn init_fetch<'w, 's>(world: UnsafeWorldCell<'w>, state: &'s Self::State, last_run: Tick, this_run: Tick) -> Self::Fetch<'w, 's> { let ($($filter,)*) = state; ($(OrFetch { // SAFETY: The invariants are upheld by the caller. @@ -410,7 +416,7 @@ macro_rules! impl_or_query_filter { } #[inline] - unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, table: &'w Table) { + unsafe fn set_table<'w, 's>(fetch: &mut Self::Fetch<'w, 's>, state: &'s Self::State, table: &'w Table) { let ($($filter,)*) = fetch; let ($($state,)*) = state; $( @@ -423,9 +429,9 @@ macro_rules! impl_or_query_filter { } #[inline] - unsafe fn set_archetype<'w>( - fetch: &mut Self::Fetch<'w>, - state: & Self::State, + unsafe fn set_archetype<'w, 's>( + fetch: &mut Self::Fetch<'w, 's>, + state: &'s Self::State, archetype: &'w Archetype, table: &'w Table ) { @@ -495,7 +501,7 @@ macro_rules! impl_or_query_filter { #[inline(always)] unsafe fn filter_fetch( - fetch: &mut Self::Fetch<'_>, + fetch: &mut Self::Fetch<'_, '_>, entity: Entity, table_row: TableRow ) -> bool { @@ -528,7 +534,7 @@ macro_rules! impl_tuple_query_filter { #[inline(always)] unsafe fn filter_fetch( - fetch: &mut Self::Fetch<'_>, + fetch: &mut Self::Fetch<'_, '_>, entity: Entity, table_row: TableRow ) -> bool { @@ -568,10 +574,13 @@ pub struct Allows(PhantomData); /// `update_component_access` adds an archetypal filter for `T`. /// This is sound because it doesn't affect the query unsafe impl WorldQuery for Allows { - type Fetch<'w> = (); + type Fetch<'w, 's> = (); type State = ComponentId; - fn shrink_fetch<'wlong: 'wshort, 'wshort>(_: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {} + fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( + _: Self::Fetch<'wlong, 's>, + ) -> Self::Fetch<'wshort, 's> { + } #[inline] unsafe fn init_fetch(_: UnsafeWorldCell, _: &ComponentId, _: Tick, _: Tick) {} @@ -609,7 +618,7 @@ unsafe impl QueryFilter for Allows { const IS_ARCHETYPAL: bool = true; #[inline(always)] - unsafe fn filter_fetch(_: &mut Self::Fetch<'_>, _: Entity, _: TableRow) -> bool { + unsafe fn filter_fetch(_: &mut Self::Fetch<'_, '_>, _: Entity, _: TableRow) -> bool { true } } @@ -710,21 +719,23 @@ impl Clone for AddedFetch<'_, T> { /// `update_component_access` adds a `With` filter for a component. /// This is sound because `matches_component_set` returns whether the set contains that component. unsafe impl WorldQuery for Added { - type Fetch<'w> = AddedFetch<'w, T>; + type Fetch<'w, 's> = AddedFetch<'w, T>; type State = ComponentId; - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { + fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( + fetch: Self::Fetch<'wlong, 's>, + ) -> Self::Fetch<'wshort, 's> { fetch } #[inline] - unsafe fn init_fetch<'w>( + unsafe fn init_fetch<'w, 's>( world: UnsafeWorldCell<'w>, - &id: &ComponentId, + &id: &'s ComponentId, last_run: Tick, this_run: Tick, - ) -> Self::Fetch<'w> { - Self::Fetch::<'w> { + ) -> Self::Fetch<'w, 's> { + Self::Fetch::<'w, 's> { ticks: StorageSwitch::new( || None, || { @@ -748,9 +759,9 @@ unsafe impl WorldQuery for Added { }; #[inline] - unsafe fn set_archetype<'w>( - fetch: &mut Self::Fetch<'w>, - component_id: &ComponentId, + unsafe fn set_archetype<'w, 's>( + fetch: &mut Self::Fetch<'w, 's>, + component_id: &'s ComponentId, _archetype: &'w Archetype, table: &'w Table, ) { @@ -763,9 +774,9 @@ unsafe impl WorldQuery for Added { } #[inline] - unsafe fn set_table<'w>( - fetch: &mut Self::Fetch<'w>, - &component_id: &ComponentId, + unsafe fn set_table<'w, 's>( + fetch: &mut Self::Fetch<'w, 's>, + &component_id: &'s ComponentId, table: &'w Table, ) { let table_ticks = Some( @@ -807,7 +818,7 @@ unsafe impl QueryFilter for Added { const IS_ARCHETYPAL: bool = false; #[inline(always)] unsafe fn filter_fetch( - fetch: &mut Self::Fetch<'_>, + fetch: &mut Self::Fetch<'_, '_>, entity: Entity, table_row: TableRow, ) -> bool { @@ -936,21 +947,23 @@ impl Clone for ChangedFetch<'_, T> { /// `update_component_access` adds a `With` filter for a component. /// This is sound because `matches_component_set` returns whether the set contains that component. unsafe impl WorldQuery for Changed { - type Fetch<'w> = ChangedFetch<'w, T>; + type Fetch<'w, 's> = ChangedFetch<'w, T>; type State = ComponentId; - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { + fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( + fetch: Self::Fetch<'wlong, 's>, + ) -> Self::Fetch<'wshort, 's> { fetch } #[inline] - unsafe fn init_fetch<'w>( + unsafe fn init_fetch<'w, 's>( world: UnsafeWorldCell<'w>, - &id: &ComponentId, + &id: &'s ComponentId, last_run: Tick, this_run: Tick, - ) -> Self::Fetch<'w> { - Self::Fetch::<'w> { + ) -> Self::Fetch<'w, 's> { + Self::Fetch::<'w, 's> { ticks: StorageSwitch::new( || None, || { @@ -974,9 +987,9 @@ unsafe impl WorldQuery for Changed { }; #[inline] - unsafe fn set_archetype<'w>( - fetch: &mut Self::Fetch<'w>, - component_id: &ComponentId, + unsafe fn set_archetype<'w, 's>( + fetch: &mut Self::Fetch<'w, 's>, + component_id: &'s ComponentId, _archetype: &'w Archetype, table: &'w Table, ) { @@ -989,9 +1002,9 @@ unsafe impl WorldQuery for Changed { } #[inline] - unsafe fn set_table<'w>( - fetch: &mut Self::Fetch<'w>, - &component_id: &ComponentId, + unsafe fn set_table<'w, 's>( + fetch: &mut Self::Fetch<'w, 's>, + &component_id: &'s ComponentId, table: &'w Table, ) { let table_ticks = Some( @@ -1034,7 +1047,7 @@ unsafe impl QueryFilter for Changed { #[inline(always)] unsafe fn filter_fetch( - fetch: &mut Self::Fetch<'_>, + fetch: &mut Self::Fetch<'_, '_>, entity: Entity, table_row: TableRow, ) -> bool { @@ -1133,20 +1146,22 @@ pub struct SpawnedFetch<'w> { // SAFETY: WorldQuery impl accesses no components or component ticks unsafe impl WorldQuery for Spawned { - type Fetch<'w> = SpawnedFetch<'w>; + type Fetch<'w, 's> = SpawnedFetch<'w>; type State = (); - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { + fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( + fetch: Self::Fetch<'wlong, 's>, + ) -> Self::Fetch<'wshort, 's> { fetch } #[inline] - unsafe fn init_fetch<'w>( + unsafe fn init_fetch<'w, 's>( world: UnsafeWorldCell<'w>, - _state: &(), + _state: &'s (), last_run: Tick, this_run: Tick, - ) -> Self::Fetch<'w> { + ) -> Self::Fetch<'w, 's> { SpawnedFetch { entities: world.entities(), last_run, @@ -1157,16 +1172,21 @@ unsafe impl WorldQuery for Spawned { const IS_DENSE: bool = true; #[inline] - unsafe fn set_archetype<'w>( - _fetch: &mut Self::Fetch<'w>, - _state: &(), + unsafe fn set_archetype<'w, 's>( + _fetch: &mut Self::Fetch<'w, 's>, + _state: &'s (), _archetype: &'w Archetype, _table: &'w Table, ) { } #[inline] - unsafe fn set_table<'w>(_fetch: &mut Self::Fetch<'w>, _state: &(), _table: &'w Table) {} + unsafe fn set_table<'w, 's>( + _fetch: &mut Self::Fetch<'w, 's>, + _state: &'s (), + _table: &'w Table, + ) { + } #[inline] fn update_component_access(_state: &(), _access: &mut FilteredAccess) {} @@ -1188,7 +1208,7 @@ unsafe impl QueryFilter for Spawned { #[inline(always)] unsafe fn filter_fetch( - fetch: &mut Self::Fetch<'_>, + fetch: &mut Self::Fetch<'_, '_>, entity: Entity, _table_row: TableRow, ) -> bool { diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index baf0d72697..a3b3d02c24 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -140,7 +140,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> { range: Option>, ) -> B where - Func: FnMut(B, D::Item<'w>) -> B, + Func: FnMut(B, D::Item<'w, 's>) -> B, { if self.cursor.is_dense { // SAFETY: `self.cursor.is_dense` is true, so storage ids are guaranteed to be table ids. @@ -203,7 +203,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> { rows: Range, ) -> B where - Func: FnMut(B, D::Item<'w>) -> B, + Func: FnMut(B, D::Item<'w, 's>) -> B, { if table.is_empty() { return accum; @@ -255,7 +255,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> { indices: Range, ) -> B where - Func: FnMut(B, D::Item<'w>) -> B, + Func: FnMut(B, D::Item<'w, 's>) -> B, { if archetype.is_empty() { return accum; @@ -324,7 +324,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> { rows: Range, ) -> B where - Func: FnMut(B, D::Item<'w>) -> B, + Func: FnMut(B, D::Item<'w, 's>) -> B, { if archetype.is_empty() { return accum; @@ -492,7 +492,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> { impl ExactSizeIterator + DoubleEndedIterator + FusedIterator + 'w, > where - for<'lw> L::Item<'lw>: Ord, + for<'lw, 'ls> L::Item<'lw, 'ls>: Ord, { self.sort_impl::(|keyed_query| keyed_query.sort()) } @@ -549,7 +549,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> { impl ExactSizeIterator + DoubleEndedIterator + FusedIterator + 'w, > where - for<'lw> L::Item<'lw>: Ord, + for<'lw, 'ls> L::Item<'lw, 'ls>: Ord, { self.sort_impl::(|keyed_query| keyed_query.sort_unstable()) } @@ -605,7 +605,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> { /// ``` pub fn sort_by( self, - mut compare: impl FnMut(&L::Item<'_>, &L::Item<'_>) -> Ordering, + mut compare: impl FnMut(&L::Item<'_, '_>, &L::Item<'_, '_>) -> Ordering, ) -> QuerySortedIter< 'w, 's, @@ -637,7 +637,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> { /// This will panic if `next` has been called on `QueryIter` before, unless the underlying `Query` is empty. pub fn sort_unstable_by( self, - mut compare: impl FnMut(&L::Item<'_>, &L::Item<'_>) -> Ordering, + mut compare: impl FnMut(&L::Item<'_, '_>, &L::Item<'_, '_>) -> Ordering, ) -> QuerySortedIter< 'w, 's, @@ -729,7 +729,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> { /// ``` pub fn sort_by_key( self, - mut f: impl FnMut(&L::Item<'_>) -> K, + mut f: impl FnMut(&L::Item<'_, '_>) -> K, ) -> QuerySortedIter< 'w, 's, @@ -762,7 +762,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> { /// This will panic if `next` has been called on `QueryIter` before, unless the underlying `Query` is empty. pub fn sort_unstable_by_key( self, - mut f: impl FnMut(&L::Item<'_>) -> K, + mut f: impl FnMut(&L::Item<'_, '_>) -> K, ) -> QuerySortedIter< 'w, 's, @@ -797,7 +797,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> { /// This will panic if `next` has been called on `QueryIter` before, unless the underlying `Query` is empty. pub fn sort_by_cached_key( self, - mut f: impl FnMut(&L::Item<'_>) -> K, + mut f: impl FnMut(&L::Item<'_, '_>) -> K, ) -> QuerySortedIter< 'w, 's, @@ -827,7 +827,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> { /// This will panic if `next` has been called on `QueryIter` before, unless the underlying `Query` is empty. fn sort_impl( self, - f: impl FnOnce(&mut Vec<(L::Item<'_>, NeutralOrd)>), + f: impl FnOnce(&mut Vec<(L::Item<'_, '_>, NeutralOrd)>), ) -> QuerySortedIter< 'w, 's, @@ -856,7 +856,11 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> { .map(|(key, entity)| (key, NeutralOrd(entity))) .collect(); f(&mut keyed_query); - let entity_iter = keyed_query.into_iter().map(|(.., entity)| entity.0); + let entity_iter = keyed_query + .into_iter() + .map(|(.., entity)| entity.0) + .collect::>() + .into_iter(); // SAFETY: // `self.world` has permission to access the required components. // Each lens query item is dropped before the respective actual query item is accessed. @@ -873,7 +877,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> { } impl<'w, 's, D: QueryData, F: QueryFilter> Iterator for QueryIter<'w, 's, D, F> { - type Item = D::Item<'w>; + type Item = D::Item<'w, 's>; #[inline(always)] fn next(&mut self) -> Option { @@ -975,7 +979,7 @@ where entities: &'w Entities, tables: &'w Tables, archetypes: &'w Archetypes, - fetch: D::Fetch<'w>, + fetch: D::Fetch<'w, 's>, query_state: &'s QueryState, } @@ -1010,7 +1014,7 @@ where /// # Safety /// `entity` must stem from `self.entity_iter`, and not have been passed before. #[inline(always)] - unsafe fn fetch_next(&mut self, entity: Entity) -> D::Item<'w> { + unsafe fn fetch_next(&mut self, entity: Entity) -> D::Item<'w, 's> { let (location, archetype, table); // SAFETY: // `tables` and `archetypes` belong to the same world that the [`QueryIter`] @@ -1048,7 +1052,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> Iterator where I: Iterator, { - type Item = D::Item<'w>; + type Item = D::Item<'w, 's>; #[inline(always)] fn next(&mut self) -> Option { @@ -1119,8 +1123,8 @@ pub struct QueryManyIter<'w, 's, D: QueryData, F: QueryFilter, I: Iterator, - filter: F::Fetch<'w>, + fetch: D::Fetch<'w, 's>, + filter: F::Fetch<'w, 's>, query_state: &'s QueryState, } @@ -1167,10 +1171,10 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> entities: &'w Entities, tables: &'w Tables, archetypes: &'w Archetypes, - fetch: &mut D::Fetch<'w>, - filter: &mut F::Fetch<'w>, + fetch: &mut D::Fetch<'w, 's>, + filter: &mut F::Fetch<'w, 's>, query_state: &'s QueryState, - ) -> Option> { + ) -> Option> { for entity_borrow in entity_iter { let entity = entity_borrow.entity(); let Some(location) = entities.get(entity) else { @@ -1212,7 +1216,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> /// Get next result from the query #[inline(always)] - pub fn fetch_next(&mut self) -> Option> { + pub fn fetch_next(&mut self) -> Option> { // SAFETY: // All arguments stem from self. // We are limiting the returned reference to self, @@ -1336,7 +1340,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> impl ExactSizeIterator + DoubleEndedIterator + FusedIterator + 'w, > where - for<'lw> L::Item<'lw>: Ord, + for<'lw, 'ls> L::Item<'lw, 'ls>: Ord, { self.sort_impl::(|keyed_query| keyed_query.sort()) } @@ -1394,7 +1398,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> impl ExactSizeIterator + DoubleEndedIterator + FusedIterator + 'w, > where - for<'lw> L::Item<'lw>: Ord, + for<'lw, 'ls> L::Item<'lw, 'ls>: Ord, { self.sort_impl::(|keyed_query| keyed_query.sort_unstable()) } @@ -1451,7 +1455,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> /// ``` pub fn sort_by( self, - mut compare: impl FnMut(&L::Item<'_>, &L::Item<'_>) -> Ordering, + mut compare: impl FnMut(&L::Item<'_, '_>, &L::Item<'_, '_>) -> Ordering, ) -> QuerySortedManyIter< 'w, 's, @@ -1482,7 +1486,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> /// called on [`QueryManyIter`] before. pub fn sort_unstable_by( self, - mut compare: impl FnMut(&L::Item<'_>, &L::Item<'_>) -> Ordering, + mut compare: impl FnMut(&L::Item<'_, '_>, &L::Item<'_, '_>) -> Ordering, ) -> QuerySortedManyIter< 'w, 's, @@ -1576,7 +1580,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> /// ``` pub fn sort_by_key( self, - mut f: impl FnMut(&L::Item<'_>) -> K, + mut f: impl FnMut(&L::Item<'_, '_>) -> K, ) -> QuerySortedManyIter< 'w, 's, @@ -1608,7 +1612,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> /// called on [`QueryManyIter`] before. pub fn sort_unstable_by_key( self, - mut f: impl FnMut(&L::Item<'_>) -> K, + mut f: impl FnMut(&L::Item<'_, '_>) -> K, ) -> QuerySortedManyIter< 'w, 's, @@ -1642,7 +1646,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> /// called on [`QueryManyIter`] before. pub fn sort_by_cached_key( self, - mut f: impl FnMut(&L::Item<'_>) -> K, + mut f: impl FnMut(&L::Item<'_, '_>) -> K, ) -> QuerySortedManyIter< 'w, 's, @@ -1671,7 +1675,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> /// called on [`QueryManyIter`] before. fn sort_impl( self, - f: impl FnOnce(&mut Vec<(L::Item<'_>, NeutralOrd)>), + f: impl FnOnce(&mut Vec<(L::Item<'_, '_>, NeutralOrd)>), ) -> QuerySortedManyIter< 'w, 's, @@ -1721,7 +1725,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: DoubleEndedIterator Option> { + pub fn fetch_next_back(&mut self) -> Option> { // SAFETY: // All arguments stem from self. // We are limiting the returned reference to self, @@ -1745,7 +1749,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: DoubleEndedIterator> Iterator for QueryManyIter<'w, 's, D, F, I> { - type Item = D::Item<'w>; + type Item = D::Item<'w, 's>; #[inline(always)] fn next(&mut self) -> Option { @@ -1861,7 +1865,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: EntitySetIterator> impl<'w, 's, D: QueryData, F: QueryFilter, I: EntitySetIterator> Iterator for QueryManyUniqueIter<'w, 's, D, F, I> { - type Item = D::Item<'w>; + type Item = D::Item<'w, 's>; #[inline(always)] fn next(&mut self) -> Option { @@ -1915,7 +1919,7 @@ pub struct QuerySortedManyIter<'w, 's, D: QueryData, F: QueryFilter, I: Iterator entities: &'w Entities, tables: &'w Tables, archetypes: &'w Archetypes, - fetch: D::Fetch<'w>, + fetch: D::Fetch<'w, 's>, query_state: &'s QueryState, } @@ -1954,7 +1958,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> /// It is always safe for shared access. /// `entity` must stem from `self.entity_iter`, and not have been passed before. #[inline(always)] - unsafe fn fetch_next_aliased_unchecked(&mut self, entity: Entity) -> D::Item<'w> { + unsafe fn fetch_next_aliased_unchecked(&mut self, entity: Entity) -> D::Item<'w, 's> { let (location, archetype, table); // SAFETY: // `tables` and `archetypes` belong to the same world that the [`QueryIter`] @@ -1988,7 +1992,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> /// Get next result from the query #[inline(always)] - pub fn fetch_next(&mut self) -> Option> { + pub fn fetch_next(&mut self) -> Option> { let entity = self.entity_iter.next()?; // SAFETY: @@ -2007,7 +2011,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: DoubleEndedIterator { /// Get next result from the query #[inline(always)] - pub fn fetch_next_back(&mut self) -> Option> { + pub fn fetch_next_back(&mut self) -> Option> { let entity = self.entity_iter.next_back()?; // SAFETY: @@ -2024,7 +2028,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: DoubleEndedIterator impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, I: Iterator> Iterator for QuerySortedManyIter<'w, 's, D, F, I> { - type Item = D::Item<'w>; + type Item = D::Item<'w, 's>; #[inline(always)] fn next(&mut self) -> Option { @@ -2185,7 +2189,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, const K: usize> QueryCombinationIter< /// . /// It is always safe for shared access. #[inline] - unsafe fn fetch_next_aliased_unchecked(&mut self) -> Option<[D::Item<'w>; K]> { + unsafe fn fetch_next_aliased_unchecked(&mut self) -> Option<[D::Item<'w, 's>; K]> { // PERF: can speed up the following code using `cursor.remaining()` instead of `next_item.is_none()` // when D::IS_ARCHETYPAL && F::IS_ARCHETYPAL // @@ -2211,9 +2215,9 @@ impl<'w, 's, D: QueryData, F: QueryFilter, const K: usize> QueryCombinationIter< } } - let mut values = MaybeUninit::<[D::Item<'w>; K]>::uninit(); + let mut values = MaybeUninit::<[D::Item<'w, 's>; K]>::uninit(); - let ptr = values.as_mut_ptr().cast::>(); + let ptr = values.as_mut_ptr().cast::>(); for (offset, cursor) in self.cursors.iter_mut().enumerate() { ptr.add(offset).write(cursor.peek_last().unwrap()); } @@ -2223,7 +2227,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, const K: usize> QueryCombinationIter< /// Get next combination of queried components #[inline] - pub fn fetch_next(&mut self) -> Option<[D::Item<'_>; K]> { + pub fn fetch_next(&mut self) -> Option<[D::Item<'_, 's>; K]> { // SAFETY: we are limiting the returned reference to self, // making sure this method cannot be called multiple times without getting rid // of any previously returned unique references first, thus preventing aliasing. @@ -2240,7 +2244,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, const K: usize> QueryCombinationIter< impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, const K: usize> Iterator for QueryCombinationIter<'w, 's, D, F, K> { - type Item = [D::Item<'w>; K]; + type Item = [D::Item<'w, 's>; K]; #[inline] fn next(&mut self) -> Option { @@ -2309,8 +2313,8 @@ struct QueryIterationCursor<'w, 's, D: QueryData, F: QueryFilter> { storage_id_iter: core::slice::Iter<'s, StorageId>, table_entities: &'w [Entity], archetype_entities: &'w [ArchetypeEntity], - fetch: D::Fetch<'w>, - filter: F::Fetch<'w>, + fetch: D::Fetch<'w, 's>, + filter: F::Fetch<'w, 's>, // length of the table or length of the archetype, depending on whether both `D`'s and `F`'s fetches are dense current_len: u32, // either table row or archetype index, depending on whether both `D`'s and `F`'s fetches are dense @@ -2390,7 +2394,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIterationCursor<'w, 's, D, F> { /// The result of `next` and any previous calls to `peek_last` with this row must have been /// dropped to prevent aliasing mutable references. #[inline] - unsafe fn peek_last(&mut self) -> Option> { + unsafe fn peek_last(&mut self) -> Option> { if self.current_row > 0 { let index = self.current_row - 1; if self.is_dense { @@ -2457,7 +2461,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIterationCursor<'w, 's, D, F> { tables: &'w Tables, archetypes: &'w Archetypes, query_state: &'s QueryState, - ) -> Option> { + ) -> Option> { if self.is_dense { loop { // we are on the beginning of the query, or finished processing a table, so skip to the next diff --git a/crates/bevy_ecs/src/query/mod.rs b/crates/bevy_ecs/src/query/mod.rs index eb5e001e4d..db22c152f7 100644 --- a/crates/bevy_ecs/src/query/mod.rs +++ b/crates/bevy_ecs/src/query/mod.rs @@ -819,34 +819,37 @@ mod tests { /// SAFETY: /// `update_component_access` adds resource read access for `R`. unsafe impl WorldQuery for ReadsRData { - type Fetch<'w> = (); + type Fetch<'w, 's> = (); type State = ComponentId; - fn shrink_fetch<'wlong: 'wshort, 'wshort>(_: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {} + fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( + _: Self::Fetch<'wlong, 's>, + ) -> Self::Fetch<'wshort, 's> { + } - unsafe fn init_fetch<'w>( + unsafe fn init_fetch<'w, 's>( _world: UnsafeWorldCell<'w>, - _state: &Self::State, + _state: &'s Self::State, _last_run: Tick, _this_run: Tick, - ) -> Self::Fetch<'w> { + ) -> Self::Fetch<'w, 's> { } const IS_DENSE: bool = true; #[inline] - unsafe fn set_archetype<'w>( - _fetch: &mut Self::Fetch<'w>, - _state: &Self::State, + unsafe fn set_archetype<'w, 's>( + _fetch: &mut Self::Fetch<'w, 's>, + _state: &'s Self::State, _archetype: &'w Archetype, _table: &Table, ) { } #[inline] - unsafe fn set_table<'w>( - _fetch: &mut Self::Fetch<'w>, - _state: &Self::State, + unsafe fn set_table<'w, 's>( + _fetch: &mut Self::Fetch<'w, 's>, + _state: &'s Self::State, _table: &'w Table, ) { } @@ -882,16 +885,19 @@ mod tests { unsafe impl QueryData for ReadsRData { const IS_READ_ONLY: bool = true; type ReadOnly = Self; - type Item<'w> = (); + type Item<'w, 's> = (); - fn shrink<'wlong: 'wshort, 'wshort>(_item: Self::Item<'wlong>) -> Self::Item<'wshort> {} + fn shrink<'wlong: 'wshort, 'wshort, 's>( + _item: Self::Item<'wlong, 's>, + ) -> Self::Item<'wshort, 's> { + } #[inline(always)] - unsafe fn fetch<'w>( - _fetch: &mut Self::Fetch<'w>, + unsafe fn fetch<'w, 's>( + _fetch: &mut Self::Fetch<'w, 's>, _entity: Entity, _table_row: TableRow, - ) -> Self::Item<'w> { + ) -> Self::Item<'w, 's> { } } diff --git a/crates/bevy_ecs/src/query/par_iter.rs b/crates/bevy_ecs/src/query/par_iter.rs index c685659260..b8d8618fa5 100644 --- a/crates/bevy_ecs/src/query/par_iter.rs +++ b/crates/bevy_ecs/src/query/par_iter.rs @@ -39,7 +39,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryParIter<'w, 's, D, F> { /// /// [`ComputeTaskPool`]: bevy_tasks::ComputeTaskPool #[inline] - pub fn for_each) + Send + Sync + Clone>(self, func: FN) { + pub fn for_each) + Send + Sync + Clone>(self, func: FN) { self.for_each_init(|| {}, |_, item| func(item)); } @@ -76,7 +76,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryParIter<'w, 's, D, F> { #[inline] pub fn for_each_init(self, init: INIT, func: FN) where - FN: Fn(&mut T, QueryItem<'w, D>) + Send + Sync + Clone, + FN: Fn(&mut T, QueryItem<'w, 's, D>) + Send + Sync + Clone, INIT: Fn() -> T + Sync + Send + Clone, { let func = |mut init, item| { @@ -190,7 +190,7 @@ impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, E: EntityEquivalent + Sync> /// /// [`ComputeTaskPool`]: bevy_tasks::ComputeTaskPool #[inline] - pub fn for_each) + Send + Sync + Clone>(self, func: FN) { + pub fn for_each) + Send + Sync + Clone>(self, func: FN) { self.for_each_init(|| {}, |_, item| func(item)); } @@ -247,7 +247,7 @@ impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, E: EntityEquivalent + Sync> #[inline] pub fn for_each_init(self, init: INIT, func: FN) where - FN: Fn(&mut T, QueryItem<'w, D>) + Send + Sync + Clone, + FN: Fn(&mut T, QueryItem<'w, 's, D>) + Send + Sync + Clone, INIT: Fn() -> T + Sync + Send + Clone, { let func = |mut init, item| { @@ -345,7 +345,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, E: EntityEquivalent + Sync> /// /// [`ComputeTaskPool`]: bevy_tasks::ComputeTaskPool #[inline] - pub fn for_each) + Send + Sync + Clone>(self, func: FN) { + pub fn for_each) + Send + Sync + Clone>(self, func: FN) { self.for_each_init(|| {}, |_, item| func(item)); } @@ -402,7 +402,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, E: EntityEquivalent + Sync> #[inline] pub fn for_each_init(self, init: INIT, func: FN) where - FN: Fn(&mut T, QueryItem<'w, D>) + Send + Sync + Clone, + FN: Fn(&mut T, QueryItem<'w, 's, D>) + Send + Sync + Clone, INIT: Fn() -> T + Sync + Send + Clone, { let func = |mut init, item| { diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 836fefbdfd..005e324cbb 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -845,13 +845,17 @@ impl QueryState { /// /// This can only be called for read-only queries, see [`Self::get_mut`] for write-queries. /// + /// If you need to get multiple items at once but get borrowing errors, + /// consider using [`Self::update_archetypes`] followed by multiple [`Self::get_manual`] calls, + /// or making a single call with [`Self::get_many`] or [`Self::iter_many`]. + /// /// This is always guaranteed to run in `O(1)` time. #[inline] pub fn get<'w>( &mut self, world: &'w World, entity: Entity, - ) -> Result, QueryEntityError> { + ) -> Result, QueryEntityError> { self.query(world).get_inner(entity) } @@ -892,7 +896,7 @@ impl QueryState { &mut self, world: &'w World, entities: [Entity; N], - ) -> Result<[ROQueryItem<'w, D>; N], QueryEntityError> { + ) -> Result<[ROQueryItem<'w, '_, D>; N], QueryEntityError> { self.query(world).get_many_inner(entities) } @@ -930,7 +934,7 @@ impl QueryState { &mut self, world: &'w World, entities: UniqueEntityArray, - ) -> Result<[ROQueryItem<'w, D>; N], QueryEntityError> { + ) -> Result<[ROQueryItem<'w, '_, D>; N], QueryEntityError> { self.query(world).get_many_unique_inner(entities) } @@ -942,7 +946,7 @@ impl QueryState { &mut self, world: &'w mut World, entity: Entity, - ) -> Result, QueryEntityError> { + ) -> Result, QueryEntityError> { self.query_mut(world).get_inner(entity) } @@ -989,7 +993,7 @@ impl QueryState { &mut self, world: &'w mut World, entities: [Entity; N], - ) -> Result<[D::Item<'w>; N], QueryEntityError> { + ) -> Result<[D::Item<'w, '_>; N], QueryEntityError> { self.query_mut(world).get_many_mut_inner(entities) } @@ -1034,7 +1038,7 @@ impl QueryState { &mut self, world: &'w mut World, entities: UniqueEntityArray, - ) -> Result<[D::Item<'w>; N], QueryEntityError> { + ) -> Result<[D::Item<'w, '_>; N], QueryEntityError> { self.query_mut(world).get_many_unique_inner(entities) } @@ -1056,7 +1060,7 @@ impl QueryState { &self, world: &'w World, entity: Entity, - ) -> Result, QueryEntityError> { + ) -> Result, QueryEntityError> { self.query_manual(world).get_inner(entity) } @@ -1073,13 +1077,16 @@ impl QueryState { &mut self, world: UnsafeWorldCell<'w>, entity: Entity, - ) -> Result, QueryEntityError> { + ) -> Result, QueryEntityError> { self.query_unchecked(world).get_inner(entity) } /// Returns an [`Iterator`] over the query results for the given [`World`]. /// /// This can only be called for read-only queries, see [`Self::iter_mut`] for write-queries. + /// + /// If you need to iterate multiple times at once but get borrowing errors, + /// consider using [`Self::update_archetypes`] followed by multiple [`Self::iter_manual`] calls. #[inline] pub fn iter<'w, 's>(&'s mut self, world: &'w World) -> QueryIter<'w, 's, D::ReadOnly, F> { self.query(world).into_iter() @@ -1168,6 +1175,9 @@ impl QueryState { /// Items are returned in the order of the list of entities. /// Entities that don't match the query are skipped. /// + /// If you need to iterate multiple times at once but get borrowing errors, + /// consider using [`Self::update_archetypes`] followed by multiple [`Self::iter_many_manual`] calls. + /// /// # See also /// /// - [`iter_many_mut`](Self::iter_many_mut) to get mutable query items. @@ -1387,8 +1397,8 @@ impl QueryState { /// /// [`ComputeTaskPool`]: bevy_tasks::ComputeTaskPool #[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))] - pub(crate) unsafe fn par_fold_init_unchecked_manual<'w, T, FN, INIT>( - &self, + pub(crate) unsafe fn par_fold_init_unchecked_manual<'w, 's, T, FN, INIT>( + &'s self, init_accum: INIT, world: UnsafeWorldCell<'w>, batch_size: u32, @@ -1396,7 +1406,7 @@ impl QueryState { last_run: Tick, this_run: Tick, ) where - FN: Fn(T, D::Item<'w>) -> T + Send + Sync + Clone, + FN: Fn(T, D::Item<'w, 's>) -> T + Send + Sync + Clone, INIT: Fn() -> T + Sync + Send + Clone, { // NOTE: If you are changing query iteration code, remember to update the following places, where relevant: @@ -1501,8 +1511,8 @@ impl QueryState { /// /// [`ComputeTaskPool`]: bevy_tasks::ComputeTaskPool #[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))] - pub(crate) unsafe fn par_many_unique_fold_init_unchecked_manual<'w, T, FN, INIT, E>( - &self, + pub(crate) unsafe fn par_many_unique_fold_init_unchecked_manual<'w, 's, T, FN, INIT, E>( + &'s self, init_accum: INIT, world: UnsafeWorldCell<'w>, entity_list: &UniqueEntityEquivalentSlice, @@ -1511,7 +1521,7 @@ impl QueryState { last_run: Tick, this_run: Tick, ) where - FN: Fn(T, D::Item<'w>) -> T + Send + Sync + Clone, + FN: Fn(T, D::Item<'w, 's>) -> T + Send + Sync + Clone, INIT: Fn() -> T + Sync + Send + Clone, E: EntityEquivalent + Sync, { @@ -1564,8 +1574,8 @@ impl QueryState { /// /// [`ComputeTaskPool`]: bevy_tasks::ComputeTaskPool #[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))] - pub(crate) unsafe fn par_many_fold_init_unchecked_manual<'w, T, FN, INIT, E>( - &self, + pub(crate) unsafe fn par_many_fold_init_unchecked_manual<'w, 's, T, FN, INIT, E>( + &'s self, init_accum: INIT, world: UnsafeWorldCell<'w>, entity_list: &[E], @@ -1574,7 +1584,7 @@ impl QueryState { last_run: Tick, this_run: Tick, ) where - FN: Fn(T, D::Item<'w>) -> T + Send + Sync + Clone, + FN: Fn(T, D::Item<'w, 's>) -> T + Send + Sync + Clone, INIT: Fn() -> T + Sync + Send + Clone, E: EntityEquivalent + Sync, { @@ -1686,7 +1696,10 @@ impl QueryState { /// /// Simply unwrapping the [`Result`] also works, but should generally be reserved for tests. #[inline] - pub fn single<'w>(&mut self, world: &'w World) -> Result, QuerySingleError> { + pub fn single<'w>( + &mut self, + world: &'w World, + ) -> Result, QuerySingleError> { self.query(world).single_inner() } @@ -1703,7 +1716,7 @@ impl QueryState { pub fn single_mut<'w>( &mut self, world: &'w mut World, - ) -> Result, QuerySingleError> { + ) -> Result, QuerySingleError> { self.query_mut(world).single_inner() } @@ -1720,7 +1733,7 @@ impl QueryState { pub unsafe fn single_unchecked<'w>( &mut self, world: UnsafeWorldCell<'w>, - ) -> Result, QuerySingleError> { + ) -> Result, QuerySingleError> { self.query_unchecked(world).single_inner() } @@ -1742,7 +1755,7 @@ impl QueryState { world: UnsafeWorldCell<'w>, last_run: Tick, this_run: Tick, - ) -> Result, QuerySingleError> { + ) -> Result, QuerySingleError> { // SAFETY: // - The caller ensured we have the correct access to the world. // - The caller ensured that the world matches. diff --git a/crates/bevy_ecs/src/query/world_query.rs b/crates/bevy_ecs/src/query/world_query.rs index a6bcbf58bd..e856be7619 100644 --- a/crates/bevy_ecs/src/query/world_query.rs +++ b/crates/bevy_ecs/src/query/world_query.rs @@ -42,7 +42,7 @@ use variadics_please::all_tuples; /// [`QueryFilter`]: crate::query::QueryFilter pub unsafe trait WorldQuery { /// Per archetype/table state retrieved by this [`WorldQuery`] to compute [`Self::Item`](crate::query::QueryData::Item) for each entity. - type Fetch<'a>: Clone; + type Fetch<'w, 's>: Clone; /// State used to construct a [`Self::Fetch`](WorldQuery::Fetch). This will be cached inside [`QueryState`](crate::query::QueryState), /// so it is best to move as much data / computation here as possible to reduce the cost of @@ -50,7 +50,9 @@ pub unsafe trait WorldQuery { type State: Send + Sync + Sized; /// This function manually implements subtyping for the query fetches. - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort>; + fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( + fetch: Self::Fetch<'wlong, 's>, + ) -> Self::Fetch<'wshort, 's>; /// Creates a new instance of [`Self::Fetch`](WorldQuery::Fetch), /// by combining data from the [`World`] with the cached [`Self::State`](WorldQuery::State). @@ -62,12 +64,12 @@ pub unsafe trait WorldQuery { /// in to this function. /// - `world` must have the **right** to access any access registered in `update_component_access`. /// - There must not be simultaneous resource access conflicting with readonly resource access registered in [`WorldQuery::update_component_access`]. - unsafe fn init_fetch<'w>( + unsafe fn init_fetch<'w, 's>( world: UnsafeWorldCell<'w>, - state: &Self::State, + state: &'s Self::State, last_run: Tick, this_run: Tick, - ) -> Self::Fetch<'w>; + ) -> Self::Fetch<'w, 's>; /// Returns true if (and only if) every table of every archetype matched by this fetch contains /// all of the matched components. @@ -87,9 +89,9 @@ pub unsafe trait WorldQuery { /// - `archetype` and `tables` must be from the same [`World`] that [`WorldQuery::init_state`] was called on. /// - `table` must correspond to `archetype`. /// - `state` must be the [`State`](Self::State) that `fetch` was initialized with. - unsafe fn set_archetype<'w>( - fetch: &mut Self::Fetch<'w>, - state: &Self::State, + unsafe fn set_archetype<'w, 's>( + fetch: &mut Self::Fetch<'w, 's>, + state: &'s Self::State, archetype: &'w Archetype, table: &'w Table, ); @@ -101,7 +103,11 @@ pub unsafe trait WorldQuery { /// /// - `table` must be from the same [`World`] that [`WorldQuery::init_state`] was called on. /// - `state` must be the [`State`](Self::State) that `fetch` was initialized with. - unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, table: &'w Table); + unsafe fn set_table<'w, 's>( + fetch: &mut Self::Fetch<'w, 's>, + state: &'s Self::State, + table: &'w Table, + ); /// Adds any component accesses used by this [`WorldQuery`] to `access`. /// @@ -154,11 +160,11 @@ macro_rules! impl_tuple_world_query { /// `update_component_access` adds all `With` and `Without` filters from the subqueries. /// This is sound because `matches_component_set` always returns `false` if any the subqueries' implementations return `false`. unsafe impl<$($name: WorldQuery),*> WorldQuery for ($($name,)*) { - type Fetch<'w> = ($($name::Fetch<'w>,)*); + type Fetch<'w, 's> = ($($name::Fetch<'w, 's>,)*); type State = ($($name::State,)*); - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { + fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>(fetch: Self::Fetch<'wlong, 's>) -> Self::Fetch<'wshort, 's> { let ($($name,)*) = fetch; ($( $name::shrink_fetch($name), @@ -166,7 +172,7 @@ macro_rules! impl_tuple_world_query { } #[inline] - unsafe fn init_fetch<'w>(world: UnsafeWorldCell<'w>, state: &Self::State, last_run: Tick, this_run: Tick) -> Self::Fetch<'w> { + unsafe fn init_fetch<'w, 's>(world: UnsafeWorldCell<'w>, state: &'s Self::State, last_run: Tick, this_run: Tick) -> Self::Fetch<'w, 's> { let ($($name,)*) = state; // SAFETY: The invariants are upheld by the caller. ($(unsafe { $name::init_fetch(world, $name, last_run, this_run) },)*) @@ -175,9 +181,9 @@ macro_rules! impl_tuple_world_query { const IS_DENSE: bool = true $(&& $name::IS_DENSE)*; #[inline] - unsafe fn set_archetype<'w>( - fetch: &mut Self::Fetch<'w>, - state: &Self::State, + unsafe fn set_archetype<'w, 's>( + fetch: &mut Self::Fetch<'w, 's>, + state: &'s Self::State, archetype: &'w Archetype, table: &'w Table ) { @@ -188,7 +194,7 @@ macro_rules! impl_tuple_world_query { } #[inline] - unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, table: &'w Table) { + unsafe fn set_table<'w, 's>(fetch: &mut Self::Fetch<'w, 's>, state: &'s Self::State, table: &'w Table) { let ($($name,)*) = fetch; let ($($state,)*) = state; // SAFETY: The invariants are upheld by the caller. diff --git a/crates/bevy_ecs/src/relationship/relationship_query.rs b/crates/bevy_ecs/src/relationship/relationship_query.rs index a2ec937c29..a7acea7de0 100644 --- a/crates/bevy_ecs/src/relationship/relationship_query.rs +++ b/crates/bevy_ecs/src/relationship/relationship_query.rs @@ -14,7 +14,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// target entity of that relationship. pub fn related(&'w self, entity: Entity) -> Option where - ::ReadOnly: QueryData = &'w R>, + ::ReadOnly: QueryData = &'w R>, { self.get(entity).map(R::get).ok() } @@ -26,7 +26,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { entity: Entity, ) -> impl Iterator + 'w where - ::ReadOnly: QueryData = &'w S>, + ::ReadOnly: QueryData = &'w S>, { self.get(entity) .into_iter() @@ -42,7 +42,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// If your relationship is not a tree (like Bevy's hierarchy), be sure to stop if you encounter a duplicate entity. pub fn root_ancestor(&'w self, entity: Entity) -> Entity where - ::ReadOnly: QueryData = &'w R>, + ::ReadOnly: QueryData = &'w R>, { // Recursively search up the tree until we're out of parents match self.get(entity) { @@ -60,9 +60,9 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { pub fn iter_leaves( &'w self, entity: Entity, - ) -> impl Iterator + 'w + ) -> impl Iterator + use<'w, 's, S, D, F> where - ::ReadOnly: QueryData = &'w S>, + ::ReadOnly: QueryData = &'w S>, SourceIter<'w, S>: DoubleEndedIterator, { self.iter_descendants_depth_first(entity).filter(|entity| { @@ -80,7 +80,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { entity: Entity, ) -> impl Iterator + 'w where - D::ReadOnly: QueryData = (Option<&'w R>, Option<&'w R::RelationshipTarget>)>, + D::ReadOnly: QueryData = (Option<&'w R>, Option<&'w R::RelationshipTarget>)>, { self.get(entity) .ok() @@ -103,7 +103,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { entity: Entity, ) -> DescendantIter<'w, 's, D, F, S> where - D::ReadOnly: QueryData = &'w S>, + D::ReadOnly: QueryData = &'w S>, { DescendantIter::new(self, entity) } @@ -120,7 +120,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { entity: Entity, ) -> DescendantDepthFirstIter<'w, 's, D, F, S> where - D::ReadOnly: QueryData = &'w S>, + D::ReadOnly: QueryData = &'w S>, SourceIter<'w, S>: DoubleEndedIterator, { DescendantDepthFirstIter::new(self, entity) @@ -137,7 +137,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { entity: Entity, ) -> AncestorIter<'w, 's, D, F, R> where - D::ReadOnly: QueryData = &'w R>, + D::ReadOnly: QueryData = &'w R>, { AncestorIter::new(self, entity) } @@ -148,7 +148,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// Traverses the hierarchy breadth-first. pub struct DescendantIter<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipTarget> where - D::ReadOnly: QueryData = &'w S>, + D::ReadOnly: QueryData = &'w S>, { children_query: &'w Query<'w, 's, D, F>, vecdeque: VecDeque, @@ -156,7 +156,7 @@ where impl<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipTarget> DescendantIter<'w, 's, D, F, S> where - D::ReadOnly: QueryData = &'w S>, + D::ReadOnly: QueryData = &'w S>, { /// Returns a new [`DescendantIter`]. pub fn new(children_query: &'w Query<'w, 's, D, F>, entity: Entity) -> Self { @@ -174,7 +174,7 @@ where impl<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipTarget> Iterator for DescendantIter<'w, 's, D, F, S> where - D::ReadOnly: QueryData = &'w S>, + D::ReadOnly: QueryData = &'w S>, { type Item = Entity; @@ -194,7 +194,7 @@ where /// Traverses the hierarchy depth-first. pub struct DescendantDepthFirstIter<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipTarget> where - D::ReadOnly: QueryData = &'w S>, + D::ReadOnly: QueryData = &'w S>, { children_query: &'w Query<'w, 's, D, F>, stack: SmallVec<[Entity; 8]>, @@ -203,7 +203,7 @@ where impl<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipTarget> DescendantDepthFirstIter<'w, 's, D, F, S> where - D::ReadOnly: QueryData = &'w S>, + D::ReadOnly: QueryData = &'w S>, SourceIter<'w, S>: DoubleEndedIterator, { /// Returns a new [`DescendantDepthFirstIter`]. @@ -220,7 +220,7 @@ where impl<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipTarget> Iterator for DescendantDepthFirstIter<'w, 's, D, F, S> where - D::ReadOnly: QueryData = &'w S>, + D::ReadOnly: QueryData = &'w S>, SourceIter<'w, S>: DoubleEndedIterator, { type Item = Entity; @@ -239,7 +239,7 @@ where /// An [`Iterator`] of [`Entity`]s over the ancestors of an [`Entity`]. pub struct AncestorIter<'w, 's, D: QueryData, F: QueryFilter, R: Relationship> where - D::ReadOnly: QueryData = &'w R>, + D::ReadOnly: QueryData = &'w R>, { parent_query: &'w Query<'w, 's, D, F>, next: Option, @@ -247,7 +247,7 @@ where impl<'w, 's, D: QueryData, F: QueryFilter, R: Relationship> AncestorIter<'w, 's, D, F, R> where - D::ReadOnly: QueryData = &'w R>, + D::ReadOnly: QueryData = &'w R>, { /// Returns a new [`AncestorIter`]. pub fn new(parent_query: &'w Query<'w, 's, D, F>, entity: Entity) -> Self { @@ -261,7 +261,7 @@ where impl<'w, 's, D: QueryData, F: QueryFilter, R: Relationship> Iterator for AncestorIter<'w, 's, D, F, R> where - D::ReadOnly: QueryData = &'w R>, + D::ReadOnly: QueryData = &'w R>, { type Item = Entity; diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index dfc07d23f1..2664d3a44f 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -1185,7 +1185,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// [`par_iter_mut`]: Self::par_iter_mut /// [`World`]: crate::world::World #[inline] - pub fn par_iter(&self) -> QueryParIter<'_, '_, D::ReadOnly, F> { + pub fn par_iter(&self) -> QueryParIter<'_, 's, D::ReadOnly, F> { self.as_readonly().par_iter_inner() } @@ -1220,7 +1220,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// [`par_iter`]: Self::par_iter /// [`World`]: crate::world::World #[inline] - pub fn par_iter_mut(&mut self) -> QueryParIter<'_, '_, D, F> { + pub fn par_iter_mut(&mut self) -> QueryParIter<'_, 's, D, F> { self.reborrow().par_iter_inner() } @@ -1280,7 +1280,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { pub fn par_iter_many>( &self, entities: EntityList, - ) -> QueryParManyIter<'_, '_, D::ReadOnly, F, EntityList::Item> { + ) -> QueryParManyIter<'_, 's, D::ReadOnly, F, EntityList::Item> { QueryParManyIter { world: self.world, state: self.state.as_readonly(), @@ -1309,7 +1309,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { pub fn par_iter_many_unique>( &self, entities: EntityList, - ) -> QueryParManyUniqueIter<'_, '_, D::ReadOnly, F, EntityList::Item> { + ) -> QueryParManyUniqueIter<'_, 's, D::ReadOnly, F, EntityList::Item> { QueryParManyUniqueIter { world: self.world, state: self.state.as_readonly(), @@ -1338,7 +1338,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { pub fn par_iter_many_unique_mut>( &mut self, entities: EntityList, - ) -> QueryParManyUniqueIter<'_, '_, D, F, EntityList::Item> { + ) -> QueryParManyUniqueIter<'_, 's, D, F, EntityList::Item> { QueryParManyUniqueIter { world: self.world, state: self.state, @@ -1383,7 +1383,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// /// - [`get_mut`](Self::get_mut) to get a mutable query item. #[inline] - pub fn get(&self, entity: Entity) -> Result, QueryEntityError> { + pub fn get(&self, entity: Entity) -> Result, QueryEntityError> { self.as_readonly().get_inner(entity) } @@ -1434,7 +1434,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { pub fn get_many( &self, entities: [Entity; N], - ) -> Result<[ROQueryItem<'_, D>; N], QueryEntityError> { + ) -> Result<[ROQueryItem<'_, 's, D>; N], QueryEntityError> { // Note that we call a separate `*_inner` method from `get_many_mut` // because we don't need to check for duplicates. self.as_readonly().get_many_inner(entities) @@ -1485,7 +1485,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { pub fn get_many_unique( &self, entities: UniqueEntityArray, - ) -> Result<[ROQueryItem<'_, D>; N], QueryEntityError> { + ) -> Result<[ROQueryItem<'_, 's, D>; N], QueryEntityError> { self.as_readonly().get_many_unique_inner(entities) } @@ -1519,7 +1519,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// /// - [`get`](Self::get) to get a read-only query item. #[inline] - pub fn get_mut(&mut self, entity: Entity) -> Result, QueryEntityError> { + pub fn get_mut(&mut self, entity: Entity) -> Result, QueryEntityError> { self.reborrow().get_inner(entity) } @@ -1534,7 +1534,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// /// - [`get_mut`](Self::get_mut) to get the item using a mutable borrow of the [`Query`]. #[inline] - pub fn get_inner(self, entity: Entity) -> Result, QueryEntityError> { + pub fn get_inner(self, entity: Entity) -> Result, QueryEntityError> { // SAFETY: system runs without conflicts with other systems. // same-system queries have runtime borrow checks when they conflict unsafe { @@ -1662,7 +1662,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { pub fn get_many_mut( &mut self, entities: [Entity; N], - ) -> Result<[D::Item<'_>; N], QueryEntityError> { + ) -> Result<[D::Item<'_, 's>; N], QueryEntityError> { self.reborrow().get_many_mut_inner(entities) } @@ -1730,7 +1730,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { pub fn get_many_unique_mut( &mut self, entities: UniqueEntityArray, - ) -> Result<[D::Item<'_>; N], QueryEntityError> { + ) -> Result<[D::Item<'_, 's>; N], QueryEntityError> { self.reborrow().get_many_unique_inner(entities) } @@ -1749,7 +1749,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { pub fn get_many_mut_inner( self, entities: [Entity; N], - ) -> Result<[D::Item<'w>; N], QueryEntityError> { + ) -> Result<[D::Item<'w, 's>; N], QueryEntityError> { // Verify that all entities are unique for i in 0..N { for j in 0..i { @@ -1777,7 +1777,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { pub fn get_many_inner( self, entities: [Entity; N], - ) -> Result<[D::Item<'w>; N], QueryEntityError> + ) -> Result<[D::Item<'w, 's>; N], QueryEntityError> where D: ReadOnlyQueryData, { @@ -1799,7 +1799,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { pub fn get_many_unique_inner( self, entities: UniqueEntityArray, - ) -> Result<[D::Item<'w>; N], QueryEntityError> { + ) -> Result<[D::Item<'w, 's>; N], QueryEntityError> { // SAFETY: All entities are unique, so the results don't alias. unsafe { self.get_many_impl(entities.into_inner()) } } @@ -1814,7 +1814,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { unsafe fn get_many_impl( self, entities: [Entity; N], - ) -> Result<[D::Item<'w>; N], QueryEntityError> { + ) -> Result<[D::Item<'w, 's>; N], QueryEntityError> { let mut values = [(); N].map(|_| MaybeUninit::uninit()); for (value, entity) in core::iter::zip(&mut values, entities) { @@ -1842,7 +1842,10 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// /// - [`get_mut`](Self::get_mut) for the safe version. #[inline] - pub unsafe fn get_unchecked(&self, entity: Entity) -> Result, QueryEntityError> { + pub unsafe fn get_unchecked( + &self, + entity: Entity, + ) -> Result, QueryEntityError> { // SAFETY: The caller promises that this will not result in multiple mutable references. unsafe { self.reborrow_unsafe() }.get_inner(entity) } @@ -1878,7 +1881,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// /// - [`single_mut`](Self::single_mut) to get the mutable query item. #[inline] - pub fn single(&self) -> Result, QuerySingleError> { + pub fn single(&self) -> Result, QuerySingleError> { self.as_readonly().single_inner() } @@ -1907,7 +1910,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// /// - [`single`](Self::single) to get the read-only query item. #[inline] - pub fn single_mut(&mut self) -> Result, QuerySingleError> { + pub fn single_mut(&mut self) -> Result, QuerySingleError> { self.reborrow().single_inner() } @@ -1939,7 +1942,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// - [`single_mut`](Self::single_mut) to get the mutable query item. /// - [`single_inner`](Self::single_inner) for the panicking version. #[inline] - pub fn single_inner(self) -> Result, QuerySingleError> { + pub fn single_inner(self) -> Result, QuerySingleError> { let mut query = self.into_iter(); let first = query.next(); let extra = query.next().is_some(); @@ -2451,7 +2454,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { } impl<'w, 's, D: QueryData, F: QueryFilter> IntoIterator for Query<'w, 's, D, F> { - type Item = D::Item<'w>; + type Item = D::Item<'w, 's>; type IntoIter = QueryIter<'w, 's, D, F>; fn into_iter(self) -> Self::IntoIter { @@ -2464,7 +2467,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> IntoIterator for Query<'w, 's, D, F> } impl<'w, 's, D: QueryData, F: QueryFilter> IntoIterator for &'w Query<'_, 's, D, F> { - type Item = ROQueryItem<'w, D>; + type Item = ROQueryItem<'w, 's, D>; type IntoIter = QueryIter<'w, 's, D::ReadOnly, F>; fn into_iter(self) -> Self::IntoIter { @@ -2473,7 +2476,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> IntoIterator for &'w Query<'_, 's, D, } impl<'w, 's, D: QueryData, F: QueryFilter> IntoIterator for &'w mut Query<'_, 's, D, F> { - type Item = D::Item<'w>; + type Item = D::Item<'w, 's>; type IntoIter = QueryIter<'w, 's, D, F>; fn into_iter(self) -> Self::IntoIter { @@ -2588,28 +2591,28 @@ impl<'w, 'q, Q: QueryData, F: QueryFilter> From<&'q mut Query<'w, '_, Q, F>> /// ``` /// Note that because [`Single`] implements [`Deref`] and [`DerefMut`], methods and fields like `health` can be accessed directly. /// You can also access the underlying data manually, by calling `.deref`/`.deref_mut`, or by using the `*` operator. -pub struct Single<'w, D: QueryData, F: QueryFilter = ()> { - pub(crate) item: D::Item<'w>, +pub struct Single<'w, 's, D: QueryData, F: QueryFilter = ()> { + pub(crate) item: D::Item<'w, 's>, pub(crate) _filter: PhantomData, } -impl<'w, D: QueryData, F: QueryFilter> Deref for Single<'w, D, F> { - type Target = D::Item<'w>; +impl<'w, 's, D: QueryData, F: QueryFilter> Deref for Single<'w, 's, D, F> { + type Target = D::Item<'w, 's>; fn deref(&self) -> &Self::Target { &self.item } } -impl<'w, D: QueryData, F: QueryFilter> DerefMut for Single<'w, D, F> { +impl<'w, 's, D: QueryData, F: QueryFilter> DerefMut for Single<'w, 's, D, F> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.item } } -impl<'w, D: QueryData, F: QueryFilter> Single<'w, D, F> { +impl<'w, 's, D: QueryData, F: QueryFilter> Single<'w, 's, D, F> { /// Returns the inner item with ownership. - pub fn into_inner(self) -> D::Item<'w> { + pub fn into_inner(self) -> D::Item<'w, 's> { self.item } } diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 153f85f336..ac2637ed7a 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -389,9 +389,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> { +unsafe impl<'a, 'b, D: QueryData + 'static, F: QueryFilter + 'static> SystemParam + for Single<'a, 'b, D, F> +{ type State = QueryState; - type Item<'w, 's> = Single<'w, D, F>; + type Item<'w, 's> = Single<'w, 's, D, F>; fn init_state(world: &mut World) -> Self::State { Query::init_state(world) @@ -451,8 +453,8 @@ unsafe impl<'a, D: QueryData + 'static, F: QueryFilter + 'static> SystemParam fo } // SAFETY: QueryState is constrained to read-only fetches, so it only reads World. -unsafe impl<'a, D: ReadOnlyQueryData + 'static, F: QueryFilter + 'static> ReadOnlySystemParam - for Single<'a, D, F> +unsafe impl<'a, 'b, D: ReadOnlyQueryData + 'static, F: QueryFilter + 'static> ReadOnlySystemParam + for Single<'a, 'b, D, F> { } diff --git a/crates/bevy_ecs/src/traversal.rs b/crates/bevy_ecs/src/traversal.rs index d6527a10fa..577720fd5d 100644 --- a/crates/bevy_ecs/src/traversal.rs +++ b/crates/bevy_ecs/src/traversal.rs @@ -1,6 +1,10 @@ //! A trait for components that let you traverse the ECS. -use crate::{entity::Entity, query::ReadOnlyQueryData, relationship::Relationship}; +use crate::{ + entity::Entity, + query::{ReadOnlyQueryData, ReleaseStateQueryData}, + relationship::Relationship, +}; /// A component that can point to another entity, and which can be used to define a path through the ECS. /// @@ -20,13 +24,13 @@ use crate::{entity::Entity, query::ReadOnlyQueryData, relationship::Relationship /// [specify the direction]: crate::event::EntityEvent::Traversal /// [event propagation]: crate::observer::On::propagate /// [observers]: crate::observer::Observer -pub trait Traversal: ReadOnlyQueryData { +pub trait Traversal: ReadOnlyQueryData + ReleaseStateQueryData { /// Returns the next entity to visit. - fn traverse(item: Self::Item<'_>, data: &D) -> Option; + fn traverse(item: Self::Item<'_, '_>, data: &D) -> Option; } impl Traversal for () { - fn traverse(_: Self::Item<'_>, _data: &D) -> Option { + fn traverse(_: Self::Item<'_, '_>, _data: &D) -> Option { None } } @@ -39,7 +43,7 @@ impl Traversal for () { /// /// [event propagation]: crate::observer::On::propagate impl Traversal for &R { - fn traverse(item: Self::Item<'_>, _data: &D) -> Option { + fn traverse(item: Self::Item<'_, '_>, _data: &D) -> Option { Some(item.get()) } } diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index ab470efcc7..d29c3db428 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -16,7 +16,7 @@ use crate::{ event::EntityEvent, lifecycle::{DESPAWN, REMOVE, REPLACE}, observer::Observer, - query::{Access, DebugCheckedUnwrap, ReadOnlyQueryData}, + query::{Access, DebugCheckedUnwrap, ReadOnlyQueryData, ReleaseStateQueryData}, relationship::RelationshipHookMode, resource::Resource, system::IntoObserverSystem, @@ -279,14 +279,16 @@ impl<'w> EntityRef<'w> { /// # Panics /// /// If the entity does not have the components required by the query `Q`. - pub fn components(&self) -> Q::Item<'w> { + pub fn components(&self) -> Q::Item<'w, 'static> { self.get_components::() .expect("Query does not match the current entity") } /// Returns read-only components for the current entity that match the query `Q`, /// or `None` if the entity does not have the components required by the query `Q`. - pub fn get_components(&self) -> Option> { + pub fn get_components( + &self, + ) -> Option> { // SAFETY: We have read-only access to all components of this entity. unsafe { self.cell.get_components::() } } @@ -546,13 +548,15 @@ impl<'w> EntityMut<'w> { /// # Panics /// /// If the entity does not have the components required by the query `Q`. - pub fn components(&self) -> Q::Item<'_> { + pub fn components(&self) -> Q::Item<'_, 'static> { self.as_readonly().components::() } /// Returns read-only components for the current entity that match the query `Q`, /// or `None` if the entity does not have the components required by the query `Q`. - pub fn get_components(&self) -> Option> { + pub fn get_components( + &self, + ) -> Option> { self.as_readonly().get_components::() } @@ -1310,7 +1314,7 @@ impl<'w> EntityWorldMut<'w> { /// If the entity does not have the components required by the query `Q` or if the entity /// has been despawned while this `EntityWorldMut` is still alive. #[inline] - pub fn components(&self) -> Q::Item<'_> { + pub fn components(&self) -> Q::Item<'_, 'static> { self.as_readonly().components::() } @@ -1321,7 +1325,9 @@ impl<'w> EntityWorldMut<'w> { /// /// If the entity has been despawned while this `EntityWorldMut` is still alive. #[inline] - pub fn get_components(&self) -> Option> { + pub fn get_components( + &self, + ) -> Option> { self.as_readonly().get_components::() } diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index 458ea5aa17..6c618a287f 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -11,7 +11,7 @@ use crate::{ lifecycle::RemovedComponentEvents, observer::Observers, prelude::Component, - query::{DebugCheckedUnwrap, ReadOnlyQueryData}, + query::{DebugCheckedUnwrap, ReadOnlyQueryData, ReleaseStateQueryData}, resource::Resource, storage::{ComponentSparseSet, Storages, Table}, world::RawCommandQueue, @@ -998,7 +998,9 @@ impl<'w> UnsafeEntityCell<'w> { /// It is the caller's responsibility to ensure that /// - the [`UnsafeEntityCell`] has permission to access the queried data immutably /// - no mutable references to the queried data exist at the same time - pub(crate) unsafe fn get_components(&self) -> Option> { + pub(crate) unsafe fn get_components( + &self, + ) -> Option> { // SAFETY: World is only used to access query data and initialize query state let state = unsafe { let world = self.world().world(); @@ -1028,7 +1030,8 @@ impl<'w> UnsafeEntityCell<'w> { // Table corresponds to archetype. State is the same state used to init fetch above. unsafe { Q::set_archetype(&mut fetch, &state, archetype, table) } // SAFETY: Called after set_archetype above. Entity and location are guaranteed to exist. - unsafe { Some(Q::fetch(&mut fetch, self.id(), location.table_row)) } + let item = unsafe { Q::fetch(&mut fetch, self.id(), location.table_row) }; + Some(Q::release_state(item)) } else { None } diff --git a/crates/bevy_gizmos/src/lib.rs b/crates/bevy_gizmos/src/lib.rs index de48d94e42..5804729e06 100755 --- a/crates/bevy_gizmos/src/lib.rs +++ b/crates/bevy_gizmos/src/lib.rs @@ -631,8 +631,8 @@ impl RenderCommand

for SetLineGizmoBindGroup #[inline] fn render<'w>( _item: &P, - _view: ROQueryItem<'w, Self::ViewQuery>, - uniform_index: Option>, + _view: ROQueryItem<'w, '_, Self::ViewQuery>, + uniform_index: Option>, bind_group: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { @@ -662,8 +662,8 @@ impl RenderCommand

for DrawLineGizmo #[inline] fn render<'w>( _item: &P, - _view: ROQueryItem<'w, Self::ViewQuery>, - config: Option>, + _view: ROQueryItem<'w, '_, Self::ViewQuery>, + config: Option>, line_gizmos: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { @@ -725,8 +725,8 @@ impl RenderCommand

for DrawLineJointGizmo { #[inline] fn render<'w>( _item: &P, - _view: ROQueryItem<'w, Self::ViewQuery>, - config: Option>, + _view: ROQueryItem<'w, '_, Self::ViewQuery>, + config: Option>, line_gizmos: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { diff --git a/crates/bevy_input_focus/src/lib.rs b/crates/bevy_input_focus/src/lib.rs index 1fe7c4b7ef..cbf88740fd 100644 --- a/crates/bevy_input_focus/src/lib.rs +++ b/crates/bevy_input_focus/src/lib.rs @@ -155,7 +155,7 @@ pub struct WindowTraversal { } impl Traversal> for WindowTraversal { - fn traverse(item: Self::Item<'_>, event: &FocusedInput) -> Option { + fn traverse(item: Self::Item<'_, '_>, event: &FocusedInput) -> Option { let WindowTraversalItem { child_of, window } = item; // Send event to parent, if it has one. diff --git a/crates/bevy_pbr/src/atmosphere/mod.rs b/crates/bevy_pbr/src/atmosphere/mod.rs index 8b08751428..a55403630a 100644 --- a/crates/bevy_pbr/src/atmosphere/mod.rs +++ b/crates/bevy_pbr/src/atmosphere/mod.rs @@ -309,7 +309,7 @@ impl ExtractComponent for Atmosphere { type Out = Atmosphere; - fn extract_component(item: QueryItem<'_, Self::QueryData>) -> Option { + fn extract_component(item: QueryItem<'_, '_, Self::QueryData>) -> Option { Some(item.clone()) } } @@ -405,7 +405,7 @@ impl ExtractComponent for AtmosphereSettings { type Out = AtmosphereSettings; - fn extract_component(item: QueryItem<'_, Self::QueryData>) -> Option { + fn extract_component(item: QueryItem<'_, '_, Self::QueryData>) -> Option { Some(item.clone()) } } diff --git a/crates/bevy_pbr/src/atmosphere/node.rs b/crates/bevy_pbr/src/atmosphere/node.rs index 851447d760..e09b27c590 100644 --- a/crates/bevy_pbr/src/atmosphere/node.rs +++ b/crates/bevy_pbr/src/atmosphere/node.rs @@ -181,7 +181,7 @@ impl ViewNode for RenderSkyNode { view_uniforms_offset, lights_uniforms_offset, render_sky_pipeline_id, - ): QueryItem<'w, Self::ViewQuery>, + ): QueryItem<'w, '_, Self::ViewQuery>, world: &'w World, ) -> Result<(), NodeRunError> { let pipeline_cache = world.resource::(); diff --git a/crates/bevy_pbr/src/light_probe/environment_map.rs b/crates/bevy_pbr/src/light_probe/environment_map.rs index b31801f757..fa55b4d94a 100644 --- a/crates/bevy_pbr/src/light_probe/environment_map.rs +++ b/crates/bevy_pbr/src/light_probe/environment_map.rs @@ -192,7 +192,7 @@ impl ExtractInstance for EnvironmentMapIds { type QueryFilter = (); - fn extract(item: QueryItem<'_, Self::QueryData>) -> Option { + fn extract(item: QueryItem<'_, '_, Self::QueryData>) -> Option { Some(EnvironmentMapIds { diffuse: item.diffuse_map.id(), specular: item.specular_map.id(), diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 8c408233e1..d8a3256b13 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -1540,7 +1540,7 @@ fn extract_mesh_for_gpu_building( not_shadow_caster, no_automatic_batching, visibility_range, - ): ::Item<'_>, + ): ::Item<'_, '_>, render_visibility_ranges: &RenderVisibilityRanges, render_mesh_instances: &RenderMeshInstancesGpu, queue: &mut RenderMeshInstanceGpuQueue, @@ -2874,7 +2874,7 @@ impl RenderCommand

for SetMeshViewBindGroup view_environment_map, mesh_view_bind_group, maybe_oit_layers_count_offset, - ): ROQueryItem<'w, Self::ViewQuery>, + ): ROQueryItem<'w, '_, Self::ViewQuery>, _entity: Option<()>, _: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, diff --git a/crates/bevy_pbr/src/ssr/mod.rs b/crates/bevy_pbr/src/ssr/mod.rs index 9f7dbb2f76..22d5acd1e9 100644 --- a/crates/bevy_pbr/src/ssr/mod.rs +++ b/crates/bevy_pbr/src/ssr/mod.rs @@ -280,7 +280,7 @@ impl ViewNode for ScreenSpaceReflectionsNode { view_environment_map_offset, view_bind_group, ssr_pipeline_id, - ): QueryItem<'w, Self::ViewQuery>, + ): QueryItem<'w, '_, Self::ViewQuery>, world: &'w World, ) -> Result<(), NodeRunError> { // Grab the render pipeline. @@ -498,7 +498,7 @@ impl ExtractComponent for ScreenSpaceReflections { type Out = ScreenSpaceReflectionsUniform; - fn extract_component(settings: QueryItem<'_, Self::QueryData>) -> Option { + fn extract_component(settings: QueryItem<'_, '_, Self::QueryData>) -> Option { if !DEPTH_TEXTURE_SAMPLING_SUPPORTED { once!(info!( "Disabling screen-space reflections on this platform because depth textures \ diff --git a/crates/bevy_pbr/src/volumetric_fog/render.rs b/crates/bevy_pbr/src/volumetric_fog/render.rs index 45f694e546..625ff42dc5 100644 --- a/crates/bevy_pbr/src/volumetric_fog/render.rs +++ b/crates/bevy_pbr/src/volumetric_fog/render.rs @@ -347,7 +347,7 @@ impl ViewNode for VolumetricFogNode { view_ssr_offset, msaa, view_environment_map_offset, - ): QueryItem<'w, Self::ViewQuery>, + ): QueryItem<'w, '_, Self::ViewQuery>, world: &'w World, ) -> Result<(), NodeRunError> { let pipeline_cache = world.resource::(); diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index cc64ad2e4f..e42e1309ec 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -374,7 +374,7 @@ impl ViewNode for Wireframe3dNode { &self, graph: &mut RenderGraphContext, render_context: &mut RenderContext<'w>, - (camera, view, target, depth): QueryItem<'w, Self::ViewQuery>, + (camera, view, target, depth): QueryItem<'w, '_, Self::ViewQuery>, world: &'w World, ) -> Result<(), NodeRunError> { let Some(wireframe_phase) = world.get_resource::>() diff --git a/crates/bevy_picking/src/events.rs b/crates/bevy_picking/src/events.rs index 2116d986af..a7a3979c59 100644 --- a/crates/bevy_picking/src/events.rs +++ b/crates/bevy_picking/src/events.rs @@ -86,7 +86,7 @@ impl Traversal> for PointerTraversal where E: Debug + Clone + Reflect, { - fn traverse(item: Self::Item<'_>, pointer: &Pointer) -> Option { + fn traverse(item: Self::Item<'_, '_>, pointer: &Pointer) -> Option { let PointerTraversalItem { child_of, window } = item; // Send event to parent, if it has one. diff --git a/crates/bevy_render/macros/src/extract_component.rs b/crates/bevy_render/macros/src/extract_component.rs index 2bfd0e0e11..8526f7b889 100644 --- a/crates/bevy_render/macros/src/extract_component.rs +++ b/crates/bevy_render/macros/src/extract_component.rs @@ -43,7 +43,7 @@ pub fn derive_extract_component(input: TokenStream) -> TokenStream { type QueryFilter = #filter; type Out = Self; - fn extract_component(item: #bevy_ecs_path::query::QueryItem<'_, Self::QueryData>) -> Option { + fn extract_component(item: #bevy_ecs_path::query::QueryItem<'_, '_, Self::QueryData>) -> Option { Some(item.clone()) } } diff --git a/crates/bevy_render/src/extract_component.rs b/crates/bevy_render/src/extract_component.rs index e1f528d6ab..b7bb05e425 100644 --- a/crates/bevy_render/src/extract_component.rs +++ b/crates/bevy_render/src/extract_component.rs @@ -60,7 +60,7 @@ pub trait ExtractComponent: Component { // type Out: Component = Self; /// Defines how the component is transferred into the "render world". - fn extract_component(item: QueryItem<'_, Self::QueryData>) -> Option; + fn extract_component(item: QueryItem<'_, '_, Self::QueryData>) -> Option; } /// This plugin prepares the components of the corresponding type for the GPU diff --git a/crates/bevy_render/src/extract_instances.rs b/crates/bevy_render/src/extract_instances.rs index a8e5a9ecbd..cd194b7c40 100644 --- a/crates/bevy_render/src/extract_instances.rs +++ b/crates/bevy_render/src/extract_instances.rs @@ -34,7 +34,7 @@ pub trait ExtractInstance: Send + Sync + Sized + 'static { type QueryFilter: QueryFilter; /// Defines how the component is transferred into the "render world". - fn extract(item: QueryItem<'_, Self::QueryData>) -> Option; + fn extract(item: QueryItem<'_, '_, Self::QueryData>) -> Option; } /// This plugin extracts one or more components into the "render world" as diff --git a/crates/bevy_render/src/render_graph/node.rs b/crates/bevy_render/src/render_graph/node.rs index 0a634c2598..4355892487 100644 --- a/crates/bevy_render/src/render_graph/node.rs +++ b/crates/bevy_render/src/render_graph/node.rs @@ -366,7 +366,7 @@ pub trait ViewNode { &self, graph: &mut RenderGraphContext, render_context: &mut RenderContext<'w>, - view_query: QueryItem<'w, Self::ViewQuery>, + view_query: QueryItem<'w, '_, Self::ViewQuery>, world: &'w World, ) -> Result<(), NodeRunError>; } diff --git a/crates/bevy_render/src/render_phase/draw.rs b/crates/bevy_render/src/render_phase/draw.rs index 374dc6b330..42a6725c8c 100644 --- a/crates/bevy_render/src/render_phase/draw.rs +++ b/crates/bevy_render/src/render_phase/draw.rs @@ -213,8 +213,8 @@ pub trait RenderCommand { /// issuing draw calls, etc.) via the [`TrackedRenderPass`]. fn render<'w>( item: &P, - view: ROQueryItem<'w, Self::ViewQuery>, - entity: Option>, + view: ROQueryItem<'w, '_, Self::ViewQuery>, + entity: Option>, param: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult; @@ -246,8 +246,8 @@ macro_rules! render_command_tuple_impl { )] fn render<'w>( _item: &P, - ($($view,)*): ROQueryItem<'w, Self::ViewQuery>, - maybe_entities: Option>, + ($($view,)*): ROQueryItem<'w, '_, Self::ViewQuery>, + maybe_entities: Option>, ($($name,)*): SystemParamItem<'w, '_, Self::Param>, _pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { diff --git a/crates/bevy_render/src/sync_world.rs b/crates/bevy_render/src/sync_world.rs index 6dceaba3c0..35b500059e 100644 --- a/crates/bevy_render/src/sync_world.rs +++ b/crates/bevy_render/src/sync_world.rs @@ -281,7 +281,7 @@ mod render_entities_world_query_impls { archetype::Archetype, component::{ComponentId, Components, Tick}, entity::Entity, - query::{FilteredAccess, QueryData, ReadOnlyQueryData, WorldQuery}, + query::{FilteredAccess, QueryData, ReadOnlyQueryData, ReleaseStateQueryData, WorldQuery}, storage::{Table, TableRow}, world::{unsafe_world_cell::UnsafeWorldCell, World}, }; @@ -289,22 +289,22 @@ mod render_entities_world_query_impls { /// SAFETY: defers completely to `&RenderEntity` implementation, /// and then only modifies the output safely. unsafe impl WorldQuery for RenderEntity { - type Fetch<'w> = <&'static RenderEntity as WorldQuery>::Fetch<'w>; + type Fetch<'w, 's> = <&'static RenderEntity as WorldQuery>::Fetch<'w, 's>; type State = <&'static RenderEntity as WorldQuery>::State; - fn shrink_fetch<'wlong: 'wshort, 'wshort>( - fetch: Self::Fetch<'wlong>, - ) -> Self::Fetch<'wshort> { + fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( + fetch: Self::Fetch<'wlong, 's>, + ) -> Self::Fetch<'wshort, 's> { fetch } #[inline] - unsafe fn init_fetch<'w>( + unsafe fn init_fetch<'w, 's>( world: UnsafeWorldCell<'w>, - component_id: &ComponentId, + component_id: &'s ComponentId, last_run: Tick, this_run: Tick, - ) -> Self::Fetch<'w> { + ) -> Self::Fetch<'w, 's> { // SAFETY: defers to the `&T` implementation, with T set to `RenderEntity`. unsafe { <&RenderEntity as WorldQuery>::init_fetch(world, component_id, last_run, this_run) @@ -314,9 +314,9 @@ mod render_entities_world_query_impls { const IS_DENSE: bool = <&'static RenderEntity as WorldQuery>::IS_DENSE; #[inline] - unsafe fn set_archetype<'w>( - fetch: &mut Self::Fetch<'w>, - component_id: &ComponentId, + unsafe fn set_archetype<'w, 's>( + fetch: &mut Self::Fetch<'w, 's>, + component_id: &'s ComponentId, archetype: &'w Archetype, table: &'w Table, ) { @@ -327,9 +327,9 @@ mod render_entities_world_query_impls { } #[inline] - unsafe fn set_table<'w>( - fetch: &mut Self::Fetch<'w>, - &component_id: &ComponentId, + unsafe fn set_table<'w, 's>( + fetch: &mut Self::Fetch<'w, 's>, + &component_id: &'s ComponentId, table: &'w Table, ) { // SAFETY: defers to the `&T` implementation, with T set to `RenderEntity`. @@ -364,18 +364,20 @@ mod render_entities_world_query_impls { unsafe impl QueryData for RenderEntity { const IS_READ_ONLY: bool = true; type ReadOnly = RenderEntity; - type Item<'w> = Entity; + type Item<'w, 's> = Entity; - fn shrink<'wlong: 'wshort, 'wshort>(item: Entity) -> Entity { + fn shrink<'wlong: 'wshort, 'wshort, 's>( + item: Self::Item<'wlong, 's>, + ) -> Self::Item<'wshort, 's> { item } #[inline(always)] - unsafe fn fetch<'w>( - fetch: &mut Self::Fetch<'w>, + unsafe fn fetch<'w, 's>( + fetch: &mut Self::Fetch<'w, 's>, entity: Entity, table_row: TableRow, - ) -> Self::Item<'w> { + ) -> Self::Item<'w, 's> { // SAFETY: defers to the `&T` implementation, with T set to `RenderEntity`. let component = unsafe { <&RenderEntity as QueryData>::fetch(fetch, entity, table_row) }; @@ -386,25 +388,31 @@ mod render_entities_world_query_impls { // SAFETY: the underlying `Entity` is copied, and no mutable access is provided. unsafe impl ReadOnlyQueryData for RenderEntity {} + impl ReleaseStateQueryData for RenderEntity { + fn release_state<'w>(item: Self::Item<'w, '_>) -> Self::Item<'w, 'static> { + item + } + } + /// SAFETY: defers completely to `&RenderEntity` implementation, /// and then only modifies the output safely. unsafe impl WorldQuery for MainEntity { - type Fetch<'w> = <&'static MainEntity as WorldQuery>::Fetch<'w>; + type Fetch<'w, 's> = <&'static MainEntity as WorldQuery>::Fetch<'w, 's>; type State = <&'static MainEntity as WorldQuery>::State; - fn shrink_fetch<'wlong: 'wshort, 'wshort>( - fetch: Self::Fetch<'wlong>, - ) -> Self::Fetch<'wshort> { + fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( + fetch: Self::Fetch<'wlong, 's>, + ) -> Self::Fetch<'wshort, 's> { fetch } #[inline] - unsafe fn init_fetch<'w>( + unsafe fn init_fetch<'w, 's>( world: UnsafeWorldCell<'w>, - component_id: &ComponentId, + component_id: &'s ComponentId, last_run: Tick, this_run: Tick, - ) -> Self::Fetch<'w> { + ) -> Self::Fetch<'w, 's> { // SAFETY: defers to the `&T` implementation, with T set to `MainEntity`. unsafe { <&MainEntity as WorldQuery>::init_fetch(world, component_id, last_run, this_run) @@ -414,8 +422,8 @@ mod render_entities_world_query_impls { const IS_DENSE: bool = <&'static MainEntity as WorldQuery>::IS_DENSE; #[inline] - unsafe fn set_archetype<'w>( - fetch: &mut Self::Fetch<'w>, + unsafe fn set_archetype<'w, 's>( + fetch: &mut Self::Fetch<'w, 's>, component_id: &ComponentId, archetype: &'w Archetype, table: &'w Table, @@ -427,9 +435,9 @@ mod render_entities_world_query_impls { } #[inline] - unsafe fn set_table<'w>( - fetch: &mut Self::Fetch<'w>, - &component_id: &ComponentId, + unsafe fn set_table<'w, 's>( + fetch: &mut Self::Fetch<'w, 's>, + &component_id: &'s ComponentId, table: &'w Table, ) { // SAFETY: defers to the `&T` implementation, with T set to `MainEntity`. @@ -464,18 +472,20 @@ mod render_entities_world_query_impls { unsafe impl QueryData for MainEntity { const IS_READ_ONLY: bool = true; type ReadOnly = MainEntity; - type Item<'w> = Entity; + type Item<'w, 's> = Entity; - fn shrink<'wlong: 'wshort, 'wshort>(item: Entity) -> Entity { + fn shrink<'wlong: 'wshort, 'wshort, 's>( + item: Self::Item<'wlong, 's>, + ) -> Self::Item<'wshort, 's> { item } #[inline(always)] - unsafe fn fetch<'w>( - fetch: &mut Self::Fetch<'w>, + unsafe fn fetch<'w, 's>( + fetch: &mut Self::Fetch<'w, 's>, entity: Entity, table_row: TableRow, - ) -> Self::Item<'w> { + ) -> Self::Item<'w, 's> { // SAFETY: defers to the `&T` implementation, with T set to `MainEntity`. let component = unsafe { <&MainEntity as QueryData>::fetch(fetch, entity, table_row) }; component.id() @@ -484,6 +494,12 @@ mod render_entities_world_query_impls { // SAFETY: the underlying `Entity` is copied, and no mutable access is provided. unsafe impl ReadOnlyQueryData for MainEntity {} + + impl ReleaseStateQueryData for MainEntity { + fn release_state<'w>(item: Self::Item<'w, '_>) -> Self::Item<'w, 'static> { + item + } + } } #[cfg(test)] diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index a3d9ee3eb2..204dd85e24 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -778,7 +778,7 @@ impl RenderCommand

for SetMesh2dViewBindGroup( _item: &P, - (view_uniform, mesh2d_view_bind_group): ROQueryItem<'w, Self::ViewQuery>, + (view_uniform, mesh2d_view_bind_group): ROQueryItem<'w, '_, Self::ViewQuery>, _view: Option<()>, _param: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, diff --git a/crates/bevy_sprite/src/mesh2d/wireframe2d.rs b/crates/bevy_sprite/src/mesh2d/wireframe2d.rs index 8ffb12a582..03de94be1c 100644 --- a/crates/bevy_sprite/src/mesh2d/wireframe2d.rs +++ b/crates/bevy_sprite/src/mesh2d/wireframe2d.rs @@ -372,7 +372,7 @@ impl ViewNode for Wireframe2dNode { &self, graph: &mut RenderGraphContext, render_context: &mut RenderContext<'w>, - (camera, view, target, depth): QueryItem<'w, Self::ViewQuery>, + (camera, view, target, depth): QueryItem<'w, '_, Self::ViewQuery>, world: &'w World, ) -> Result<(), NodeRunError> { let Some(wireframe_phase) = diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index 7602addc0b..761e2c628a 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -908,7 +908,7 @@ impl RenderCommand

for SetSpriteViewBindGroup( _item: &P, - (view_uniform, sprite_view_bind_group): ROQueryItem<'w, Self::ViewQuery>, + (view_uniform, sprite_view_bind_group): ROQueryItem<'w, '_, Self::ViewQuery>, _entity: Option<()>, _param: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, @@ -925,7 +925,7 @@ impl RenderCommand

for SetSpriteTextureBindGrou fn render<'w>( item: &P, - view: ROQueryItem<'w, Self::ViewQuery>, + view: ROQueryItem<'w, '_, Self::ViewQuery>, _entity: Option<()>, (image_bind_groups, batches): SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, @@ -955,7 +955,7 @@ impl RenderCommand

for DrawSpriteBatch { fn render<'w>( item: &P, - view: ROQueryItem<'w, Self::ViewQuery>, + view: ROQueryItem<'w, '_, Self::ViewQuery>, _entity: Option<()>, (sprite_meta, batches): SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, diff --git a/crates/bevy_ui/src/render/ui_material_pipeline.rs b/crates/bevy_ui/src/render/ui_material_pipeline.rs index ebdeacccf9..3ad4f4ea6a 100644 --- a/crates/bevy_ui/src/render/ui_material_pipeline.rs +++ b/crates/bevy_ui/src/render/ui_material_pipeline.rs @@ -276,7 +276,7 @@ impl RenderCommand

fn render<'w>( _item: &P, _view: (), - material_handle: Option>, + material_handle: Option>, materials: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { diff --git a/examples/ecs/custom_query_param.rs b/examples/ecs/custom_query_param.rs index 39b3e6f072..8960557ef4 100644 --- a/examples/ecs/custom_query_param.rs +++ b/examples/ecs/custom_query_param.rs @@ -137,7 +137,7 @@ fn print_components_iter_mut( println!("Print components (iter_mut):"); for e in &mut query { // Re-declaring the variable to illustrate the type of the actual iterator item. - let e: CustomQueryItem<'_, _, _> = e; + let e: CustomQueryItem<'_, '_, _, _> = e; println!("Entity: {}", e.entity); println!("A: {:?}", e.a); println!("B: {:?}", e.b); @@ -155,7 +155,7 @@ fn print_components_iter( println!("Print components (iter):"); for e in &query { // Re-declaring the variable to illustrate the type of the actual iterator item. - let e: CustomQueryReadOnlyItem<'_, _, _> = e; + let e: CustomQueryReadOnlyItem<'_, '_, _, _> = e; println!("Entity: {}", e.entity); println!("A: {:?}", e.a); println!("B: {:?}", e.b); diff --git a/examples/shader/custom_phase_item.rs b/examples/shader/custom_phase_item.rs index 1ec61f8126..b363a7c27f 100644 --- a/examples/shader/custom_phase_item.rs +++ b/examples/shader/custom_phase_item.rs @@ -73,8 +73,8 @@ where fn render<'w>( _: &P, - _: ROQueryItem<'w, Self::ViewQuery>, - _: Option>, + _: ROQueryItem<'w, '_, Self::ViewQuery>, + _: Option>, custom_phase_item_buffers: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { diff --git a/examples/shader/custom_render_phase.rs b/examples/shader/custom_render_phase.rs index 6499ca6bd2..8bbf57d73c 100644 --- a/examples/shader/custom_render_phase.rs +++ b/examples/shader/custom_render_phase.rs @@ -588,7 +588,7 @@ impl ViewNode for CustomDrawNode { &self, graph: &mut RenderGraphContext, render_context: &mut RenderContext<'w>, - (camera, view, target): QueryItem<'w, Self::ViewQuery>, + (camera, view, target): QueryItem<'w, '_, Self::ViewQuery>, world: &'w World, ) -> Result<(), NodeRunError> { // First, we need to get our phases resource diff --git a/examples/shader/custom_shader_instancing.rs b/examples/shader/custom_shader_instancing.rs index d81c6e53b6..06845b0617 100644 --- a/examples/shader/custom_shader_instancing.rs +++ b/examples/shader/custom_shader_instancing.rs @@ -88,7 +88,7 @@ impl ExtractComponent for InstanceMaterialData { type QueryFilter = (); type Out = Self; - fn extract_component(item: QueryItem<'_, Self::QueryData>) -> Option { + fn extract_component(item: QueryItem<'_, '_, Self::QueryData>) -> Option { Some(InstanceMaterialData(item.0.clone())) } } diff --git a/release-content/migration-guides/query_items_borrow_from_query_state.md b/release-content/migration-guides/query_items_borrow_from_query_state.md new file mode 100644 index 0000000000..6c5aff0637 --- /dev/null +++ b/release-content/migration-guides/query_items_borrow_from_query_state.md @@ -0,0 +1,65 @@ +--- +title: Query items can borrow from query state +pull_requests: [15396] +--- + +The `QueryData::Item` and `WorldQuery::Fetch` associated types and the `QueryItem` and `ROQueryItem` type aliases now have an additional lifetime parameter corresponding to the `'s` lifetime in `Query`. +Manual implementations of `WorldQuery` and `QueryData` will need to update the method signatures to include the new lifetimes. +Other uses of the types will need to be updated to include a lifetime parameter, although it can usually be passed as `'_`. +In particular, `ROQueryItem` is used when implementing `RenderCommand`. + +Before: + +```rust +// 0.16 +fn render<'w>( + item: &P, + view: ROQueryItem<'w, Self::ViewQuery>, + entity: Option>, + param: SystemParamItem<'w, '_, Self::Param>, + pass: &mut TrackedRenderPass<'w>, +) -> RenderCommandResult; + +// 0.17 +fn render<'w>( + item: &P, + view: ROQueryItem<'w, '_, Self::ViewQuery>, + entity: Option>, + param: SystemParamItem<'w, '_, Self::Param>, + pass: &mut TrackedRenderPass<'w>, +) -> RenderCommandResult; +``` + +--- + +Methods on `QueryState` that take `&mut self` may now result in conflicting borrows if the query items capture the lifetime of the mutable reference. +This affects `get()`, `iter()`, and others. +To fix the errors, first call `QueryState::update_archetypes()`, and then replace a call `state.foo(world, param)` with `state.query_manual(world).foo_inner(param)`. +Alternately, you may be able to restructure the code to call `state.query(world)` once and then make multiple calls using the `Query`. + +```rust +let mut state: QueryState<_, _> = ...; + +// 0.16 +let d1 = state.get(world, e1); +let d2 = state.get(world, e2); // Error: cannot borrow `state` as mutable more than once at a time + +println!("{d1:?}"); +println!("{d2:?}"); + +// 0.17 +state.update_archetypes(world); +let d1 = state.get_manual(world, e1); +let d2 = state.get_manual(world, e2); +// OR +state.update_archetypes(world); +let d1 = state.query_manual(world).get_inner(e1); +let d2 = state.query_manual(world).get_inner(e2); +// OR +let query = state.query(world); +let d1 = query.get_inner(e1); +let d1 = query.get_inner(e2); + +println!("{d1:?}"); +println!("{d2:?}"); +``` From c8cb7bdf5771c2baab3de82edaf254888a5c27b4 Mon Sep 17 00:00:00 2001 From: Lucas Franca Date: Mon, 16 Jun 2025 18:19:47 -0300 Subject: [PATCH 02/25] Allow passing number of thread for building and testing to CI (#19359) # Objective Fixes #16051 Closes #16145 ## Solution Allow passing `--build-jobs` and `--test-threads` to `ci` i.e. ``` cargo run -p ci -- --build-jobs 4 --test-threads 4 ``` ## Testing running ci locally --------- Co-authored-by: Benjamin Brienen --- tools/ci/Cargo.toml | 1 - tools/ci/src/args.rs | 37 ++++++++ tools/ci/src/ci.rs | 84 ++++++++++--------- tools/ci/src/commands/bench_check.rs | 8 +- tools/ci/src/commands/clippy.rs | 8 +- tools/ci/src/commands/compile.rs | 17 ++-- tools/ci/src/commands/compile_check.rs | 8 +- tools/ci/src/commands/compile_fail.rs | 19 +++-- tools/ci/src/commands/doc.rs | 9 +- tools/ci/src/commands/doc_check.rs | 8 +- tools/ci/src/commands/doc_test.rs | 16 ++-- tools/ci/src/commands/example_check.rs | 8 +- tools/ci/src/commands/format.rs | 4 +- tools/ci/src/commands/integration_test.rs | 15 ++-- .../ci/src/commands/integration_test_check.rs | 12 ++- .../ci/src/commands/integration_test_clean.rs | 4 +- tools/ci/src/commands/lints.rs | 9 +- tools/ci/src/commands/test.rs | 13 ++- tools/ci/src/commands/test_check.rs | 8 +- tools/ci/src/main.rs | 1 + tools/ci/src/prepare.rs | 17 +--- 21 files changed, 181 insertions(+), 125 deletions(-) create mode 100644 tools/ci/src/args.rs diff --git a/tools/ci/Cargo.toml b/tools/ci/Cargo.toml index 65d6b7b1be..d42eb55505 100644 --- a/tools/ci/Cargo.toml +++ b/tools/ci/Cargo.toml @@ -8,7 +8,6 @@ license = "MIT OR Apache-2.0" [dependencies] argh = "0.1" xshell = "0.2" -bitflags = "2.3" [lints] workspace = true diff --git a/tools/ci/src/args.rs b/tools/ci/src/args.rs new file mode 100644 index 0000000000..574d98f192 --- /dev/null +++ b/tools/ci/src/args.rs @@ -0,0 +1,37 @@ +use crate::CI; + +/// Arguments that are available to CI commands. +#[derive(Copy, Clone, PartialEq, Eq)] +pub struct Args { + keep_going: bool, + test_threads: Option, + build_jobs: Option, +} + +impl Args { + #[inline(always)] + pub fn keep_going(&self) -> Option<&'static str> { + self.keep_going.then_some("--no-fail-fast") + } + + #[inline(always)] + pub fn build_jobs(&self) -> Option { + self.build_jobs.map(|jobs| format!("--jobs={jobs}")) + } + + #[inline(always)] + pub fn test_threads(&self) -> Option { + self.test_threads + .map(|threads| format!("--test-threads={threads}")) + } +} + +impl From<&CI> for Args { + fn from(value: &CI) -> Self { + Args { + keep_going: value.keep_going, + test_threads: value.test_threads, + build_jobs: value.build_jobs, + } + } +} diff --git a/tools/ci/src/ci.rs b/tools/ci/src/ci.rs index 349e74a5a0..8b8556d90a 100644 --- a/tools/ci/src/ci.rs +++ b/tools/ci/src/ci.rs @@ -1,6 +1,7 @@ use crate::{ + args::Args, commands, - prepare::{Flag, Prepare, PreparedCommand}, + prepare::{Prepare, PreparedCommand}, }; use argh::FromArgs; @@ -12,7 +13,15 @@ pub struct CI { /// continue running commands even if one fails #[argh(switch)] - keep_going: bool, + pub(crate) keep_going: bool, + + /// parallelism of `cargo test` + #[argh(option)] + pub(crate) test_threads: Option, + + /// number of build jobs + #[argh(option)] + pub(crate) build_jobs: Option, } impl CI { @@ -22,7 +31,6 @@ impl CI { /// This is usually related to differing toolchains and configuration. pub fn run(self) { let sh = xshell::Shell::new().unwrap(); - let prepared_commands = self.prepare(&sh); let mut failures = vec![]; @@ -59,34 +67,30 @@ impl CI { } fn prepare<'a>(&self, sh: &'a xshell::Shell) -> Vec> { - let mut flags = Flag::empty(); - - if self.keep_going { - flags |= Flag::KEEP_GOING; - } - + let args = self.into(); match &self.command { - Some(command) => command.prepare(sh, flags), + Some(command) => command.prepare(sh, args), None => { // Note that we are running the subcommands directly rather than using any aliases let mut cmds = vec![]; - cmds.append(&mut commands::FormatCommand::default().prepare(sh, flags)); - cmds.append(&mut commands::ClippyCommand::default().prepare(sh, flags)); - cmds.append(&mut commands::TestCommand::default().prepare(sh, flags)); - cmds.append(&mut commands::TestCheckCommand::default().prepare(sh, flags)); - cmds.append(&mut commands::IntegrationTestCommand::default().prepare(sh, flags)); + cmds.append(&mut commands::FormatCommand::default().prepare(sh, args)); + cmds.append(&mut commands::ClippyCommand::default().prepare(sh, args)); + cmds.append(&mut commands::TestCommand::default().prepare(sh, args)); + cmds.append(&mut commands::TestCheckCommand::default().prepare(sh, args)); + cmds.append(&mut commands::IntegrationTestCommand::default().prepare(sh, args)); cmds.append( - &mut commands::IntegrationTestCheckCommand::default().prepare(sh, flags), + &mut commands::IntegrationTestCheckCommand::default().prepare(sh, args), ); cmds.append( - &mut commands::IntegrationTestCleanCommand::default().prepare(sh, flags), + &mut commands::IntegrationTestCleanCommand::default().prepare(sh, args), ); - cmds.append(&mut commands::DocCheckCommand::default().prepare(sh, flags)); - cmds.append(&mut commands::DocTestCommand::default().prepare(sh, flags)); - cmds.append(&mut commands::CompileCheckCommand::default().prepare(sh, flags)); - cmds.append(&mut commands::CompileFailCommand::default().prepare(sh, flags)); - cmds.append(&mut commands::BenchCheckCommand::default().prepare(sh, flags)); - cmds.append(&mut commands::ExampleCheckCommand::default().prepare(sh, flags)); + cmds.append(&mut commands::DocCheckCommand::default().prepare(sh, args)); + cmds.append(&mut commands::DocTestCommand::default().prepare(sh, args)); + cmds.append(&mut commands::CompileCheckCommand::default().prepare(sh, args)); + cmds.append(&mut commands::CompileFailCommand::default().prepare(sh, args)); + cmds.append(&mut commands::BenchCheckCommand::default().prepare(sh, args)); + cmds.append(&mut commands::ExampleCheckCommand::default().prepare(sh, args)); + cmds } } @@ -118,25 +122,25 @@ enum Commands { } impl Prepare for Commands { - fn prepare<'a>(&self, sh: &'a xshell::Shell, flags: Flag) -> Vec> { + fn prepare<'a>(&self, sh: &'a xshell::Shell, args: Args) -> Vec> { match self { - Commands::Lints(subcommand) => subcommand.prepare(sh, flags), - Commands::Doc(subcommand) => subcommand.prepare(sh, flags), - Commands::Compile(subcommand) => subcommand.prepare(sh, flags), + Commands::Lints(subcommand) => subcommand.prepare(sh, args), + Commands::Doc(subcommand) => subcommand.prepare(sh, args), + Commands::Compile(subcommand) => subcommand.prepare(sh, args), - Commands::Format(subcommand) => subcommand.prepare(sh, flags), - Commands::Clippy(subcommand) => subcommand.prepare(sh, flags), - Commands::Test(subcommand) => subcommand.prepare(sh, flags), - Commands::TestCheck(subcommand) => subcommand.prepare(sh, flags), - Commands::IntegrationTest(subcommand) => subcommand.prepare(sh, flags), - Commands::IntegrationTestCheck(subcommand) => subcommand.prepare(sh, flags), - Commands::IntegrationTestClean(subcommand) => subcommand.prepare(sh, flags), - Commands::DocCheck(subcommand) => subcommand.prepare(sh, flags), - Commands::DocTest(subcommand) => subcommand.prepare(sh, flags), - Commands::CompileCheck(subcommand) => subcommand.prepare(sh, flags), - Commands::CompileFail(subcommand) => subcommand.prepare(sh, flags), - Commands::BenchCheck(subcommand) => subcommand.prepare(sh, flags), - Commands::ExampleCheck(subcommand) => subcommand.prepare(sh, flags), + Commands::Format(subcommand) => subcommand.prepare(sh, args), + Commands::Clippy(subcommand) => subcommand.prepare(sh, args), + Commands::Test(subcommand) => subcommand.prepare(sh, args), + Commands::TestCheck(subcommand) => subcommand.prepare(sh, args), + Commands::IntegrationTest(subcommand) => subcommand.prepare(sh, args), + Commands::IntegrationTestCheck(subcommand) => subcommand.prepare(sh, args), + Commands::IntegrationTestClean(subcommand) => subcommand.prepare(sh, args), + Commands::DocCheck(subcommand) => subcommand.prepare(sh, args), + Commands::DocTest(subcommand) => subcommand.prepare(sh, args), + Commands::CompileCheck(subcommand) => subcommand.prepare(sh, args), + Commands::CompileFail(subcommand) => subcommand.prepare(sh, args), + Commands::BenchCheck(subcommand) => subcommand.prepare(sh, args), + Commands::ExampleCheck(subcommand) => subcommand.prepare(sh, args), } } } diff --git a/tools/ci/src/commands/bench_check.rs b/tools/ci/src/commands/bench_check.rs index 9e72ab0a64..748f10a730 100644 --- a/tools/ci/src/commands/bench_check.rs +++ b/tools/ci/src/commands/bench_check.rs @@ -1,4 +1,4 @@ -use crate::{Flag, Prepare, PreparedCommand}; +use crate::{args::Args, Prepare, PreparedCommand}; use argh::FromArgs; use xshell::cmd; @@ -8,11 +8,13 @@ use xshell::cmd; pub struct BenchCheckCommand {} impl Prepare for BenchCheckCommand { - fn prepare<'a>(&self, sh: &'a xshell::Shell, _flags: Flag) -> Vec> { + fn prepare<'a>(&self, sh: &'a xshell::Shell, args: Args) -> Vec> { + let jobs = args.build_jobs(); + vec![PreparedCommand::new::( cmd!( sh, - "cargo check --benches --target-dir ../target --manifest-path ./benches/Cargo.toml" + "cargo check --benches {jobs...} --target-dir ../target --manifest-path ./benches/Cargo.toml" ), "Failed to check the benches.", )] diff --git a/tools/ci/src/commands/clippy.rs b/tools/ci/src/commands/clippy.rs index 5e097c05a1..7d3d9afb29 100644 --- a/tools/ci/src/commands/clippy.rs +++ b/tools/ci/src/commands/clippy.rs @@ -1,4 +1,4 @@ -use crate::{Flag, Prepare, PreparedCommand}; +use crate::{args::Args, Prepare, PreparedCommand}; use argh::FromArgs; use xshell::cmd; @@ -8,11 +8,13 @@ use xshell::cmd; pub struct ClippyCommand {} impl Prepare for ClippyCommand { - fn prepare<'a>(&self, sh: &'a xshell::Shell, _flags: Flag) -> Vec> { + fn prepare<'a>(&self, sh: &'a xshell::Shell, args: Args) -> Vec> { + let jobs = args.build_jobs(); + vec![PreparedCommand::new::( cmd!( sh, - "cargo clippy --workspace --all-targets --all-features -- -Dwarnings" + "cargo clippy --workspace --all-targets --all-features {jobs...} -- -Dwarnings" ), "Please fix clippy errors in output above.", )] diff --git a/tools/ci/src/commands/compile.rs b/tools/ci/src/commands/compile.rs index f445aaca57..622b5b27f0 100644 --- a/tools/ci/src/commands/compile.rs +++ b/tools/ci/src/commands/compile.rs @@ -1,9 +1,10 @@ use crate::{ + args::Args, commands::{ BenchCheckCommand, CompileCheckCommand, CompileFailCommand, ExampleCheckCommand, IntegrationTestCheckCommand, TestCheckCommand, }, - Flag, Prepare, PreparedCommand, + Prepare, PreparedCommand, }; use argh::FromArgs; @@ -13,14 +14,14 @@ use argh::FromArgs; pub struct CompileCommand {} impl Prepare for CompileCommand { - fn prepare<'a>(&self, sh: &'a xshell::Shell, flags: Flag) -> Vec> { + fn prepare<'a>(&self, sh: &'a xshell::Shell, args: Args) -> Vec> { let mut commands = vec![]; - commands.append(&mut CompileFailCommand::default().prepare(sh, flags)); - commands.append(&mut BenchCheckCommand::default().prepare(sh, flags)); - commands.append(&mut ExampleCheckCommand::default().prepare(sh, flags)); - commands.append(&mut CompileCheckCommand::default().prepare(sh, flags)); - commands.append(&mut TestCheckCommand::default().prepare(sh, flags)); - commands.append(&mut IntegrationTestCheckCommand::default().prepare(sh, flags)); + commands.append(&mut CompileFailCommand::default().prepare(sh, args)); + commands.append(&mut BenchCheckCommand::default().prepare(sh, args)); + commands.append(&mut ExampleCheckCommand::default().prepare(sh, args)); + commands.append(&mut CompileCheckCommand::default().prepare(sh, args)); + commands.append(&mut TestCheckCommand::default().prepare(sh, args)); + commands.append(&mut IntegrationTestCheckCommand::default().prepare(sh, args)); commands } } diff --git a/tools/ci/src/commands/compile_check.rs b/tools/ci/src/commands/compile_check.rs index 62d8a8da75..e3628dc381 100644 --- a/tools/ci/src/commands/compile_check.rs +++ b/tools/ci/src/commands/compile_check.rs @@ -1,4 +1,4 @@ -use crate::{Flag, Prepare, PreparedCommand}; +use crate::{args::Args, Prepare, PreparedCommand}; use argh::FromArgs; use xshell::cmd; @@ -8,9 +8,11 @@ use xshell::cmd; pub struct CompileCheckCommand {} impl Prepare for CompileCheckCommand { - fn prepare<'a>(&self, sh: &'a xshell::Shell, _flags: Flag) -> Vec> { + fn prepare<'a>(&self, sh: &'a xshell::Shell, args: Args) -> Vec> { + let jobs = args.build_jobs(); + vec![PreparedCommand::new::( - cmd!(sh, "cargo check --workspace"), + cmd!(sh, "cargo check --workspace {jobs...}"), "Please fix compiler errors in output above.", )] } diff --git a/tools/ci/src/commands/compile_fail.rs b/tools/ci/src/commands/compile_fail.rs index 65a91e6d2f..e2c6e62d94 100644 --- a/tools/ci/src/commands/compile_fail.rs +++ b/tools/ci/src/commands/compile_fail.rs @@ -1,4 +1,4 @@ -use crate::{Flag, Prepare, PreparedCommand}; +use crate::{args::Args, Prepare, PreparedCommand}; use argh::FromArgs; use xshell::cmd; @@ -8,11 +8,12 @@ use xshell::cmd; pub struct CompileFailCommand {} impl Prepare for CompileFailCommand { - fn prepare<'a>(&self, sh: &'a xshell::Shell, flags: Flag) -> Vec> { - let no_fail_fast = flags - .contains(Flag::KEEP_GOING) - .then_some("--no-fail-fast") - .unwrap_or_default(); + fn prepare<'a>(&self, sh: &'a xshell::Shell, args: Args) -> Vec> { + let no_fail_fast = args.keep_going(); + let jobs = args.build_jobs(); + let test_threads = args.test_threads(); + let jobs_ref = jobs.as_ref(); + let test_threads_ref = test_threads.as_ref(); let mut commands = vec![]; @@ -21,7 +22,7 @@ impl Prepare for CompileFailCommand { // - See crates/bevy_macros_compile_fail_tests/README.md commands.push( PreparedCommand::new::( - cmd!(sh, "cargo test --target-dir ../../../target {no_fail_fast}"), + cmd!(sh, "cargo test --target-dir ../../../target {no_fail_fast...} {jobs_ref...} -- {test_threads_ref...}"), "Compiler errors of the macros compile fail tests seem to be different than expected! Check locally and compare rust versions.", ) .with_subdir("crates/bevy_derive/compile_fail"), @@ -32,7 +33,7 @@ impl Prepare for CompileFailCommand { // - See crates/bevy_ecs_compile_fail_tests/README.md commands.push( PreparedCommand::new::( - cmd!(sh, "cargo test --target-dir ../../../target {no_fail_fast}"), + cmd!(sh, "cargo test --target-dir ../../../target {no_fail_fast...} {jobs_ref...} -- {test_threads_ref...}"), "Compiler errors of the ECS compile fail tests seem to be different than expected! Check locally and compare rust versions.", ) .with_subdir("crates/bevy_ecs/compile_fail"), @@ -43,7 +44,7 @@ impl Prepare for CompileFailCommand { // - See crates/bevy_reflect_compile_fail_tests/README.md commands.push( PreparedCommand::new::( - cmd!(sh, "cargo test --target-dir ../../../target {no_fail_fast}"), + cmd!(sh, "cargo test --target-dir ../../../target {no_fail_fast...} {jobs...} -- {test_threads...}"), "Compiler errors of the Reflect compile fail tests seem to be different than expected! Check locally and compare rust versions.", ) .with_subdir("crates/bevy_reflect/compile_fail"), diff --git a/tools/ci/src/commands/doc.rs b/tools/ci/src/commands/doc.rs index fb8074ca8c..90fbbd30a5 100644 --- a/tools/ci/src/commands/doc.rs +++ b/tools/ci/src/commands/doc.rs @@ -1,6 +1,7 @@ use crate::{ + args::Args, commands::{DocCheckCommand, DocTestCommand}, - Flag, Prepare, PreparedCommand, + Prepare, PreparedCommand, }; use argh::FromArgs; @@ -10,10 +11,10 @@ use argh::FromArgs; pub struct DocCommand {} impl Prepare for DocCommand { - fn prepare<'a>(&self, sh: &'a xshell::Shell, flags: Flag) -> Vec> { + fn prepare<'a>(&self, sh: &'a xshell::Shell, args: Args) -> Vec> { let mut commands = vec![]; - commands.append(&mut DocTestCommand::default().prepare(sh, flags)); - commands.append(&mut DocCheckCommand::default().prepare(sh, flags)); + commands.append(&mut DocTestCommand::default().prepare(sh, args)); + commands.append(&mut DocCheckCommand::default().prepare(sh, args)); commands } } diff --git a/tools/ci/src/commands/doc_check.rs b/tools/ci/src/commands/doc_check.rs index ef5fc502df..75bf0cddf1 100644 --- a/tools/ci/src/commands/doc_check.rs +++ b/tools/ci/src/commands/doc_check.rs @@ -1,4 +1,4 @@ -use crate::{Flag, Prepare, PreparedCommand}; +use crate::{args::Args, Prepare, PreparedCommand}; use argh::FromArgs; use xshell::cmd; @@ -8,11 +8,13 @@ use xshell::cmd; pub struct DocCheckCommand {} impl Prepare for DocCheckCommand { - fn prepare<'a>(&self, sh: &'a xshell::Shell, _flags: Flag) -> Vec> { + fn prepare<'a>(&self, sh: &'a xshell::Shell, args: Args) -> Vec> { + let jobs = args.build_jobs(); + vec![PreparedCommand::new::( cmd!( sh, - "cargo doc --workspace --all-features --no-deps --document-private-items --keep-going" + "cargo doc --workspace --all-features --no-deps --document-private-items {jobs...} --keep-going" ), "Please fix doc warnings in output above.", ) diff --git a/tools/ci/src/commands/doc_test.rs b/tools/ci/src/commands/doc_test.rs index c1ce22c3e6..67d1d21720 100644 --- a/tools/ci/src/commands/doc_test.rs +++ b/tools/ci/src/commands/doc_test.rs @@ -1,4 +1,4 @@ -use crate::{Flag, Prepare, PreparedCommand}; +use crate::{args::Args, Prepare, PreparedCommand}; use argh::FromArgs; use xshell::cmd; @@ -8,14 +8,16 @@ use xshell::cmd; pub struct DocTestCommand {} impl Prepare for DocTestCommand { - fn prepare<'a>(&self, sh: &'a xshell::Shell, flags: Flag) -> Vec> { - let no_fail_fast = flags - .contains(Flag::KEEP_GOING) - .then_some("--no-fail-fast") - .unwrap_or_default(); + fn prepare<'a>(&self, sh: &'a xshell::Shell, args: Args) -> Vec> { + let no_fail_fast = args.keep_going(); + let jobs = args.build_jobs(); + let test_threads = args.test_threads(); vec![PreparedCommand::new::( - cmd!(sh, "cargo test --workspace --doc {no_fail_fast}"), + cmd!( + sh, + "cargo test --workspace --doc {no_fail_fast...} {jobs...} -- {test_threads...}" + ), "Please fix failing doc tests in output above.", )] } diff --git a/tools/ci/src/commands/example_check.rs b/tools/ci/src/commands/example_check.rs index d3d3f5ddf2..6533dbac0e 100644 --- a/tools/ci/src/commands/example_check.rs +++ b/tools/ci/src/commands/example_check.rs @@ -1,4 +1,4 @@ -use crate::{Flag, Prepare, PreparedCommand}; +use crate::{args::Args, Prepare, PreparedCommand}; use argh::FromArgs; use xshell::cmd; @@ -8,9 +8,11 @@ use xshell::cmd; pub struct ExampleCheckCommand {} impl Prepare for ExampleCheckCommand { - fn prepare<'a>(&self, sh: &'a xshell::Shell, _flags: Flag) -> Vec> { + fn prepare<'a>(&self, sh: &'a xshell::Shell, args: Args) -> Vec> { + let jobs = args.build_jobs(); + vec![PreparedCommand::new::( - cmd!(sh, "cargo check --workspace --examples"), + cmd!(sh, "cargo check --workspace --examples {jobs...}"), "Please fix compiler errors for examples in output above.", )] } diff --git a/tools/ci/src/commands/format.rs b/tools/ci/src/commands/format.rs index f5aacc5301..a6ae00cf1f 100644 --- a/tools/ci/src/commands/format.rs +++ b/tools/ci/src/commands/format.rs @@ -1,4 +1,4 @@ -use crate::{Flag, Prepare, PreparedCommand}; +use crate::{args::Args, Prepare, PreparedCommand}; use argh::FromArgs; use xshell::cmd; @@ -8,7 +8,7 @@ use xshell::cmd; pub struct FormatCommand {} impl Prepare for FormatCommand { - fn prepare<'a>(&self, sh: &'a xshell::Shell, _flags: Flag) -> Vec> { + fn prepare<'a>(&self, sh: &'a xshell::Shell, _args: Args) -> Vec> { vec![PreparedCommand::new::( cmd!(sh, "cargo fmt --all -- --check"), "Please run 'cargo fmt --all' to format your code.", diff --git a/tools/ci/src/commands/integration_test.rs b/tools/ci/src/commands/integration_test.rs index b86a027a56..35fefacd24 100644 --- a/tools/ci/src/commands/integration_test.rs +++ b/tools/ci/src/commands/integration_test.rs @@ -1,4 +1,4 @@ -use crate::{commands::get_integration_tests, Flag, Prepare, PreparedCommand}; +use crate::{args::Args, commands::get_integration_tests, Prepare, PreparedCommand}; use argh::FromArgs; use xshell::cmd; @@ -8,11 +8,12 @@ use xshell::cmd; pub struct IntegrationTestCommand {} impl Prepare for IntegrationTestCommand { - fn prepare<'a>(&self, sh: &'a xshell::Shell, flags: Flag) -> Vec> { - let no_fail_fast = flags - .contains(Flag::KEEP_GOING) - .then_some("--no-fail-fast") - .unwrap_or_default(); + fn prepare<'a>(&self, sh: &'a xshell::Shell, args: Args) -> Vec> { + let no_fail_fast = args.keep_going(); + let jobs = args.build_jobs(); + let test_threads = args.test_threads(); + let jobs_ref = jobs.as_ref(); + let test_threads_ref = test_threads.as_ref(); get_integration_tests(sh) .into_iter() @@ -20,7 +21,7 @@ impl Prepare for IntegrationTestCommand { PreparedCommand::new::( cmd!( sh, - "cargo test --manifest-path {path}/Cargo.toml --tests {no_fail_fast}" + "cargo test --manifest-path {path}/Cargo.toml --tests {no_fail_fast...} {jobs_ref...} -- {test_threads_ref...}" ), "Please fix failing integration tests in output above.", ) diff --git a/tools/ci/src/commands/integration_test_check.rs b/tools/ci/src/commands/integration_test_check.rs index d10808a746..af47b9bffa 100644 --- a/tools/ci/src/commands/integration_test_check.rs +++ b/tools/ci/src/commands/integration_test_check.rs @@ -1,6 +1,6 @@ use std::path::Path; -use crate::{Flag, Prepare, PreparedCommand}; +use crate::{args::Args, Prepare, PreparedCommand}; use argh::FromArgs; use xshell::cmd; @@ -21,12 +21,18 @@ pub fn get_integration_tests(sh: &xshell::Shell) -> Vec { pub struct IntegrationTestCheckCommand {} impl Prepare for IntegrationTestCheckCommand { - fn prepare<'a>(&self, sh: &'a xshell::Shell, _flags: Flag) -> Vec> { + fn prepare<'a>(&self, sh: &'a xshell::Shell, args: Args) -> Vec> { + let jobs = args.build_jobs(); + let jobs_ref = jobs.as_ref(); + get_integration_tests(sh) .into_iter() .map(|path| { PreparedCommand::new::( - cmd!(sh, "cargo check --manifest-path {path}/Cargo.toml --tests"), + cmd!( + sh, + "cargo check --manifest-path {path}/Cargo.toml --tests {jobs_ref...}" + ), "Please fix compiler errors for tests in output above.", ) }) diff --git a/tools/ci/src/commands/integration_test_clean.rs b/tools/ci/src/commands/integration_test_clean.rs index 9ad7a44f0b..5ac13bf6ff 100644 --- a/tools/ci/src/commands/integration_test_clean.rs +++ b/tools/ci/src/commands/integration_test_clean.rs @@ -1,4 +1,4 @@ -use crate::{Flag, Prepare, PreparedCommand}; +use crate::{args::Args, Prepare, PreparedCommand}; use argh::FromArgs; use xshell::cmd; @@ -10,7 +10,7 @@ use super::get_integration_tests; pub struct IntegrationTestCleanCommand {} impl Prepare for IntegrationTestCleanCommand { - fn prepare<'a>(&self, sh: &'a xshell::Shell, _flags: Flag) -> Vec> { + fn prepare<'a>(&self, sh: &'a xshell::Shell, _args: Args) -> Vec> { get_integration_tests(sh) .into_iter() .map(|path| { diff --git a/tools/ci/src/commands/lints.rs b/tools/ci/src/commands/lints.rs index befdaf5fc5..f21bb1b8ef 100644 --- a/tools/ci/src/commands/lints.rs +++ b/tools/ci/src/commands/lints.rs @@ -1,6 +1,7 @@ use crate::{ + args::Args, commands::{ClippyCommand, FormatCommand}, - Flag, Prepare, PreparedCommand, + Prepare, PreparedCommand, }; use argh::FromArgs; @@ -10,10 +11,10 @@ use argh::FromArgs; pub struct LintsCommand {} impl Prepare for LintsCommand { - fn prepare<'a>(&self, sh: &'a xshell::Shell, flags: Flag) -> Vec> { + fn prepare<'a>(&self, sh: &'a xshell::Shell, args: Args) -> Vec> { let mut commands = vec![]; - commands.append(&mut FormatCommand::default().prepare(sh, flags)); - commands.append(&mut ClippyCommand::default().prepare(sh, flags)); + commands.append(&mut FormatCommand::default().prepare(sh, args)); + commands.append(&mut ClippyCommand::default().prepare(sh, args)); commands } } diff --git a/tools/ci/src/commands/test.rs b/tools/ci/src/commands/test.rs index bdb4b663d1..a904e59c46 100644 --- a/tools/ci/src/commands/test.rs +++ b/tools/ci/src/commands/test.rs @@ -1,4 +1,4 @@ -use crate::{Flag, Prepare, PreparedCommand}; +use crate::{args::Args, Prepare, PreparedCommand}; use argh::FromArgs; use xshell::cmd; @@ -8,18 +8,17 @@ use xshell::cmd; pub struct TestCommand {} impl Prepare for TestCommand { - fn prepare<'a>(&self, sh: &'a xshell::Shell, flags: Flag) -> Vec> { - let no_fail_fast = flags - .contains(Flag::KEEP_GOING) - .then_some("--no-fail-fast") - .unwrap_or_default(); + fn prepare<'a>(&self, sh: &'a xshell::Shell, args: Args) -> Vec> { + let no_fail_fast = args.keep_going(); + let jobs = args.build_jobs(); + let test_threads = args.test_threads(); vec![PreparedCommand::new::( cmd!( sh, // `--benches` runs each benchmark once in order to verify that they behave // correctly and do not panic. - "cargo test --workspace --lib --bins --tests --benches {no_fail_fast}" + "cargo test --workspace --lib --bins --tests --benches {no_fail_fast...} {jobs...} -- {test_threads...}" ), "Please fix failing tests in output above.", )] diff --git a/tools/ci/src/commands/test_check.rs b/tools/ci/src/commands/test_check.rs index 8671b4f6cf..9f62b15ad1 100644 --- a/tools/ci/src/commands/test_check.rs +++ b/tools/ci/src/commands/test_check.rs @@ -1,4 +1,4 @@ -use crate::{Flag, Prepare, PreparedCommand}; +use crate::{args::Args, Prepare, PreparedCommand}; use argh::FromArgs; use xshell::cmd; @@ -8,9 +8,11 @@ use xshell::cmd; pub struct TestCheckCommand {} impl Prepare for TestCheckCommand { - fn prepare<'a>(&self, sh: &'a xshell::Shell, _flags: Flag) -> Vec> { + fn prepare<'a>(&self, sh: &'a xshell::Shell, args: Args) -> Vec> { + let jobs = args.build_jobs(); + vec![PreparedCommand::new::( - cmd!(sh, "cargo check --workspace --tests"), + cmd!(sh, "cargo check --workspace --tests {jobs...}"), "Please fix compiler errors for tests in output above.", )] } diff --git a/tools/ci/src/main.rs b/tools/ci/src/main.rs index 40893a528d..7758fc9202 100644 --- a/tools/ci/src/main.rs +++ b/tools/ci/src/main.rs @@ -1,5 +1,6 @@ //! CI script used for Bevy. +mod args; mod ci; mod commands; mod prepare; diff --git a/tools/ci/src/prepare.rs b/tools/ci/src/prepare.rs index 923bc33f80..b3a3d8f568 100644 --- a/tools/ci/src/prepare.rs +++ b/tools/ci/src/prepare.rs @@ -1,4 +1,4 @@ -use bitflags::bitflags; +use crate::args::Args; /// Trait for preparing a subcommand to be run. pub trait Prepare { @@ -7,7 +7,7 @@ pub trait Prepare { /// # Example /// /// ``` - /// # use crate::{Flag, Prepare, PreparedCommand}; + /// # use crate::{args::Args, Prepare, PreparedCommand}; /// # use argh::FromArgs; /// # use xshell::Shell; /// # @@ -16,7 +16,7 @@ pub trait Prepare { /// struct CheckCommand {} /// /// impl Prepare for CheckCommand { - /// fn prepare<'a>(&self, sh: &'a Shell, flags: Flag) -> Vec> { + /// fn prepare<'a>(&self, sh: &'a Shell, args: Args) -> Vec> { /// vec![PreparedCommand::new::( /// cmd!(sh, "cargo check --workspace"), /// "Please fix linter errors", @@ -24,16 +24,7 @@ pub trait Prepare { /// } /// } /// ``` - fn prepare<'a>(&self, sh: &'a xshell::Shell, flags: Flag) -> Vec>; -} - -bitflags! { - /// Flags that modify how commands are run. - #[derive(Clone, Copy, Debug, PartialEq, Eq)] - pub struct Flag: u32 { - /// Forces certain checks to continue running even if they hit an error. - const KEEP_GOING = 1 << 0; - } + fn prepare<'a>(&self, sh: &'a xshell::Shell, args: Args) -> Vec>; } /// A command with associated metadata, created from a command that implements [`Prepare`]. From 6db71367d4d2d968b47e9382b85c495596b21f0f Mon Sep 17 00:00:00 2001 From: Austreelis <56743515+Austreelis@users.noreply.github.com> Date: Mon, 16 Jun 2025 23:24:01 +0200 Subject: [PATCH 03/25] allow access to the source error of `AssetLoaderError` and downcasting (#19471) # Objective I have a custom asset loader, and need access to the error it reports when failing to load (e.g. through `AssetLoadFailedEvent { error: AssetLoadError::AssetLoaderError(loader_error), .. }`). However `AssetLoaderError` doesn't expose its `::source()` (i.e. its `error` field. It only formats it when `Display`ed. *I haven't searched for issues about it.* ## Solution - Annotate `AssetLoaderError`'s `error` field with `#[source]`. - Don't include the error when `AssetLoaderError` is `Display`ed (when one prints an error's source stack like a backtrace, it would now be dupplicated). - (optional, included as a separated commit) Add a getter for the `&dyn Error` stored in the `error` field (whithin an `Arc`). This is more ergonomic than using `Error::source()` because it casts an `&Arc` into an `&dyn Error`, meaning one has to downcast it twice to get the original error from the loader, including once where you have to specify the correct type of the *private* `error` field. So downcasting from `Error::source()` effectively rely on the internal implementation of `AssetLoaderError`. The getter instead return the trait object directly, which mean it will directly downcast to the expected loader error type. I didn't included a test that checks that double-downcasting `::source()` doesn't break user code that would rely on the private field's type. ## Testing - Downcasting the trait objects for both `source()` and the `error()` getter work as described above. - `cargo test -p bevy_asset --all-features` pass without errors. --------- Co-authored-by: austreelis --- crates/bevy_asset/src/server/mod.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/bevy_asset/src/server/mod.rs b/crates/bevy_asset/src/server/mod.rs index 2b3898cd54..e120888616 100644 --- a/crates/bevy_asset/src/server/mod.rs +++ b/crates/bevy_asset/src/server/mod.rs @@ -1953,6 +1953,14 @@ impl AssetLoaderError { pub fn path(&self) -> &AssetPath<'static> { &self.path } + + /// The error the loader reported when attempting to load the asset. + /// + /// If you know the type of the error the asset loader returned, you can use + /// [`BevyError::downcast_ref()`] to get it. + pub fn error(&self) -> &BevyError { + &self.error + } } /// An error that occurs while resolving an asset added by `add_async`. From 3b25b38bdc26b255df9fa2827de9e92b053edad4 Mon Sep 17 00:00:00 2001 From: theotherphil Date: Mon, 16 Jun 2025 22:26:24 +0100 Subject: [PATCH 04/25] deny(missing_docs) for bevy_reflect (#19481) ## Objective Deny missing docs in bevy_reflect, towards https://github.com/bevyengine/bevy/issues/3492. ## Solution Add the missing docs! ## Testing N/A --- crates/bevy_reflect/src/array.rs | 1 + crates/bevy_reflect/src/attributes.rs | 5 + crates/bevy_reflect/src/enums/dynamic_enum.rs | 3 + crates/bevy_reflect/src/enums/enum_trait.rs | 6 ++ crates/bevy_reflect/src/enums/variants.rs | 3 + crates/bevy_reflect/src/error.rs | 15 ++- crates/bevy_reflect/src/fields.rs | 3 + crates/bevy_reflect/src/func/args/arg.rs | 3 + crates/bevy_reflect/src/func/args/error.rs | 6 ++ crates/bevy_reflect/src/func/error.rs | 12 ++- crates/bevy_reflect/src/func/info.rs | 6 ++ crates/bevy_reflect/src/func/mod.rs | 2 +- crates/bevy_reflect/src/func/registry.rs | 1 + crates/bevy_reflect/src/kind.rs | 92 +++++++++++++++++++ crates/bevy_reflect/src/lib.rs | 1 - crates/bevy_reflect/src/map.rs | 2 + crates/bevy_reflect/src/reflect.rs | 19 +++- .../src/serde/de/deserialize_with_registry.rs | 3 + .../src/serde/de/registrations.rs | 1 + crates/bevy_reflect/src/serde/mod.rs | 2 + .../src/serde/ser/serializable.rs | 2 + .../src/serde/ser/serialize_with_registry.rs | 3 + crates/bevy_reflect/src/std_traits.rs | 3 + crates/bevy_reflect/src/struct_trait.rs | 2 + crates/bevy_reflect/src/tuple.rs | 1 + crates/bevy_reflect/src/tuple_struct.rs | 1 + crates/bevy_reflect/src/type_info.rs | 31 ++++++- crates/bevy_reflect/src/type_registry.rs | 6 ++ crates/bevy_reflect/src/utility.rs | 3 +- 29 files changed, 229 insertions(+), 9 deletions(-) diff --git a/crates/bevy_reflect/src/array.rs b/crates/bevy_reflect/src/array.rs index 55f62b34c8..df9580b820 100644 --- a/crates/bevy_reflect/src/array.rs +++ b/crates/bevy_reflect/src/array.rs @@ -167,6 +167,7 @@ pub struct DynamicArray { } impl DynamicArray { + /// Creates a new [`DynamicArray`]. #[inline] pub fn new(values: Box<[Box]>) -> Self { Self { diff --git a/crates/bevy_reflect/src/attributes.rs b/crates/bevy_reflect/src/attributes.rs index 728102c4b0..4d8154fad3 100644 --- a/crates/bevy_reflect/src/attributes.rs +++ b/crates/bevy_reflect/src/attributes.rs @@ -1,3 +1,5 @@ +//! Types and functions for creating, manipulating and querying [`CustomAttributes`]. + use crate::Reflect; use alloc::boxed::Box; use bevy_utils::TypeIdMap; @@ -98,16 +100,19 @@ struct CustomAttribute { } impl CustomAttribute { + /// Creates a new [`CustomAttribute`] containing `value`. pub fn new(value: T) -> Self { Self { value: Box::new(value), } } + /// Returns a reference to the attribute's value if it is of type `T`, or [`None`] if not. pub fn value(&self) -> Option<&T> { self.value.downcast_ref() } + /// Returns a reference to the attribute's value as a [`Reflect`] trait object. pub fn reflect_value(&self) -> &dyn Reflect { &*self.value } diff --git a/crates/bevy_reflect/src/enums/dynamic_enum.rs b/crates/bevy_reflect/src/enums/dynamic_enum.rs index 3f0b275519..2835306b22 100644 --- a/crates/bevy_reflect/src/enums/dynamic_enum.rs +++ b/crates/bevy_reflect/src/enums/dynamic_enum.rs @@ -13,9 +13,12 @@ use derive_more::derive::From; /// A dynamic representation of an enum variant. #[derive(Debug, Default, From)] pub enum DynamicVariant { + /// A unit variant. #[default] Unit, + /// A tuple variant. Tuple(DynamicTuple), + /// A struct variant. Struct(DynamicStruct), } diff --git a/crates/bevy_reflect/src/enums/enum_trait.rs b/crates/bevy_reflect/src/enums/enum_trait.rs index 126c407f23..32e4b96124 100644 --- a/crates/bevy_reflect/src/enums/enum_trait.rs +++ b/crates/bevy_reflect/src/enums/enum_trait.rs @@ -263,6 +263,7 @@ pub struct VariantFieldIter<'a> { } impl<'a> VariantFieldIter<'a> { + /// Creates a new [`VariantFieldIter`]. pub fn new(container: &'a dyn Enum) -> Self { Self { container, @@ -295,12 +296,16 @@ impl<'a> Iterator for VariantFieldIter<'a> { impl<'a> ExactSizeIterator for VariantFieldIter<'a> {} +/// A field in the current enum variant. pub enum VariantField<'a> { + /// The name and value of a field in a struct variant. Struct(&'a str, &'a dyn PartialReflect), + /// The value of a field in a tuple variant. Tuple(&'a dyn PartialReflect), } impl<'a> VariantField<'a> { + /// Returns the name of a struct variant field, or [`None`] for a tuple variant field. pub fn name(&self) -> Option<&'a str> { if let Self::Struct(name, ..) = self { Some(*name) @@ -309,6 +314,7 @@ impl<'a> VariantField<'a> { } } + /// Gets a reference to the value of this field. pub fn value(&self) -> &'a dyn PartialReflect { match *self { Self::Struct(_, value) | Self::Tuple(value) => value, diff --git a/crates/bevy_reflect/src/enums/variants.rs b/crates/bevy_reflect/src/enums/variants.rs index 55ccb8efb1..d4fcc2845a 100644 --- a/crates/bevy_reflect/src/enums/variants.rs +++ b/crates/bevy_reflect/src/enums/variants.rs @@ -47,7 +47,9 @@ pub enum VariantInfoError { /// [type]: VariantType #[error("variant type mismatch: expected {expected:?}, received {received:?}")] TypeMismatch { + /// Expected variant type. expected: VariantType, + /// Received variant type. received: VariantType, }, } @@ -84,6 +86,7 @@ pub enum VariantInfo { } impl VariantInfo { + /// The name of the enum variant. pub fn name(&self) -> &'static str { match self { Self::Struct(info) => info.name(), diff --git a/crates/bevy_reflect/src/error.rs b/crates/bevy_reflect/src/error.rs index a13b55cdc0..d8bb8a9e14 100644 --- a/crates/bevy_reflect/src/error.rs +++ b/crates/bevy_reflect/src/error.rs @@ -11,14 +11,20 @@ pub enum ReflectCloneError { /// /// [`PartialReflect::reflect_clone`]: crate::PartialReflect::reflect_clone #[error("`PartialReflect::reflect_clone` not implemented for `{type_path}`")] - NotImplemented { type_path: Cow<'static, str> }, + NotImplemented { + /// The fully qualified path of the type that [`PartialReflect::reflect_clone`](crate::PartialReflect::reflect_clone) is not implemented for. + type_path: Cow<'static, str>, + }, /// The type cannot be cloned via [`PartialReflect::reflect_clone`]. /// /// This type should be returned when a type is intentionally opting out of reflection cloning. /// /// [`PartialReflect::reflect_clone`]: crate::PartialReflect::reflect_clone #[error("`{type_path}` cannot be made cloneable for `PartialReflect::reflect_clone`")] - NotCloneable { type_path: Cow<'static, str> }, + NotCloneable { + /// The fully qualified path of the type that cannot be cloned via [`PartialReflect::reflect_clone`](crate::PartialReflect::reflect_clone). + type_path: Cow<'static, str>, + }, /// The field cannot be cloned via [`PartialReflect::reflect_clone`]. /// /// When [deriving `Reflect`], this usually means that a field marked with `#[reflect(ignore)]` @@ -33,8 +39,11 @@ pub enum ReflectCloneError { full_path(.field, .variant.as_deref(), .container_type_path) )] FieldNotCloneable { + /// Struct field or enum variant field which cannot be cloned. field: FieldId, + /// Variant this field is part of if the container is an enum, otherwise [`None`]. variant: Option>, + /// Fully qualified path of the type containing this field. container_type_path: Cow<'static, str>, }, /// Could not downcast to the expected type. @@ -44,7 +53,9 @@ pub enum ReflectCloneError { /// [`Reflect`]: crate::Reflect #[error("expected downcast to `{expected}`, but received `{received}`")] FailedDowncast { + /// The fully qualified path of the type that was expected. expected: Cow<'static, str>, + /// The fully qualified path of the type that was received. received: Cow<'static, str>, }, } diff --git a/crates/bevy_reflect/src/fields.rs b/crates/bevy_reflect/src/fields.rs index 21d4ccd98a..53223835b3 100644 --- a/crates/bevy_reflect/src/fields.rs +++ b/crates/bevy_reflect/src/fields.rs @@ -82,6 +82,7 @@ pub struct UnnamedField { } impl UnnamedField { + /// Create a new [`UnnamedField`]. pub fn new(index: usize) -> Self { Self { index, @@ -135,7 +136,9 @@ impl UnnamedField { /// A representation of a field's accessor. #[derive(Clone, Debug, PartialEq, Eq)] pub enum FieldId { + /// Access a field by name. Named(Cow<'static, str>), + /// Access a field by index. Unnamed(usize), } diff --git a/crates/bevy_reflect/src/func/args/arg.rs b/crates/bevy_reflect/src/func/args/arg.rs index 8ca03aafd3..1c157a6b2f 100644 --- a/crates/bevy_reflect/src/func/args/arg.rs +++ b/crates/bevy_reflect/src/func/args/arg.rs @@ -196,8 +196,11 @@ impl<'a> Arg<'a> { /// [`DynamicFunctionMut`]: crate::func::DynamicFunctionMut #[derive(Debug)] pub enum ArgValue<'a> { + /// An owned argument. Owned(Box), + /// An immutable reference argument. Ref(&'a dyn PartialReflect), + /// A mutable reference argument. Mut(&'a mut dyn PartialReflect), } diff --git a/crates/bevy_reflect/src/func/args/error.rs b/crates/bevy_reflect/src/func/args/error.rs index bd32bd5e5a..20b6cd6220 100644 --- a/crates/bevy_reflect/src/func/args/error.rs +++ b/crates/bevy_reflect/src/func/args/error.rs @@ -12,15 +12,21 @@ pub enum ArgError { /// The argument is not the expected type. #[error("expected `{expected}` but received `{received}` (@ argument index {index})")] UnexpectedType { + /// Argument index. index: usize, + /// Expected argument type path. expected: Cow<'static, str>, + /// Received argument type path. received: Cow<'static, str>, }, /// The argument has the wrong ownership. #[error("expected {expected} value but received {received} value (@ argument index {index})")] InvalidOwnership { + /// Argument index. index: usize, + /// Expected ownership. expected: Ownership, + /// Received ownership. received: Ownership, }, /// Occurs when attempting to access an argument from an empty [`ArgList`]. diff --git a/crates/bevy_reflect/src/func/error.rs b/crates/bevy_reflect/src/func/error.rs index d9d105db1b..dc442e9da8 100644 --- a/crates/bevy_reflect/src/func/error.rs +++ b/crates/bevy_reflect/src/func/error.rs @@ -18,11 +18,18 @@ pub enum FunctionError { ArgError(#[from] ArgError), /// The number of arguments provided does not match the expected number. #[error("received {received} arguments but expected one of {expected:?}")] - ArgCountMismatch { expected: ArgCount, received: usize }, + ArgCountMismatch { + /// Expected argument count. [`ArgCount`] for overloaded functions will contain multiple possible counts. + expected: ArgCount, + /// Number of arguments received. + received: usize, + }, /// No overload was found for the given set of arguments. #[error("no overload found for arguments with signature `{received:?}`, expected one of `{expected:?}`")] NoOverload { + /// The set of available argument signatures. expected: HashSet, + /// The received argument signature. received: ArgumentSignature, }, } @@ -47,6 +54,9 @@ pub enum FunctionOverloadError { /// An error that occurs when attempting to add a function overload with a duplicate signature. #[error("could not add function overload: duplicate found for signature `{0:?}`")] DuplicateSignature(ArgumentSignature), + /// An attempt was made to add an overload with more than [`ArgCount::MAX_COUNT`] arguments. + /// + /// [`ArgCount::MAX_COUNT`]: crate::func::args::ArgCount #[error( "argument signature `{:?}` has too many arguments (max {})", 0, diff --git a/crates/bevy_reflect/src/func/info.rs b/crates/bevy_reflect/src/func/info.rs index 4b130e5772..2f5f82fbf5 100644 --- a/crates/bevy_reflect/src/func/info.rs +++ b/crates/bevy_reflect/src/func/info.rs @@ -235,6 +235,12 @@ impl TryFrom<[SignatureInfo; N]> for FunctionInfo { } } +/// Type information for the signature of a [`DynamicFunction`] or [`DynamicFunctionMut`]. +/// +/// Every [`FunctionInfo`] contains one or more [`SignatureInfo`]s. +/// +/// [`DynamicFunction`]: crate::func::DynamicFunction +/// [`DynamicFunctionMut`]: crate::func::DynamicFunctionMut #[derive(Debug, Clone)] pub struct SignatureInfo { name: Option>, diff --git a/crates/bevy_reflect/src/func/mod.rs b/crates/bevy_reflect/src/func/mod.rs index 74a89282c6..237bc9eafc 100644 --- a/crates/bevy_reflect/src/func/mod.rs +++ b/crates/bevy_reflect/src/func/mod.rs @@ -42,7 +42,7 @@ //! //! A "function" is a callable that does not capture its environment. //! These are typically defined with the `fn` keyword, which are referred to as _named_ functions. -//! But they are also _anonymous_ functions, which are unnamed and defined with anonymous function syntax. +//! But there are also _anonymous_ functions, which are unnamed and defined with anonymous function syntax. //! //! ```rust //! // This is a named function: diff --git a/crates/bevy_reflect/src/func/registry.rs b/crates/bevy_reflect/src/func/registry.rs index e476353b8b..08ed7bd7f1 100644 --- a/crates/bevy_reflect/src/func/registry.rs +++ b/crates/bevy_reflect/src/func/registry.rs @@ -336,6 +336,7 @@ impl Debug for FunctionRegistry { /// A synchronized wrapper around a [`FunctionRegistry`]. #[derive(Clone, Default, Debug)] pub struct FunctionRegistryArc { + /// The wrapped [`FunctionRegistry`]. pub internal: Arc>, } diff --git a/crates/bevy_reflect/src/kind.rs b/crates/bevy_reflect/src/kind.rs index 3eef10d0e5..3764e863ba 100644 --- a/crates/bevy_reflect/src/kind.rs +++ b/crates/bevy_reflect/src/kind.rs @@ -134,7 +134,9 @@ macro_rules! impl_reflect_kind_conversions { #[derive(Debug, Error)] #[error("kind mismatch: expected {expected:?}, received {received:?}")] pub struct ReflectKindMismatchError { + /// Expected kind. pub expected: ReflectKind, + /// Received kind. pub received: ReflectKind, } @@ -176,16 +178,46 @@ macro_rules! impl_cast_method { /// /// ["kinds"]: ReflectKind pub enum ReflectRef<'a> { + /// An immutable reference to a [struct-like] type. + /// + /// [struct-like]: Struct Struct(&'a dyn Struct), + /// An immutable reference to a [tuple-struct-like] type. + /// + /// [tuple-struct-like]: TupleStruct TupleStruct(&'a dyn TupleStruct), + /// An immutable reference to a [tuple-like] type. + /// + /// [tuple-like]: Tuple Tuple(&'a dyn Tuple), + /// An immutable reference to a [list-like] type. + /// + /// [list-like]: List List(&'a dyn List), + /// An immutable reference to an [array-like] type. + /// + /// [array-like]: Array Array(&'a dyn Array), + /// An immutable reference to a [map-like] type. + /// + /// [map-like]: Map Map(&'a dyn Map), + /// An immutable reference to a [set-like] type. + /// + /// [set-like]: Set Set(&'a dyn Set), + /// An immutable reference to an [enum-like] type. + /// + /// [enum-like]: Enum Enum(&'a dyn Enum), + /// An immutable reference to a [function-like] type. + /// + /// [function-like]: Function #[cfg(feature = "functions")] Function(&'a dyn Function), + /// An immutable refeence to an [opaque] type. + /// + /// [opaque]: ReflectKind::Opaque Opaque(&'a dyn PartialReflect), } impl_reflect_kind_conversions!(ReflectRef<'_>); @@ -211,16 +243,46 @@ impl<'a> ReflectRef<'a> { /// /// ["kinds"]: ReflectKind pub enum ReflectMut<'a> { + /// A mutable reference to a [struct-like] type. + /// + /// [struct-like]: Struct Struct(&'a mut dyn Struct), + /// A mutable reference to a [tuple-struct-like] type. + /// + /// [tuple-struct-like]: TupleStruct TupleStruct(&'a mut dyn TupleStruct), + /// A mutable reference to a [tuple-like] type. + /// + /// [tuple-like]: Tuple Tuple(&'a mut dyn Tuple), + /// A mutable reference to a [list-like] type. + /// + /// [list-like]: List List(&'a mut dyn List), + /// A mutable reference to an [array-like] type. + /// + /// [array-like]: Array Array(&'a mut dyn Array), + /// A mutable reference to a [map-like] type. + /// + /// [map-like]: Map Map(&'a mut dyn Map), + /// A mutable reference to a [set-like] type. + /// + /// [set-like]: Set Set(&'a mut dyn Set), + /// A mutable reference to an [enum-like] type. + /// + /// [enum-like]: Enum Enum(&'a mut dyn Enum), #[cfg(feature = "functions")] + /// A mutable reference to a [function-like] type. + /// + /// [function-like]: Function Function(&'a mut dyn Function), + /// A mutable refeence to an [opaque] type. + /// + /// [opaque]: ReflectKind::Opaque Opaque(&'a mut dyn PartialReflect), } impl_reflect_kind_conversions!(ReflectMut<'_>); @@ -246,16 +308,46 @@ impl<'a> ReflectMut<'a> { /// /// ["kinds"]: ReflectKind pub enum ReflectOwned { + /// An owned [struct-like] type. + /// + /// [struct-like]: Struct Struct(Box), + /// An owned [tuple-struct-like] type. + /// + /// [tuple-struct-like]: TupleStruct TupleStruct(Box), + /// An owned [tuple-like] type. + /// + /// [tuple-like]: Tuple Tuple(Box), + /// An owned [list-like] type. + /// + /// [list-like]: List List(Box), + /// An owned [array-like] type. + /// + /// [array-like]: Array Array(Box), + /// An owned [map-like] type. + /// + /// [map-like]: Map Map(Box), + /// An owned [set-like] type. + /// + /// [set-like]: Set Set(Box), + /// An owned [enum-like] type. + /// + /// [enum-like]: Enum Enum(Box), + /// An owned [function-like] type. + /// + /// [function-like]: Function #[cfg(feature = "functions")] Function(Box), + /// An owned [opaque] type. + /// + /// [opaque]: ReflectKind::Opaque Opaque(Box), } impl_reflect_kind_conversions!(ReflectOwned); diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index 0f399afd59..eabfdc0eac 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -1,4 +1,3 @@ -#![expect(missing_docs, reason = "Not all docs are written yet, see #3492.")] #![cfg_attr( any(docsrs, docsrs_dep), expect( diff --git a/crates/bevy_reflect/src/map.rs b/crates/bevy_reflect/src/map.rs index 1a1fcefb63..20531569e8 100644 --- a/crates/bevy_reflect/src/map.rs +++ b/crates/bevy_reflect/src/map.rs @@ -192,6 +192,8 @@ impl MapInfo { impl_generic_info_methods!(generics); } +/// Used to produce an error message when an attempt is made to hash +/// a [`PartialReflect`] value that does not support hashing. #[macro_export] macro_rules! hash_error { ( $key:expr ) => {{ diff --git a/crates/bevy_reflect/src/reflect.rs b/crates/bevy_reflect/src/reflect.rs index 04e4a2a4b0..c1e283a5f4 100644 --- a/crates/bevy_reflect/src/reflect.rs +++ b/crates/bevy_reflect/src/reflect.rs @@ -21,32 +21,47 @@ pub enum ApplyError { #[error("attempted to apply `{from_kind}` to `{to_kind}`")] /// Attempted to apply the wrong [kind](ReflectKind) to a type, e.g. a struct to an enum. MismatchedKinds { + /// Kind of the value we attempted to apply. from_kind: ReflectKind, + /// Kind of the type we attempted to apply the value to. to_kind: ReflectKind, }, #[error("enum variant `{variant_name}` doesn't have a field named `{field_name}`")] /// Enum variant that we tried to apply to was missing a field. MissingEnumField { + /// Name of the enum variant. variant_name: Box, + /// Name of the missing field. field_name: Box, }, #[error("`{from_type}` is not `{to_type}`")] /// Tried to apply incompatible types. MismatchedTypes { + /// Type of the value we attempted to apply. from_type: Box, + /// Type we attempted to apply the value to. to_type: Box, }, #[error("attempted to apply type with {from_size} size to a type with {to_size} size")] - /// Attempted to apply to types with mismatched sizes, e.g. a [u8; 4] to [u8; 3]. - DifferentSize { from_size: usize, to_size: usize }, + /// Attempted to apply an [array-like] type to another of different size, e.g. a [u8; 4] to [u8; 3]. + /// + /// [array-like]: crate::Array + DifferentSize { + /// Size of the value we attempted to apply, in elements. + from_size: usize, + /// Size of the type we attempted to apply the value to, in elements. + to_size: usize, + }, #[error("variant with name `{variant_name}` does not exist on enum `{enum_name}`")] /// The enum we tried to apply to didn't contain a variant with the give name. UnknownVariant { + /// Name of the enum. enum_name: Box, + /// Name of the missing variant. variant_name: Box, }, } diff --git a/crates/bevy_reflect/src/serde/de/deserialize_with_registry.rs b/crates/bevy_reflect/src/serde/de/deserialize_with_registry.rs index f92a8e68e2..8a216f87b9 100644 --- a/crates/bevy_reflect/src/serde/de/deserialize_with_registry.rs +++ b/crates/bevy_reflect/src/serde/de/deserialize_with_registry.rs @@ -42,6 +42,9 @@ use serde::Deserializer; /// [`ReflectDeserializer`]: crate::serde::ReflectDeserializer /// [via the registry]: TypeRegistry::register_type_data pub trait DeserializeWithRegistry<'de>: Sized { + /// Deserialize this value using the given [Deserializer] and [`TypeRegistry`]. + /// + /// [`Deserializer`]: ::serde::Deserializer fn deserialize(deserializer: D, registry: &TypeRegistry) -> Result where D: Deserializer<'de>; diff --git a/crates/bevy_reflect/src/serde/de/registrations.rs b/crates/bevy_reflect/src/serde/de/registrations.rs index adc0025c54..768b8ed32f 100644 --- a/crates/bevy_reflect/src/serde/de/registrations.rs +++ b/crates/bevy_reflect/src/serde/de/registrations.rs @@ -15,6 +15,7 @@ pub struct TypeRegistrationDeserializer<'a> { } impl<'a> TypeRegistrationDeserializer<'a> { + /// Creates a new [`TypeRegistrationDeserializer`]. pub fn new(registry: &'a TypeRegistry) -> Self { Self { registry } } diff --git a/crates/bevy_reflect/src/serde/mod.rs b/crates/bevy_reflect/src/serde/mod.rs index 032590e0c7..2ee47d4a7f 100644 --- a/crates/bevy_reflect/src/serde/mod.rs +++ b/crates/bevy_reflect/src/serde/mod.rs @@ -1,3 +1,5 @@ +//! Serde integration for reflected types. + mod de; mod ser; mod type_data; diff --git a/crates/bevy_reflect/src/serde/ser/serializable.rs b/crates/bevy_reflect/src/serde/ser/serializable.rs index 6a8a4c978f..c83737c842 100644 --- a/crates/bevy_reflect/src/serde/ser/serializable.rs +++ b/crates/bevy_reflect/src/serde/ser/serializable.rs @@ -3,7 +3,9 @@ use core::ops::Deref; /// A type-erased serializable value. pub enum Serializable<'a> { + /// An owned serializable value. Owned(Box), + /// An immutable reference to a serializable value. Borrowed(&'a dyn erased_serde::Serialize), } diff --git a/crates/bevy_reflect/src/serde/ser/serialize_with_registry.rs b/crates/bevy_reflect/src/serde/ser/serialize_with_registry.rs index 9c5bfb06f1..f9e6370799 100644 --- a/crates/bevy_reflect/src/serde/ser/serialize_with_registry.rs +++ b/crates/bevy_reflect/src/serde/ser/serialize_with_registry.rs @@ -40,6 +40,9 @@ use serde::{Serialize, Serializer}; /// [`ReflectSerializer`]: crate::serde::ReflectSerializer /// [via the registry]: TypeRegistry::register_type_data pub trait SerializeWithRegistry { + /// Serialize this value using the given [Serializer] and [`TypeRegistry`]. + /// + /// [`Serializer`]: ::serde::Serializer fn serialize(&self, serializer: S, registry: &TypeRegistry) -> Result where S: Serializer; diff --git a/crates/bevy_reflect/src/std_traits.rs b/crates/bevy_reflect/src/std_traits.rs index cad001132b..9b7f46c300 100644 --- a/crates/bevy_reflect/src/std_traits.rs +++ b/crates/bevy_reflect/src/std_traits.rs @@ -1,3 +1,5 @@ +//! Module containing the [`ReflectDefault`] type. + use crate::{FromType, Reflect}; use alloc::boxed::Box; @@ -10,6 +12,7 @@ pub struct ReflectDefault { } impl ReflectDefault { + /// Returns the default value for a type. pub fn default(&self) -> Box { (self.default)() } diff --git a/crates/bevy_reflect/src/struct_trait.rs b/crates/bevy_reflect/src/struct_trait.rs index 4346f55e27..e419947b3a 100644 --- a/crates/bevy_reflect/src/struct_trait.rs +++ b/crates/bevy_reflect/src/struct_trait.rs @@ -71,6 +71,7 @@ pub trait Struct: PartialReflect { /// Returns an iterator over the values of the reflectable fields for this struct. fn iter_fields(&self) -> FieldIter; + /// Creates a new [`DynamicStruct`] from this struct. fn to_dynamic_struct(&self) -> DynamicStruct { let mut dynamic_struct = DynamicStruct::default(); dynamic_struct.set_represented_type(self.get_represented_type_info()); @@ -192,6 +193,7 @@ pub struct FieldIter<'a> { } impl<'a> FieldIter<'a> { + /// Creates a new [`FieldIter`]. pub fn new(value: &'a dyn Struct) -> Self { FieldIter { struct_val: value, diff --git a/crates/bevy_reflect/src/tuple.rs b/crates/bevy_reflect/src/tuple.rs index 8bdd08099b..51f402c698 100644 --- a/crates/bevy_reflect/src/tuple.rs +++ b/crates/bevy_reflect/src/tuple.rs @@ -76,6 +76,7 @@ pub struct TupleFieldIter<'a> { } impl<'a> TupleFieldIter<'a> { + /// Creates a new [`TupleFieldIter`]. pub fn new(value: &'a dyn Tuple) -> Self { TupleFieldIter { tuple: value, diff --git a/crates/bevy_reflect/src/tuple_struct.rs b/crates/bevy_reflect/src/tuple_struct.rs index ab5b99a96b..cceab9904e 100644 --- a/crates/bevy_reflect/src/tuple_struct.rs +++ b/crates/bevy_reflect/src/tuple_struct.rs @@ -146,6 +146,7 @@ pub struct TupleStructFieldIter<'a> { } impl<'a> TupleStructFieldIter<'a> { + /// Creates a new [`TupleStructFieldIter`]. pub fn new(value: &'a dyn TupleStruct) -> Self { TupleStructFieldIter { tuple_struct: value, diff --git a/crates/bevy_reflect/src/type_info.rs b/crates/bevy_reflect/src/type_info.rs index 1a3be15c36..122ace0293 100644 --- a/crates/bevy_reflect/src/type_info.rs +++ b/crates/bevy_reflect/src/type_info.rs @@ -169,7 +169,9 @@ pub enum TypeInfoError { /// [kind]: ReflectKind #[error("kind mismatch: expected {expected:?}, received {received:?}")] KindMismatch { + /// Expected kind. expected: ReflectKind, + /// Received kind. received: ReflectKind, }, } @@ -183,7 +185,7 @@ pub enum TypeInfoError { /// 3. [`PartialReflect::get_represented_type_info`] /// 4. [`TypeRegistry::get_type_info`] /// -/// Each return a static reference to [`TypeInfo`], but they all have their own use cases. +/// Each returns a static reference to [`TypeInfo`], but they all have their own use cases. /// For example, if you know the type at compile time, [`Typed::type_info`] is probably /// the simplest. If you have a `dyn Reflect` you can use [`DynamicTyped::reflect_type_info`]. /// If all you have is a `dyn PartialReflect`, you'll probably want [`PartialReflect::get_represented_type_info`]. @@ -199,14 +201,40 @@ pub enum TypeInfoError { /// [type path]: TypePath::type_path #[derive(Debug, Clone)] pub enum TypeInfo { + /// Type information for a [struct-like] type. + /// + /// [struct-like]: crate::Struct Struct(StructInfo), + /// Type information for a [tuple-struct-like] type. + /// + /// [tuple-struct-like]: crate::TupleStruct TupleStruct(TupleStructInfo), + /// Type information for a [tuple-like] type. + /// + /// [tuple-like]: crate::Tuple Tuple(TupleInfo), + /// Type information for a [list-like] type. + /// + /// [list-like]: crate::List List(ListInfo), + /// Type information for an [array-like] type. + /// + /// [array-like]: crate::Array Array(ArrayInfo), + /// Type information for a [map-like] type. + /// + /// [map-like]: crate::Map Map(MapInfo), + /// Type information for a [set-like] type. + /// + /// [set-like]: crate::Set Set(SetInfo), + /// Type information for an [enum-like] type. + /// + /// [enum-like]: crate::Enum Enum(EnumInfo), + /// Type information for an opaque type - see the [`OpaqueInfo`] docs for + /// a discussion of opaque types. Opaque(OpaqueInfo), } @@ -557,6 +585,7 @@ pub struct OpaqueInfo { } impl OpaqueInfo { + /// Creates a new [`OpaqueInfo`]. pub fn new() -> Self { Self { ty: Type::of::(), diff --git a/crates/bevy_reflect/src/type_registry.rs b/crates/bevy_reflect/src/type_registry.rs index 5827ebdac5..1f18396be2 100644 --- a/crates/bevy_reflect/src/type_registry.rs +++ b/crates/bevy_reflect/src/type_registry.rs @@ -38,6 +38,7 @@ pub struct TypeRegistry { /// A synchronized wrapper around a [`TypeRegistry`]. #[derive(Clone, Default)] pub struct TypeRegistryArc { + /// The wrapped [`TypeRegistry`]. pub internal: Arc>, } @@ -313,6 +314,7 @@ impl TypeRegistry { data.insert(D::from_type()); } + /// Whether the type with given [`TypeId`] has been registered in this registry. pub fn contains(&self, type_id: TypeId) -> bool { self.registrations.contains_key(&type_id) } @@ -684,6 +686,7 @@ impl Clone for TypeRegistration { /// /// [crate-level documentation]: crate pub trait TypeData: Downcast + Send + Sync { + /// Creates a type-erased clone of this value. fn clone_type_data(&self) -> Box; } impl_downcast!(TypeData); @@ -702,6 +705,7 @@ where /// This is used by the `#[derive(Reflect)]` macro to generate an implementation /// of [`TypeData`] to pass to [`TypeRegistration::insert`]. pub trait FromType { + /// Creates an instance of `Self` for type `T`. fn from_type() -> Self; } @@ -746,6 +750,8 @@ impl ReflectSerialize { /// [`FromType::from_type`]. #[derive(Clone)] pub struct ReflectDeserialize { + /// Function used by [`ReflectDeserialize::deserialize`] to + /// perform deserialization. pub func: fn( deserializer: &mut dyn erased_serde::Deserializer, ) -> Result, erased_serde::Error>, diff --git a/crates/bevy_reflect/src/utility.rs b/crates/bevy_reflect/src/utility.rs index 5735a29dbe..db8416bd6c 100644 --- a/crates/bevy_reflect/src/utility.rs +++ b/crates/bevy_reflect/src/utility.rs @@ -16,6 +16,7 @@ use core::{ /// /// [`Non`]: NonGenericTypeCell pub trait TypedProperty: sealed::Sealed { + /// The type of the value stored in [`GenericTypeCell`]. type Stored: 'static; } @@ -201,7 +202,7 @@ impl Default for NonGenericTypeCell { /// static CELL: GenericTypePathCell = GenericTypePathCell::new(); /// CELL.get_or_insert::(|| format!("my_crate::foo::Foo<{}>", T::type_path())) /// } -/// +/// /// fn short_type_path() -> &'static str { /// static CELL: GenericTypePathCell = GenericTypePathCell::new(); /// CELL.get_or_insert::(|| format!("Foo<{}>", T::short_type_path())) From 8661e914a57d4e5c8bec20f0a840041c0e8aea25 Mon Sep 17 00:00:00 2001 From: SilentSpaceTraveller Date: Mon, 16 Jun 2025 23:30:55 +0200 Subject: [PATCH 05/25] bevy_log: refactor how log layers are wired together (#19248) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Objective Current way to wire `Layer`s together using `layer.with(new_layer)` in the `bevy_log` plugin is brittle and not flexible. As #17722 demonstrated, the current solution makes it very hard to do any kind of advanced wiring, as the type system of `tracing::Subscriber` gets in the way very quickly (the type of each new layer depends on the type of the previous ones). We want to make it easier to have more complex wiring of `Layers`. It would be hard to solve #19085 without it ## Solution It aims to be functionally equivalent. - Replace of using `layer.with(new_layer)` . We now add `layer.boxed()` to a `Vec`. It is a solution recommended by `tracing_subscriber::Layer` for complex wiring cases (See https://docs.rs/tracing-subscriber/latest/tracing_subscriber/layer/index.html#runtime-configuration-with-layers) - Do some refactoring and clean up that is now enabled by the new solution ## Testing - Ran CI locally on Linux - Ran the logs examples - Need people familiar with the features `trace`, `tracing-chrome`, `tracing-tracy` to check that it still works as expected - Need people with access to `ios`, `android` and `wasm` to check it as well. --------- Co-authored-by: Alice Cecile Co-authored-by: Kristoffer Søholm --- crates/bevy_log/src/lib.rs | 237 +++++++++++++++++++++---------------- examples/app/log_layers.rs | 11 +- 2 files changed, 143 insertions(+), 105 deletions(-) diff --git a/crates/bevy_log/src/lib.rs b/crates/bevy_log/src/lib.rs index 7a80a21cc3..c0ad83468d 100644 --- a/crates/bevy_log/src/lib.rs +++ b/crates/bevy_log/src/lib.rs @@ -56,7 +56,6 @@ use bevy_app::{App, Plugin}; use tracing_log::LogTracer; use tracing_subscriber::{ filter::{FromEnvError, ParseError}, - layer::Layered, prelude::*, registry::Registry, EnvFilter, Layer, @@ -84,6 +83,8 @@ pub(crate) struct FlushGuard(SyncCell); /// logging to `stdout`. /// * Using [`android_log-sys`](https://crates.io/crates/android_log-sys) on Android, /// logging to Android logs. +/// * Using [`tracing_oslog`](https://crates.io/crates/tracing_oslog) on iOS, +/// logging to iOS logs. /// * Using [`tracing-wasm`](https://crates.io/crates/tracing-wasm) in Wasm, logging /// to the browser console. /// @@ -254,26 +255,15 @@ pub struct LogPlugin { /// timestamp from the log output. /// /// Please see the `examples/log_layers.rs` for a complete example. - pub fmt_layer: fn(app: &mut App) -> Option, + /// + /// Note that this field has no effect when `os_target` is `android`, `ios` or `wasm`, as on those + /// platforms we don't use [`tracing_subscriber::fmt::Layer`] but rather the platform default. + pub fmt_layer: fn(app: &mut App) -> Option, } /// A boxed [`Layer`] that can be used with [`LogPlugin::custom_layer`]. pub type BoxedLayer = Box + Send + Sync + 'static>; -#[cfg(feature = "trace")] -type BaseSubscriber = - Layered + Send + Sync>>, Registry>>; - -#[cfg(feature = "trace")] -type PreFmtSubscriber = Layered, BaseSubscriber>; - -#[cfg(not(feature = "trace"))] -type PreFmtSubscriber = - Layered + Send + Sync>>, Registry>>; - -/// A boxed [`Layer`] that can be used with [`LogPlugin::fmt_layer`]. -pub type BoxedFmtLayer = Box + Send + Sync + 'static>; - /// The default [`LogPlugin`] [`EnvFilter`]. pub const DEFAULT_FILTER: &str = "wgpu=error,naga=warn"; @@ -300,30 +290,25 @@ impl Plugin for LogPlugin { })); } - let finished_subscriber; - let subscriber = Registry::default(); + // We use a Vec of BoxedLayer instead of adding each layer individually using the + // `layer.with(next_layer)`. + // Otherwise, the types of each successive layer becomes unwieldy, + // as the type of each new layer would depend on the types of the previous layers. + let mut layers: Vec = Vec::new(); - // add optional layer provided by user - let subscriber = subscriber.with((self.custom_layer)(app)); + // Add optional layer provided by user + // As they are added first, any of the following layers won't be applied. + // In particular, it won't be affected by the filtering we put in place next. + if let Some(layer) = (self.custom_layer)(app) { + layers.push(layer); + } - let default_filter = { format!("{},{}", self.level, self.filter) }; - let filter_layer = EnvFilter::try_from_default_env() - .or_else(|from_env_error| { - _ = from_env_error - .source() - .and_then(|source| source.downcast_ref::()) - .map(|parse_err| { - // we cannot use the `error!` macro here because the logger is not ready yet. - eprintln!("LogPlugin failed to parse filter from env: {}", parse_err); - }); - - Ok::(EnvFilter::builder().parse_lossy(&default_filter)) - }) - .unwrap(); - let subscriber = subscriber.with(filter_layer); + layers.push(Self::build_filter_layer(self.level, &self.filter)); #[cfg(feature = "trace")] - let subscriber = subscriber.with(tracing_error::ErrorLayer::default()); + layers.push(tracing_error::ErrorLayer::default().boxed()); + + layers.push(Self::build_system_output_layer((self.fmt_layer)(app))); #[cfg(all( not(target_arch = "wasm32"), @@ -332,76 +317,19 @@ impl Plugin for LogPlugin { ))] { #[cfg(feature = "tracing-chrome")] - let chrome_layer = { - let mut layer = tracing_chrome::ChromeLayerBuilder::new(); - if let Ok(path) = std::env::var("TRACE_CHROME") { - layer = layer.file(path); - } - let (chrome_layer, guard) = layer - .name_fn(Box::new(|event_or_span| match event_or_span { - tracing_chrome::EventOrSpan::Event(event) => event.metadata().name().into(), - tracing_chrome::EventOrSpan::Span(span) => { - if let Some(fields) = - span.extensions().get::>() - { - format!("{}: {}", span.metadata().name(), fields.fields.as_str()) - } else { - span.metadata().name().into() - } - } - })) - .build(); + { + let (chrome_layer, guard) = Self::build_chrome_layer(); app.insert_resource(FlushGuard(SyncCell::new(guard))); - chrome_layer - }; - + layers.push(chrome_layer); + } #[cfg(feature = "tracing-tracy")] - let tracy_layer = tracing_tracy::TracyLayer::default(); - - let fmt_layer = (self.fmt_layer)(app).unwrap_or_else(|| { - // note: the implementation of `Default` reads from the env var NO_COLOR - // to decide whether to use ANSI color codes, which is common convention - // https://no-color.org/ - Box::new(tracing_subscriber::fmt::Layer::default().with_writer(std::io::stderr)) - }); - - // bevy_render::renderer logs a `tracy.frame_mark` event every frame - // at Level::INFO. Formatted logs should omit it. - #[cfg(feature = "tracing-tracy")] - let fmt_layer = - fmt_layer.with_filter(tracing_subscriber::filter::FilterFn::new(|meta| { - meta.fields().field("tracy.frame_mark").is_none() - })); - - let subscriber = subscriber.with(fmt_layer); - - #[cfg(feature = "tracing-chrome")] - let subscriber = subscriber.with(chrome_layer); - #[cfg(feature = "tracing-tracy")] - let subscriber = subscriber.with(tracy_layer); - finished_subscriber = subscriber; + layers.push(tracing_tracy::TracyLayer::default().boxed()); } - #[cfg(target_arch = "wasm32")] - { - finished_subscriber = subscriber.with(tracing_wasm::WASMLayer::new( - tracing_wasm::WASMLayerConfig::default(), - )); - } - - #[cfg(target_os = "android")] - { - finished_subscriber = subscriber.with(android_tracing::AndroidLayer::default()); - } - - #[cfg(target_os = "ios")] - { - finished_subscriber = subscriber.with(tracing_oslog::OsLogger::default()); - } + let subscriber = Registry::default().with(layers); let logger_already_set = LogTracer::init().is_err(); - let subscriber_already_set = - tracing::subscriber::set_global_default(finished_subscriber).is_err(); + let subscriber_already_set = tracing::subscriber::set_global_default(subscriber).is_err(); match (logger_already_set, subscriber_already_set) { (true, true) => error!( @@ -413,3 +341,112 @@ impl Plugin for LogPlugin { } } } + +impl LogPlugin { + /// Build a [`BoxedLayer`] that will filter which logs are outputted. + /// It will read the `RUST_LOG` env variable to override the settings + /// on a given run, the default will fallback to the provided `level` and `filter` + fn build_filter_layer(level: Level, filter: &str) -> BoxedLayer { + let default_filter = { format!("{},{}", level, filter) }; + + EnvFilter::try_from_default_env() + .or_else(|from_env_error| { + _ = from_env_error + .source() + .and_then(|source| source.downcast_ref::()) + .map(|parse_err| { + #[expect( + clippy::print_stderr, + reason = "We cannot use the `error!` macro here because the logger is not ready yet." + )] + { + eprintln!("LogPlugin failed to parse filter from env: {}", parse_err); + } + }); + + Ok::(EnvFilter::builder().parse_lossy(&default_filter)) + }) + .unwrap().boxed() + } + + #[cfg(feature = "tracing-chrome")] + /// [`BoxedLayer`] to build the necessary output when the `tracing-chrome` feature is enabled. + /// The [`tracing_chrome::FlushGuard`] must be kept around till we don't need to output logs + /// any more + fn build_chrome_layer() -> (BoxedLayer, tracing_chrome::FlushGuard) { + let mut layer = tracing_chrome::ChromeLayerBuilder::new(); + if let Ok(path) = std::env::var("TRACE_CHROME") { + layer = layer.file(path); + } + let (chrome_layer, guard) = layer + .name_fn(Box::new(|event_or_span| match event_or_span { + tracing_chrome::EventOrSpan::Event(event) => event.metadata().name().into(), + tracing_chrome::EventOrSpan::Span(span) => { + if let Some(fields) = span.extensions().get::>() + { + format!("{}: {}", span.metadata().name(), fields.fields.as_str()) + } else { + span.metadata().name().into() + } + } + })) + .build(); + (chrome_layer.boxed(), guard) + } + + #[expect( + clippy::allow_attributes, + reason = "We can't switch to `expect` for allow(unused_variables) as we use it if not on those platforms" + )] + #[allow(unused_variables, reason = "Not used on `wasm32`, `android` or `ios")] + /// Build a [`BoxedLayer`] that outputs logs to the system default. + /// On most platforms, it will be `stderr` with [`tracing_subscriber::fmt::Layer`], expect on `android`, `ios` and` wasm32` where it + /// uses those system default log infrastructure. + /// It is possible to override how you output those logs by providing a `custom_format_layer`. + /// Note that won't have an effect on platform that don't use [`tracing_subscriber::fmt::Layer`] + fn build_system_output_layer(custom_format_layer: Option) -> BoxedLayer { + let layer: BoxedLayer; + #[cfg(target_arch = "wasm32")] + { + layer = tracing_wasm::WASMLayer::new(tracing_wasm::WASMLayerConfig::default()).boxed(); + } + + #[cfg(target_os = "android")] + { + layer = android_tracing::AndroidLayer::default().boxed(); + } + + #[cfg(target_os = "ios")] + { + layer = tracing_oslog::OsLogger::default().boxed(); + } + + #[cfg(all( + not(target_arch = "wasm32"), + not(target_os = "android"), + not(target_os = "ios") + ))] + { + layer = { + let fmt_layer = custom_format_layer.unwrap_or_else(|| { + tracing_subscriber::fmt::Layer::default() + // note: the implementation of `Default` reads from the env var NO_COLOR + // to decide whether to use ANSI color codes, which is common convention + // https://no-color.org/ + .with_writer(std::io::stderr) + .boxed() + }); + + // bevy_render::renderer logs a `tracy.frame_mark` event every frame + // at Level::INFO. Formatted logs should omit it. + #[cfg(feature = "tracing-tracy")] + let fmt_layer = + fmt_layer.with_filter(tracing_subscriber::filter::FilterFn::new(|meta| { + meta.fields().field("tracy.frame_mark").is_none() + })); + fmt_layer.boxed() + } + } + layer + } +} diff --git a/examples/app/log_layers.rs b/examples/app/log_layers.rs index 8f454e9ee0..83e17ef107 100644 --- a/examples/app/log_layers.rs +++ b/examples/app/log_layers.rs @@ -4,7 +4,7 @@ use bevy::{ log::{ tracing::{self, Subscriber}, tracing_subscriber::Layer, - BoxedFmtLayer, BoxedLayer, + BoxedLayer, }, prelude::*, }; @@ -41,12 +41,13 @@ fn custom_layer(_app: &mut App) -> Option { // `fmt_layer` option. // // In this example, we're disabling the timestamp in the log output. -fn fmt_layer(_app: &mut App) -> Option { - Some(Box::new( +fn fmt_layer(_app: &mut App) -> Option { + Some( bevy::log::tracing_subscriber::fmt::Layer::default() .without_time() - .with_writer(std::io::stderr), - )) + .with_writer(std::io::stderr) + .boxed(), + ) } fn main() { From 1f2fd3d29dfd854f25ae917ebe1c63d727a16340 Mon Sep 17 00:00:00 2001 From: mgi388 <135186256+mgi388@users.noreply.github.com> Date: Tue, 17 Jun 2025 07:34:22 +1000 Subject: [PATCH 06/25] Fix SubStates with multiple source states not reacting to all source changes (#19595) # Objective - Fix issue where `SubStates` depending on multiple source states would only react when _all_ source states changed simultaneously. - SubStates should be created/destroyed whenever _any_ of their source states transitions, not only when all change together. # Solution - Changed the "did parent change" detection logic from AND to OR. We need to check if _any_ of the event readers changed, not if _all_ of them changed. - See https://github.com/bevyengine/bevy/actions/runs/15610159742/job/43968937544?pr=19595 for failing test proof before I pushed the fix. - The generated code we want needs `||`s not `&&`s like this: ```rust fn register_sub_state_systems_in_schedule>(schedule: &mut Schedule) { let apply_state_transition = |(mut ereader0, mut ereader1, mut ereader2): ( EventReader>, EventReader>, EventReader>, ), event: EventWriter>, commands: Commands, current_state_res: Option>>, next_state_res: Option>>, (s0, s1, s2): ( Option>>, Option>>, Option>>, )| { // With `||` we can correctly count parent changed if any of the sources changed. let parent_changed = (ereader0.read().last().is_some() || ereader1.read().last().is_some() || ereader2.read().last().is_some()); let next_state = take_next_state(next_state_res); if !parent_changed && next_state.is_none() { return; } // ... } } ``` # Testing - Add new test. - Check the fix worked in my game. --- crates/bevy_state/src/state/mod.rs | 197 +++++++++++++++++++++++ crates/bevy_state/src/state/state_set.rs | 2 +- 2 files changed, 198 insertions(+), 1 deletion(-) diff --git a/crates/bevy_state/src/state/mod.rs b/crates/bevy_state/src/state/mod.rs index 9267478281..61ee0627a3 100644 --- a/crates/bevy_state/src/state/mod.rs +++ b/crates/bevy_state/src/state/mod.rs @@ -642,6 +642,203 @@ mod tests { } } + #[derive(PartialEq, Eq, Debug, Hash, Clone)] + enum MultiSourceComputedState { + FromSimpleBTrue, + FromSimple2B2, + FromBoth, + } + + impl ComputedStates for MultiSourceComputedState { + type SourceStates = (SimpleState, SimpleState2); + + fn compute((simple_state, simple_state2): (SimpleState, SimpleState2)) -> Option { + match (simple_state, simple_state2) { + // If both are in their special states, prioritize the "both" variant. + (SimpleState::B(true), SimpleState2::B2) => Some(Self::FromBoth), + // If only SimpleState is B(true). + (SimpleState::B(true), _) => Some(Self::FromSimpleBTrue), + // If only SimpleState2 is B2. + (_, SimpleState2::B2) => Some(Self::FromSimple2B2), + // Otherwise, no computed state. + _ => None, + } + } + } + + /// This test ensures that [`ComputedStates`] with multiple source states + /// react when any source changes. + #[test] + fn computed_state_with_multiple_sources_should_react_to_any_source_change() { + let mut world = World::new(); + EventRegistry::register_event::>(&mut world); + EventRegistry::register_event::>(&mut world); + EventRegistry::register_event::>(&mut world); + + world.init_resource::>(); + world.init_resource::>(); + + let mut schedules = Schedules::new(); + let mut apply_changes = Schedule::new(StateTransition); + SimpleState::register_state(&mut apply_changes); + SimpleState2::register_state(&mut apply_changes); + MultiSourceComputedState::register_computed_state_systems(&mut apply_changes); + schedules.insert(apply_changes); + + world.insert_resource(schedules); + setup_state_transitions_in_world(&mut world); + + // Initial state: SimpleState::A, SimpleState2::A1 and + // MultiSourceComputedState should not exist yet. + world.run_schedule(StateTransition); + assert_eq!(world.resource::>().0, SimpleState::A); + assert_eq!(world.resource::>().0, SimpleState2::A1); + assert!(!world.contains_resource::>()); + + // Change only SimpleState to B(true) - this should trigger + // MultiSourceComputedState. + world.insert_resource(NextState::Pending(SimpleState::B(true))); + world.run_schedule(StateTransition); + assert_eq!( + world.resource::>().0, + SimpleState::B(true) + ); + assert_eq!(world.resource::>().0, SimpleState2::A1); + // The computed state should exist because SimpleState changed to + // B(true). + assert!(world.contains_resource::>()); + assert_eq!( + world.resource::>().0, + MultiSourceComputedState::FromSimpleBTrue + ); + + // Reset SimpleState to A - computed state should be removed. + world.insert_resource(NextState::Pending(SimpleState::A)); + world.run_schedule(StateTransition); + assert!(!world.contains_resource::>()); + + // Now change only SimpleState2 to B2 - this should also trigger + // MultiSourceComputedState. + world.insert_resource(NextState::Pending(SimpleState2::B2)); + world.run_schedule(StateTransition); + assert_eq!(world.resource::>().0, SimpleState::A); + assert_eq!(world.resource::>().0, SimpleState2::B2); + // The computed state should exist because SimpleState2 changed to B2. + assert!(world.contains_resource::>()); + assert_eq!( + world.resource::>().0, + MultiSourceComputedState::FromSimple2B2 + ); + + // Test that changes to both states work. + world.insert_resource(NextState::Pending(SimpleState::B(true))); + world.insert_resource(NextState::Pending(SimpleState2::A1)); + world.run_schedule(StateTransition); + assert_eq!( + world.resource::>().0, + MultiSourceComputedState::FromSimpleBTrue + ); + } + + // Test SubState that depends on multiple source states. + #[derive(PartialEq, Eq, Debug, Default, Hash, Clone)] + enum MultiSourceSubState { + #[default] + Active, + } + + impl SubStates for MultiSourceSubState { + type SourceStates = (SimpleState, SimpleState2); + + fn should_exist( + (simple_state, simple_state2): (SimpleState, SimpleState2), + ) -> Option { + // SubState should exist when: + // - SimpleState is B(true), OR + // - SimpleState2 is B2 + match (simple_state, simple_state2) { + (SimpleState::B(true), _) | (_, SimpleState2::B2) => Some(Self::Active), + _ => None, + } + } + } + + impl States for MultiSourceSubState { + const DEPENDENCY_DEPTH: usize = ::SourceStates::SET_DEPENDENCY_DEPTH + 1; + } + + impl FreelyMutableState for MultiSourceSubState {} + + /// This test ensures that [`SubStates`] with multiple source states react + /// when any source changes. + #[test] + fn sub_state_with_multiple_sources_should_react_to_any_source_change() { + let mut world = World::new(); + EventRegistry::register_event::>(&mut world); + EventRegistry::register_event::>(&mut world); + EventRegistry::register_event::>(&mut world); + + world.init_resource::>(); + world.init_resource::>(); + + let mut schedules = Schedules::new(); + let mut apply_changes = Schedule::new(StateTransition); + SimpleState::register_state(&mut apply_changes); + SimpleState2::register_state(&mut apply_changes); + MultiSourceSubState::register_sub_state_systems(&mut apply_changes); + schedules.insert(apply_changes); + + world.insert_resource(schedules); + setup_state_transitions_in_world(&mut world); + + // Initial state: SimpleState::A, SimpleState2::A1 and + // MultiSourceSubState should not exist yet. + world.run_schedule(StateTransition); + assert_eq!(world.resource::>().0, SimpleState::A); + assert_eq!(world.resource::>().0, SimpleState2::A1); + assert!(!world.contains_resource::>()); + + // Change only SimpleState to B(true) - this should trigger + // MultiSourceSubState. + world.insert_resource(NextState::Pending(SimpleState::B(true))); + world.run_schedule(StateTransition); + assert_eq!( + world.resource::>().0, + SimpleState::B(true) + ); + assert_eq!(world.resource::>().0, SimpleState2::A1); + // The sub state should exist because SimpleState changed to B(true). + assert!(world.contains_resource::>()); + + // Reset to initial state. + world.insert_resource(NextState::Pending(SimpleState::A)); + world.run_schedule(StateTransition); + assert!(!world.contains_resource::>()); + + // Now change only SimpleState2 to B2 - this should also trigger + // MultiSourceSubState creation. + world.insert_resource(NextState::Pending(SimpleState2::B2)); + world.run_schedule(StateTransition); + assert_eq!(world.resource::>().0, SimpleState::A); + assert_eq!(world.resource::>().0, SimpleState2::B2); + // The sub state should exist because SimpleState2 changed to B2. + assert!(world.contains_resource::>()); + + // Finally, test that it works when both change simultaneously. + world.insert_resource(NextState::Pending(SimpleState::B(false))); + world.insert_resource(NextState::Pending(SimpleState2::A1)); + world.run_schedule(StateTransition); + // After this transition, the state should not exist since SimpleState + // is B(false). + assert!(!world.contains_resource::>()); + + // Change both at the same time. + world.insert_resource(NextState::Pending(SimpleState::B(true))); + world.insert_resource(NextState::Pending(SimpleState2::B2)); + world.run_schedule(StateTransition); + assert!(world.contains_resource::>()); + } + #[test] fn check_transition_orders() { let mut world = World::new(); diff --git a/crates/bevy_state/src/state/state_set.rs b/crates/bevy_state/src/state/state_set.rs index 69a6c41b3d..3cf1e1d260 100644 --- a/crates/bevy_state/src/state/state_set.rs +++ b/crates/bevy_state/src/state/state_set.rs @@ -293,7 +293,7 @@ macro_rules! impl_state_set_sealed_tuples { current_state_res: Option>>, next_state_res: Option>>, ($($val),*,): ($(Option>>),*,)| { - let parent_changed = ($($evt.read().last().is_some())&&*); + let parent_changed = ($($evt.read().last().is_some())||*); let next_state = take_next_state(next_state_res); if !parent_changed && next_state.is_none() { From 9b743d2a430e6770509bb4464fcbed5419ce56d5 Mon Sep 17 00:00:00 2001 From: Jan Hohenheim Date: Mon, 16 Jun 2025 23:47:34 +0200 Subject: [PATCH 07/25] Allow users to fix glTF coordinate system imports (#19633) # Objective *Fixes #5670 as an opt-in for now* glTF uses the following coordinate system: - forward: Z - up: Y - right: -X and Bevy uses: - forward: -Z - up: Y - right: X For the longest time, Bevy has simply ignored this distinction. That caused issues when working across programs, as most software respects the glTF coordinate system when importing and exporting glTFs. Your scene might have looked correct in Blender, Maya, TrenchBroom, etc. but everything would be flipped when importing it into Bevy! ## Solution Add an option to the glTF loader to perform coordinate conversion. Note that this makes a distinction in the camera nodes, as glTF uses a different coordinate system for them. ## Follow Ups - Add global glTF loader settings, similar to the image loader, so that users can make third-party crates also load their glTFs with corrected coordinates - Decide on a migration strategy to make this the future default - Create an issue - Get feedback from Patrick Walton and Cart (not pinging them here to not spam them) - Include this pic for reference of how Blender assumes -Y as forward: ![image](https://github.com/user-attachments/assets/8f5ae364-48f0-46e4-922b-50bccb8d58b3) ## Testing I ran all glTF animation examples with the new setting enabled to validate that they look the same, just flipped. Also got a nice test scene from Chris that includes a camera inside the glTF. Thanks @ChristopherBiscardi! Blender (-Y forward): ![image](https://github.com/user-attachments/assets/129013f1-a025-488a-8764-c7ee5e7019a1) Bevy (-Z forward, but the model looks the wrong way): ![image](https://github.com/user-attachments/assets/842e00e0-48ce-4ca7-a88e-ea458ecbf852) Bevy with `convert_coordinates` enabled (-Z forward): ![image](https://github.com/user-attachments/assets/e97f3797-75a0-4d2b-ac54-130ba69f0a3c) Validation that the axes are correct with F3D's glTF viewer (+Z forward): ![image](https://github.com/user-attachments/assets/b9f02adf-a7b0-4a18-821f-fdd04426d3bd) --- crates/bevy_gltf/src/convert_coordinates.rs | 80 +++++++++++++++++++ crates/bevy_gltf/src/lib.rs | 1 + crates/bevy_gltf/src/loader/gltf_ext/scene.rs | 18 ++++- crates/bevy_gltf/src/loader/mod.rs | 55 +++++++++++-- crates/bevy_gltf/src/vertex_attributes.rs | 71 +++++++++++----- .../release-notes/convert-coordinates.md | 50 ++++++++++++ 6 files changed, 245 insertions(+), 30 deletions(-) create mode 100644 crates/bevy_gltf/src/convert_coordinates.rs create mode 100644 release-content/release-notes/convert-coordinates.md diff --git a/crates/bevy_gltf/src/convert_coordinates.rs b/crates/bevy_gltf/src/convert_coordinates.rs new file mode 100644 index 0000000000..4148cecd9a --- /dev/null +++ b/crates/bevy_gltf/src/convert_coordinates.rs @@ -0,0 +1,80 @@ +use core::f32::consts::PI; + +use bevy_math::{Mat4, Quat, Vec3}; +use bevy_transform::components::Transform; + +pub(crate) trait ConvertCoordinates { + /// Converts the glTF coordinates to Bevy's coordinate system. + /// - glTF: + /// - forward: Z + /// - up: Y + /// - right: -X + /// - Bevy: + /// - forward: -Z + /// - up: Y + /// - right: X + /// + /// See + fn convert_coordinates(self) -> Self; +} + +pub(crate) trait ConvertCameraCoordinates { + /// Like `convert_coordinates`, but uses the following for the lens rotation: + /// - forward: -Z + /// - up: Y + /// - right: X + /// + /// See + fn convert_camera_coordinates(self) -> Self; +} + +impl ConvertCoordinates for Vec3 { + fn convert_coordinates(self) -> Self { + Vec3::new(-self.x, self.y, -self.z) + } +} + +impl ConvertCoordinates for [f32; 3] { + fn convert_coordinates(self) -> Self { + [-self[0], self[1], -self[2]] + } +} + +impl ConvertCoordinates for [f32; 4] { + fn convert_coordinates(self) -> Self { + // Solution of q' = r q r* + [-self[0], self[1], -self[2], self[3]] + } +} + +impl ConvertCoordinates for Quat { + fn convert_coordinates(self) -> Self { + // Solution of q' = r q r* + Quat::from_array([-self.x, self.y, -self.z, self.w]) + } +} + +impl ConvertCoordinates for Mat4 { + fn convert_coordinates(self) -> Self { + let m: Mat4 = Mat4::from_scale(Vec3::new(-1.0, 1.0, -1.0)); + // Same as the original matrix + let m_inv = m; + m_inv * self * m + } +} + +impl ConvertCoordinates for Transform { + fn convert_coordinates(mut self) -> Self { + self.translation = self.translation.convert_coordinates(); + self.rotation = self.rotation.convert_coordinates(); + self + } +} + +impl ConvertCameraCoordinates for Transform { + fn convert_camera_coordinates(mut self) -> Self { + self.translation = self.translation.convert_coordinates(); + self.rotate_y(PI); + self + } +} diff --git a/crates/bevy_gltf/src/lib.rs b/crates/bevy_gltf/src/lib.rs index 97600f3ed0..4262d43eb7 100644 --- a/crates/bevy_gltf/src/lib.rs +++ b/crates/bevy_gltf/src/lib.rs @@ -91,6 +91,7 @@ //! You can use [`GltfAssetLabel`] to ensure you are using the correct label. mod assets; +mod convert_coordinates; mod label; mod loader; mod vertex_attributes; diff --git a/crates/bevy_gltf/src/loader/gltf_ext/scene.rs b/crates/bevy_gltf/src/loader/gltf_ext/scene.rs index 83e6778b99..3fce51d527 100644 --- a/crates/bevy_gltf/src/loader/gltf_ext/scene.rs +++ b/crates/bevy_gltf/src/loader/gltf_ext/scene.rs @@ -10,7 +10,10 @@ use itertools::Itertools; #[cfg(feature = "bevy_animation")] use bevy_platform::collections::{HashMap, HashSet}; -use crate::GltfError; +use crate::{ + convert_coordinates::{ConvertCameraCoordinates as _, ConvertCoordinates as _}, + GltfError, +}; pub(crate) fn node_name(node: &Node) -> Name { let name = node @@ -26,8 +29,8 @@ pub(crate) fn node_name(node: &Node) -> Name { /// on [`Node::transform()`](gltf::Node::transform) directly because it uses optimized glam types and /// if `libm` feature of `bevy_math` crate is enabled also handles cross /// platform determinism properly. -pub(crate) fn node_transform(node: &Node) -> Transform { - match node.transform() { +pub(crate) fn node_transform(node: &Node, convert_coordinates: bool) -> Transform { + let transform = match node.transform() { gltf::scene::Transform::Matrix { matrix } => { Transform::from_matrix(Mat4::from_cols_array_2d(&matrix)) } @@ -40,6 +43,15 @@ pub(crate) fn node_transform(node: &Node) -> Transform { rotation: bevy_math::Quat::from_array(rotation), scale: Vec3::from(scale), }, + }; + if convert_coordinates { + if node.camera().is_some() { + transform.convert_camera_coordinates() + } else { + transform.convert_coordinates() + } + } else { + transform } } diff --git a/crates/bevy_gltf/src/loader/mod.rs b/crates/bevy_gltf/src/loader/mod.rs index abc555002a..5e0f752e50 100644 --- a/crates/bevy_gltf/src/loader/mod.rs +++ b/crates/bevy_gltf/src/loader/mod.rs @@ -84,6 +84,7 @@ use self::{ texture::{texture_handle, texture_sampler, texture_transform_to_affine2}, }, }; +use crate::convert_coordinates::ConvertCoordinates as _; /// An error that occurs when loading a glTF file. #[derive(Error, Debug)] @@ -191,6 +192,16 @@ pub struct GltfLoaderSettings { pub default_sampler: Option, /// If true, the loader will ignore sampler data from gltf and use the default sampler. pub override_sampler: bool, + /// If true, the loader will convert glTF coordinates to Bevy's coordinate system. + /// - glTF: + /// - forward: Z + /// - up: Y + /// - right: -X + /// - Bevy: + /// - forward: -Z + /// - up: Y + /// - right: X + pub convert_coordinates: bool, } impl Default for GltfLoaderSettings { @@ -203,6 +214,7 @@ impl Default for GltfLoaderSettings { include_source: false, default_sampler: None, override_sampler: false, + convert_coordinates: false, } } } @@ -303,7 +315,16 @@ async fn load_gltf<'a, 'b, 'c>( match outputs { ReadOutputs::Translations(tr) => { let translation_property = animated_field!(Transform::translation); - let translations: Vec = tr.map(Vec3::from).collect(); + let translations: Vec = tr + .map(Vec3::from) + .map(|verts| { + if settings.convert_coordinates { + Vec3::convert_coordinates(verts) + } else { + verts + } + }) + .collect(); if keyframe_timestamps.len() == 1 { Some(VariableCurve::new(AnimatableCurve::new( translation_property, @@ -350,8 +371,17 @@ async fn load_gltf<'a, 'b, 'c>( } ReadOutputs::Rotations(rots) => { let rotation_property = animated_field!(Transform::rotation); - let rotations: Vec = - rots.into_f32().map(Quat::from_array).collect(); + let rotations: Vec = rots + .into_f32() + .map(Quat::from_array) + .map(|quat| { + if settings.convert_coordinates { + Quat::convert_coordinates(quat) + } else { + quat + } + }) + .collect(); if keyframe_timestamps.len() == 1 { Some(VariableCurve::new(AnimatableCurve::new( rotation_property, @@ -633,6 +663,7 @@ async fn load_gltf<'a, 'b, 'c>( accessor, &buffer_data, &loader.custom_vertex_attributes, + settings.convert_coordinates, ) { Ok((attribute, values)) => mesh.insert_attribute(attribute, values), Err(err) => warn!("{}", err), @@ -752,7 +783,17 @@ async fn load_gltf<'a, 'b, 'c>( let reader = gltf_skin.reader(|buffer| Some(&buffer_data[buffer.index()])); let local_to_bone_bind_matrices: Vec = reader .read_inverse_bind_matrices() - .map(|mats| mats.map(|mat| Mat4::from_cols_array_2d(&mat)).collect()) + .map(|mats| { + mats.map(|mat| Mat4::from_cols_array_2d(&mat)) + .map(|mat| { + if settings.convert_coordinates { + mat.convert_coordinates() + } else { + mat + } + }) + .collect() + }) .unwrap_or_else(|| { core::iter::repeat_n(Mat4::IDENTITY, gltf_skin.joints().len()).collect() }); @@ -834,7 +875,7 @@ async fn load_gltf<'a, 'b, 'c>( &node, children, mesh, - node_transform(&node), + node_transform(&node, settings.convert_coordinates), skin, node.extras().as_deref().map(GltfExtras::from), ); @@ -1306,7 +1347,7 @@ fn load_node( document: &Document, ) -> Result<(), GltfError> { let mut gltf_error = None; - let transform = node_transform(gltf_node); + let transform = node_transform(gltf_node, settings.convert_coordinates); let world_transform = *parent_transform * transform; // according to https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#instantiation, // if the determinant of the transform is negative we must invert the winding order of @@ -1359,7 +1400,6 @@ fn load_node( }, ..OrthographicProjection::default_3d() }; - Projection::Orthographic(orthographic_projection) } gltf::camera::Projection::Perspective(perspective) => { @@ -1377,6 +1417,7 @@ fn load_node( Projection::Perspective(perspective_projection) } }; + node.insert(( Camera3d::default(), projection, diff --git a/crates/bevy_gltf/src/vertex_attributes.rs b/crates/bevy_gltf/src/vertex_attributes.rs index d4ae811c90..2620d608a0 100644 --- a/crates/bevy_gltf/src/vertex_attributes.rs +++ b/crates/bevy_gltf/src/vertex_attributes.rs @@ -6,6 +6,8 @@ use gltf::{ }; use thiserror::Error; +use crate::convert_coordinates::ConvertCoordinates; + /// Represents whether integer data requires normalization #[derive(Copy, Clone)] struct Normalization(bool); @@ -132,15 +134,23 @@ impl<'a> VertexAttributeIter<'a> { } /// Materializes values for any supported format of vertex attribute - fn into_any_values(self) -> Result { + fn into_any_values(self, convert_coordinates: bool) -> Result { match self { VertexAttributeIter::F32(it) => Ok(Values::Float32(it.collect())), VertexAttributeIter::U32(it) => Ok(Values::Uint32(it.collect())), VertexAttributeIter::F32x2(it) => Ok(Values::Float32x2(it.collect())), VertexAttributeIter::U32x2(it) => Ok(Values::Uint32x2(it.collect())), - VertexAttributeIter::F32x3(it) => Ok(Values::Float32x3(it.collect())), + VertexAttributeIter::F32x3(it) => Ok(if convert_coordinates { + Values::Float32x3(it.map(ConvertCoordinates::convert_coordinates).collect()) + } else { + Values::Float32x3(it.collect()) + }), VertexAttributeIter::U32x3(it) => Ok(Values::Uint32x3(it.collect())), - VertexAttributeIter::F32x4(it) => Ok(Values::Float32x4(it.collect())), + VertexAttributeIter::F32x4(it) => Ok(if convert_coordinates { + Values::Float32x4(it.map(ConvertCoordinates::convert_coordinates).collect()) + } else { + Values::Float32x4(it.collect()) + }), VertexAttributeIter::U32x4(it) => Ok(Values::Uint32x4(it.collect())), VertexAttributeIter::S16x2(it, n) => { Ok(n.apply_either(it.collect(), Values::Snorm16x2, Values::Sint16x2)) @@ -188,7 +198,7 @@ impl<'a> VertexAttributeIter<'a> { VertexAttributeIter::U16x4(it, Normalization(true)) => Ok(Values::Float32x4( ReadColors::RgbaU16(it).into_rgba_f32().collect(), )), - s => s.into_any_values(), + s => s.into_any_values(false), } } @@ -198,7 +208,7 @@ impl<'a> VertexAttributeIter<'a> { VertexAttributeIter::U8x4(it, Normalization(false)) => { Ok(Values::Uint16x4(ReadJoints::U8(it).into_u16().collect())) } - s => s.into_any_values(), + s => s.into_any_values(false), } } @@ -211,7 +221,7 @@ impl<'a> VertexAttributeIter<'a> { VertexAttributeIter::U16x4(it, Normalization(true)) => { Ok(Values::Float32x4(ReadWeights::U16(it).into_f32().collect())) } - s => s.into_any_values(), + s => s.into_any_values(false), } } @@ -224,7 +234,7 @@ impl<'a> VertexAttributeIter<'a> { VertexAttributeIter::U16x2(it, Normalization(true)) => Ok(Values::Float32x2( ReadTexCoords::U16(it).into_f32().collect(), )), - s => s.into_any_values(), + s => s.into_any_values(false), } } } @@ -252,28 +262,49 @@ pub(crate) fn convert_attribute( accessor: gltf::Accessor, buffer_data: &Vec>, custom_vertex_attributes: &HashMap, MeshVertexAttribute>, + convert_coordinates: bool, ) -> Result<(MeshVertexAttribute, Values), ConvertAttributeError> { - if let Some((attribute, conversion)) = match &semantic { - gltf::Semantic::Positions => Some((Mesh::ATTRIBUTE_POSITION, ConversionMode::Any)), - gltf::Semantic::Normals => Some((Mesh::ATTRIBUTE_NORMAL, ConversionMode::Any)), - gltf::Semantic::Tangents => Some((Mesh::ATTRIBUTE_TANGENT, ConversionMode::Any)), - gltf::Semantic::Colors(0) => Some((Mesh::ATTRIBUTE_COLOR, ConversionMode::Rgba)), - gltf::Semantic::TexCoords(0) => Some((Mesh::ATTRIBUTE_UV_0, ConversionMode::TexCoord)), - gltf::Semantic::TexCoords(1) => Some((Mesh::ATTRIBUTE_UV_1, ConversionMode::TexCoord)), - gltf::Semantic::Joints(0) => { - Some((Mesh::ATTRIBUTE_JOINT_INDEX, ConversionMode::JointIndex)) + if let Some((attribute, conversion, convert_coordinates)) = match &semantic { + gltf::Semantic::Positions => Some(( + Mesh::ATTRIBUTE_POSITION, + ConversionMode::Any, + convert_coordinates, + )), + gltf::Semantic::Normals => Some(( + Mesh::ATTRIBUTE_NORMAL, + ConversionMode::Any, + convert_coordinates, + )), + gltf::Semantic::Tangents => Some(( + Mesh::ATTRIBUTE_TANGENT, + ConversionMode::Any, + convert_coordinates, + )), + gltf::Semantic::Colors(0) => Some((Mesh::ATTRIBUTE_COLOR, ConversionMode::Rgba, false)), + gltf::Semantic::TexCoords(0) => { + Some((Mesh::ATTRIBUTE_UV_0, ConversionMode::TexCoord, false)) } - gltf::Semantic::Weights(0) => { - Some((Mesh::ATTRIBUTE_JOINT_WEIGHT, ConversionMode::JointWeight)) + gltf::Semantic::TexCoords(1) => { + Some((Mesh::ATTRIBUTE_UV_1, ConversionMode::TexCoord, false)) } + gltf::Semantic::Joints(0) => Some(( + Mesh::ATTRIBUTE_JOINT_INDEX, + ConversionMode::JointIndex, + false, + )), + gltf::Semantic::Weights(0) => Some(( + Mesh::ATTRIBUTE_JOINT_WEIGHT, + ConversionMode::JointWeight, + false, + )), gltf::Semantic::Extras(name) => custom_vertex_attributes .get(name.as_str()) - .map(|attr| (*attr, ConversionMode::Any)), + .map(|attr| (*attr, ConversionMode::Any, false)), _ => None, } { let raw_iter = VertexAttributeIter::from_accessor(accessor.clone(), buffer_data); let converted_values = raw_iter.and_then(|iter| match conversion { - ConversionMode::Any => iter.into_any_values(), + ConversionMode::Any => iter.into_any_values(convert_coordinates), ConversionMode::Rgba => iter.into_rgba_values(), ConversionMode::TexCoord => iter.into_tex_coord_values(), ConversionMode::JointIndex => iter.into_joint_index_values(), diff --git a/release-content/release-notes/convert-coordinates.md b/release-content/release-notes/convert-coordinates.md new file mode 100644 index 0000000000..22e69fd4ef --- /dev/null +++ b/release-content/release-notes/convert-coordinates.md @@ -0,0 +1,50 @@ +--- +title: Allow importing glTFs with a corrected coordinate system +authors: ["@janhohenheim"] +pull_requests: [19633] +--- + +glTF uses the following coordinate system: + +- forward: Z +- up: Y +- right: -X + +and Bevy uses: + +- forward: -Z +- up: Y +- right: X + +This means that to correctly import glTFs into Bevy, vertex data should be rotated by 180 degrees around the Y axis. +For the longest time, Bevy has simply ignored this distinction. That caused issues when working across programs, as most software respects the +glTF coordinate system when importing and exporting glTFs. Your scene might have looked correct in Blender, Maya, TrenchBroom, etc. but everything would be flipped when importing it into Bevy! + +Long-term, we'd like to fix our glTF imports to use the correct coordinate system by default. +But changing the import behavior would mean that *all* imported glTFs of *all* users would suddenly look different, breaking their scenes! +Not to mention that any bugs in the conversion code would be incredibly frustating for users. + +This is why we are now gradually rolling out support for corrected glTF imports. Starting now you can opt into the new behavior by setting the `GltfLoaderSettings`: + +```rust +// old behavior, ignores glTF's coordinate system +let handle = asset_server.load("fox.gltf#Scene0"); + +// new behavior, converts glTF's coordinate system into Bevy's coordinate system +let handle = asset_server.load_with_settings( + "fox.gltf#Scene0", + |settings: &mut GltfLoaderSettings| { + settings.convert_coordinates = true; + }, +); +``` + +Afterwards, your scene will be oriented such that your modeling software's forward direction correctly corresponds to Bevy's forward direction. + +For example, Blender assumes -Y to be forward, so exporting the following model to glTF and loading it in Bevy with the new settings will ensure everything is +oriented the right way across all programs in your pipeline: + + +![Blender Coordinate System](blender-coords.png) + +If you opt into this, please let us know how it's working out! Is your scene looking like you expected? Are the animations playing correctly? Is the camera at the right place? Are the lights shining from the right spots? From 0c63aa2405c1e30fa17eb8702d7fa03f1dabb306 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Mon, 16 Jun 2025 22:55:32 +0100 Subject: [PATCH 08/25] Document `TextShadow` not supported by `Text2d` (#19533) # Objective Document `TextShadow` not supported by `Text2d` Co-authored-by: Alice Cecile --- crates/bevy_ui/src/ui_node.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index 1c5ed364b9..d81f80f8b9 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -2875,6 +2875,8 @@ impl ComputedNodeTarget { } /// Adds a shadow behind text +/// +/// Not supported by `Text2d` #[derive(Component, Copy, Clone, Debug, PartialEq, Reflect)] #[reflect(Component, Default, Debug, Clone, PartialEq)] pub struct TextShadow { From 7237c2173bce13ff37024cdb5a29306a07538264 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Mon, 16 Jun 2025 15:00:33 -0700 Subject: [PATCH 09/25] Make observers metadata / storage publicly visible (#19608) # Objective Our strategy for storing observers is made up of several moving parts, which are ultimately fairly simple nested HashMaps. These types are currently `pub`, but lack any meaningful way to access this data. We have three options here: 1. Make these internals not `pub` at all. 2. Make the data read-only accessible. 3. Make the data mutably accessible. ## Solution I've opted for option 2, exposing read-only values. This is consistent with our existing approach to the ECS internals, allowing for easier debugging without risking wanton data corruption. If one day you would like to mutably access this data, please open an issue clearly explaining what you're trying to do. This was a pretty mechanical change, exposing fields via getters. I've also opted to do my best to clarify some field names and documentation: please double-check those for correctness. It was hard to be fully confident, as the field names and documentation was not very clear ;) ## Testing I spent some time going through the code paths, making sure that users can trace all the way from `World` to the leaf nodes. Reviewers, please ensure the same! ## Notes for reviewers This is part of a broader observer overhaul: I fully expect us to change up these internals and break these shiny new APIs. Probably even within the same cycle! But clean up your work area first: this sort of read-only getter and improved docs will be important to replace as we work. --- .../bevy_ecs/src/observer/entity_observer.rs | 8 +- crates/bevy_ecs/src/observer/mod.rs | 105 +++++++++++++----- crates/bevy_ecs/src/world/mod.rs | 6 + 3 files changed, 90 insertions(+), 29 deletions(-) diff --git a/crates/bevy_ecs/src/observer/entity_observer.rs b/crates/bevy_ecs/src/observer/entity_observer.rs index 5530b1f0fc..23be0e9672 100644 --- a/crates/bevy_ecs/src/observer/entity_observer.rs +++ b/crates/bevy_ecs/src/observer/entity_observer.rs @@ -97,7 +97,7 @@ fn component_clone_observed_by(_source: &SourceComponent, ctx: &mut ComponentClo let event_types = observer_state.descriptor.events.clone(); let components = observer_state.descriptor.components.clone(); for event_type in event_types { - let observers = world.observers.get_observers(event_type); + let observers = world.observers.get_observers_mut(event_type); if components.is_empty() { if let Some(map) = observers.entity_observers.get(&source).cloned() { observers.entity_observers.insert(target, map); @@ -108,8 +108,10 @@ fn component_clone_observed_by(_source: &SourceComponent, ctx: &mut ComponentClo else { continue; }; - if let Some(map) = observers.entity_map.get(&source).cloned() { - observers.entity_map.insert(target, map); + if let Some(map) = + observers.entity_component_observers.get(&source).cloned() + { + observers.entity_component_observers.insert(target, map); } } } diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index 4db65b888d..b3a8b3b5cd 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -515,40 +515,72 @@ impl ObserverTrigger { } } -// Map between an observer entity and its runner -type ObserverMap = EntityHashMap; +/// Map between an observer entity and its [`ObserverRunner`] +pub type ObserverMap = EntityHashMap; -/// Collection of [`ObserverRunner`] for [`Observer`] registered to a particular trigger targeted at a specific component. +/// Collection of [`ObserverRunner`] for [`Observer`] registered to a particular event targeted at a specific component. /// /// This is stored inside of [`CachedObservers`]. #[derive(Default, Debug)] pub struct CachedComponentObservers { - // Observers listening to triggers targeting this component - map: ObserverMap, - // Observers listening to triggers targeting this component on a specific entity - entity_map: EntityHashMap, + // Observers listening to events targeting this component, but not a specific entity + global_observers: ObserverMap, + // Observers listening to events targeting this component on a specific entity + entity_component_observers: EntityHashMap, } -/// Collection of [`ObserverRunner`] for [`Observer`] registered to a particular trigger. +impl CachedComponentObservers { + /// Returns the observers listening for this trigger, regardless of target. + /// These observers will also respond to events targeting specific entities. + pub fn global_observers(&self) -> &ObserverMap { + &self.global_observers + } + + /// Returns the observers listening for this trigger targeting this component on a specific entity. + pub fn entity_component_observers(&self) -> &EntityHashMap { + &self.entity_component_observers + } +} + +/// Collection of [`ObserverRunner`] for [`Observer`] registered to a particular event. /// /// This is stored inside of [`Observers`], specialized for each kind of observer. #[derive(Default, Debug)] pub struct CachedObservers { - // Observers listening for any time this trigger is fired - map: ObserverMap, + // Observers listening for any time this event is fired, regardless of target + // This will also respond to events targeting specific components or entities + global_observers: ObserverMap, // Observers listening for this trigger fired at a specific component component_observers: HashMap, // Observers listening for this trigger fired at a specific entity entity_observers: EntityHashMap, } +impl CachedObservers { + /// Returns the observers listening for this trigger, regardless of target. + /// These observers will also respond to events targeting specific components or entities. + pub fn global_observers(&self) -> &ObserverMap { + &self.global_observers + } + + /// Returns the observers listening for this trigger targeting components. + pub fn get_component_observers(&self) -> &HashMap { + &self.component_observers + } + + /// Returns the observers listening for this trigger targeting entities. + pub fn entity_observers(&self) -> &HashMap { + &self.component_observers + } +} + /// An internal lookup table tracking all of the observers in the world. /// /// Stores a cache mapping trigger ids to the registered observers. /// Some observer kinds (like [lifecycle](crate::lifecycle) observers) have a dedicated field, /// saving lookups for the most common triggers. /// -/// This is stored as a field of the [`World`]. +/// This can be accessed via [`World::observers`]. #[derive(Default, Debug)] pub struct Observers { // Cached ECS observers to save a lookup most common triggers. @@ -562,7 +594,7 @@ pub struct Observers { } impl Observers { - pub(crate) fn get_observers(&mut self, event_type: ComponentId) -> &mut CachedObservers { + pub(crate) fn get_observers_mut(&mut self, event_type: ComponentId) -> &mut CachedObservers { use crate::lifecycle::*; match event_type { @@ -575,7 +607,11 @@ impl Observers { } } - pub(crate) fn try_get_observers(&self, event_type: ComponentId) -> Option<&CachedObservers> { + /// Attempts to get the observers for the given `event_type`. + /// + /// When accessing the observers for lifecycle events, such as [`Add`], [`Insert`], [`Replace`], [`Remove`], and [`Despawn`], + /// use the [`ComponentId`] constants from the [`lifecycle`](crate::lifecycle) module. + pub fn try_get_observers(&self, event_type: ComponentId) -> Option<&CachedObservers> { use crate::lifecycle::*; match event_type { @@ -630,7 +666,10 @@ impl Observers { ); }; // Trigger observers listening for any kind of this trigger - observers.map.iter().for_each(&mut trigger_observer); + observers + .global_observers + .iter() + .for_each(&mut trigger_observer); // Trigger entity observers listening for this kind of trigger if let Some(target_entity) = current_target { @@ -643,12 +682,15 @@ impl Observers { trigger_for_components.for_each(|id| { if let Some(component_observers) = observers.component_observers.get(&id) { component_observers - .map + .global_observers .iter() .for_each(&mut trigger_observer); if let Some(target_entity) = current_target { - if let Some(map) = component_observers.entity_map.get(&target_entity) { + if let Some(map) = component_observers + .entity_component_observers + .get(&target_entity) + { map.iter().for_each(&mut trigger_observer); } } @@ -926,10 +968,12 @@ impl World { let descriptor = &observer_state.descriptor; for &event_type in &descriptor.events { - let cache = observers.get_observers(event_type); + let cache = observers.get_observers_mut(event_type); if descriptor.components.is_empty() && descriptor.entities.is_empty() { - cache.map.insert(observer_entity, observer_state.runner); + cache + .global_observers + .insert(observer_entity, observer_state.runner); } else if descriptor.components.is_empty() { // Observer is not targeting any components so register it as an entity observer for &watched_entity in &observer_state.descriptor.entities { @@ -951,11 +995,16 @@ impl World { }); if descriptor.entities.is_empty() { // Register for all triggers targeting the component - observers.map.insert(observer_entity, observer_state.runner); + observers + .global_observers + .insert(observer_entity, observer_state.runner); } else { // Register for each watched entity for &watched_entity in &descriptor.entities { - let map = observers.entity_map.entry(watched_entity).or_default(); + let map = observers + .entity_component_observers + .entry(watched_entity) + .or_default(); map.insert(observer_entity, observer_state.runner); } } @@ -970,9 +1019,9 @@ impl World { let observers = &mut self.observers; for &event_type in &descriptor.events { - let cache = observers.get_observers(event_type); + let cache = observers.get_observers_mut(event_type); if descriptor.components.is_empty() && descriptor.entities.is_empty() { - cache.map.remove(&entity); + cache.global_observers.remove(&entity); } else if descriptor.components.is_empty() { for watched_entity in &descriptor.entities { // This check should be unnecessary since this observer hasn't been unregistered yet @@ -990,20 +1039,24 @@ impl World { continue; }; if descriptor.entities.is_empty() { - observers.map.remove(&entity); + observers.global_observers.remove(&entity); } else { for watched_entity in &descriptor.entities { - let Some(map) = observers.entity_map.get_mut(watched_entity) else { + let Some(map) = + observers.entity_component_observers.get_mut(watched_entity) + else { continue; }; map.remove(&entity); if map.is_empty() { - observers.entity_map.remove(watched_entity); + observers.entity_component_observers.remove(watched_entity); } } } - if observers.map.is_empty() && observers.entity_map.is_empty() { + if observers.global_observers.is_empty() + && observers.entity_component_observers.is_empty() + { cache.component_observers.remove(component); if let Some(flag) = Observers::is_archetype_cached(event_type) { if let Some(by_component) = archetypes.by_component.get(component) { diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index ed7c1f2cdd..95accb20b5 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -261,6 +261,12 @@ impl World { &self.removed_components } + /// Retrieves this world's [`Observers`] list + #[inline] + pub fn observers(&self) -> &Observers { + &self.observers + } + /// Creates a new [`Commands`] instance that writes to the world's command queue /// Use [`World::flush`] to apply all queued commands #[inline] From 7c2289c96ebfd9c43bdca31aeb7136dde6a91769 Mon Sep 17 00:00:00 2001 From: atlv Date: Mon, 16 Jun 2025 18:02:30 -0400 Subject: [PATCH 10/25] Clarify GlobalTransform::to_isometry doc (#19645) # Objective - to_isometry is not a direct conversion, it involves computation. the docs could be clearer ## Solution - Improve docs ## Testing - its docs --- crates/bevy_transform/src/components/global_transform.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/bevy_transform/src/components/global_transform.rs b/crates/bevy_transform/src/components/global_transform.rs index b10d5a9d1a..d9fcf80737 100644 --- a/crates/bevy_transform/src/components/global_transform.rs +++ b/crates/bevy_transform/src/components/global_transform.rs @@ -139,8 +139,9 @@ impl GlobalTransform { } } - /// Returns the isometric part of the transformation as an [isometry]. Any scaling done by the - /// transformation will be ignored. + /// Computes a Scale-Rotation-Translation decomposition of the transformation and returns + /// the isometric part as an [isometry]. Any scaling done by the transformation will be ignored. + /// Note: this is a somewhat costly and lossy conversion. /// /// The transform is expected to be non-degenerate and without shearing, or the output /// will be invalid. From 209866cc27d5627d2e937b971a1a316d18bcf2b1 Mon Sep 17 00:00:00 2001 From: HippoGamus Date: Tue, 17 Jun 2025 00:05:19 +0200 Subject: [PATCH 11/25] Fix Keyboard observer not updating SliderValue (#19661) # Objective When the `CoreSlider`s `on_change` is set to None, Keyboard input, like ArrowKeys, does not update the `SliderValue`. ## Solution Handle the missing case, like it is done for Pointer. ## Testing - Did you test these changes? Yes: core_widgets & core_widgets_observers in both examples one has to remove / comment out the setting of `CoreSlider::on_change` to test the case of `on_change` being none. - Are there any parts that need more testing? No not that I am aware of. - How can other people (reviewers) test your changes? Is there anything specific they need to know? Yes: core_widgets & core_widgets_observers in both examples one has to remove / comment out the setting of `CoreSlider::on_change` to test the case of `on_change` being none. - If relevant, what platforms did you test these changes on, and are there any important ones you can't test? I tested on linux + wayland. But it is unlikely that it would effect outcomes. --------- Co-authored-by: Alice Cecile --- crates/bevy_core_widgets/src/core_slider.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/bevy_core_widgets/src/core_slider.rs b/crates/bevy_core_widgets/src/core_slider.rs index 63a606be78..d7ab387852 100644 --- a/crates/bevy_core_widgets/src/core_slider.rs +++ b/crates/bevy_core_widgets/src/core_slider.rs @@ -394,6 +394,10 @@ fn slider_on_key_input( trigger.propagate(false); if let Some(on_change) = slider.on_change { commands.run_system_with(on_change, new_value); + } else { + commands + .entity(trigger.target()) + .insert(SliderValue(new_value)); } } } From c0fa10b0c3a3cb1dbc565573b1d7601884798e49 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Mon, 16 Jun 2025 23:07:54 +0100 Subject: [PATCH 12/25] slider widget track click position `UiScale` fix (#19676) # Objective The position for track clicks in `core_slider` is calculated incorrectly when using `UiScale`. ## Solution `trigger.event().pointer_location.position` uses logical window coordinates, that is: `position = physical_position / window_scale_factor` while `ComputedNodeTarget::scale_factor` returns the window scale factor multiplied by Ui Scale: `target_scale_factor = window_scale_factor * ui_scale` So to get the physical position need to divide by the `UiScale`: ``` position * target_scale_factor / ui_scale = (physical_postion / window_scale_factor) * (window_scale_factor * ui_scale) / ui_scale = physical_position ``` I thought this was fixed during the slider PR review, but must have got missed somewhere or lost in a merge. ## Testing Can test using the `core_widgets` example` with `.insert_resource(UiScale(2.))` added to the bevy app. --- crates/bevy_core_widgets/src/core_slider.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/bevy_core_widgets/src/core_slider.rs b/crates/bevy_core_widgets/src/core_slider.rs index d7ab387852..e07a61a9c2 100644 --- a/crates/bevy_core_widgets/src/core_slider.rs +++ b/crates/bevy_core_widgets/src/core_slider.rs @@ -211,6 +211,7 @@ pub(crate) fn slider_on_pointer_down( focus: Option>, focus_visible: Option>, mut commands: Commands, + ui_scale: Res, ) { if q_thumb.contains(trigger.target()) { // Thumb click, stop propagation to prevent track click. @@ -255,7 +256,7 @@ pub(crate) fn slider_on_pointer_down( // Detect track click. let local_pos = transform.try_inverse().unwrap().transform_point2( - trigger.event().pointer_location.position * node_target.scale_factor(), + trigger.event().pointer_location.position * node_target.scale_factor() / ui_scale.0, ); let track_width = node.size().x - thumb_size; // Avoid division by zero From 20781371e8022421a2d544e217f266360b7c8e5e Mon Sep 17 00:00:00 2001 From: Erick Z <567737+eckz@users.noreply.github.com> Date: Tue, 17 Jun 2025 00:09:15 +0200 Subject: [PATCH 13/25] Register `ReflectSerialize` for &'static str (#19680) # Objective - When trying to serialize an structure that contains `&'static str` using only Reflection, I get the following error: ``` "type `&str` did not register the `ReflectSerialize` or `ReflectSerializeWithRegistry` type data. For certain types, this may need to be registered manually using `register_type_data` (stack: ... -> `core::option::Option<&str>` -> `&str`)") ``` ## Solution - Register `ReflectSerialize` for `&str` ## Testing - `cargo run -p ci`: OK --- crates/bevy_reflect/src/impls/core/primitives.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/bevy_reflect/src/impls/core/primitives.rs b/crates/bevy_reflect/src/impls/core/primitives.rs index 8bee33b4c8..3600f2ece5 100644 --- a/crates/bevy_reflect/src/impls/core/primitives.rs +++ b/crates/bevy_reflect/src/impls/core/primitives.rs @@ -282,6 +282,7 @@ impl GetTypeRegistration for &'static str { let mut registration = TypeRegistration::of::(); registration.insert::(FromType::::from_type()); registration.insert::(FromType::::from_type()); + registration.insert::(FromType::::from_type()); registration } } From 2915a3b90389cc46314878facc9cbfad7a14d177 Mon Sep 17 00:00:00 2001 From: atlv Date: Tue, 17 Jun 2025 14:37:26 -0400 Subject: [PATCH 14/25] rename GlobalTransform::compute_matrix to to_matrix (#19643) # Objective - compute_matrix doesn't compute anything, it just puts an Affine3A into a Mat4. the name is inaccurate ## Solution - rename it to conform with to_isometry (which, ironically, does compute a decomposition which is rather expensive) ## Testing - Its a rename. If it compiles, its good to go --------- Co-authored-by: Alice Cecile --- crates/bevy_pbr/src/atmosphere/resources.rs | 2 +- crates/bevy_pbr/src/cluster/assign.rs | 2 +- crates/bevy_pbr/src/light/mod.rs | 4 ++-- crates/bevy_pbr/src/light_probe/mod.rs | 2 +- crates/bevy_pbr/src/prepass/mod.rs | 4 ++-- crates/bevy_pbr/src/volumetric_fog/render.rs | 4 ++-- .../src/mesh_picking/ray_cast/intersections.rs | 16 ++++++++-------- .../src/mesh_picking/ray_cast/mod.rs | 4 ++-- crates/bevy_render/src/camera/camera.rs | 8 +++----- crates/bevy_render/src/camera/projection.rs | 3 +-- crates/bevy_render/src/view/mod.rs | 4 ++-- crates/bevy_solari/src/scene/binder.rs | 2 +- .../src/components/global_transform.rs | 2 +- .../rename_global_transform_compute_matrix.md | 6 ++++++ 14 files changed, 33 insertions(+), 30 deletions(-) create mode 100644 release-content/migration-guides/rename_global_transform_compute_matrix.md diff --git a/crates/bevy_pbr/src/atmosphere/resources.rs b/crates/bevy_pbr/src/atmosphere/resources.rs index 8c57fb8eb9..d7c93c4418 100644 --- a/crates/bevy_pbr/src/atmosphere/resources.rs +++ b/crates/bevy_pbr/src/atmosphere/resources.rs @@ -560,7 +560,7 @@ pub(super) fn prepare_atmosphere_transforms( }; for (entity, view) in &views { - let world_from_view = view.world_from_view.compute_matrix(); + let world_from_view = view.world_from_view.to_matrix(); let camera_z = world_from_view.z_axis.truncate(); let camera_y = world_from_view.y_axis.truncate(); let atmo_z = camera_z diff --git a/crates/bevy_pbr/src/cluster/assign.rs b/crates/bevy_pbr/src/cluster/assign.rs index 1b7b3563d7..b0c0fb6347 100644 --- a/crates/bevy_pbr/src/cluster/assign.rs +++ b/crates/bevy_pbr/src/cluster/assign.rs @@ -353,7 +353,7 @@ pub(crate) fn assign_objects_to_clusters( let mut requested_cluster_dimensions = config.dimensions_for_screen_size(screen_size); - let world_from_view = camera_transform.compute_matrix(); + let world_from_view = camera_transform.to_matrix(); let view_from_world_scale = camera_transform.compute_transform().scale.recip(); let view_from_world_scale_max = view_from_world_scale.abs().max_element(); let view_from_world = world_from_view.inverse(); diff --git a/crates/bevy_pbr/src/light/mod.rs b/crates/bevy_pbr/src/light/mod.rs index 4ff4662bb2..1ff057b0a1 100644 --- a/crates/bevy_pbr/src/light/mod.rs +++ b/crates/bevy_pbr/src/light/mod.rs @@ -341,7 +341,7 @@ pub fn build_directional_light_cascades( .iter() .filter_map(|(entity, transform, projection, camera)| { if camera.is_active { - Some((entity, projection, transform.compute_matrix())) + Some((entity, projection, transform.to_matrix())) } else { None } @@ -357,7 +357,7 @@ pub fn build_directional_light_cascades( // light_to_world has orthogonal upper-left 3x3 and zero translation. // Even though only the direction (i.e. rotation) of the light matters, we don't constrain // users to not change any other aspects of the transform - there's no guarantee - // `transform.compute_matrix()` will give us a matrix with our desired properties. + // `transform.to_matrix()` will give us a matrix with our desired properties. // Instead, we directly create a good matrix from just the rotation. let world_from_light = Mat4::from_quat(transform.compute_transform().rotation); let light_to_world_inverse = world_from_light.inverse(); diff --git a/crates/bevy_pbr/src/light_probe/mod.rs b/crates/bevy_pbr/src/light_probe/mod.rs index 74710ce1d5..6f863273ef 100644 --- a/crates/bevy_pbr/src/light_probe/mod.rs +++ b/crates/bevy_pbr/src/light_probe/mod.rs @@ -595,7 +595,7 @@ where ) -> Option> { environment_map.id(image_assets).map(|id| LightProbeInfo { world_from_light: light_probe_transform.affine(), - light_from_world: light_probe_transform.compute_matrix().inverse(), + light_from_world: light_probe_transform.to_matrix().inverse(), asset_id: id, intensity: environment_map.intensity(), affects_lightmapped_mesh_diffuse: environment_map.affects_lightmapped_mesh_diffuse(), diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index 1ae0c7fa84..aef2b74177 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -216,7 +216,7 @@ pub fn update_previous_view_data( query: Query<(Entity, &Camera, &GlobalTransform), Or<(With, With)>>, ) { for (entity, camera, camera_transform) in &query { - let world_from_view = camera_transform.compute_matrix(); + let world_from_view = camera_transform.to_matrix(); let view_from_world = world_from_view.inverse(); let view_from_clip = camera.clip_from_view().inverse(); @@ -703,7 +703,7 @@ pub fn prepare_previous_view_uniforms( let prev_view_data = match maybe_previous_view_uniforms { Some(previous_view) => previous_view.clone(), None => { - let world_from_view = camera.world_from_view.compute_matrix(); + let world_from_view = camera.world_from_view.to_matrix(); let view_from_world = world_from_view.inverse(); let view_from_clip = camera.clip_from_view.inverse(); diff --git a/crates/bevy_pbr/src/volumetric_fog/render.rs b/crates/bevy_pbr/src/volumetric_fog/render.rs index 625ff42dc5..cf2989a980 100644 --- a/crates/bevy_pbr/src/volumetric_fog/render.rs +++ b/crates/bevy_pbr/src/volumetric_fog/render.rs @@ -700,7 +700,7 @@ pub fn prepare_volumetric_fog_uniforms( // Do this up front to avoid O(n^2) matrix inversion. local_from_world_matrices.clear(); for (_, _, fog_transform) in fog_volumes.iter() { - local_from_world_matrices.push(fog_transform.compute_matrix().inverse()); + local_from_world_matrices.push(fog_transform.to_matrix().inverse()); } let uniform_count = view_targets.iter().len() * local_from_world_matrices.len(); @@ -712,7 +712,7 @@ pub fn prepare_volumetric_fog_uniforms( }; for (view_entity, extracted_view, volumetric_fog) in view_targets.iter() { - let world_from_view = extracted_view.world_from_view.compute_matrix(); + let world_from_view = extracted_view.world_from_view.to_matrix(); let mut view_fog_volumes = vec![]; diff --git a/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs b/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs index 9988a96e19..5ac2d89887 100644 --- a/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs +++ b/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs @@ -317,7 +317,7 @@ mod tests { #[test] fn ray_mesh_intersection_simple() { let ray = Ray3d::new(Vec3::ZERO, Dir3::X); - let mesh_transform = GlobalTransform::IDENTITY.compute_matrix(); + let mesh_transform = GlobalTransform::IDENTITY.to_matrix(); let positions = &[V0, V1, V2]; let vertex_normals = None; let indices: Option<&[u16]> = None; @@ -338,7 +338,7 @@ mod tests { #[test] fn ray_mesh_intersection_indices() { let ray = Ray3d::new(Vec3::ZERO, Dir3::X); - let mesh_transform = GlobalTransform::IDENTITY.compute_matrix(); + let mesh_transform = GlobalTransform::IDENTITY.to_matrix(); let positions = &[V0, V1, V2]; let vertex_normals = None; let indices: Option<&[u16]> = Some(&[0, 1, 2]); @@ -359,7 +359,7 @@ mod tests { #[test] fn ray_mesh_intersection_indices_vertex_normals() { let ray = Ray3d::new(Vec3::ZERO, Dir3::X); - let mesh_transform = GlobalTransform::IDENTITY.compute_matrix(); + let mesh_transform = GlobalTransform::IDENTITY.to_matrix(); let positions = &[V0, V1, V2]; let vertex_normals: Option<&[[f32; 3]]> = Some(&[[-1., 0., 0.], [-1., 0., 0.], [-1., 0., 0.]]); @@ -381,7 +381,7 @@ mod tests { #[test] fn ray_mesh_intersection_vertex_normals() { let ray = Ray3d::new(Vec3::ZERO, Dir3::X); - let mesh_transform = GlobalTransform::IDENTITY.compute_matrix(); + let mesh_transform = GlobalTransform::IDENTITY.to_matrix(); let positions = &[V0, V1, V2]; let vertex_normals: Option<&[[f32; 3]]> = Some(&[[-1., 0., 0.], [-1., 0., 0.], [-1., 0., 0.]]); @@ -403,7 +403,7 @@ mod tests { #[test] fn ray_mesh_intersection_missing_vertex_normals() { let ray = Ray3d::new(Vec3::ZERO, Dir3::X); - let mesh_transform = GlobalTransform::IDENTITY.compute_matrix(); + let mesh_transform = GlobalTransform::IDENTITY.to_matrix(); let positions = &[V0, V1, V2]; let vertex_normals: Option<&[[f32; 3]]> = Some(&[]); let indices: Option<&[u16]> = None; @@ -424,7 +424,7 @@ mod tests { #[test] fn ray_mesh_intersection_indices_missing_vertex_normals() { let ray = Ray3d::new(Vec3::ZERO, Dir3::X); - let mesh_transform = GlobalTransform::IDENTITY.compute_matrix(); + let mesh_transform = GlobalTransform::IDENTITY.to_matrix(); let positions = &[V0, V1, V2]; let vertex_normals: Option<&[[f32; 3]]> = Some(&[]); let indices: Option<&[u16]> = Some(&[0, 1, 2]); @@ -445,7 +445,7 @@ mod tests { #[test] fn ray_mesh_intersection_not_enough_indices() { let ray = Ray3d::new(Vec3::ZERO, Dir3::X); - let mesh_transform = GlobalTransform::IDENTITY.compute_matrix(); + let mesh_transform = GlobalTransform::IDENTITY.to_matrix(); let positions = &[V0, V1, V2]; let vertex_normals = None; let indices: Option<&[u16]> = Some(&[0]); @@ -466,7 +466,7 @@ mod tests { #[test] fn ray_mesh_intersection_bad_indices() { let ray = Ray3d::new(Vec3::ZERO, Dir3::X); - let mesh_transform = GlobalTransform::IDENTITY.compute_matrix(); + let mesh_transform = GlobalTransform::IDENTITY.to_matrix(); let positions = &[V0, V1, V2]; let vertex_normals = None; let indices: Option<&[u16]> = Some(&[0, 1, 3]); 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 c1f465b96a..e42dc160e2 100644 --- a/crates/bevy_picking/src/mesh_picking/ray_cast/mod.rs +++ b/crates/bevy_picking/src/mesh_picking/ray_cast/mod.rs @@ -233,7 +233,7 @@ impl<'w, 's> MeshRayCast<'w, 's> { if let Some(distance) = ray_aabb_intersection_3d( ray, &Aabb3d::new(aabb.center, aabb.half_extents), - &transform.compute_matrix(), + &transform.to_matrix(), ) { aabb_hits_tx.send((FloatOrd(distance), entity)).ok(); } @@ -287,7 +287,7 @@ impl<'w, 's> MeshRayCast<'w, 's> { // Perform the actual ray cast. let _ray_cast_guard = ray_cast_guard.enter(); - let transform = transform.compute_matrix(); + let transform = transform.to_matrix(); let intersection = ray_intersection_over_mesh(mesh, &transform, ray, backfaces); if let Some(intersection) = intersection { diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index fd3b8cb4b2..2bddcd0d05 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -601,8 +601,7 @@ impl Camera { rect_relative.y = 1.0 - rect_relative.y; let ndc = rect_relative * 2. - Vec2::ONE; - let ndc_to_world = - camera_transform.compute_matrix() * self.computed.clip_from_view.inverse(); + let ndc_to_world = camera_transform.to_matrix() * self.computed.clip_from_view.inverse(); let world_near_plane = ndc_to_world.project_point3(ndc.extend(1.)); // Using EPSILON because an ndc with Z = 0 returns NaNs. let world_far_plane = ndc_to_world.project_point3(ndc.extend(f32::EPSILON)); @@ -668,7 +667,7 @@ impl Camera { ) -> Option { // Build a transformation matrix to convert from world space to NDC using camera data let clip_from_world: Mat4 = - self.computed.clip_from_view * camera_transform.compute_matrix().inverse(); + self.computed.clip_from_view * camera_transform.to_matrix().inverse(); let ndc_space_coords: Vec3 = clip_from_world.project_point3(world_position); (!ndc_space_coords.is_nan()).then_some(ndc_space_coords) @@ -689,8 +688,7 @@ impl Camera { /// Will panic if the projection matrix is invalid (has a determinant of 0) and `glam_assert` is enabled. pub fn ndc_to_world(&self, camera_transform: &GlobalTransform, ndc: Vec3) -> Option { // Build a transformation matrix to convert from NDC to world space using camera data - let ndc_to_world = - camera_transform.compute_matrix() * self.computed.clip_from_view.inverse(); + let ndc_to_world = camera_transform.to_matrix() * self.computed.clip_from_view.inverse(); let world_space_coords = ndc_to_world.project_point3(ndc); diff --git a/crates/bevy_render/src/camera/projection.rs b/crates/bevy_render/src/camera/projection.rs index a7796a1d1a..ee2a5080d2 100644 --- a/crates/bevy_render/src/camera/projection.rs +++ b/crates/bevy_render/src/camera/projection.rs @@ -93,8 +93,7 @@ pub trait CameraProjection { /// This code is called by [`update_frusta`](crate::view::visibility::update_frusta) system /// for each camera to update its frustum. fn compute_frustum(&self, camera_transform: &GlobalTransform) -> Frustum { - let clip_from_world = - self.get_clip_from_view() * camera_transform.compute_matrix().inverse(); + let clip_from_world = self.get_clip_from_view() * camera_transform.to_matrix().inverse(); Frustum::from_clip_from_world_custom_far( &clip_from_world, &camera_transform.translation(), diff --git a/crates/bevy_render/src/view/mod.rs b/crates/bevy_render/src/view/mod.rs index d25e8e7f49..b2b90d0b24 100644 --- a/crates/bevy_render/src/view/mod.rs +++ b/crates/bevy_render/src/view/mod.rs @@ -316,7 +316,7 @@ pub struct ExtractedView { impl ExtractedView { /// Creates a 3D rangefinder for a view pub fn rangefinder3d(&self) -> ViewRangefinder3d { - ViewRangefinder3d::from_world_from_view(&self.world_from_view.compute_matrix()) + ViewRangefinder3d::from_world_from_view(&self.world_from_view.to_matrix()) } } @@ -934,7 +934,7 @@ pub fn prepare_view_uniforms( } let view_from_clip = clip_from_view.inverse(); - let world_from_view = extracted_view.world_from_view.compute_matrix(); + let world_from_view = extracted_view.world_from_view.to_matrix(); let view_from_world = world_from_view.inverse(); let clip_from_world = if temporal_jitter.is_some() { diff --git a/crates/bevy_solari/src/scene/binder.rs b/crates/bevy_solari/src/scene/binder.rs index 59a8020566..889efb538e 100644 --- a/crates/bevy_solari/src/scene/binder.rs +++ b/crates/bevy_solari/src/scene/binder.rs @@ -143,7 +143,7 @@ pub fn prepare_raytracing_scene_bindings( continue; }; - let transform = transform.compute_matrix(); + let transform = transform.to_matrix(); *tlas.get_mut_single(instance_id).unwrap() = Some(TlasInstance::new( blas, tlas_transform(&transform), diff --git a/crates/bevy_transform/src/components/global_transform.rs b/crates/bevy_transform/src/components/global_transform.rs index d9fcf80737..cd7db6ef71 100644 --- a/crates/bevy_transform/src/components/global_transform.rs +++ b/crates/bevy_transform/src/components/global_transform.rs @@ -115,7 +115,7 @@ impl GlobalTransform { /// Returns the 3d affine transformation matrix as a [`Mat4`]. #[inline] - pub fn compute_matrix(&self) -> Mat4 { + pub fn to_matrix(&self) -> Mat4 { Mat4::from(self.0) } diff --git a/release-content/migration-guides/rename_global_transform_compute_matrix.md b/release-content/migration-guides/rename_global_transform_compute_matrix.md new file mode 100644 index 0000000000..d4676ecc5e --- /dev/null +++ b/release-content/migration-guides/rename_global_transform_compute_matrix.md @@ -0,0 +1,6 @@ +--- +title: GlobalTransform::compute_matrix rename +pull_requests: [19643] +--- + +`GlobalTransform::compute_matrix` has been renamed to `GlobalTransform::to_matrix` because it does not compute anything, it simply moves data into a different type. From d1c6fbea570be64f8e24e943ee5a3cef221794e0 Mon Sep 17 00:00:00 2001 From: Alejandro Pascual Date: Tue, 17 Jun 2025 21:48:37 +0200 Subject: [PATCH 15/25] Support fallible one-shot systems (#19678) Closes #19677. I don't think that the output type needs to be `Send`. I've done some test at it seems to work fine without it, which in IMO makes sense, but please correct me if that is not the case. --- .../bevy_ecs/src/system/commands/command.rs | 15 ++++-- crates/bevy_ecs/src/system/commands/mod.rs | 16 +++--- crates/bevy_ecs/src/system/system_registry.rs | 54 +++++++++++++++++-- 3 files changed, 70 insertions(+), 15 deletions(-) diff --git a/crates/bevy_ecs/src/system/commands/command.rs b/crates/bevy_ecs/src/system/commands/command.rs index 84a2fdf4e9..f3fc677e47 100644 --- a/crates/bevy_ecs/src/system/commands/command.rs +++ b/crates/bevy_ecs/src/system/commands/command.rs @@ -144,10 +144,11 @@ where /// A [`Command`] that runs the given system, /// caching its [`SystemId`] in a [`CachedSystemId`](crate::system::CachedSystemId) resource. -pub fn run_system_cached(system: S) -> impl Command +pub fn run_system_cached(system: S) -> impl Command where + O: 'static, M: 'static, - S: IntoSystem<(), (), M> + Send + 'static, + S: IntoSystem<(), O, M> + Send + 'static, { move |world: &mut World| -> Result { world.run_system_cached(system)?; @@ -157,11 +158,15 @@ where /// A [`Command`] that runs the given system with the given input value, /// caching its [`SystemId`] in a [`CachedSystemId`](crate::system::CachedSystemId) resource. -pub fn run_system_cached_with(system: S, input: I::Inner<'static>) -> impl Command +pub fn run_system_cached_with( + system: S, + input: I::Inner<'static>, +) -> impl Command where I: SystemInput: Send> + Send + 'static, + O: 'static, M: 'static, - S: IntoSystem + Send + 'static, + S: IntoSystem + Send + 'static, { move |world: &mut World| -> Result { world.run_system_cached_with(system, input)?; @@ -175,7 +180,7 @@ where pub fn unregister_system(system_id: SystemId) -> impl Command where I: SystemInput + Send + 'static, - O: Send + 'static, + O: 'static, { move |world: &mut World| -> Result { world.unregister_system(system_id)?; diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index d36588d377..84f9784228 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -872,7 +872,7 @@ impl<'w, 's> Commands<'w, 's> { /// /// It will internally return a [`RegisteredSystemError`](crate::system::system_registry::RegisteredSystemError), /// which will be handled by [logging the error at the `warn` level](warn). - pub fn run_system(&mut self, id: SystemId) { + pub fn run_system(&mut self, id: SystemId<(), O>) { self.queue(command::run_system(id).handle_error_with(warn)); } @@ -965,7 +965,7 @@ impl<'w, 's> Commands<'w, 's> { ) -> SystemId where I: SystemInput + Send + 'static, - O: Send + 'static, + O: 'static, { let entity = self.spawn_empty().id(); let system = RegisteredSystem::::new(Box::new(IntoSystem::into_system(system))); @@ -990,7 +990,7 @@ impl<'w, 's> Commands<'w, 's> { pub fn unregister_system(&mut self, system_id: SystemId) where I: SystemInput + Send + 'static, - O: Send + 'static, + O: 'static, { self.queue(command::unregister_system(system_id).handle_error_with(warn)); } @@ -1039,10 +1039,11 @@ impl<'w, 's> Commands<'w, 's> { /// consider passing them in as inputs via [`Commands::run_system_cached_with`]. /// /// If that's not an option, consider [`Commands::register_system`] instead. - pub fn run_system_cached(&mut self, system: S) + pub fn run_system_cached(&mut self, system: S) where + O: 'static, M: 'static, - S: IntoSystem<(), (), M> + Send + 'static, + S: IntoSystem<(), O, M> + Send + 'static, { self.queue(command::run_system_cached(system).handle_error_with(warn)); } @@ -1069,11 +1070,12 @@ impl<'w, 's> Commands<'w, 's> { /// consider passing them in as inputs. /// /// If that's not an option, consider [`Commands::register_system`] instead. - pub fn run_system_cached_with(&mut self, system: S, input: I::Inner<'static>) + pub fn run_system_cached_with(&mut self, system: S, input: I::Inner<'static>) where I: SystemInput: Send> + Send + 'static, + O: 'static, M: 'static, - S: IntoSystem + Send + 'static, + S: IntoSystem + Send + 'static, { self.queue(command::run_system_cached_with(system, input).handle_error_with(warn)); } diff --git a/crates/bevy_ecs/src/system/system_registry.rs b/crates/bevy_ecs/src/system/system_registry.rs index 272cc85d0d..6889a8f4cd 100644 --- a/crates/bevy_ecs/src/system/system_registry.rs +++ b/crates/bevy_ecs/src/system/system_registry.rs @@ -651,6 +651,19 @@ mod tests { assert_eq!(output, NonCopy(3)); } + #[test] + fn fallible_system() { + fn sys() -> Result<()> { + Err("error")?; + Ok(()) + } + + let mut world = World::new(); + let fallible_system_id = world.register_system(sys); + let output = world.run_system(fallible_system_id); + assert!(matches!(output, Ok(Err(_)))); + } + #[test] fn exclusive_system() { let mut world = World::new(); @@ -751,19 +764,54 @@ mod tests { assert!(matches!(output, Ok(x) if x == four())); } + #[test] + fn cached_fallible_system() { + fn sys() -> Result<()> { + Err("error")?; + Ok(()) + } + + let mut world = World::new(); + let fallible_system_id = world.register_system_cached(sys); + let output = world.run_system(fallible_system_id); + assert!(matches!(output, Ok(Err(_)))); + let output = world.run_system_cached(sys); + assert!(matches!(output, Ok(Err(_)))); + let output = world.run_system_cached_with(sys, ()); + assert!(matches!(output, Ok(Err(_)))); + } + #[test] fn cached_system_commands() { fn sys(mut counter: ResMut) { - counter.0 = 1; + counter.0 += 1; } let mut world = World::new(); world.insert_resource(Counter(0)); - world.commands().run_system_cached(sys); world.flush_commands(); - assert_eq!(world.resource::().0, 1); + world.commands().run_system_cached_with(sys, ()); + world.flush_commands(); + assert_eq!(world.resource::().0, 2); + } + + #[test] + fn cached_fallible_system_commands() { + fn sys(mut counter: ResMut) -> Result { + counter.0 += 1; + Ok(()) + } + + let mut world = World::new(); + world.insert_resource(Counter(0)); + world.commands().run_system_cached(sys); + world.flush_commands(); + assert_eq!(world.resource::().0, 1); + world.commands().run_system_cached_with(sys, ()); + world.flush_commands(); + assert_eq!(world.resource::().0, 2); } #[test] From a750cfe4a178d303efc3d8c14ac626fd69402df3 Mon Sep 17 00:00:00 2001 From: Jan Hohenheim Date: Tue, 17 Jun 2025 22:20:13 +0200 Subject: [PATCH 16/25] Split CursorOptions off of Window (#19668) # Objective - Fixes #19627 - Tackles part of #19644 - Supersedes #19629 - `Window` has become a very very very big component - As such, our change detection does not *really* work on it, as e.g. moving the mouse will cause a change for the entire window - We circumvented this with a cache - But, some things *shouldn't* be cached as they can be changed from outside the user's control, notably the cursor grab mode on web - So, we need to disable the cache for that - But because change detection is broken, that would result in the cursor grab mode being set every frame the mouse is moved - That is usually *not* what a dev wants, as it forces the cursor to be locked even when the end-user is trying to free the cursor on the browser - the cache in this situation is invalid due to #8949 ## Solution - Split `Window` into multiple components, each with working change detection - Disable caching of the cursor grab mode - This will only attempt to force the grab mode when the `CursorOptions` were touched by the user, which is *much* rarer than simply moving the mouse. - If this PR is merged, I'll do the exact same for the other constituents of `Window` as a follow-up ## Testing - Ran all the changed examples --- crates/bevy_window/src/lib.rs | 17 +- crates/bevy_window/src/window.rs | 9 +- crates/bevy_winit/src/lib.rs | 6 +- crates/bevy_winit/src/state.rs | 11 +- crates/bevy_winit/src/system.rs | 162 +++++++++++------- crates/bevy_winit/src/winit_windows.rs | 15 +- examples/games/desk_toy.rs | 11 +- examples/helpers/camera_controller.rs | 16 +- examples/input/mouse_grab.rs | 15 +- examples/ui/window_fallthrough.rs | 6 +- examples/window/window_settings.rs | 10 +- .../migration-guides/split-window.md | 44 +++++ .../fixed-window-position.patch | 2 +- 13 files changed, 212 insertions(+), 112 deletions(-) create mode 100644 release-content/migration-guides/split-window.md diff --git a/crates/bevy_window/src/lib.rs b/crates/bevy_window/src/lib.rs index fb8f1fb18f..22e657cf03 100644 --- a/crates/bevy_window/src/lib.rs +++ b/crates/bevy_window/src/lib.rs @@ -57,6 +57,7 @@ impl Default for WindowPlugin { fn default() -> Self { WindowPlugin { primary_window: Some(Window::default()), + primary_cursor_options: Some(CursorOptions::default()), exit_condition: ExitCondition::OnAllClosed, close_when_requested: true, } @@ -76,6 +77,13 @@ pub struct WindowPlugin { /// [`exit_on_all_closed`]. pub primary_window: Option, + /// Settings for the cursor on the primary window. + /// + /// Defaults to `Some(CursorOptions::default())`. + /// + /// Has no effect if [`WindowPlugin::primary_window`] is `None`. + pub primary_cursor_options: Option, + /// Whether to exit the app when there are no open windows. /// /// If disabling this, ensure that you send the [`bevy_app::AppExit`] @@ -122,10 +130,14 @@ impl Plugin for WindowPlugin { .add_event::(); if let Some(primary_window) = &self.primary_window { - app.world_mut().spawn(primary_window.clone()).insert(( + let mut entity_commands = app.world_mut().spawn(primary_window.clone()); + entity_commands.insert(( PrimaryWindow, RawHandleWrapperHolder(Arc::new(Mutex::new(None))), )); + if let Some(primary_cursor_options) = &self.primary_cursor_options { + entity_commands.insert(primary_cursor_options.clone()); + } } match self.exit_condition { @@ -168,7 +180,8 @@ impl Plugin for WindowPlugin { // Register window descriptor and related types #[cfg(feature = "bevy_reflect")] app.register_type::() - .register_type::(); + .register_type::() + .register_type::(); } } diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index 4f7d7b2f7b..e2a9ca3c0f 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -158,10 +158,8 @@ impl ContainsEntity for NormalizedWindowRef { all(feature = "serialize", feature = "bevy_reflect"), reflect(Serialize, Deserialize) )] +#[require(CursorOptions)] pub struct Window { - /// The cursor options of this window. Cursor icons are set with the `Cursor` component on the - /// window entity. - pub cursor_options: CursorOptions, /// What presentation mode to give the window. pub present_mode: PresentMode, /// Which fullscreen or windowing mode should be used. @@ -470,7 +468,6 @@ impl Default for Window { Self { title: DEFAULT_WINDOW_TITLE.to_owned(), name: None, - cursor_options: Default::default(), present_mode: Default::default(), mode: Default::default(), position: Default::default(), @@ -728,11 +725,11 @@ impl WindowResizeConstraints { } /// Cursor data for a [`Window`]. -#[derive(Debug, Clone)] +#[derive(Component, Debug, Clone)] #[cfg_attr( feature = "bevy_reflect", derive(Reflect), - reflect(Debug, Default, Clone) + reflect(Component, Debug, Default, Clone) )] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr( diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index 6d814d4ac2..8926095dc0 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -25,8 +25,8 @@ use winit::{event_loop::EventLoop, window::WindowId}; use bevy_a11y::AccessibilityRequested; use bevy_app::{App, Last, Plugin}; use bevy_ecs::prelude::*; -use bevy_window::{exit_on_all_closed, Window, WindowCreated}; -use system::{changed_windows, check_keyboard_focus_lost, despawn_windows}; +use bevy_window::{exit_on_all_closed, CursorOptions, Window, WindowCreated}; +use system::{changed_cursor_options, changed_windows, check_keyboard_focus_lost, despawn_windows}; pub use system::{create_monitors, create_windows}; #[cfg(all(target_family = "wasm", target_os = "unknown"))] pub use winit::platform::web::CustomCursorExtWebSys; @@ -142,6 +142,7 @@ impl Plugin for WinitPlugin { // `exit_on_all_closed` only checks if windows exist but doesn't access data, // so we don't need to care about its ordering relative to `changed_windows` changed_windows.ambiguous_with(exit_on_all_closed), + changed_cursor_options, despawn_windows, check_keyboard_focus_lost, ) @@ -211,6 +212,7 @@ pub type CreateWindowParams<'w, 's, F = ()> = ( ( Entity, &'static mut Window, + &'static CursorOptions, Option<&'static RawHandleWrapperHolder>, ), F, diff --git a/crates/bevy_winit/src/state.rs b/crates/bevy_winit/src/state.rs index 934de5dad7..5b873d4620 100644 --- a/crates/bevy_winit/src/state.rs +++ b/crates/bevy_winit/src/state.rs @@ -46,7 +46,7 @@ use bevy_window::{ WindowScaleFactorChanged, WindowThemeChanged, }; #[cfg(target_os = "android")] -use bevy_window::{PrimaryWindow, RawHandleWrapper}; +use bevy_window::{CursorOptions, PrimaryWindow, RawHandleWrapper}; use crate::{ accessibility::ACCESS_KIT_ADAPTERS, @@ -474,7 +474,7 @@ impl ApplicationHandler for WinitAppRunnerState { if let Ok((window_component, mut cache)) = windows.get_mut(self.world_mut(), window) { if window_component.is_changed() { - cache.window = window_component.clone(); + **cache = window_component.clone(); } } }); @@ -605,10 +605,12 @@ impl WinitAppRunnerState { { // Get windows that are cached but without raw handles. Those window were already created, but got their // handle wrapper removed when the app was suspended. + let mut query = self.world_mut() - .query_filtered::<(Entity, &Window), (With, Without)>(); - if let Ok((entity, window)) = query.single(&self.world()) { + .query_filtered::<(Entity, &Window, &CursorOptions), (With, Without)>(); + if let Ok((entity, window, cursor_options)) = query.single(&self.world()) { let window = window.clone(); + let cursor_options = cursor_options.clone(); WINIT_WINDOWS.with_borrow_mut(|winit_windows| { ACCESS_KIT_ADAPTERS.with_borrow_mut(|adapters| { @@ -622,6 +624,7 @@ impl WinitAppRunnerState { event_loop, entity, &window, + &cursor_options, adapters, &mut handlers, &accessibility_requested, diff --git a/crates/bevy_winit/src/system.rs b/crates/bevy_winit/src/system.rs index 873949ea89..3cc7c5a5f6 100644 --- a/crates/bevy_winit/src/system.rs +++ b/crates/bevy_winit/src/system.rs @@ -1,6 +1,8 @@ use std::collections::HashMap; +use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ + change_detection::DetectChangesMut, entity::Entity, event::EventWriter, lifecycle::RemovedComponents, @@ -10,9 +12,9 @@ use bevy_ecs::{ }; use bevy_input::keyboard::{Key, KeyCode, KeyboardFocusLost, KeyboardInput}; use bevy_window::{ - ClosingWindow, Monitor, PrimaryMonitor, RawHandleWrapper, VideoMode, Window, WindowClosed, - WindowClosing, WindowCreated, WindowEvent, WindowFocused, WindowMode, WindowResized, - WindowWrapper, + ClosingWindow, CursorOptions, Monitor, PrimaryMonitor, RawHandleWrapper, VideoMode, Window, + WindowClosed, WindowClosing, WindowCreated, WindowEvent, WindowFocused, WindowMode, + WindowResized, WindowWrapper, }; use tracing::{error, info, warn}; @@ -59,7 +61,7 @@ pub fn create_windows( ) { WINIT_WINDOWS.with_borrow_mut(|winit_windows| { ACCESS_KIT_ADAPTERS.with_borrow_mut(|adapters| { - for (entity, mut window, handle_holder) in &mut created_windows { + for (entity, mut window, cursor_options, handle_holder) in &mut created_windows { if winit_windows.get_window(entity).is_some() { continue; } @@ -70,6 +72,7 @@ pub fn create_windows( event_loop, entity, &window, + cursor_options, adapters, &mut handlers, &accessibility_requested, @@ -85,9 +88,8 @@ pub fn create_windows( .set_scale_factor_and_apply_to_physical_size(winit_window.scale_factor() as f32); commands.entity(entity).insert(( - CachedWindow { - window: window.clone(), - }, + CachedWindow(window.clone()), + CachedCursorOptions(cursor_options.clone()), WinitWindowPressedKeys::default(), )); @@ -281,10 +283,12 @@ pub(crate) fn despawn_windows( } /// The cached state of the window so we can check which properties were changed from within the app. -#[derive(Debug, Clone, Component)] -pub struct CachedWindow { - pub window: Window, -} +#[derive(Debug, Clone, Component, Deref, DerefMut)] +pub(crate) struct CachedWindow(Window); + +/// The cached state of the window so we can check which properties were changed from within the app. +#[derive(Debug, Clone, Component, Deref, DerefMut)] +pub(crate) struct CachedCursorOptions(CursorOptions); /// Propagates changes from [`Window`] entities to the [`winit`] backend. /// @@ -306,11 +310,11 @@ pub(crate) fn changed_windows( continue; }; - if window.title != cache.window.title { + if window.title != cache.title { winit_window.set_title(window.title.as_str()); } - if window.mode != cache.window.mode { + if window.mode != cache.mode { let new_mode = match window.mode { WindowMode::BorderlessFullscreen(monitor_selection) => { Some(Some(winit::window::Fullscreen::Borderless(select_monitor( @@ -352,15 +356,15 @@ pub(crate) fn changed_windows( } } - if window.resolution != cache.window.resolution { + if window.resolution != cache.resolution { let mut physical_size = PhysicalSize::new( window.resolution.physical_width(), window.resolution.physical_height(), ); let cached_physical_size = PhysicalSize::new( - cache.window.physical_width(), - cache.window.physical_height(), + cache.physical_width(), + cache.physical_height(), ); let base_scale_factor = window.resolution.base_scale_factor(); @@ -368,12 +372,12 @@ pub(crate) fn changed_windows( // Note: this may be different from `winit`'s base scale factor if // `scale_factor_override` is set to Some(f32) let scale_factor = window.scale_factor(); - let cached_scale_factor = cache.window.scale_factor(); + let cached_scale_factor = cache.scale_factor(); // Check and update `winit`'s physical size only if the window is not maximized if scale_factor != cached_scale_factor && !winit_window.is_maximized() { let logical_size = - if let Some(cached_factor) = cache.window.resolution.scale_factor_override() { + if let Some(cached_factor) = cache.resolution.scale_factor_override() { physical_size.to_logical::(cached_factor as f64) } else { physical_size.to_logical::(base_scale_factor as f64) @@ -397,7 +401,7 @@ pub(crate) fn changed_windows( } } - if window.physical_cursor_position() != cache.window.physical_cursor_position() { + if window.physical_cursor_position() != cache.physical_cursor_position() { if let Some(physical_position) = window.physical_cursor_position() { let position = PhysicalPosition::new(physical_position.x, physical_position.y); @@ -407,44 +411,23 @@ pub(crate) fn changed_windows( } } - if window.cursor_options.grab_mode != cache.window.cursor_options.grab_mode - && crate::winit_windows::attempt_grab(winit_window, window.cursor_options.grab_mode) - .is_err() - { - window.cursor_options.grab_mode = cache.window.cursor_options.grab_mode; - } - - if window.cursor_options.visible != cache.window.cursor_options.visible { - winit_window.set_cursor_visible(window.cursor_options.visible); - } - - if window.cursor_options.hit_test != cache.window.cursor_options.hit_test { - if let Err(err) = winit_window.set_cursor_hittest(window.cursor_options.hit_test) { - window.cursor_options.hit_test = cache.window.cursor_options.hit_test; - warn!( - "Could not set cursor hit test for window {}: {}", - window.title, err - ); - } - } - - if window.decorations != cache.window.decorations + if window.decorations != cache.decorations && window.decorations != winit_window.is_decorated() { winit_window.set_decorations(window.decorations); } - if window.resizable != cache.window.resizable + if window.resizable != cache.resizable && window.resizable != winit_window.is_resizable() { winit_window.set_resizable(window.resizable); } - if window.enabled_buttons != cache.window.enabled_buttons { + if window.enabled_buttons != cache.enabled_buttons { winit_window.set_enabled_buttons(convert_enabled_buttons(window.enabled_buttons)); } - if window.resize_constraints != cache.window.resize_constraints { + if window.resize_constraints != cache.resize_constraints { let constraints = window.resize_constraints.check_constraints(); let min_inner_size = LogicalSize { width: constraints.min_width, @@ -461,7 +444,7 @@ pub(crate) fn changed_windows( } } - if window.position != cache.window.position { + if window.position != cache.position { if let Some(position) = crate::winit_window_position( &window.position, &window.resolution, @@ -502,62 +485,62 @@ pub(crate) fn changed_windows( } } - if window.focused != cache.window.focused && window.focused { + if window.focused != cache.focused && window.focused { winit_window.focus_window(); } - if window.window_level != cache.window.window_level { + if window.window_level != cache.window_level { winit_window.set_window_level(convert_window_level(window.window_level)); } // Currently unsupported changes - if window.transparent != cache.window.transparent { - window.transparent = cache.window.transparent; + if window.transparent != cache.transparent { + window.transparent = cache.transparent; warn!("Winit does not currently support updating transparency after window creation."); } #[cfg(target_arch = "wasm32")] - if window.canvas != cache.window.canvas { - window.canvas.clone_from(&cache.window.canvas); + if window.canvas != cache.canvas { + window.canvas.clone_from(&cache.canvas); warn!( "Bevy currently doesn't support modifying the window canvas after initialization." ); } - if window.ime_enabled != cache.window.ime_enabled { + if window.ime_enabled != cache.ime_enabled { winit_window.set_ime_allowed(window.ime_enabled); } - if window.ime_position != cache.window.ime_position { + if window.ime_position != cache.ime_position { winit_window.set_ime_cursor_area( LogicalPosition::new(window.ime_position.x, window.ime_position.y), PhysicalSize::new(10, 10), ); } - if window.window_theme != cache.window.window_theme { + if window.window_theme != cache.window_theme { winit_window.set_theme(window.window_theme.map(convert_window_theme)); } - if window.visible != cache.window.visible { + if window.visible != cache.visible { winit_window.set_visible(window.visible); } #[cfg(target_os = "ios")] { - if window.recognize_pinch_gesture != cache.window.recognize_pinch_gesture { + if window.recognize_pinch_gesture != cache.recognize_pinch_gesture { winit_window.recognize_pinch_gesture(window.recognize_pinch_gesture); } - if window.recognize_rotation_gesture != cache.window.recognize_rotation_gesture { + if window.recognize_rotation_gesture != cache.recognize_rotation_gesture { winit_window.recognize_rotation_gesture(window.recognize_rotation_gesture); } - if window.recognize_doubletap_gesture != cache.window.recognize_doubletap_gesture { + if window.recognize_doubletap_gesture != cache.recognize_doubletap_gesture { winit_window.recognize_doubletap_gesture(window.recognize_doubletap_gesture); } - if window.recognize_pan_gesture != cache.window.recognize_pan_gesture { + if window.recognize_pan_gesture != cache.recognize_pan_gesture { match ( window.recognize_pan_gesture, - cache.window.recognize_pan_gesture, + cache.recognize_pan_gesture, ) { (Some(_), Some(_)) => { warn!("Bevy currently doesn't support modifying PanGesture number of fingers recognition. Please disable it before re-enabling it with the new number of fingers"); @@ -567,16 +550,15 @@ pub(crate) fn changed_windows( } } - if window.prefers_home_indicator_hidden != cache.window.prefers_home_indicator_hidden { + if window.prefers_home_indicator_hidden != cache.prefers_home_indicator_hidden { winit_window .set_prefers_home_indicator_hidden(window.prefers_home_indicator_hidden); } - if window.prefers_status_bar_hidden != cache.window.prefers_status_bar_hidden { + if window.prefers_status_bar_hidden != cache.prefers_status_bar_hidden { winit_window.set_prefers_status_bar_hidden(window.prefers_status_bar_hidden); } if window.preferred_screen_edges_deferring_system_gestures != cache - .window .preferred_screen_edges_deferring_system_gestures { use crate::converters::convert_screen_edge; @@ -585,7 +567,59 @@ pub(crate) fn changed_windows( winit_window.set_preferred_screen_edges_deferring_system_gestures(preferred_edge); } } - cache.window = window.clone(); + **cache = window.clone(); + } + }); +} + +pub(crate) fn changed_cursor_options( + mut changed_windows: Query< + ( + Entity, + &Window, + &mut CursorOptions, + &mut CachedCursorOptions, + ), + Changed, + >, + _non_send_marker: NonSendMarker, +) { + WINIT_WINDOWS.with_borrow(|winit_windows| { + for (entity, window, mut cursor_options, mut cache) in &mut changed_windows { + // This system already only runs when the cursor options change, so we need to bypass change detection or the next frame will also run this system + let cursor_options = cursor_options.bypass_change_detection(); + let Some(winit_window) = winit_windows.get_window(entity) else { + continue; + }; + // Don't check the cache for the grab mode. It can change through external means, leaving the cache outdated. + if let Err(err) = + crate::winit_windows::attempt_grab(winit_window, cursor_options.grab_mode) + { + warn!( + "Could not set cursor grab mode for window {}: {}", + window.title, err + ); + cursor_options.grab_mode = cache.grab_mode; + } else { + cache.grab_mode = cursor_options.grab_mode; + } + + if cursor_options.visible != cache.visible { + winit_window.set_cursor_visible(cursor_options.visible); + cache.visible = cursor_options.visible; + } + + if cursor_options.hit_test != cache.hit_test { + if let Err(err) = winit_window.set_cursor_hittest(cursor_options.hit_test) { + warn!( + "Could not set cursor hit test for window {}: {}", + window.title, err + ); + cursor_options.hit_test = cache.hit_test; + } else { + cache.hit_test = cursor_options.hit_test; + } + } } }); } diff --git a/crates/bevy_winit/src/winit_windows.rs b/crates/bevy_winit/src/winit_windows.rs index 8bf326f453..5110e670c2 100644 --- a/crates/bevy_winit/src/winit_windows.rs +++ b/crates/bevy_winit/src/winit_windows.rs @@ -4,8 +4,8 @@ use bevy_ecs::entity::Entity; use bevy_ecs::entity::EntityHashMap; use bevy_platform::collections::HashMap; use bevy_window::{ - CursorGrabMode, MonitorSelection, VideoModeSelection, Window, WindowMode, WindowPosition, - WindowResolution, WindowWrapper, + CursorGrabMode, CursorOptions, MonitorSelection, VideoModeSelection, Window, WindowMode, + WindowPosition, WindowResolution, WindowWrapper, }; use tracing::warn; @@ -58,6 +58,7 @@ impl WinitWindows { event_loop: &ActiveEventLoop, entity: Entity, window: &Window, + cursor_options: &CursorOptions, adapters: &mut AccessKitAdapters, handlers: &mut WinitActionRequestHandlers, accessibility_requested: &AccessibilityRequested, @@ -310,16 +311,16 @@ impl WinitWindows { winit_window.set_visible(window.visible); // Do not set the grab mode on window creation if it's none. It can fail on mobile. - if window.cursor_options.grab_mode != CursorGrabMode::None { - let _ = attempt_grab(&winit_window, window.cursor_options.grab_mode); + if cursor_options.grab_mode != CursorGrabMode::None { + let _ = attempt_grab(&winit_window, cursor_options.grab_mode); } - winit_window.set_cursor_visible(window.cursor_options.visible); + winit_window.set_cursor_visible(cursor_options.visible); // Do not set the cursor hittest on window creation if it's false, as it will always fail on // some platforms and log an unfixable warning. - if !window.cursor_options.hit_test { - if let Err(err) = winit_window.set_cursor_hittest(window.cursor_options.hit_test) { + if !cursor_options.hit_test { + if let Err(err) = winit_window.set_cursor_hittest(cursor_options.hit_test) { warn!( "Could not set cursor hit test for window {}: {}", window.title, err diff --git a/examples/games/desk_toy.rs b/examples/games/desk_toy.rs index c25286dd9c..b5c638348e 100644 --- a/examples/games/desk_toy.rs +++ b/examples/games/desk_toy.rs @@ -10,7 +10,7 @@ use bevy::{ app::AppExit, input::common_conditions::{input_just_pressed, input_just_released}, prelude::*, - window::{PrimaryWindow, WindowLevel}, + window::{CursorOptions, PrimaryWindow, WindowLevel}, }; #[cfg(target_os = "macos")] @@ -219,12 +219,13 @@ fn get_cursor_world_pos( /// Update whether the window is clickable or not fn update_cursor_hit_test( cursor_world_pos: Res, - mut primary_window: Single<&mut Window, With>, + primary_window: Single<(&Window, &mut CursorOptions), With>, bevy_logo_transform: Single<&Transform, With>, ) { + let (window, mut cursor_options) = primary_window.into_inner(); // If the window has decorations (e.g. a border) then it should be clickable - if primary_window.decorations { - primary_window.cursor_options.hit_test = true; + if window.decorations { + cursor_options.hit_test = true; return; } @@ -234,7 +235,7 @@ fn update_cursor_hit_test( }; // If the cursor is within the radius of the Bevy logo make the window clickable otherwise the window is not clickable - primary_window.cursor_options.hit_test = bevy_logo_transform + cursor_options.hit_test = bevy_logo_transform .translation .truncate() .distance(cursor_world_pos) diff --git a/examples/helpers/camera_controller.rs b/examples/helpers/camera_controller.rs index a60aa69a5e..3f6e4ed477 100644 --- a/examples/helpers/camera_controller.rs +++ b/examples/helpers/camera_controller.rs @@ -8,7 +8,7 @@ use bevy::{ input::mouse::{AccumulatedMouseMotion, AccumulatedMouseScroll, MouseScrollUnit}, prelude::*, - window::CursorGrabMode, + window::{CursorGrabMode, CursorOptions}, }; use std::{f32::consts::*, fmt}; @@ -126,7 +126,7 @@ Freecam Controls: fn run_camera_controller( time: Res { - type Fetch<'w, 's> = AssetChangedFetch<'w, A>; + type Fetch<'w> = AssetChangedFetch<'w, A>; type State = AssetChangedState; - fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( - fetch: Self::Fetch<'wlong, 's>, - ) -> Self::Fetch<'wshort, 's> { + fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { fetch } @@ -165,7 +163,7 @@ unsafe impl WorldQuery for AssetChanged { state: &'s Self::State, last_run: Tick, this_run: Tick, - ) -> Self::Fetch<'w, 's> { + ) -> Self::Fetch<'w> { // SAFETY: // - `AssetChanges` is private and only accessed mutably in the `AssetEventSystems` system set. // - `resource_id` was obtained from the type ID of `AssetChanges`. @@ -204,7 +202,7 @@ unsafe impl WorldQuery for AssetChanged { const IS_DENSE: bool = <&A>::IS_DENSE; unsafe fn set_archetype<'w, 's>( - fetch: &mut Self::Fetch<'w, 's>, + fetch: &mut Self::Fetch<'w>, state: &'s Self::State, archetype: &'w Archetype, table: &'w Table, @@ -218,7 +216,7 @@ unsafe impl WorldQuery for AssetChanged { } unsafe fn set_table<'w, 's>( - fetch: &mut Self::Fetch<'w, 's>, + fetch: &mut Self::Fetch<'w>, state: &Self::State, table: &'w Table, ) { @@ -271,14 +269,15 @@ unsafe impl QueryFilter for AssetChanged { #[inline] unsafe fn filter_fetch( - fetch: &mut Self::Fetch<'_, '_>, + state: &Self::State, + fetch: &mut Self::Fetch<'_>, entity: Entity, table_row: TableRow, ) -> bool { fetch.inner.as_mut().is_some_and(|inner| { // SAFETY: We delegate to the inner `fetch` for `A` unsafe { - let handle = <&A>::fetch(inner, entity, table_row); + let handle = <&A>::fetch(&state.asset_id, inner, entity, table_row); fetch.check.has_changed(handle) } }) diff --git a/crates/bevy_ecs/macros/src/query_data.rs b/crates/bevy_ecs/macros/src/query_data.rs index 44021f27a7..910d9ce3b6 100644 --- a/crates/bevy_ecs/macros/src/query_data.rs +++ b/crates/bevy_ecs/macros/src/query_data.rs @@ -74,13 +74,23 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream { let user_generics = ast.generics.clone(); let (user_impl_generics, user_ty_generics, user_where_clauses) = user_generics.split_for_impl(); let user_generics_with_world = { + let mut generics = ast.generics.clone(); + generics.params.insert(0, parse_quote!('__w)); + generics + }; + let (user_impl_generics_with_world, user_ty_generics_with_world, user_where_clauses_with_world) = + user_generics_with_world.split_for_impl(); + let user_generics_with_world_and_state = { let mut generics = ast.generics; generics.params.insert(0, parse_quote!('__w)); generics.params.insert(0, parse_quote!('__s)); generics }; - let (user_impl_generics_with_world, user_ty_generics_with_world, user_where_clauses_with_world) = - user_generics_with_world.split_for_impl(); + let ( + user_impl_generics_with_world_and_state, + user_ty_generics_with_world_and_state, + user_where_clauses_with_world_and_state, + ) = user_generics_with_world_and_state.split_for_impl(); let struct_name = ast.ident; let read_only_struct_name = if attributes.is_mutable { @@ -165,13 +175,13 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream { &visibility, &item_struct_name, &field_types, - &user_impl_generics_with_world, + &user_impl_generics_with_world_and_state, &field_attrs, &field_visibilities, &field_idents, &user_ty_generics, - &user_ty_generics_with_world, - user_where_clauses_with_world, + &user_ty_generics_with_world_and_state, + user_where_clauses_with_world_and_state, ); let mutable_world_query_impl = world_query_impl( &path, @@ -200,13 +210,13 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream { &visibility, &read_only_item_struct_name, &read_only_field_types, - &user_impl_generics_with_world, + &user_impl_generics_with_world_and_state, &field_attrs, &field_visibilities, &field_idents, &user_ty_generics, - &user_ty_generics_with_world, - user_where_clauses_with_world, + &user_ty_generics_with_world_and_state, + user_where_clauses_with_world_and_state, ); let readonly_world_query_impl = world_query_impl( &path, @@ -257,7 +267,7 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream { for #read_only_struct_name #user_ty_generics #user_where_clauses { const IS_READ_ONLY: bool = true; type ReadOnly = #read_only_struct_name #user_ty_generics; - type Item<'__w, '__s> = #read_only_item_struct_name #user_ty_generics_with_world; + type Item<'__w, '__s> = #read_only_item_struct_name #user_ty_generics_with_world_and_state; fn shrink<'__wlong: '__wshort, '__wshort, '__s>( item: Self::Item<'__wlong, '__s> @@ -280,12 +290,13 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream { /// SAFETY: we call `fetch` for each member that implements `Fetch`. #[inline(always)] unsafe fn fetch<'__w, '__s>( - _fetch: &mut ::Fetch<'__w, '__s>, + _state: &'__s Self::State, + _fetch: &mut ::Fetch<'__w>, _entity: #path::entity::Entity, _table_row: #path::storage::TableRow, ) -> Self::Item<'__w, '__s> { Self::Item { - #(#field_idents: <#read_only_field_types>::fetch(&mut _fetch.#named_field_idents, _entity, _table_row),)* + #(#field_idents: <#read_only_field_types>::fetch(&_state.#named_field_idents, &mut _fetch.#named_field_idents, _entity, _table_row),)* } } } @@ -314,7 +325,7 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream { for #struct_name #user_ty_generics #user_where_clauses { const IS_READ_ONLY: bool = #is_read_only; type ReadOnly = #read_only_struct_name #user_ty_generics; - type Item<'__w, '__s> = #item_struct_name #user_ty_generics_with_world; + type Item<'__w, '__s> = #item_struct_name #user_ty_generics_with_world_and_state; fn shrink<'__wlong: '__wshort, '__wshort, '__s>( item: Self::Item<'__wlong, '__s> @@ -337,12 +348,13 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream { /// SAFETY: we call `fetch` for each member that implements `Fetch`. #[inline(always)] unsafe fn fetch<'__w, '__s>( - _fetch: &mut ::Fetch<'__w, '__s>, + _state: &'__s Self::State, + _fetch: &mut ::Fetch<'__w>, _entity: #path::entity::Entity, _table_row: #path::storage::TableRow, ) -> Self::Item<'__w, '__s> { Self::Item { - #(#field_idents: <#field_types>::fetch(&mut _fetch.#named_field_idents, _entity, _table_row),)* + #(#field_idents: <#field_types>::fetch(&_state.#named_field_idents, &mut _fetch.#named_field_idents, _entity, _table_row),)* } } } diff --git a/crates/bevy_ecs/macros/src/query_filter.rs b/crates/bevy_ecs/macros/src/query_filter.rs index acc5c3d41b..5ae2d2325f 100644 --- a/crates/bevy_ecs/macros/src/query_filter.rs +++ b/crates/bevy_ecs/macros/src/query_filter.rs @@ -23,7 +23,6 @@ pub fn derive_query_filter_impl(input: TokenStream) -> TokenStream { let user_generics_with_world = { let mut generics = ast.generics; generics.params.insert(0, parse_quote!('__w)); - generics.params.insert(0, parse_quote!('__s)); generics }; let (user_impl_generics_with_world, user_ty_generics_with_world, user_where_clauses_with_world) = @@ -102,12 +101,13 @@ pub fn derive_query_filter_impl(input: TokenStream) -> TokenStream { #[allow(unused_variables)] #[inline(always)] - unsafe fn filter_fetch<'__w, '__s>( - _fetch: &mut ::Fetch<'__w, '__s>, + unsafe fn filter_fetch<'__w>( + _state: &Self::State, + _fetch: &mut ::Fetch<'__w>, _entity: #path::entity::Entity, _table_row: #path::storage::TableRow, ) -> bool { - true #(&& <#field_types>::filter_fetch(&mut _fetch.#named_field_idents, _entity, _table_row))* + true #(&& <#field_types>::filter_fetch(&_state.#named_field_idents, &mut _fetch.#named_field_idents, _entity, _table_row))* } } }; diff --git a/crates/bevy_ecs/macros/src/world_query.rs b/crates/bevy_ecs/macros/src/world_query.rs index c064b05b97..5a7d164b80 100644 --- a/crates/bevy_ecs/macros/src/world_query.rs +++ b/crates/bevy_ecs/macros/src/world_query.rs @@ -10,13 +10,13 @@ pub(crate) fn item_struct( visibility: &Visibility, item_struct_name: &Ident, field_types: &Vec, - user_impl_generics_with_world: &ImplGenerics, + user_impl_generics_with_world_and_state: &ImplGenerics, field_attrs: &Vec>, field_visibilities: &Vec, field_idents: &Vec, user_ty_generics: &TypeGenerics, - user_ty_generics_with_world: &TypeGenerics, - user_where_clauses_with_world: Option<&WhereClause>, + user_ty_generics_with_world_and_state: &TypeGenerics, + user_where_clauses_with_world_and_state: Option<&WhereClause>, ) -> proc_macro2::TokenStream { let item_attrs = quote! { #[doc = concat!( @@ -33,20 +33,20 @@ pub(crate) fn item_struct( Fields::Named(_) => quote! { #derive_macro_call #item_attrs - #visibility struct #item_struct_name #user_impl_generics_with_world #user_where_clauses_with_world { + #visibility struct #item_struct_name #user_impl_generics_with_world_and_state #user_where_clauses_with_world_and_state { #(#(#field_attrs)* #field_visibilities #field_idents: <#field_types as #path::query::QueryData>::Item<'__w, '__s>,)* } }, Fields::Unnamed(_) => quote! { #derive_macro_call #item_attrs - #visibility struct #item_struct_name #user_impl_generics_with_world #user_where_clauses_with_world( + #visibility struct #item_struct_name #user_impl_generics_with_world_and_state #user_where_clauses_with_world_and_state( #( #field_visibilities <#field_types as #path::query::QueryData>::Item<'__w, '__s>, )* ); }, Fields::Unit => quote! { #item_attrs - #visibility type #item_struct_name #user_ty_generics_with_world = #struct_name #user_ty_generics; + #visibility type #item_struct_name #user_ty_generics_with_world_and_state = #struct_name #user_ty_generics; }, } } @@ -78,8 +78,8 @@ pub(crate) fn world_query_impl( )] #[automatically_derived] #visibility struct #fetch_struct_name #user_impl_generics_with_world #user_where_clauses_with_world { - #(#named_field_idents: <#field_types as #path::query::WorldQuery>::Fetch<'__w, '__s>,)* - #marker_name: (&'__w(), &'__s()), + #(#named_field_idents: <#field_types as #path::query::WorldQuery>::Fetch<'__w>,)* + #marker_name: &'__w(), } impl #user_impl_generics_with_world Clone for #fetch_struct_name #user_ty_generics_with_world @@ -87,7 +87,7 @@ pub(crate) fn world_query_impl( fn clone(&self) -> Self { Self { #(#named_field_idents: self.#named_field_idents.clone(),)* - #marker_name: (&(), &()), + #marker_name: &(), } } } @@ -96,17 +96,17 @@ pub(crate) fn world_query_impl( unsafe impl #user_impl_generics #path::query::WorldQuery for #struct_name #user_ty_generics #user_where_clauses { - type Fetch<'__w, '__s> = #fetch_struct_name #user_ty_generics_with_world; + type Fetch<'__w> = #fetch_struct_name #user_ty_generics_with_world; type State = #state_struct_name #user_ty_generics; - fn shrink_fetch<'__wlong: '__wshort, '__wshort, '__s>( - fetch: <#struct_name #user_ty_generics as #path::query::WorldQuery>::Fetch<'__wlong, '__s> - ) -> <#struct_name #user_ty_generics as #path::query::WorldQuery>::Fetch<'__wshort, '__s> { + fn shrink_fetch<'__wlong: '__wshort, '__wshort>( + fetch: <#struct_name #user_ty_generics as #path::query::WorldQuery>::Fetch<'__wlong> + ) -> <#struct_name #user_ty_generics as #path::query::WorldQuery>::Fetch<'__wshort> { #fetch_struct_name { #( #named_field_idents: <#field_types>::shrink_fetch(fetch.#named_field_idents), )* - #marker_name: (&(), &()), + #marker_name: &(), } } @@ -115,7 +115,7 @@ pub(crate) fn world_query_impl( state: &'__s Self::State, _last_run: #path::component::Tick, _this_run: #path::component::Tick, - ) -> ::Fetch<'__w, '__s> { + ) -> ::Fetch<'__w> { #fetch_struct_name { #(#named_field_idents: <#field_types>::init_fetch( @@ -125,7 +125,7 @@ pub(crate) fn world_query_impl( _this_run, ), )* - #marker_name: (&(), &()), + #marker_name: &(), } } @@ -134,7 +134,7 @@ pub(crate) fn world_query_impl( /// SAFETY: we call `set_archetype` for each member that implements `Fetch` #[inline] unsafe fn set_archetype<'__w, '__s>( - _fetch: &mut ::Fetch<'__w, '__s>, + _fetch: &mut ::Fetch<'__w>, _state: &'__s Self::State, _archetype: &'__w #path::archetype::Archetype, _table: &'__w #path::storage::Table @@ -145,7 +145,7 @@ pub(crate) fn world_query_impl( /// SAFETY: we call `set_table` for each member that implements `Fetch` #[inline] unsafe fn set_table<'__w, '__s>( - _fetch: &mut ::Fetch<'__w, '__s>, + _fetch: &mut ::Fetch<'__w>, _state: &'__s Self::State, _table: &'__w #path::storage::Table ) { diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index 4abdbbe530..0a13b61819 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -326,7 +326,8 @@ pub unsafe trait QueryData: WorldQuery { /// `table_row` must be in the range of the current table and archetype. /// - There must not be simultaneous conflicting component access registered in `update_component_access`. unsafe fn fetch<'w, 's>( - fetch: &mut Self::Fetch<'w, 's>, + state: &'s Self::State, + fetch: &mut Self::Fetch<'w>, entity: Entity, table_row: TableRow, ) -> Self::Item<'w, 's>; @@ -359,27 +360,24 @@ pub trait ReleaseStateQueryData: QueryData { /// `update_component_access` does nothing. /// This is sound because `fetch` does not access components. unsafe impl WorldQuery for Entity { - type Fetch<'w, 's> = (); + type Fetch<'w> = (); type State = (); - fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( - _: Self::Fetch<'wlong, 's>, - ) -> Self::Fetch<'wshort, 's> { - } + fn shrink_fetch<'wlong: 'wshort, 'wshort>(_: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {} unsafe fn init_fetch<'w, 's>( _world: UnsafeWorldCell<'w>, _state: &'s Self::State, _last_run: Tick, _this_run: Tick, - ) -> Self::Fetch<'w, 's> { + ) -> Self::Fetch<'w> { } const IS_DENSE: bool = true; #[inline] unsafe fn set_archetype<'w, 's>( - _fetch: &mut Self::Fetch<'w, 's>, + _fetch: &mut Self::Fetch<'w>, _state: &'s Self::State, _archetype: &'w Archetype, _table: &Table, @@ -388,7 +386,7 @@ unsafe impl WorldQuery for Entity { #[inline] unsafe fn set_table<'w, 's>( - _fetch: &mut Self::Fetch<'w, 's>, + _fetch: &mut Self::Fetch<'w>, _state: &'s Self::State, _table: &'w Table, ) { @@ -425,7 +423,8 @@ unsafe impl QueryData for Entity { #[inline(always)] unsafe fn fetch<'w, 's>( - _fetch: &mut Self::Fetch<'w, 's>, + _state: &'s Self::State, + _fetch: &mut Self::Fetch<'w>, entity: Entity, _table_row: TableRow, ) -> Self::Item<'w, 's> { @@ -446,12 +445,10 @@ impl ReleaseStateQueryData for Entity { /// `update_component_access` does nothing. /// This is sound because `fetch` does not access components. unsafe impl WorldQuery for EntityLocation { - type Fetch<'w, 's> = &'w Entities; + type Fetch<'w> = &'w Entities; type State = (); - fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( - fetch: Self::Fetch<'wlong, 's>, - ) -> Self::Fetch<'wshort, 's> { + fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { fetch } @@ -460,7 +457,7 @@ unsafe impl WorldQuery for EntityLocation { _state: &'s Self::State, _last_run: Tick, _this_run: Tick, - ) -> Self::Fetch<'w, 's> { + ) -> Self::Fetch<'w> { world.entities() } @@ -470,7 +467,7 @@ unsafe impl WorldQuery for EntityLocation { #[inline] unsafe fn set_archetype<'w, 's>( - _fetch: &mut Self::Fetch<'w, 's>, + _fetch: &mut Self::Fetch<'w>, _state: &'s Self::State, _archetype: &'w Archetype, _table: &Table, @@ -479,7 +476,7 @@ unsafe impl WorldQuery for EntityLocation { #[inline] unsafe fn set_table<'w, 's>( - _fetch: &mut Self::Fetch<'w, 's>, + _fetch: &mut Self::Fetch<'w>, _state: &'s Self::State, _table: &'w Table, ) { @@ -515,7 +512,8 @@ unsafe impl QueryData for EntityLocation { #[inline(always)] unsafe fn fetch<'w, 's>( - fetch: &mut Self::Fetch<'w, 's>, + _state: &'s Self::State, + fetch: &mut Self::Fetch<'w>, entity: Entity, _table_row: TableRow, ) -> Self::Item<'w, 's> { @@ -606,12 +604,10 @@ pub struct SpawnDetailsFetch<'w> { // SAFETY: // No components are accessed. unsafe impl WorldQuery for SpawnDetails { - type Fetch<'w, 's> = SpawnDetailsFetch<'w>; + type Fetch<'w> = SpawnDetailsFetch<'w>; type State = (); - fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( - fetch: Self::Fetch<'wlong, 's>, - ) -> Self::Fetch<'wshort, 's> { + fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { fetch } @@ -620,7 +616,7 @@ unsafe impl WorldQuery for SpawnDetails { _state: &'s Self::State, last_run: Tick, this_run: Tick, - ) -> Self::Fetch<'w, 's> { + ) -> Self::Fetch<'w> { SpawnDetailsFetch { entities: world.entities(), last_run, @@ -632,7 +628,7 @@ unsafe impl WorldQuery for SpawnDetails { #[inline] unsafe fn set_archetype<'w, 's>( - _fetch: &mut Self::Fetch<'w, 's>, + _fetch: &mut Self::Fetch<'w>, _state: &'s Self::State, _archetype: &'w Archetype, _table: &'w Table, @@ -641,7 +637,7 @@ unsafe impl WorldQuery for SpawnDetails { #[inline] unsafe fn set_table<'w, 's>( - _fetch: &mut Self::Fetch<'w, 's>, + _fetch: &mut Self::Fetch<'w>, _state: &'s Self::State, _table: &'w Table, ) { @@ -679,7 +675,8 @@ unsafe impl QueryData for SpawnDetails { #[inline(always)] unsafe fn fetch<'w, 's>( - fetch: &mut Self::Fetch<'w, 's>, + _state: &'s Self::State, + fetch: &mut Self::Fetch<'w>, entity: Entity, _table_row: TableRow, ) -> Self::Item<'w, 's> { @@ -722,12 +719,10 @@ pub struct EntityFetch<'w> { /// This is sound because `update_component_access` sets read access for all components and panic when appropriate. /// Filters are unchanged. unsafe impl<'a> WorldQuery for EntityRef<'a> { - type Fetch<'w, 's> = EntityFetch<'w>; + type Fetch<'w> = EntityFetch<'w>; type State = (); - fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( - fetch: Self::Fetch<'wlong, 's>, - ) -> Self::Fetch<'wshort, 's> { + fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { fetch } @@ -736,7 +731,7 @@ unsafe impl<'a> WorldQuery for EntityRef<'a> { _state: &'s Self::State, last_run: Tick, this_run: Tick, - ) -> Self::Fetch<'w, 's> { + ) -> Self::Fetch<'w> { EntityFetch { world, last_run, @@ -748,7 +743,7 @@ unsafe impl<'a> WorldQuery for EntityRef<'a> { #[inline] unsafe fn set_archetype<'w, 's>( - _fetch: &mut Self::Fetch<'w, 's>, + _fetch: &mut Self::Fetch<'w>, _state: &'s Self::State, _archetype: &'w Archetype, _table: &Table, @@ -757,7 +752,7 @@ unsafe impl<'a> WorldQuery for EntityRef<'a> { #[inline] unsafe fn set_table<'w, 's>( - _fetch: &mut Self::Fetch<'w, 's>, + _fetch: &mut Self::Fetch<'w>, _state: &'s Self::State, _table: &'w Table, ) { @@ -799,7 +794,8 @@ unsafe impl<'a> QueryData for EntityRef<'a> { #[inline(always)] unsafe fn fetch<'w, 's>( - fetch: &mut Self::Fetch<'w, 's>, + _state: &'s Self::State, + fetch: &mut Self::Fetch<'w>, entity: Entity, _table_row: TableRow, ) -> Self::Item<'w, 's> { @@ -826,12 +822,10 @@ impl ReleaseStateQueryData for EntityRef<'_> { /// SAFETY: The accesses of `Self::ReadOnly` are a subset of the accesses of `Self` unsafe impl<'a> WorldQuery for EntityMut<'a> { - type Fetch<'w, 's> = EntityFetch<'w>; + type Fetch<'w> = EntityFetch<'w>; type State = (); - fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( - fetch: Self::Fetch<'wlong, 's>, - ) -> Self::Fetch<'wshort, 's> { + fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { fetch } @@ -840,7 +834,7 @@ unsafe impl<'a> WorldQuery for EntityMut<'a> { _state: &'s Self::State, last_run: Tick, this_run: Tick, - ) -> Self::Fetch<'w, 's> { + ) -> Self::Fetch<'w> { EntityFetch { world, last_run, @@ -852,7 +846,7 @@ unsafe impl<'a> WorldQuery for EntityMut<'a> { #[inline] unsafe fn set_archetype<'w, 's>( - _fetch: &mut Self::Fetch<'w, 's>, + _fetch: &mut Self::Fetch<'w>, _state: &'s Self::State, _archetype: &'w Archetype, _table: &Table, @@ -861,7 +855,7 @@ unsafe impl<'a> WorldQuery for EntityMut<'a> { #[inline] unsafe fn set_table<'w, 's>( - _fetch: &mut Self::Fetch<'w, 's>, + _fetch: &mut Self::Fetch<'w>, _state: &'s Self::State, _table: &'w Table, ) { @@ -903,7 +897,8 @@ unsafe impl<'a> QueryData for EntityMut<'a> { #[inline(always)] unsafe fn fetch<'w, 's>( - fetch: &mut Self::Fetch<'w, 's>, + _state: &'s Self::State, + fetch: &mut Self::Fetch<'w>, entity: Entity, _table_row: TableRow, ) -> Self::Item<'w, 's> { @@ -927,12 +922,10 @@ impl ReleaseStateQueryData for EntityMut<'_> { /// SAFETY: The accesses of `Self::ReadOnly` are a subset of the accesses of `Self` unsafe impl<'a> WorldQuery for FilteredEntityRef<'a> { - type Fetch<'w, 's> = (EntityFetch<'w>, Access); + type Fetch<'w> = (EntityFetch<'w>, Access); type State = Access; - fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( - fetch: Self::Fetch<'wlong, 's>, - ) -> Self::Fetch<'wshort, 's> { + fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { fetch } @@ -943,7 +936,7 @@ unsafe impl<'a> WorldQuery for FilteredEntityRef<'a> { _state: &'s Self::State, last_run: Tick, this_run: Tick, - ) -> Self::Fetch<'w, 's> { + ) -> Self::Fetch<'w> { let mut access = Access::default(); access.read_all_components(); ( @@ -958,7 +951,7 @@ unsafe impl<'a> WorldQuery for FilteredEntityRef<'a> { #[inline] unsafe fn set_archetype<'w, 's>( - fetch: &mut Self::Fetch<'w, 's>, + fetch: &mut Self::Fetch<'w>, state: &'s Self::State, _: &'w Archetype, _table: &Table, @@ -967,11 +960,7 @@ unsafe impl<'a> WorldQuery for FilteredEntityRef<'a> { } #[inline] - unsafe fn set_table<'w, 's>( - fetch: &mut Self::Fetch<'w, 's>, - state: &'s Self::State, - _: &'w Table, - ) { + unsafe fn set_table<'w, 's>(fetch: &mut Self::Fetch<'w>, state: &'s Self::State, _: &'w Table) { fetch.1.clone_from(state); } @@ -1035,7 +1024,8 @@ unsafe impl<'a> QueryData for FilteredEntityRef<'a> { #[inline(always)] unsafe fn fetch<'w, 's>( - (fetch, access): &mut Self::Fetch<'w, 's>, + _state: &'s Self::State, + (fetch, access): &mut Self::Fetch<'w>, entity: Entity, _table_row: TableRow, ) -> Self::Item<'w, 's> { @@ -1056,12 +1046,10 @@ unsafe impl ReadOnlyQueryData for FilteredEntityRef<'_> {} /// SAFETY: The accesses of `Self::ReadOnly` are a subset of the accesses of `Self` unsafe impl<'a> WorldQuery for FilteredEntityMut<'a> { - type Fetch<'w, 's> = (EntityFetch<'w>, Access); + type Fetch<'w> = (EntityFetch<'w>, Access); type State = Access; - fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( - fetch: Self::Fetch<'wlong, 's>, - ) -> Self::Fetch<'wshort, 's> { + fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { fetch } @@ -1072,7 +1060,7 @@ unsafe impl<'a> WorldQuery for FilteredEntityMut<'a> { _state: &'s Self::State, last_run: Tick, this_run: Tick, - ) -> Self::Fetch<'w, 's> { + ) -> Self::Fetch<'w> { let mut access = Access::default(); access.write_all_components(); ( @@ -1087,7 +1075,7 @@ unsafe impl<'a> WorldQuery for FilteredEntityMut<'a> { #[inline] unsafe fn set_archetype<'w, 's>( - fetch: &mut Self::Fetch<'w, 's>, + fetch: &mut Self::Fetch<'w>, state: &'s Self::State, _: &'w Archetype, _table: &Table, @@ -1096,11 +1084,7 @@ unsafe impl<'a> WorldQuery for FilteredEntityMut<'a> { } #[inline] - unsafe fn set_table<'w, 's>( - fetch: &mut Self::Fetch<'w, 's>, - state: &'s Self::State, - _: &'w Table, - ) { + unsafe fn set_table<'w, 's>(fetch: &mut Self::Fetch<'w>, state: &'s Self::State, _: &'w Table) { fetch.1.clone_from(state); } @@ -1162,7 +1146,8 @@ unsafe impl<'a> QueryData for FilteredEntityMut<'a> { #[inline(always)] unsafe fn fetch<'w, 's>( - (fetch, access): &mut Self::Fetch<'w, 's>, + _state: &'s Self::State, + (fetch, access): &mut Self::Fetch<'w>, entity: Entity, _table_row: TableRow, ) -> Self::Item<'w, 's> { @@ -1185,12 +1170,10 @@ unsafe impl<'a, B> WorldQuery for EntityRefExcept<'a, B> where B: Bundle, { - type Fetch<'w, 's> = EntityFetch<'w>; + type Fetch<'w> = EntityFetch<'w>; type State = SmallVec<[ComponentId; 4]>; - fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( - fetch: Self::Fetch<'wlong, 's>, - ) -> Self::Fetch<'wshort, 's> { + fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { fetch } @@ -1199,7 +1182,7 @@ where _: &'s Self::State, last_run: Tick, this_run: Tick, - ) -> Self::Fetch<'w, 's> { + ) -> Self::Fetch<'w> { EntityFetch { world, last_run, @@ -1210,14 +1193,14 @@ where const IS_DENSE: bool = true; unsafe fn set_archetype<'w, 's>( - _: &mut Self::Fetch<'w, 's>, + _: &mut Self::Fetch<'w>, _: &'s Self::State, _: &'w Archetype, _: &'w Table, ) { } - unsafe fn set_table<'w, 's>(_: &mut Self::Fetch<'w, 's>, _: &'s Self::State, _: &'w Table) {} + unsafe fn set_table<'w, 's>(_: &mut Self::Fetch<'w>, _: &'s Self::State, _: &'w Table) {} fn update_component_access( state: &Self::State, @@ -1273,7 +1256,8 @@ where } unsafe fn fetch<'w, 's>( - fetch: &mut Self::Fetch<'w, 's>, + _state: &'s Self::State, + fetch: &mut Self::Fetch<'w>, entity: Entity, _: TableRow, ) -> Self::Item<'w, 's> { @@ -1296,12 +1280,10 @@ unsafe impl<'a, B> WorldQuery for EntityMutExcept<'a, B> where B: Bundle, { - type Fetch<'w, 's> = EntityFetch<'w>; + type Fetch<'w> = EntityFetch<'w>; type State = SmallVec<[ComponentId; 4]>; - fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( - fetch: Self::Fetch<'wlong, 's>, - ) -> Self::Fetch<'wshort, 's> { + fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { fetch } @@ -1310,7 +1292,7 @@ where _: &'s Self::State, last_run: Tick, this_run: Tick, - ) -> Self::Fetch<'w, 's> { + ) -> Self::Fetch<'w> { EntityFetch { world, last_run, @@ -1321,14 +1303,14 @@ where const IS_DENSE: bool = true; unsafe fn set_archetype<'w, 's>( - _: &mut Self::Fetch<'w, 's>, + _: &mut Self::Fetch<'w>, _: &'s Self::State, _: &'w Archetype, _: &'w Table, ) { } - unsafe fn set_table<'w, 's>(_: &mut Self::Fetch<'w, 's>, _: &'s Self::State, _: &'w Table) {} + unsafe fn set_table<'w, 's>(_: &mut Self::Fetch<'w>, _: &'s Self::State, _: &'w Table) {} fn update_component_access( state: &Self::State, @@ -1385,7 +1367,8 @@ where } unsafe fn fetch<'w, 's>( - fetch: &mut Self::Fetch<'w, 's>, + _state: &'s Self::State, + fetch: &mut Self::Fetch<'w>, entity: Entity, _: TableRow, ) -> Self::Item<'w, 's> { @@ -1401,12 +1384,10 @@ where /// `update_component_access` does nothing. /// This is sound because `fetch` does not access components. unsafe impl WorldQuery for &Archetype { - type Fetch<'w, 's> = (&'w Entities, &'w Archetypes); + type Fetch<'w> = (&'w Entities, &'w Archetypes); type State = (); - fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( - fetch: Self::Fetch<'wlong, 's>, - ) -> Self::Fetch<'wshort, 's> { + fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { fetch } @@ -1415,7 +1396,7 @@ unsafe impl WorldQuery for &Archetype { _state: &'s Self::State, _last_run: Tick, _this_run: Tick, - ) -> Self::Fetch<'w, 's> { + ) -> Self::Fetch<'w> { (world.entities(), world.archetypes()) } @@ -1425,7 +1406,7 @@ unsafe impl WorldQuery for &Archetype { #[inline] unsafe fn set_archetype<'w, 's>( - _fetch: &mut Self::Fetch<'w, 's>, + _fetch: &mut Self::Fetch<'w>, _state: &'s Self::State, _archetype: &'w Archetype, _table: &Table, @@ -1434,7 +1415,7 @@ unsafe impl WorldQuery for &Archetype { #[inline] unsafe fn set_table<'w, 's>( - _fetch: &mut Self::Fetch<'w, 's>, + _fetch: &mut Self::Fetch<'w>, _state: &'s Self::State, _table: &'w Table, ) { @@ -1470,7 +1451,8 @@ unsafe impl QueryData for &Archetype { #[inline(always)] unsafe fn fetch<'w, 's>( - fetch: &mut Self::Fetch<'w, 's>, + _state: &'s Self::State, + fetch: &mut Self::Fetch<'w>, entity: Entity, _table_row: TableRow, ) -> Self::Item<'w, 's> { @@ -1515,12 +1497,10 @@ impl Copy for ReadFetch<'_, T> {} /// `update_component_access` adds a `With` filter for a component. /// This is sound because `matches_component_set` returns whether the set contains that component. unsafe impl WorldQuery for &T { - type Fetch<'w, 's> = ReadFetch<'w, T>; + type Fetch<'w> = ReadFetch<'w, T>; type State = ComponentId; - fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( - fetch: Self::Fetch<'wlong, 's>, - ) -> Self::Fetch<'wshort, 's> { + fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { fetch } @@ -1625,7 +1605,8 @@ unsafe impl QueryData for &T { #[inline(always)] unsafe fn fetch<'w, 's>( - fetch: &mut Self::Fetch<'w, 's>, + _state: &'s Self::State, + fetch: &mut Self::Fetch<'w>, entity: Entity, table_row: TableRow, ) -> Self::Item<'w, 's> { @@ -1692,12 +1673,10 @@ impl Copy for RefFetch<'_, T> {} /// `update_component_access` adds a `With` filter for a component. /// This is sound because `matches_component_set` returns whether the set contains that component. unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { - type Fetch<'w, 's> = RefFetch<'w, T>; + type Fetch<'w> = RefFetch<'w, T>; type State = ComponentId; - fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( - fetch: Self::Fetch<'wlong, 's>, - ) -> Self::Fetch<'wshort, 's> { + fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { fetch } @@ -1811,7 +1790,8 @@ unsafe impl<'__w, T: Component> QueryData for Ref<'__w, T> { #[inline(always)] unsafe fn fetch<'w, 's>( - fetch: &mut Self::Fetch<'w, 's>, + _state: &'s Self::State, + fetch: &mut Self::Fetch<'w>, entity: Entity, table_row: TableRow, ) -> Self::Item<'w, 's> { @@ -1901,12 +1881,10 @@ impl Copy for WriteFetch<'_, T> {} /// `update_component_access` adds a `With` filter for a component. /// This is sound because `matches_component_set` returns whether the set contains that component. unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { - type Fetch<'w, 's> = WriteFetch<'w, T>; + type Fetch<'w> = WriteFetch<'w, T>; type State = ComponentId; - fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( - fetch: Self::Fetch<'wlong, 's>, - ) -> Self::Fetch<'wshort, 's> { + fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { fetch } @@ -2020,7 +1998,8 @@ unsafe impl<'__w, T: Component> QueryData for &'__w mut T #[inline(always)] unsafe fn fetch<'w, 's>( - fetch: &mut Self::Fetch<'w, 's>, + _state: &'s Self::State, + fetch: &mut Self::Fetch<'w>, entity: Entity, table_row: TableRow, ) -> Self::Item<'w, 's> { @@ -2085,12 +2064,10 @@ impl> ReleaseStateQueryData for &mut T { /// `update_component_access` adds a `With` filter for a component. /// This is sound because `matches_component_set` returns whether the set contains that component. unsafe impl<'__w, T: Component> WorldQuery for Mut<'__w, T> { - type Fetch<'w, 's> = WriteFetch<'w, T>; + type Fetch<'w> = WriteFetch<'w, T>; type State = ComponentId; - fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( - fetch: Self::Fetch<'wlong, 's>, - ) -> Self::Fetch<'wshort, 's> { + fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { fetch } @@ -2175,13 +2152,14 @@ unsafe impl<'__w, T: Component> QueryData for Mut<'__w, T> #[inline(always)] // Forwarded to `&mut T` unsafe fn fetch<'w, 's>( + state: &'s Self::State, // Rust complains about lifetime bounds not matching the trait if I directly use `WriteFetch<'w, T>` right here. // But it complains nowhere else in the entire trait implementation. - fetch: &mut Self::Fetch<'w, 's>, + fetch: &mut Self::Fetch<'w>, entity: Entity, table_row: TableRow, ) -> Self::Item<'w, 's> { - <&mut T as QueryData>::fetch(fetch, entity, table_row) + <&mut T as QueryData>::fetch(state, fetch, entity, table_row) } } @@ -2192,12 +2170,12 @@ impl> ReleaseStateQueryData for Mut<'_, T> { } #[doc(hidden)] -pub struct OptionFetch<'w, 's, T: WorldQuery> { - fetch: T::Fetch<'w, 's>, +pub struct OptionFetch<'w, T: WorldQuery> { + fetch: T::Fetch<'w>, matches: bool, } -impl Clone for OptionFetch<'_, '_, T> { +impl Clone for OptionFetch<'_, T> { fn clone(&self) -> Self { Self { fetch: self.fetch.clone(), @@ -2211,12 +2189,10 @@ impl Clone for OptionFetch<'_, '_, T> { /// This is sound because `update_component_access` adds the same accesses as `T`. /// Filters are unchanged. unsafe impl WorldQuery for Option { - type Fetch<'w, 's> = OptionFetch<'w, 's, T>; + type Fetch<'w> = OptionFetch<'w, T>; type State = T::State; - fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( - fetch: Self::Fetch<'wlong, 's>, - ) -> Self::Fetch<'wshort, 's> { + fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { OptionFetch { fetch: T::shrink_fetch(fetch.fetch), matches: fetch.matches, @@ -2229,7 +2205,7 @@ unsafe impl WorldQuery for Option { state: &'s T::State, last_run: Tick, this_run: Tick, - ) -> OptionFetch<'w, 's, T> { + ) -> OptionFetch<'w, T> { OptionFetch { // SAFETY: The invariants are upheld by the caller. fetch: unsafe { T::init_fetch(world, state, last_run, this_run) }, @@ -2241,7 +2217,7 @@ unsafe impl WorldQuery for Option { #[inline] unsafe fn set_archetype<'w, 's>( - fetch: &mut OptionFetch<'w, 's, T>, + fetch: &mut OptionFetch<'w, T>, state: &'s T::State, archetype: &'w Archetype, table: &'w Table, @@ -2257,7 +2233,7 @@ unsafe impl WorldQuery for Option { #[inline] unsafe fn set_table<'w, 's>( - fetch: &mut OptionFetch<'w, 's, T>, + fetch: &mut OptionFetch<'w, T>, state: &'s T::State, table: &'w Table, ) { @@ -2315,14 +2291,15 @@ unsafe impl QueryData for Option { #[inline(always)] unsafe fn fetch<'w, 's>( - fetch: &mut Self::Fetch<'w, 's>, + state: &'s Self::State, + fetch: &mut Self::Fetch<'w>, entity: Entity, table_row: TableRow, ) -> Self::Item<'w, 's> { fetch .matches // SAFETY: The invariants are upheld by the caller. - .then(|| unsafe { T::fetch(&mut fetch.fetch, entity, table_row) }) + .then(|| unsafe { T::fetch(state, &mut fetch.fetch, entity, table_row) }) } } @@ -2410,12 +2387,10 @@ impl core::fmt::Debug for Has { /// `update_component_access` does nothing. /// This is sound because `fetch` does not access components. unsafe impl WorldQuery for Has { - type Fetch<'w, 's> = bool; + type Fetch<'w> = bool; type State = ComponentId; - fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( - fetch: Self::Fetch<'wlong, 's>, - ) -> Self::Fetch<'wshort, 's> { + fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { fetch } @@ -2425,7 +2400,7 @@ unsafe impl WorldQuery for Has { _state: &'s Self::State, _last_run: Tick, _this_run: Tick, - ) -> Self::Fetch<'w, 's> { + ) -> Self::Fetch<'w> { false } @@ -2438,7 +2413,7 @@ unsafe impl WorldQuery for Has { #[inline] unsafe fn set_archetype<'w, 's>( - fetch: &mut Self::Fetch<'w, 's>, + fetch: &mut Self::Fetch<'w>, state: &'s Self::State, archetype: &'w Archetype, _table: &Table, @@ -2448,7 +2423,7 @@ unsafe impl WorldQuery for Has { #[inline] unsafe fn set_table<'w, 's>( - fetch: &mut Self::Fetch<'w, 's>, + fetch: &mut Self::Fetch<'w>, state: &'s Self::State, table: &'w Table, ) { @@ -2493,7 +2468,8 @@ unsafe impl QueryData for Has { #[inline(always)] unsafe fn fetch<'w, 's>( - fetch: &mut Self::Fetch<'w, 's>, + _state: &'s Self::State, + fetch: &mut Self::Fetch<'w>, _entity: Entity, _table_row: TableRow, ) -> Self::Item<'w, 's> { @@ -2518,7 +2494,7 @@ impl ReleaseStateQueryData for Has { pub struct AnyOf(PhantomData); macro_rules! impl_tuple_query_data { - ($(#[$meta:meta])* $(($name: ident, $item: ident)),*) => { + ($(#[$meta:meta])* $(($name: ident, $item: ident, $state: ident)),*) => { #[expect( clippy::allow_attributes, reason = "This is a tuple-related macro; as such the lints below may not always apply." @@ -2561,13 +2537,15 @@ macro_rules! impl_tuple_query_data { #[inline(always)] unsafe fn fetch<'w, 's>( - fetch: &mut Self::Fetch<'w, 's>, + state: &'s Self::State, + fetch: &mut Self::Fetch<'w>, entity: Entity, table_row: TableRow ) -> Self::Item<'w, 's> { + let ($($state,)*) = state; let ($($name,)*) = fetch; // SAFETY: The invariants are upheld by the caller. - ($(unsafe { $name::fetch($name, entity, table_row) },)*) + ($(unsafe { $name::fetch($state, $name, entity, table_row) },)*) } } @@ -2615,10 +2593,10 @@ macro_rules! impl_anytuple_fetch { /// `update_component_access` replaces the filters with a disjunction where every element is a conjunction of the previous filters and the filters of one of the subqueries. /// This is sound because `matches_component_set` returns a disjunction of the results of the subqueries' implementations. unsafe impl<$($name: WorldQuery),*> WorldQuery for AnyOf<($($name,)*)> { - type Fetch<'w, 's> = ($(($name::Fetch<'w, 's>, bool),)*); + type Fetch<'w> = ($(($name::Fetch<'w>, bool),)*); type State = ($($name::State,)*); - fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>(fetch: Self::Fetch<'wlong, 's>) -> Self::Fetch<'wshort, 's> { + fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { let ($($name,)*) = fetch; ($( ($name::shrink_fetch($name.0), $name.1), @@ -2626,7 +2604,7 @@ macro_rules! impl_anytuple_fetch { } #[inline] - unsafe fn init_fetch<'w, 's>(_world: UnsafeWorldCell<'w>, state: &'s Self::State, _last_run: Tick, _this_run: Tick) -> Self::Fetch<'w, 's> { + unsafe fn init_fetch<'w, 's>(_world: UnsafeWorldCell<'w>, state: &'s Self::State, _last_run: Tick, _this_run: Tick) -> Self::Fetch<'w> { let ($($name,)*) = state; // SAFETY: The invariants are upheld by the caller. ($(( unsafe { $name::init_fetch(_world, $name, _last_run, _this_run) }, false),)*) @@ -2636,7 +2614,7 @@ macro_rules! impl_anytuple_fetch { #[inline] unsafe fn set_archetype<'w, 's>( - _fetch: &mut Self::Fetch<'w, 's>, + _fetch: &mut Self::Fetch<'w>, _state: &'s Self::State, _archetype: &'w Archetype, _table: &'w Table @@ -2653,7 +2631,7 @@ macro_rules! impl_anytuple_fetch { } #[inline] - unsafe fn set_table<'w, 's>(_fetch: &mut Self::Fetch<'w, 's>, _state: &'s Self::State, _table: &'w Table) { + unsafe fn set_table<'w, 's>(_fetch: &mut Self::Fetch<'w>, _state: &'s Self::State, _table: &'w Table) { let ($($name,)*) = _fetch; let ($($state,)*) = _state; $( @@ -2736,14 +2714,16 @@ macro_rules! impl_anytuple_fetch { #[inline(always)] unsafe fn fetch<'w, 's>( - _fetch: &mut Self::Fetch<'w, 's>, + _state: &'s Self::State, + _fetch: &mut Self::Fetch<'w>, _entity: Entity, _table_row: TableRow ) -> Self::Item<'w, 's> { let ($($name,)*) = _fetch; + let ($($state,)*) = _state; ($( // SAFETY: The invariants are required to be upheld by the caller. - $name.1.then(|| unsafe { $name::fetch(&mut $name.0, _entity, _table_row) }), + $name.1.then(|| unsafe { $name::fetch($state, &mut $name.0, _entity, _table_row) }), )*) } } @@ -2774,7 +2754,8 @@ all_tuples!( 0, 15, F, - i + i, + s ); all_tuples!( #[doc(fake_variadic)] @@ -2795,12 +2776,10 @@ pub(crate) struct NopWorldQuery(PhantomData); /// `update_component_access` does nothing. /// This is sound because `fetch` does not access components. unsafe impl WorldQuery for NopWorldQuery { - type Fetch<'w, 's> = (); + type Fetch<'w> = (); type State = D::State; - fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( - _fetch: Self::Fetch<'wlong, 's>, - ) -> Self::Fetch<'wshort, 's> { + fn shrink_fetch<'wlong: 'wshort, 'wshort>(_fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { } #[inline(always)] @@ -2857,7 +2836,8 @@ unsafe impl QueryData for NopWorldQuery { #[inline(always)] unsafe fn fetch<'w, 's>( - _fetch: &mut Self::Fetch<'w, 's>, + _state: &'s Self::State, + _fetch: &mut Self::Fetch<'w>, _entity: Entity, _table_row: TableRow, ) -> Self::Item<'w, 's> { @@ -2875,13 +2855,11 @@ impl ReleaseStateQueryData for NopWorldQuery { /// `update_component_access` does nothing. /// This is sound because `fetch` does not access components. unsafe impl WorldQuery for PhantomData { - type Fetch<'w, 's> = (); + type Fetch<'w> = (); type State = (); - fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( - _fetch: Self::Fetch<'wlong, 's>, - ) -> Self::Fetch<'wshort, 's> { + fn shrink_fetch<'wlong: 'wshort, 'wshort>(_fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { } unsafe fn init_fetch<'w, 's>( @@ -2889,7 +2867,7 @@ unsafe impl WorldQuery for PhantomData { _state: &'s Self::State, _last_run: Tick, _this_run: Tick, - ) -> Self::Fetch<'w, 's> { + ) -> Self::Fetch<'w> { } // `PhantomData` does not match any components, so all components it matches @@ -2897,7 +2875,7 @@ unsafe impl WorldQuery for PhantomData { const IS_DENSE: bool = true; unsafe fn set_archetype<'w, 's>( - _fetch: &mut Self::Fetch<'w, 's>, + _fetch: &mut Self::Fetch<'w>, _state: &'s Self::State, _archetype: &'w Archetype, _table: &'w Table, @@ -2905,7 +2883,7 @@ unsafe impl WorldQuery for PhantomData { } unsafe fn set_table<'w, 's>( - _fetch: &mut Self::Fetch<'w, 's>, + _fetch: &mut Self::Fetch<'w>, _state: &'s Self::State, _table: &'w Table, ) { @@ -2939,7 +2917,8 @@ unsafe impl QueryData for PhantomData { } unsafe fn fetch<'w, 's>( - _fetch: &mut Self::Fetch<'w, 's>, + _state: &'s Self::State, + _fetch: &mut Self::Fetch<'w>, _entity: Entity, _table_row: TableRow, ) -> Self::Item<'w, 's> { @@ -3077,12 +3056,12 @@ mod tests { /// `update_component_access` and `update_archetype_component_access` do nothing. /// This is sound because `fetch` does not access components. unsafe impl WorldQuery for NonReleaseQueryData { - type Fetch<'w, 's> = (); + type Fetch<'w> = (); type State = (); - fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( - _: Self::Fetch<'wlong, 's>, - ) -> Self::Fetch<'wshort, 's> { + fn shrink_fetch<'wlong: 'wshort, 'wshort>( + _: Self::Fetch<'wlong>, + ) -> Self::Fetch<'wshort> { } unsafe fn init_fetch<'w, 's>( @@ -3090,14 +3069,14 @@ mod tests { _state: &'s Self::State, _last_run: Tick, _this_run: Tick, - ) -> Self::Fetch<'w, 's> { + ) -> Self::Fetch<'w> { } const IS_DENSE: bool = true; #[inline] unsafe fn set_archetype<'w, 's>( - _fetch: &mut Self::Fetch<'w, 's>, + _fetch: &mut Self::Fetch<'w>, _state: &'s Self::State, _archetype: &'w Archetype, _table: &Table, @@ -3106,7 +3085,7 @@ mod tests { #[inline] unsafe fn set_table<'w, 's>( - _fetch: &mut Self::Fetch<'w, 's>, + _fetch: &mut Self::Fetch<'w>, _state: &'s Self::State, _table: &'w Table, ) { @@ -3146,7 +3125,8 @@ mod tests { #[inline(always)] unsafe fn fetch<'w, 's>( - _fetch: &mut Self::Fetch<'w, 's>, + _state: &'s Self::State, + _fetch: &mut Self::Fetch<'w>, _entity: Entity, _table_row: TableRow, ) -> Self::Item<'w, 's> { diff --git a/crates/bevy_ecs/src/query/filter.rs b/crates/bevy_ecs/src/query/filter.rs index f5700a33c6..312b330e04 100644 --- a/crates/bevy_ecs/src/query/filter.rs +++ b/crates/bevy_ecs/src/query/filter.rs @@ -104,7 +104,8 @@ pub unsafe trait QueryFilter: WorldQuery { /// Must always be called _after_ [`WorldQuery::set_table`] or [`WorldQuery::set_archetype`]. `entity` and /// `table_row` must be in the range of the current table and archetype. unsafe fn filter_fetch( - fetch: &mut Self::Fetch<'_, '_>, + state: &Self::State, + fetch: &mut Self::Fetch<'_>, entity: Entity, table_row: TableRow, ) -> bool; @@ -145,13 +146,10 @@ pub struct With(PhantomData); /// `update_component_access` adds a `With` filter for `T`. /// This is sound because `matches_component_set` returns whether the set contains the component. unsafe impl WorldQuery for With { - type Fetch<'w, 's> = (); + type Fetch<'w> = (); type State = ComponentId; - fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( - _: Self::Fetch<'wlong, 's>, - ) -> Self::Fetch<'wshort, 's> { - } + fn shrink_fetch<'wlong: 'wshort, 'wshort>(_: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {} #[inline] unsafe fn init_fetch( @@ -208,7 +206,8 @@ unsafe impl QueryFilter for With { #[inline(always)] unsafe fn filter_fetch( - _fetch: &mut Self::Fetch<'_, '_>, + _state: &Self::State, + _fetch: &mut Self::Fetch<'_>, _entity: Entity, _table_row: TableRow, ) -> bool { @@ -248,13 +247,10 @@ pub struct Without(PhantomData); /// `update_component_access` adds a `Without` filter for `T`. /// This is sound because `matches_component_set` returns whether the set does not contain the component. unsafe impl WorldQuery for Without { - type Fetch<'w, 's> = (); + type Fetch<'w> = (); type State = ComponentId; - fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( - _: Self::Fetch<'wlong, 's>, - ) -> Self::Fetch<'wshort, 's> { - } + fn shrink_fetch<'wlong: 'wshort, 'wshort>(_: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {} #[inline] unsafe fn init_fetch( @@ -311,7 +307,8 @@ unsafe impl QueryFilter for Without { #[inline(always)] unsafe fn filter_fetch( - _fetch: &mut Self::Fetch<'_, '_>, + _state: &Self::State, + _fetch: &mut Self::Fetch<'_>, _entity: Entity, _table_row: TableRow, ) -> bool { @@ -352,12 +349,12 @@ unsafe impl QueryFilter for Without { pub struct Or(PhantomData); #[doc(hidden)] -pub struct OrFetch<'w, 's, T: WorldQuery> { - fetch: T::Fetch<'w, 's>, +pub struct OrFetch<'w, T: WorldQuery> { + fetch: T::Fetch<'w>, matches: bool, } -impl Clone for OrFetch<'_, '_, T> { +impl Clone for OrFetch<'_, T> { fn clone(&self) -> Self { Self { fetch: self.fetch.clone(), @@ -391,10 +388,10 @@ macro_rules! impl_or_query_filter { /// `update_component_access` replace the filters with a disjunction where every element is a conjunction of the previous filters and the filters of one of the subqueries. /// This is sound because `matches_component_set` returns a disjunction of the results of the subqueries' implementations. unsafe impl<$($filter: QueryFilter),*> WorldQuery for Or<($($filter,)*)> { - type Fetch<'w, 's> = ($(OrFetch<'w, 's, $filter>,)*); + type Fetch<'w> = ($(OrFetch<'w, $filter>,)*); type State = ($($filter::State,)*); - fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>(fetch: Self::Fetch<'wlong, 's>) -> Self::Fetch<'wshort, 's> { + fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { let ($($filter,)*) = fetch; ($( OrFetch { @@ -407,7 +404,7 @@ macro_rules! impl_or_query_filter { const IS_DENSE: bool = true $(&& $filter::IS_DENSE)*; #[inline] - unsafe fn init_fetch<'w, 's>(world: UnsafeWorldCell<'w>, state: &'s Self::State, last_run: Tick, this_run: Tick) -> Self::Fetch<'w, 's> { + unsafe fn init_fetch<'w, 's>(world: UnsafeWorldCell<'w>, state: &'s Self::State, last_run: Tick, this_run: Tick) -> Self::Fetch<'w> { let ($($filter,)*) = state; ($(OrFetch { // SAFETY: The invariants are upheld by the caller. @@ -417,7 +414,7 @@ macro_rules! impl_or_query_filter { } #[inline] - unsafe fn set_table<'w, 's>(fetch: &mut Self::Fetch<'w, 's>, state: &'s Self::State, table: &'w Table) { + unsafe fn set_table<'w, 's>(fetch: &mut Self::Fetch<'w>, state: &'s Self::State, table: &'w Table) { let ($($filter,)*) = fetch; let ($($state,)*) = state; $( @@ -431,7 +428,7 @@ macro_rules! impl_or_query_filter { #[inline] unsafe fn set_archetype<'w, 's>( - fetch: &mut Self::Fetch<'w, 's>, + fetch: &mut Self::Fetch<'w>, state: &'s Self::State, archetype: &'w Archetype, table: &'w Table @@ -502,20 +499,22 @@ macro_rules! impl_or_query_filter { #[inline(always)] unsafe fn filter_fetch( - fetch: &mut Self::Fetch<'_, '_>, + state: &Self::State, + fetch: &mut Self::Fetch<'_>, entity: Entity, table_row: TableRow ) -> bool { + let ($($state,)*) = state; let ($($filter,)*) = fetch; // SAFETY: The invariants are upheld by the caller. - false $(|| ($filter.matches && unsafe { $filter::filter_fetch(&mut $filter.fetch, entity, table_row) }))* + false $(|| ($filter.matches && unsafe { $filter::filter_fetch($state, &mut $filter.fetch, entity, table_row) }))* } } }; } macro_rules! impl_tuple_query_filter { - ($(#[$meta:meta])* $($name: ident),*) => { + ($(#[$meta:meta])* $(($name: ident, $state: ident)),*) => { #[expect( clippy::allow_attributes, reason = "This is a tuple-related macro; as such the lints below may not always apply." @@ -535,13 +534,15 @@ macro_rules! impl_tuple_query_filter { #[inline(always)] unsafe fn filter_fetch( - fetch: &mut Self::Fetch<'_, '_>, + state: &Self::State, + fetch: &mut Self::Fetch<'_>, entity: Entity, table_row: TableRow ) -> bool { + let ($($state,)*) = state; let ($($name,)*) = fetch; // SAFETY: The invariants are upheld by the caller. - true $(&& unsafe { $name::filter_fetch($name, entity, table_row) })* + true $(&& unsafe { $name::filter_fetch($state, $name, entity, table_row) })* } } @@ -553,7 +554,8 @@ all_tuples!( impl_tuple_query_filter, 0, 15, - F + F, + S ); all_tuples!( #[doc(fake_variadic)] @@ -575,13 +577,10 @@ pub struct Allows(PhantomData); /// `update_component_access` adds an archetypal filter for `T`. /// This is sound because it doesn't affect the query unsafe impl WorldQuery for Allows { - type Fetch<'w, 's> = (); + type Fetch<'w> = (); type State = ComponentId; - fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( - _: Self::Fetch<'wlong, 's>, - ) -> Self::Fetch<'wshort, 's> { - } + fn shrink_fetch<'wlong: 'wshort, 'wshort>(_: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {} #[inline] unsafe fn init_fetch(_: UnsafeWorldCell, _: &ComponentId, _: Tick, _: Tick) {} @@ -619,7 +618,12 @@ unsafe impl QueryFilter for Allows { const IS_ARCHETYPAL: bool = true; #[inline(always)] - unsafe fn filter_fetch(_: &mut Self::Fetch<'_, '_>, _: Entity, _: TableRow) -> bool { + unsafe fn filter_fetch( + _: &Self::State, + _: &mut Self::Fetch<'_>, + _: Entity, + _: TableRow, + ) -> bool { true } } @@ -720,12 +724,10 @@ impl Clone for AddedFetch<'_, T> { /// `update_component_access` adds a `With` filter for a component. /// This is sound because `matches_component_set` returns whether the set contains that component. unsafe impl WorldQuery for Added { - type Fetch<'w, 's> = AddedFetch<'w, T>; + type Fetch<'w> = AddedFetch<'w, T>; type State = ComponentId; - fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( - fetch: Self::Fetch<'wlong, 's>, - ) -> Self::Fetch<'wshort, 's> { + fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { fetch } @@ -735,8 +737,8 @@ unsafe impl WorldQuery for Added { &id: &'s ComponentId, last_run: Tick, this_run: Tick, - ) -> Self::Fetch<'w, 's> { - Self::Fetch::<'w, 's> { + ) -> Self::Fetch<'w> { + Self::Fetch::<'w> { ticks: StorageSwitch::new( || None, || { @@ -761,7 +763,7 @@ unsafe impl WorldQuery for Added { #[inline] unsafe fn set_archetype<'w, 's>( - fetch: &mut Self::Fetch<'w, 's>, + fetch: &mut Self::Fetch<'w>, component_id: &'s ComponentId, _archetype: &'w Archetype, table: &'w Table, @@ -776,7 +778,7 @@ unsafe impl WorldQuery for Added { #[inline] unsafe fn set_table<'w, 's>( - fetch: &mut Self::Fetch<'w, 's>, + fetch: &mut Self::Fetch<'w>, &component_id: &'s ComponentId, table: &'w Table, ) { @@ -819,7 +821,8 @@ unsafe impl QueryFilter for Added { const IS_ARCHETYPAL: bool = false; #[inline(always)] unsafe fn filter_fetch( - fetch: &mut Self::Fetch<'_, '_>, + _state: &Self::State, + fetch: &mut Self::Fetch<'_>, entity: Entity, table_row: TableRow, ) -> bool { @@ -948,12 +951,10 @@ impl Clone for ChangedFetch<'_, T> { /// `update_component_access` adds a `With` filter for a component. /// This is sound because `matches_component_set` returns whether the set contains that component. unsafe impl WorldQuery for Changed { - type Fetch<'w, 's> = ChangedFetch<'w, T>; + type Fetch<'w> = ChangedFetch<'w, T>; type State = ComponentId; - fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( - fetch: Self::Fetch<'wlong, 's>, - ) -> Self::Fetch<'wshort, 's> { + fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { fetch } @@ -963,8 +964,8 @@ unsafe impl WorldQuery for Changed { &id: &'s ComponentId, last_run: Tick, this_run: Tick, - ) -> Self::Fetch<'w, 's> { - Self::Fetch::<'w, 's> { + ) -> Self::Fetch<'w> { + Self::Fetch::<'w> { ticks: StorageSwitch::new( || None, || { @@ -989,7 +990,7 @@ unsafe impl WorldQuery for Changed { #[inline] unsafe fn set_archetype<'w, 's>( - fetch: &mut Self::Fetch<'w, 's>, + fetch: &mut Self::Fetch<'w>, component_id: &'s ComponentId, _archetype: &'w Archetype, table: &'w Table, @@ -1004,7 +1005,7 @@ unsafe impl WorldQuery for Changed { #[inline] unsafe fn set_table<'w, 's>( - fetch: &mut Self::Fetch<'w, 's>, + fetch: &mut Self::Fetch<'w>, &component_id: &'s ComponentId, table: &'w Table, ) { @@ -1048,7 +1049,8 @@ unsafe impl QueryFilter for Changed { #[inline(always)] unsafe fn filter_fetch( - fetch: &mut Self::Fetch<'_, '_>, + _state: &Self::State, + fetch: &mut Self::Fetch<'_>, entity: Entity, table_row: TableRow, ) -> bool { @@ -1147,12 +1149,10 @@ pub struct SpawnedFetch<'w> { // SAFETY: WorldQuery impl accesses no components or component ticks unsafe impl WorldQuery for Spawned { - type Fetch<'w, 's> = SpawnedFetch<'w>; + type Fetch<'w> = SpawnedFetch<'w>; type State = (); - fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( - fetch: Self::Fetch<'wlong, 's>, - ) -> Self::Fetch<'wshort, 's> { + fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { fetch } @@ -1162,7 +1162,7 @@ unsafe impl WorldQuery for Spawned { _state: &'s (), last_run: Tick, this_run: Tick, - ) -> Self::Fetch<'w, 's> { + ) -> Self::Fetch<'w> { SpawnedFetch { entities: world.entities(), last_run, @@ -1174,7 +1174,7 @@ unsafe impl WorldQuery for Spawned { #[inline] unsafe fn set_archetype<'w, 's>( - _fetch: &mut Self::Fetch<'w, 's>, + _fetch: &mut Self::Fetch<'w>, _state: &'s (), _archetype: &'w Archetype, _table: &'w Table, @@ -1182,12 +1182,7 @@ unsafe impl WorldQuery for Spawned { } #[inline] - unsafe fn set_table<'w, 's>( - _fetch: &mut Self::Fetch<'w, 's>, - _state: &'s (), - _table: &'w Table, - ) { - } + unsafe fn set_table<'w, 's>(_fetch: &mut Self::Fetch<'w>, _state: &'s (), _table: &'w Table) {} #[inline] fn update_component_access(_state: &(), _access: &mut FilteredAccess) {} @@ -1209,7 +1204,8 @@ unsafe impl QueryFilter for Spawned { #[inline(always)] unsafe fn filter_fetch( - fetch: &mut Self::Fetch<'_, '_>, + _state: &Self::State, + fetch: &mut Self::Fetch<'_>, entity: Entity, _table_row: TableRow, ) -> bool { diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index a3b3d02c24..eb49204434 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -225,14 +225,26 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> { // SAFETY: set_table was called prior. // Caller assures `row` in range of the current archetype. - let fetched = unsafe { !F::filter_fetch(&mut self.cursor.filter, *entity, row) }; + let fetched = unsafe { + !F::filter_fetch( + &self.query_state.filter_state, + &mut self.cursor.filter, + *entity, + row, + ) + }; if fetched { continue; } // SAFETY: set_table was called prior. // Caller assures `row` in range of the current archetype. - let item = D::fetch(&mut self.cursor.fetch, *entity, row); + let item = D::fetch( + &self.query_state.fetch_state, + &mut self.cursor.fetch, + *entity, + row, + ); accum = func(accum, item); } @@ -283,6 +295,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> { // Caller assures `index` in range of the current archetype. let fetched = unsafe { !F::filter_fetch( + &self.query_state.filter_state, &mut self.cursor.filter, archetype_entity.id(), archetype_entity.table_row(), @@ -296,6 +309,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> { // Caller assures `index` in range of the current archetype. let item = unsafe { D::fetch( + &self.query_state.fetch_state, &mut self.cursor.fetch, archetype_entity.id(), archetype_entity.table_row(), @@ -356,14 +370,26 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> { // SAFETY: set_table was called prior. // Caller assures `row` in range of the current archetype. - let filter_matched = unsafe { F::filter_fetch(&mut self.cursor.filter, entity, row) }; + let filter_matched = unsafe { + F::filter_fetch( + &self.query_state.filter_state, + &mut self.cursor.filter, + entity, + row, + ) + }; if !filter_matched { continue; } // SAFETY: set_table was called prior. // Caller assures `row` in range of the current archetype. - let item = D::fetch(&mut self.cursor.fetch, entity, row); + let item = D::fetch( + &self.query_state.fetch_state, + &mut self.cursor.fetch, + entity, + row, + ); accum = func(accum, item); } @@ -979,7 +1005,7 @@ where entities: &'w Entities, tables: &'w Tables, archetypes: &'w Archetypes, - fetch: D::Fetch<'w, 's>, + fetch: D::Fetch<'w>, query_state: &'s QueryState, } @@ -1043,7 +1069,14 @@ where // SAFETY: // - set_archetype was called prior, `location.archetype_row` is an archetype index in range of the current archetype // - fetch is only called once for each entity. - unsafe { D::fetch(&mut self.fetch, entity, location.table_row) } + unsafe { + D::fetch( + &self.query_state.fetch_state, + &mut self.fetch, + entity, + location.table_row, + ) + } } } @@ -1123,8 +1156,8 @@ pub struct QueryManyIter<'w, 's, D: QueryData, F: QueryFilter, I: Iterator, - filter: F::Fetch<'w, 's>, + fetch: D::Fetch<'w>, + filter: F::Fetch<'w>, query_state: &'s QueryState, } @@ -1171,8 +1204,8 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> entities: &'w Entities, tables: &'w Tables, archetypes: &'w Archetypes, - fetch: &mut D::Fetch<'w, 's>, - filter: &mut F::Fetch<'w, 's>, + fetch: &mut D::Fetch<'w>, + filter: &mut F::Fetch<'w>, query_state: &'s QueryState, ) -> Option> { for entity_borrow in entity_iter { @@ -1204,11 +1237,20 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> // SAFETY: set_archetype was called prior. // `location.archetype_row` is an archetype index row in range of the current archetype, because if it was not, the match above would have `continue`d - if unsafe { F::filter_fetch(filter, entity, location.table_row) } { + if unsafe { + F::filter_fetch( + &query_state.filter_state, + filter, + entity, + location.table_row, + ) + } { // SAFETY: // - set_archetype was called prior, `location.archetype_row` is an archetype index in range of the current archetype // - fetch is only called once for each entity. - return Some(unsafe { D::fetch(fetch, entity, location.table_row) }); + return Some(unsafe { + D::fetch(&query_state.fetch_state, fetch, entity, location.table_row) + }); } } None @@ -1919,7 +1961,7 @@ pub struct QuerySortedManyIter<'w, 's, D: QueryData, F: QueryFilter, I: Iterator entities: &'w Entities, tables: &'w Tables, archetypes: &'w Archetypes, - fetch: D::Fetch<'w, 's>, + fetch: D::Fetch<'w>, query_state: &'s QueryState, } @@ -1987,7 +2029,14 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> // SAFETY: // - set_archetype was called prior, `location.archetype_row` is an archetype index in range of the current archetype // - fetch is only called once for each entity. - unsafe { D::fetch(&mut self.fetch, entity, location.table_row) } + unsafe { + D::fetch( + &self.query_state.fetch_state, + &mut self.fetch, + entity, + location.table_row, + ) + } } /// Get next result from the query @@ -2219,7 +2268,8 @@ impl<'w, 's, D: QueryData, F: QueryFilter, const K: usize> QueryCombinationIter< let ptr = values.as_mut_ptr().cast::>(); for (offset, cursor) in self.cursors.iter_mut().enumerate() { - ptr.add(offset).write(cursor.peek_last().unwrap()); + ptr.add(offset) + .write(cursor.peek_last(self.query_state).unwrap()); } Some(values.assume_init()) @@ -2313,8 +2363,8 @@ struct QueryIterationCursor<'w, 's, D: QueryData, F: QueryFilter> { storage_id_iter: core::slice::Iter<'s, StorageId>, table_entities: &'w [Entity], archetype_entities: &'w [ArchetypeEntity], - fetch: D::Fetch<'w, 's>, - filter: F::Fetch<'w, 's>, + fetch: D::Fetch<'w>, + filter: F::Fetch<'w>, // length of the table or length of the archetype, depending on whether both `D`'s and `F`'s fetches are dense current_len: u32, // either table row or archetype index, depending on whether both `D`'s and `F`'s fetches are dense @@ -2394,7 +2444,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIterationCursor<'w, 's, D, F> { /// The result of `next` and any previous calls to `peek_last` with this row must have been /// dropped to prevent aliasing mutable references. #[inline] - unsafe fn peek_last(&mut self) -> Option> { + unsafe fn peek_last(&mut self, query_state: &'s QueryState) -> Option> { if self.current_row > 0 { let index = self.current_row - 1; if self.is_dense { @@ -2405,6 +2455,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIterationCursor<'w, 's, D, F> { // - `*entity` and `index` are in the current table. unsafe { Some(D::fetch( + &query_state.fetch_state, &mut self.fetch, *entity, // SAFETY: This is from an exclusive range, so it can't be max. @@ -2420,6 +2471,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIterationCursor<'w, 's, D, F> { // - `archetype_entity.id()` and `archetype_entity.table_row()` are in the current archetype. unsafe { Some(D::fetch( + &query_state.fetch_state, &mut self.fetch, archetype_entity.id(), archetype_entity.table_row(), @@ -2488,7 +2540,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIterationCursor<'w, 's, D, F> { unsafe { self.table_entities.get_unchecked(self.current_row as usize) }; // SAFETY: The row is less than the u32 len, so it must not be max. let row = unsafe { TableRow::new(NonMaxU32::new_unchecked(self.current_row)) }; - if !F::filter_fetch(&mut self.filter, *entity, row) { + if !F::filter_fetch(&query_state.filter_state, &mut self.filter, *entity, row) { self.current_row += 1; continue; } @@ -2498,7 +2550,8 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIterationCursor<'w, 's, D, F> { // - `current_row` must be a table row in range of the current table, // because if it was not, then the above would have been executed. // - fetch is only called once for each `entity`. - let item = unsafe { D::fetch(&mut self.fetch, *entity, row) }; + let item = + unsafe { D::fetch(&query_state.fetch_state, &mut self.fetch, *entity, row) }; self.current_row += 1; return Some(item); @@ -2540,6 +2593,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIterationCursor<'w, 's, D, F> { .get_unchecked(self.current_row as usize) }; if !F::filter_fetch( + &query_state.filter_state, &mut self.filter, archetype_entity.id(), archetype_entity.table_row(), @@ -2555,6 +2609,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIterationCursor<'w, 's, D, F> { // - fetch is only called once for each `archetype_entity`. let item = unsafe { D::fetch( + &query_state.fetch_state, &mut self.fetch, archetype_entity.id(), archetype_entity.table_row(), diff --git a/crates/bevy_ecs/src/query/mod.rs b/crates/bevy_ecs/src/query/mod.rs index db22c152f7..7c1487fde4 100644 --- a/crates/bevy_ecs/src/query/mod.rs +++ b/crates/bevy_ecs/src/query/mod.rs @@ -819,27 +819,24 @@ mod tests { /// SAFETY: /// `update_component_access` adds resource read access for `R`. unsafe impl WorldQuery for ReadsRData { - type Fetch<'w, 's> = (); + type Fetch<'w> = (); type State = ComponentId; - fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( - _: Self::Fetch<'wlong, 's>, - ) -> Self::Fetch<'wshort, 's> { - } + fn shrink_fetch<'wlong: 'wshort, 'wshort>(_: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {} unsafe fn init_fetch<'w, 's>( _world: UnsafeWorldCell<'w>, _state: &'s Self::State, _last_run: Tick, _this_run: Tick, - ) -> Self::Fetch<'w, 's> { + ) -> Self::Fetch<'w> { } const IS_DENSE: bool = true; #[inline] unsafe fn set_archetype<'w, 's>( - _fetch: &mut Self::Fetch<'w, 's>, + _fetch: &mut Self::Fetch<'w>, _state: &'s Self::State, _archetype: &'w Archetype, _table: &Table, @@ -848,7 +845,7 @@ mod tests { #[inline] unsafe fn set_table<'w, 's>( - _fetch: &mut Self::Fetch<'w, 's>, + _fetch: &mut Self::Fetch<'w>, _state: &'s Self::State, _table: &'w Table, ) { @@ -894,7 +891,8 @@ mod tests { #[inline(always)] unsafe fn fetch<'w, 's>( - _fetch: &mut Self::Fetch<'w, 's>, + _state: &'s Self::State, + _fetch: &mut Self::Fetch<'w>, _entity: Entity, _table_row: TableRow, ) -> Self::Item<'w, 's> { diff --git a/crates/bevy_ecs/src/query/world_query.rs b/crates/bevy_ecs/src/query/world_query.rs index e856be7619..1c739927ac 100644 --- a/crates/bevy_ecs/src/query/world_query.rs +++ b/crates/bevy_ecs/src/query/world_query.rs @@ -42,7 +42,7 @@ use variadics_please::all_tuples; /// [`QueryFilter`]: crate::query::QueryFilter pub unsafe trait WorldQuery { /// Per archetype/table state retrieved by this [`WorldQuery`] to compute [`Self::Item`](crate::query::QueryData::Item) for each entity. - type Fetch<'w, 's>: Clone; + type Fetch<'w>: Clone; /// State used to construct a [`Self::Fetch`](WorldQuery::Fetch). This will be cached inside [`QueryState`](crate::query::QueryState), /// so it is best to move as much data / computation here as possible to reduce the cost of @@ -50,9 +50,7 @@ pub unsafe trait WorldQuery { type State: Send + Sync + Sized; /// This function manually implements subtyping for the query fetches. - fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( - fetch: Self::Fetch<'wlong, 's>, - ) -> Self::Fetch<'wshort, 's>; + fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort>; /// Creates a new instance of [`Self::Fetch`](WorldQuery::Fetch), /// by combining data from the [`World`] with the cached [`Self::State`](WorldQuery::State). @@ -69,7 +67,7 @@ pub unsafe trait WorldQuery { state: &'s Self::State, last_run: Tick, this_run: Tick, - ) -> Self::Fetch<'w, 's>; + ) -> Self::Fetch<'w>; /// Returns true if (and only if) every table of every archetype matched by this fetch contains /// all of the matched components. @@ -90,7 +88,7 @@ pub unsafe trait WorldQuery { /// - `table` must correspond to `archetype`. /// - `state` must be the [`State`](Self::State) that `fetch` was initialized with. unsafe fn set_archetype<'w, 's>( - fetch: &mut Self::Fetch<'w, 's>, + fetch: &mut Self::Fetch<'w>, state: &'s Self::State, archetype: &'w Archetype, table: &'w Table, @@ -104,7 +102,7 @@ pub unsafe trait WorldQuery { /// - `table` must be from the same [`World`] that [`WorldQuery::init_state`] was called on. /// - `state` must be the [`State`](Self::State) that `fetch` was initialized with. unsafe fn set_table<'w, 's>( - fetch: &mut Self::Fetch<'w, 's>, + fetch: &mut Self::Fetch<'w>, state: &'s Self::State, table: &'w Table, ); @@ -160,11 +158,11 @@ macro_rules! impl_tuple_world_query { /// `update_component_access` adds all `With` and `Without` filters from the subqueries. /// This is sound because `matches_component_set` always returns `false` if any the subqueries' implementations return `false`. unsafe impl<$($name: WorldQuery),*> WorldQuery for ($($name,)*) { - type Fetch<'w, 's> = ($($name::Fetch<'w, 's>,)*); + type Fetch<'w> = ($($name::Fetch<'w>,)*); type State = ($($name::State,)*); - fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>(fetch: Self::Fetch<'wlong, 's>) -> Self::Fetch<'wshort, 's> { + fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { let ($($name,)*) = fetch; ($( $name::shrink_fetch($name), @@ -172,7 +170,7 @@ macro_rules! impl_tuple_world_query { } #[inline] - unsafe fn init_fetch<'w, 's>(world: UnsafeWorldCell<'w>, state: &'s Self::State, last_run: Tick, this_run: Tick) -> Self::Fetch<'w, 's> { + unsafe fn init_fetch<'w, 's>(world: UnsafeWorldCell<'w>, state: &'s Self::State, last_run: Tick, this_run: Tick) -> Self::Fetch<'w> { let ($($name,)*) = state; // SAFETY: The invariants are upheld by the caller. ($(unsafe { $name::init_fetch(world, $name, last_run, this_run) },)*) @@ -182,7 +180,7 @@ macro_rules! impl_tuple_world_query { #[inline] unsafe fn set_archetype<'w, 's>( - fetch: &mut Self::Fetch<'w, 's>, + fetch: &mut Self::Fetch<'w>, state: &'s Self::State, archetype: &'w Archetype, table: &'w Table @@ -194,7 +192,7 @@ macro_rules! impl_tuple_world_query { } #[inline] - unsafe fn set_table<'w, 's>(fetch: &mut Self::Fetch<'w, 's>, state: &'s Self::State, table: &'w Table) { + unsafe fn set_table<'w, 's>(fetch: &mut Self::Fetch<'w>, state: &'s Self::State, table: &'w Table) { let ($($name,)*) = fetch; let ($($state,)*) = state; // SAFETY: The invariants are upheld by the caller. diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index b29436debb..6e44301b18 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -1582,8 +1582,18 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { D::set_archetype(&mut fetch, &self.state.fetch_state, archetype, table); F::set_archetype(&mut filter, &self.state.filter_state, archetype, table); - if F::filter_fetch(&mut filter, entity, location.table_row) { - Ok(D::fetch(&mut fetch, entity, location.table_row)) + if F::filter_fetch( + &self.state.filter_state, + &mut filter, + entity, + location.table_row, + ) { + Ok(D::fetch( + &self.state.fetch_state, + &mut fetch, + entity, + location.table_row, + )) } else { Err(QueryEntityError::QueryDoesNotMatch( entity, diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index 6c618a287f..38d4333843 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -1030,7 +1030,7 @@ impl<'w> UnsafeEntityCell<'w> { // Table corresponds to archetype. State is the same state used to init fetch above. unsafe { Q::set_archetype(&mut fetch, &state, archetype, table) } // SAFETY: Called after set_archetype above. Entity and location are guaranteed to exist. - let item = unsafe { Q::fetch(&mut fetch, self.id(), location.table_row) }; + let item = unsafe { Q::fetch(&state, &mut fetch, self.id(), location.table_row) }; Some(Q::release_state(item)) } else { None diff --git a/crates/bevy_render/src/sync_world.rs b/crates/bevy_render/src/sync_world.rs index 35b500059e..b216b5fd32 100644 --- a/crates/bevy_render/src/sync_world.rs +++ b/crates/bevy_render/src/sync_world.rs @@ -289,12 +289,12 @@ mod render_entities_world_query_impls { /// SAFETY: defers completely to `&RenderEntity` implementation, /// and then only modifies the output safely. unsafe impl WorldQuery for RenderEntity { - type Fetch<'w, 's> = <&'static RenderEntity as WorldQuery>::Fetch<'w, 's>; + type Fetch<'w> = <&'static RenderEntity as WorldQuery>::Fetch<'w>; type State = <&'static RenderEntity as WorldQuery>::State; - fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( - fetch: Self::Fetch<'wlong, 's>, - ) -> Self::Fetch<'wshort, 's> { + fn shrink_fetch<'wlong: 'wshort, 'wshort>( + fetch: Self::Fetch<'wlong>, + ) -> Self::Fetch<'wshort> { fetch } @@ -304,7 +304,7 @@ mod render_entities_world_query_impls { component_id: &'s ComponentId, last_run: Tick, this_run: Tick, - ) -> Self::Fetch<'w, 's> { + ) -> Self::Fetch<'w> { // SAFETY: defers to the `&T` implementation, with T set to `RenderEntity`. unsafe { <&RenderEntity as WorldQuery>::init_fetch(world, component_id, last_run, this_run) @@ -315,7 +315,7 @@ mod render_entities_world_query_impls { #[inline] unsafe fn set_archetype<'w, 's>( - fetch: &mut Self::Fetch<'w, 's>, + fetch: &mut Self::Fetch<'w>, component_id: &'s ComponentId, archetype: &'w Archetype, table: &'w Table, @@ -328,7 +328,7 @@ mod render_entities_world_query_impls { #[inline] unsafe fn set_table<'w, 's>( - fetch: &mut Self::Fetch<'w, 's>, + fetch: &mut Self::Fetch<'w>, &component_id: &'s ComponentId, table: &'w Table, ) { @@ -374,13 +374,14 @@ mod render_entities_world_query_impls { #[inline(always)] unsafe fn fetch<'w, 's>( - fetch: &mut Self::Fetch<'w, 's>, + state: &'s Self::State, + fetch: &mut Self::Fetch<'w>, entity: Entity, table_row: TableRow, ) -> Self::Item<'w, 's> { // SAFETY: defers to the `&T` implementation, with T set to `RenderEntity`. let component = - unsafe { <&RenderEntity as QueryData>::fetch(fetch, entity, table_row) }; + unsafe { <&RenderEntity as QueryData>::fetch(state, fetch, entity, table_row) }; component.id() } } @@ -397,12 +398,12 @@ mod render_entities_world_query_impls { /// SAFETY: defers completely to `&RenderEntity` implementation, /// and then only modifies the output safely. unsafe impl WorldQuery for MainEntity { - type Fetch<'w, 's> = <&'static MainEntity as WorldQuery>::Fetch<'w, 's>; + type Fetch<'w> = <&'static MainEntity as WorldQuery>::Fetch<'w>; type State = <&'static MainEntity as WorldQuery>::State; - fn shrink_fetch<'wlong: 'wshort, 'wshort, 's>( - fetch: Self::Fetch<'wlong, 's>, - ) -> Self::Fetch<'wshort, 's> { + fn shrink_fetch<'wlong: 'wshort, 'wshort>( + fetch: Self::Fetch<'wlong>, + ) -> Self::Fetch<'wshort> { fetch } @@ -412,7 +413,7 @@ mod render_entities_world_query_impls { component_id: &'s ComponentId, last_run: Tick, this_run: Tick, - ) -> Self::Fetch<'w, 's> { + ) -> Self::Fetch<'w> { // SAFETY: defers to the `&T` implementation, with T set to `MainEntity`. unsafe { <&MainEntity as WorldQuery>::init_fetch(world, component_id, last_run, this_run) @@ -423,7 +424,7 @@ mod render_entities_world_query_impls { #[inline] unsafe fn set_archetype<'w, 's>( - fetch: &mut Self::Fetch<'w, 's>, + fetch: &mut Self::Fetch<'w>, component_id: &ComponentId, archetype: &'w Archetype, table: &'w Table, @@ -436,7 +437,7 @@ mod render_entities_world_query_impls { #[inline] unsafe fn set_table<'w, 's>( - fetch: &mut Self::Fetch<'w, 's>, + fetch: &mut Self::Fetch<'w>, &component_id: &'s ComponentId, table: &'w Table, ) { @@ -482,12 +483,14 @@ mod render_entities_world_query_impls { #[inline(always)] unsafe fn fetch<'w, 's>( - fetch: &mut Self::Fetch<'w, 's>, + state: &'s Self::State, + fetch: &mut Self::Fetch<'w>, entity: Entity, table_row: TableRow, ) -> Self::Item<'w, 's> { // SAFETY: defers to the `&T` implementation, with T set to `MainEntity`. - let component = unsafe { <&MainEntity as QueryData>::fetch(fetch, entity, table_row) }; + let component = + unsafe { <&MainEntity as QueryData>::fetch(state, fetch, entity, table_row) }; component.id() } } diff --git a/release-content/migration-guides/query_items_borrow_from_query_state.md b/release-content/migration-guides/query_items_borrow_from_query_state.md index 6c5aff0637..285abb346f 100644 --- a/release-content/migration-guides/query_items_borrow_from_query_state.md +++ b/release-content/migration-guides/query_items_borrow_from_query_state.md @@ -1,9 +1,10 @@ --- title: Query items can borrow from query state -pull_requests: [15396] +pull_requests: [15396, 19720] --- -The `QueryData::Item` and `WorldQuery::Fetch` associated types and the `QueryItem` and `ROQueryItem` type aliases now have an additional lifetime parameter corresponding to the `'s` lifetime in `Query`. +The `QueryData::Item` associated type and the `QueryItem` and `ROQueryItem` type aliases now have an additional lifetime parameter corresponding to the `'s` lifetime in `Query`. +The `QueryData::fetch()` and `QueryFilter::filter_fetch()` methods have a new parameter taking a `&'s WorldQuery::State`. Manual implementations of `WorldQuery` and `QueryData` will need to update the method signatures to include the new lifetimes. Other uses of the types will need to be updated to include a lifetime parameter, although it can usually be passed as `'_`. In particular, `ROQueryItem` is used when implementing `RenderCommand`. From b129764924431ab0a09f0cbcd2860e37695cd120 Mon Sep 17 00:00:00 2001 From: theotherphil Date: Thu, 19 Jun 2025 03:02:39 +0100 Subject: [PATCH 25/25] deny(missing_docs) for bevy_asset_macros (#19719) # Objective More boilerplate docs, towards https://github.com/bevyengine/bevy/issues/3492. Another place where https://github.com/bevyengine/bevy/issues/19537 might be worth doing as a follow up - I've added a comment on that issue. --- crates/bevy_asset/macros/src/lib.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/bevy_asset/macros/src/lib.rs b/crates/bevy_asset/macros/src/lib.rs index 443bd09ab9..a7ea87b752 100644 --- a/crates/bevy_asset/macros/src/lib.rs +++ b/crates/bevy_asset/macros/src/lib.rs @@ -1,6 +1,7 @@ -#![expect(missing_docs, reason = "Not all docs are written yet, see #3492.")] #![cfg_attr(docsrs, feature(doc_auto_cfg))] +//! Macros for deriving asset traits. + use bevy_macro_utils::BevyManifest; use proc_macro::{Span, TokenStream}; use quote::{format_ident, quote}; @@ -12,6 +13,7 @@ pub(crate) fn bevy_asset_path() -> Path { const DEPENDENCY_ATTRIBUTE: &str = "dependency"; +/// Implement the `Asset` trait. #[proc_macro_derive(Asset, attributes(dependency))] pub fn derive_asset(input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as DeriveInput); @@ -30,6 +32,7 @@ pub fn derive_asset(input: TokenStream) -> TokenStream { }) } +/// Implement the `VisitAssetDependencies` trait. #[proc_macro_derive(VisitAssetDependencies, attributes(dependency))] pub fn derive_asset_dependency_visitor(input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as DeriveInput);