Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
efd1dec
add lookahead methods to WorldQuery
ecoskey Jan 3, 2026
36d9232
fix QueryData derive
ecoskey Jan 7, 2026
50f4c0d
fix query/mod.rs test
ecoskey Jan 7, 2026
7ae3236
move IS_ARCHETYPAL to WorldQuery
ecoskey Jan 8, 2026
5eebfff
add missing trait methods
ecoskey Jan 8, 2026
5d8db80
remove `QueryFilter::filter_fetch`
ecoskey Jan 8, 2026
de006fd
remove Option wrapper from `QueryData::fetch` return type
ecoskey Jan 8, 2026
227d82f
fix union impl
ecoskey Jan 9, 2026
795ce1c
pass entity slices directly
ecoskey Jan 9, 2026
06b7956
fix query/iter.rs
ecoskey Jan 9, 2026
5ddaeb9
use mutable access for Fetch
ecoskey Jan 10, 2026
e2ffd2e
fix AssetChanged
ecoskey Jan 10, 2026
6a2a1bc
fix MainEntity and RenderEntity
ecoskey Jan 10, 2026
96aab9c
fixes
ecoskey Jan 10, 2026
bd48b3c
fix AnyOf and Or
ecoskey Jan 11, 2026
f50eb32
use `return` instead of `break`
ecoskey Jan 14, 2026
5313743
docs
ecoskey Jan 14, 2026
b1858e7
add pr num to migration guide
ecoskey Jan 14, 2026
955ace7
fmt
ecoskey Jan 14, 2026
726de80
fix docs
ecoskey Jan 15, 2026
4e0b59f
fix docs more
ecoskey Jan 15, 2026
b248ca5
add QueryData::try_fetch
ecoskey Jan 22, 2026
a103033
use try_fetch in iteration code
ecoskey Jan 22, 2026
812a364
tuple lookahead caching
ecoskey Jan 22, 2026
857666b
fix tuple caching and WorldQuery docs
ecoskey Jan 31, 2026
57b2bcb
safer AssetChanged WorldQuery impl
ecoskey Jan 31, 2026
b2f5e96
fix tuple impl again
ecoskey Jan 31, 2026
8b83597
massive cleanup
ecoskey Jan 31, 2026
c42f2bd
disjunctive query caching
ecoskey Feb 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 6 additions & 9 deletions crates/bevy_asset/src/asset_changed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ unsafe impl<A: AsAssetId> WorldQuery for AssetChanged<A> {
}

const IS_DENSE: bool = <&A>::IS_DENSE;
const IS_ARCHETYPAL: bool = false;

unsafe fn set_archetype<'w, 's>(
fetch: &mut Self::Fetch<'w>,
Expand Down Expand Up @@ -261,30 +262,26 @@ unsafe impl<A: AsAssetId> WorldQuery for AssetChanged<A> {
) -> bool {
set_contains_id(state.asset_id)
}
}

#[expect(unsafe_code, reason = "QueryFilter is an unsafe trait.")]
/// SAFETY: read-only access
unsafe impl<A: AsAssetId> QueryFilter for AssetChanged<A> {
const IS_ARCHETYPAL: bool = false;

#[inline]
unsafe fn filter_fetch(
unsafe fn matches(
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`
// SAFETY: We delegate to the inner `fetch` for `A`.
unsafe {
let handle = <&A>::fetch(&state.asset_id, inner, entity, table_row);
let handle = <&A>::try_fetch(&state.asset_id, inner, entity, table_row);
handle.is_some_and(|handle| fetch.check.has_changed(handle))
}
})
}
}

impl<A: AsAssetId> QueryFilter for AssetChanged<A> {}

#[cfg(test)]
#[expect(clippy::print_stdout, reason = "Allowed in tests.")]
mod tests {
Expand Down
18 changes: 8 additions & 10 deletions crates/bevy_ecs/macros/src/query_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,6 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream {
unsafe impl #user_impl_generics #path::query::QueryData
for #read_only_struct_name #user_ty_generics #user_where_clauses {
const IS_READ_ONLY: bool = true;
const IS_ARCHETYPAL: bool = true #(&& <#read_only_field_types as #path::query::QueryData>::IS_ARCHETYPAL)*;
type ReadOnly = #read_only_struct_name #user_ty_generics;
type Item<'__w, '__s> = #read_only_item_struct_name #user_ty_generics_with_world_and_state;

Expand Down Expand Up @@ -262,10 +261,10 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream {
_fetch: &mut <Self as #path::query::WorldQuery>::Fetch<'__w>,
_entity: #path::entity::Entity,
_table_row: #path::storage::TableRow,
) -> Option<Self::Item<'__w, '__s>> {
Some(Self::Item {
#(#field_members: <#read_only_field_types>::fetch(&_state.#field_aliases, &mut _fetch.#field_aliases, _entity, _table_row)?,)*
})
) -> Self::Item<'__w, '__s> {
Self::Item {
#(#field_members: <#read_only_field_types>::fetch(&_state.#field_aliases, &mut _fetch.#field_aliases.fetch, _entity, _table_row),)*
}
}

fn iter_access(
Expand Down Expand Up @@ -304,7 +303,6 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream {
unsafe impl #user_impl_generics #path::query::QueryData
for #struct_name #user_ty_generics #user_where_clauses {
const IS_READ_ONLY: bool = #is_read_only;
const IS_ARCHETYPAL: bool = true #(&& <#field_types as #path::query::QueryData>::IS_ARCHETYPAL)*;
type ReadOnly = #read_only_struct_name #user_ty_generics;
type Item<'__w, '__s> = #item_struct_name #user_ty_generics_with_world_and_state;

Expand Down Expand Up @@ -333,10 +331,10 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream {
_fetch: &mut <Self as #path::query::WorldQuery>::Fetch<'__w>,
_entity: #path::entity::Entity,
_table_row: #path::storage::TableRow,
) -> Option<Self::Item<'__w, '__s>> {
Some(Self::Item {
#(#field_members: <#field_types>::fetch(&_state.#field_aliases, &mut _fetch.#field_aliases, _entity, _table_row)?,)*
})
) -> Self::Item<'__w, '__s> {
Self::Item {
#(#field_members: <#field_types>::fetch(&_state.#field_aliases, &mut _fetch.#field_aliases.fetch, _entity, _table_row),)*
}
}

fn iter_access(
Expand Down
17 changes: 1 addition & 16 deletions crates/bevy_ecs/macros/src/query_filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,22 +71,7 @@ pub fn derive_query_filter_impl(input: TokenStream) -> TokenStream {
);

let filter_impl = quote! {
// SAFETY: This only performs access that subqueries perform, and they impl `QueryFilter` and so perform no mutable access.
unsafe impl #user_impl_generics #path::query::QueryFilter
for #struct_name #user_ty_generics #user_where_clauses {
const IS_ARCHETYPAL: bool = true #(&& <#field_types as #path::query::QueryFilter>::IS_ARCHETYPAL)*;

#[allow(unused_variables)]
#[inline(always)]
unsafe fn filter_fetch<'__w>(
_state: &Self::State,
_fetch: &mut <Self as #path::query::WorldQuery>::Fetch<'__w>,
_entity: #path::entity::Entity,
_table_row: #path::storage::TableRow,
) -> bool {
true #(&& <#field_types>::filter_fetch(&_state.#field_aliases, &mut _fetch.#field_aliases, _entity, _table_row))*
}
}
impl #user_impl_generics #path::query::QueryFilter for #struct_name #user_ty_generics #user_where_clauses {}
};

let filter_asserts = quote! {
Expand Down
92 changes: 85 additions & 7 deletions crates/bevy_ecs/macros/src/world_query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
#(#field_aliases: <#field_types as #path::query::WorldQuery>::Fetch<'__w>,)*
#marker_name: &'__w(),
#(#field_aliases: #path::query::ChunkFetch<'__w, #field_types>,)*
#marker_name: &'__w (),
}

impl #user_impl_generics_with_world Clone for #fetch_struct_name #user_ty_generics_with_world
Expand All @@ -104,7 +104,7 @@ pub(crate) fn world_query_impl(
) -> <#struct_name #user_ty_generics as #path::query::WorldQuery>::Fetch<'__wshort> {
#fetch_struct_name {
#(
#field_aliases: <#field_types>::shrink_fetch(fetch.#field_aliases),
#field_aliases: #path::query::ChunkFetch::shrink_fetch(fetch.#field_aliases),
)*
#marker_name: &(),
}
Expand All @@ -118,18 +118,19 @@ pub(crate) fn world_query_impl(
) -> <Self as #path::query::WorldQuery>::Fetch<'__w> {
#fetch_struct_name {
#(#field_aliases:
<#field_types>::init_fetch(
#path::query::ChunkFetch::init_fetch(
_world,
&state.#field_aliases,
_last_run,
_this_run,
_this_run
),
)*
#marker_name: &(),
}
}

const IS_DENSE: bool = true #(&& <#field_types>::IS_DENSE)*;
const IS_ARCHETYPAL: bool = true #(&& <#field_types>::IS_ARCHETYPAL)*;

/// SAFETY: we call `set_archetype` for each member that implements `Fetch`
#[inline]
Expand All @@ -139,7 +140,7 @@ pub(crate) fn world_query_impl(
_archetype: &'__w #path::archetype::Archetype,
_table: &'__w #path::storage::Table
) {
#(<#field_types>::set_archetype(&mut _fetch.#field_aliases, &_state.#field_aliases, _archetype, _table);)*
#(#path::query::ChunkFetch::set_archetype(&mut _fetch.#field_aliases, &_state.#field_aliases, _archetype, _table);)*
}

/// SAFETY: we call `set_table` for each member that implements `Fetch`
Expand All @@ -149,7 +150,7 @@ pub(crate) fn world_query_impl(
_state: &'__s Self::State,
_table: &'__w #path::storage::Table
) {
#(<#field_types>::set_table(&mut _fetch.#field_aliases, &_state.#field_aliases, _table);)*
#(#path::query::ChunkFetch::set_table(&mut _fetch.#field_aliases, &_state.#field_aliases, _table);)*
}

fn update_component_access(state: &Self::State, _access: &mut #path::query::FilteredAccess) {
Expand All @@ -171,6 +172,83 @@ pub(crate) fn world_query_impl(
fn matches_component_set(state: &Self::State, _set_contains_id: &impl Fn(#path::component::ComponentId) -> bool) -> bool {
true #(&& <#field_types>::matches_component_set(&state.#field_aliases, _set_contains_id))*
}

#[inline]
unsafe fn find_table_chunk(
state: &Self::State,
fetch: &mut Self::Fetch<'_>,
table_entities: &[#path::entity::Entity],
mut rows: core::ops::Range<u32>,
) -> core::ops::Range<u32> {
if Self::IS_ARCHETYPAL || rows.is_empty() {
rows
} else {
let mut chunk = rows.clone();
loop {
#(
// SAFETY:
// - invariants except with respect to rows are upheld by caller.
// - `chunk.start..rows.end` is always a subset of `rows`
let result = unsafe {
#path::query::ChunkFetch::find_table_chunk_conjunctive(&state.#field_aliases, &mut fetch.#field_aliases, table_entities, rows.clone(), &mut chunk)
};
match result {
#path::query::FindChunkConjunctiveResult::Success => {},
#path::query::FindChunkConjunctiveResult::Retry => { continue; },
#path::query::FindChunkConjunctiveResult::Abort => { return chunk; },
}
)*

return chunk;
}
}
}

#[inline]
unsafe fn find_archetype_chunk(
state: &Self::State,
fetch: &mut Self::Fetch<'_>,
archetype_entities: &[#path::archetype::ArchetypeEntity],
mut indices: core::ops::Range<u32>,
) -> core::ops::Range<u32> {
if Self::IS_ARCHETYPAL || indices.is_empty() {
indices
} else {
let mut chunk = indices.clone();
loop {
#(
// SAFETY:
// - invariants except with respect to rows are upheld by caller.
// - `chunk.start..indices.end` is always a subset of `indices`
let result = unsafe {
#path::query::ChunkFetch::find_archetype_chunk_conjunctive(&state.#field_aliases, &mut fetch.#field_aliases, archetype_entities, indices.clone(), &mut chunk)
};
match result {
#path::query::FindChunkConjunctiveResult::Success => {},
#path::query::FindChunkConjunctiveResult::Retry => { continue; },
#path::query::FindChunkConjunctiveResult::Abort => { return chunk; },
}
)*

return chunk;
}
}
}

#[inline]
unsafe fn matches(
state: &Self::State,
fetch: &mut Self::Fetch<'_>,
entity: #path::entity::Entity,
table_row: #path::storage::TableRow,
) -> bool {
if Self::IS_ARCHETYPAL {
true
} else {
// SAFETY: invariants are upheld by the caller.
true #(&& unsafe { <#field_types>::matches(&state.#field_aliases, &mut fetch.#field_aliases.fetch, entity, table_row) })*
}
}
}
}
}
Loading