Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
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, _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, _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
52 changes: 52 additions & 0 deletions crates/bevy_ecs/macros/src/world_query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ pub(crate) fn world_query_impl(
}

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 Down Expand Up @@ -171,6 +172,57 @@ 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
} else {
// SAFETY: `rows` is only ever narrowed as we iterate subqueries, so it's
// always valid to pass to the next term. Other invariants are upheld by
// the caller.
#(rows = unsafe { <#field_types>::find_table_chunk(&state.#field_aliases, &mut fetch.#field_aliases, table_entities, rows) };)*
rows
}
}

#[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
} else {
// SAFETY: `indices` is only ever narrowed as we iterate subqueries, so it's
// always valid to pass to the next term. Other invariants are upheld by
// the caller.
#(indices = unsafe { <#field_types>::find_archetype_chunk(&state.#field_aliases, &mut fetch.#field_aliases, archetype_entities, indices) };)*
indices
}
}

#[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, entity, table_row) })*
}
}
}
}
}
Loading