mirror of
https://github.com/bevyengine/bevy.git
synced 2026-05-06 06:06:42 -04:00
b842bcb923
# Objective Enables accessing slices from tables directly via Queries. Fixes: #21861 ## Solution One new trait: - `ContiguousQueryData` allows to fetch all values from tables all at once (an implementation for `&T` returns a slice of components in the set table, for `&mut T` returns a mutable slice of components in the set table as well as a struct with methods to set update ticks (to match the `fetch` implementation)) Methods `contiguous_iter`, `contiguous_iter_mut` and similar in `Query` and `QueryState` making possible to iterate using these traits. Macro `QueryData` was updated to support contiguous items when `contiguous(target)` attribute is added (a target can be `all`, `mutable` and `immutable`, refer to the `custom_query_param` example) ## Testing - `sparse_set_contiguous_query` test verifies that you can't use `next_contiguous` with sparse set components - `test_contiguous_query_data` test verifies that returned values are valid - `base_contiguous` benchmark (file is named `iter_simple_contiguous.rs`) - `base_no_detection` benchmark (file is named `iter_simple_no_detection.rs`) - `base_no_detection_contiguous` benchmark (file is named `iter_simple_no_detection_contiguous.rs`) - `base_contiguous_avx2` benchmark (file is named `iter_simple_contiguous_avx2.rs`) --- ## Showcase Examples `contiguous_query`, `custom_query_param` ### Example ```rust // - self.0 is a World // - self.1 is a QueryState // - velocity is a slice of components with Vec3 inside. // - position is a data structure which implements Deref/DerefMut and IntoIterator methods to access the slice // as well as mechanism to update update ticks (which it does automatically on dereference), // which may be bypassed via `bypass_change_detection` methods. for (velocity, mut position) in self.1.contiguous_iter_mut(&mut self.0).unwrap() { assert!(velocity.len() == position.len()); for (v, p) in velocity.iter().zip(position.iter_mut()) { p.0 += v.0; } } ``` ### Benchmarks Code for `base` benchmark: ```rust #[derive(Component, Copy, Clone)] struct Transform(Mat4); #[derive(Component, Copy, Clone)] struct Position(Vec3); #[derive(Component, Copy, Clone)] struct Rotation(Vec3); #[derive(Component, Copy, Clone)] struct Velocity(Vec3); pub struct Benchmark<'w>(World, QueryState<(&'w Velocity, &'w mut Position)>); impl<'w> Benchmark<'w> { pub fn new() -> Self { let mut world = World::new(); world.spawn_batch(core::iter::repeat_n( ( Transform(Mat4::from_scale(Vec3::ONE)), Position(Vec3::X), Rotation(Vec3::X), Velocity(Vec3::X), ), 10_000, )); let query = world.query::<(&Velocity, &mut Position)>(); Self(world, query) } #[inline(never)] pub fn run(&mut self) { for (velocity, mut position) in self.1.iter_mut(&mut self.0) { position.0 += velocity.0; } } } ``` Iterating over 10000 entities from **a single** table and increasing a 3-dimensional vector from component `Position` by a 3-dimensional vector from component `Velocity` | Name | Time | Time (AVX2) | Description | |------------------------------|-----------|-------------|--------------------------------------------------------------------| | base | 5.5828 µs | 5.5122 µs | Iteration over components | | base_contiguous | 4.8825 µs | 1.8665 µs | Iteration over contiguous chunks | | base_contiguous_avx2 | 2.0740 µs | 1.8665 µs | Iteration over contiguous chunks with enforced avx2 optimizations | | base_no_detection | 4.8065 µs | 4.7723 µs | Iteration over components while bypassing change detection through `bypass_change_detection()` method | | base_no_detection_contiguous | 4.3979 µs | 1.5797 µs | Iteration over components without registering update ticks | Using contiguous 'iterator' makes the program a little bit faster and it can be further vectorized to make it even faster
228 lines
7.6 KiB
Rust
228 lines
7.6 KiB
Rust
//! This example illustrates the usage of the [`QueryData`] derive macro, which allows
|
|
//! defining custom query and filter types.
|
|
//!
|
|
//! While regular tuple queries work great in most of simple scenarios, using custom queries
|
|
//! declared as named structs can bring the following advantages:
|
|
//! - They help to avoid destructuring or using `q.0, q.1, ...` access pattern.
|
|
//! - Adding, removing components or changing items order with structs greatly reduces maintenance
|
|
//! burden, as you don't need to update statements that destructure tuples, care about order
|
|
//! of elements, etc. Instead, you can just add or remove places where a certain element is used.
|
|
//! - Named structs enable the composition pattern, that makes query types easier to re-use.
|
|
//! - You can bypass the limit of 15 components that exists for query tuples.
|
|
//!
|
|
//! For more details on the [`QueryData`] derive macro, see the trait documentation.
|
|
|
|
use bevy::{
|
|
ecs::query::{QueryData, QueryFilter},
|
|
prelude::*,
|
|
};
|
|
use std::fmt::Debug;
|
|
|
|
fn main() {
|
|
App::new()
|
|
.add_systems(Startup, spawn)
|
|
.add_systems(
|
|
Update,
|
|
(
|
|
print_components_read_only,
|
|
print_components_iter_mut,
|
|
print_components_iter,
|
|
print_components_tuple,
|
|
print_components_contiguous_iter,
|
|
)
|
|
.chain(),
|
|
)
|
|
.run();
|
|
}
|
|
|
|
#[derive(Component, Debug)]
|
|
struct ComponentA;
|
|
#[derive(Component, Debug)]
|
|
struct ComponentB;
|
|
#[derive(Component, Debug)]
|
|
struct ComponentC;
|
|
#[derive(Component, Debug)]
|
|
struct ComponentD;
|
|
#[derive(Component, Debug)]
|
|
struct ComponentZ;
|
|
|
|
#[derive(QueryData)]
|
|
#[query_data(derive(Debug))]
|
|
struct ReadOnlyCustomQuery<T: Component + Debug, P: Component + Debug> {
|
|
entity: Entity,
|
|
a: &'static ComponentA,
|
|
b: Option<&'static ComponentB>,
|
|
nested: NestedQuery,
|
|
optional_nested: Option<NestedQuery>,
|
|
optional_tuple: Option<(&'static ComponentB, &'static ComponentZ)>,
|
|
generic: GenericQuery<T, P>,
|
|
empty: EmptyQuery,
|
|
}
|
|
|
|
fn print_components_read_only(
|
|
query: Query<
|
|
ReadOnlyCustomQuery<ComponentC, ComponentD>,
|
|
CustomQueryFilter<ComponentC, ComponentD>,
|
|
>,
|
|
) {
|
|
println!("Print components (read_only):");
|
|
for e in &query {
|
|
println!("Entity: {}", e.entity);
|
|
println!("A: {:?}", e.a);
|
|
println!("B: {:?}", e.b);
|
|
println!("Nested: {:?}", e.nested);
|
|
println!("Optional nested: {:?}", e.optional_nested);
|
|
println!("Optional tuple: {:?}", e.optional_tuple);
|
|
println!("Generic: {:?}", e.generic);
|
|
}
|
|
println!();
|
|
}
|
|
|
|
/// If you are going to mutate the data in a query, you must mark it with the `mutable` attribute.
|
|
///
|
|
/// The [`QueryData`] derive macro will still create a read-only version, which will be have `ReadOnly`
|
|
/// suffix.
|
|
/// Note: if you want to use derive macros with read-only query variants, you need to pass them with
|
|
/// using the `derive` attribute.
|
|
#[derive(QueryData)]
|
|
#[query_data(mutable, derive(Debug))]
|
|
struct CustomQuery<T: Component + Debug, P: Component + Debug> {
|
|
entity: Entity,
|
|
a: &'static mut ComponentA,
|
|
b: Option<&'static mut ComponentB>,
|
|
nested: NestedQuery,
|
|
optional_nested: Option<NestedQuery>,
|
|
optional_tuple: Option<(NestedQuery, &'static mut ComponentZ)>,
|
|
generic: GenericQuery<T, P>,
|
|
empty: EmptyQuery,
|
|
}
|
|
|
|
// This is a valid query as well, which would iterate over every entity.
|
|
#[derive(QueryData)]
|
|
#[query_data(derive(Debug))]
|
|
struct EmptyQuery {
|
|
empty: (),
|
|
}
|
|
|
|
#[derive(QueryData)]
|
|
#[query_data(derive(Debug))]
|
|
struct NestedQuery {
|
|
c: &'static ComponentC,
|
|
d: Option<&'static ComponentD>,
|
|
}
|
|
|
|
#[derive(QueryData)]
|
|
#[query_data(derive(Debug), contiguous(mutable))]
|
|
struct GenericQuery<T: Component, P: Component> {
|
|
generic: (&'static T, &'static P),
|
|
}
|
|
|
|
#[derive(QueryFilter)]
|
|
struct CustomQueryFilter<T: Component, P: Component> {
|
|
_c: With<ComponentC>,
|
|
_d: With<ComponentD>,
|
|
_or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>,
|
|
_generic_tuple: (With<T>, With<P>),
|
|
}
|
|
|
|
fn spawn(mut commands: Commands) {
|
|
commands.spawn((ComponentA, ComponentB, ComponentC, ComponentD));
|
|
}
|
|
|
|
fn print_components_iter_mut(
|
|
mut query: Query<
|
|
CustomQuery<ComponentC, ComponentD>,
|
|
CustomQueryFilter<ComponentC, ComponentD>,
|
|
>,
|
|
) {
|
|
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;
|
|
println!("Entity: {}", e.entity);
|
|
println!("A: {:?}", e.a);
|
|
println!("B: {:?}", e.b);
|
|
println!("Optional nested: {:?}", e.optional_nested);
|
|
println!("Optional tuple: {:?}", e.optional_tuple);
|
|
println!("Nested: {:?}", e.nested);
|
|
println!("Generic: {:?}", e.generic);
|
|
}
|
|
println!();
|
|
}
|
|
|
|
fn print_components_iter(
|
|
query: Query<CustomQuery<ComponentC, ComponentD>, CustomQueryFilter<ComponentC, ComponentD>>,
|
|
) {
|
|
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;
|
|
println!("Entity: {}", e.entity);
|
|
println!("A: {:?}", e.a);
|
|
println!("B: {:?}", e.b);
|
|
println!("Nested: {:?}", e.nested);
|
|
println!("Generic: {:?}", e.generic);
|
|
}
|
|
println!();
|
|
}
|
|
|
|
type NestedTupleQuery<'w> = (&'w ComponentC, &'w ComponentD);
|
|
type GenericTupleQuery<'w, T, P> = (&'w T, &'w P);
|
|
|
|
fn print_components_tuple(
|
|
query: Query<
|
|
(
|
|
Entity,
|
|
&ComponentA,
|
|
&ComponentB,
|
|
NestedTupleQuery,
|
|
GenericTupleQuery<ComponentC, ComponentD>,
|
|
),
|
|
(
|
|
With<ComponentC>,
|
|
With<ComponentD>,
|
|
Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>,
|
|
),
|
|
>,
|
|
) {
|
|
println!("Print components (tuple):");
|
|
for (entity, a, b, nested, (generic_c, generic_d)) in &query {
|
|
println!("Entity: {entity}");
|
|
println!("A: {a:?}");
|
|
println!("B: {b:?}");
|
|
println!("Nested: {:?} {:?}", nested.0, nested.1);
|
|
println!("Generic: {generic_c:?} {generic_d:?}");
|
|
}
|
|
println!();
|
|
}
|
|
|
|
/// If you are going to contiguously iterate the data in a query, you must mark it with the `contiguous` attribute,
|
|
/// which accepts one of 3 targets (`all`, `immutable` and `mutable`)
|
|
///
|
|
/// - `all` will make read only query as well as mutable query both be able to be iterated contiguosly
|
|
/// - `mutable` will only make the original query (i.e., in that case [`CustomContiguousQuery`]) be able to be iterated contiguously
|
|
/// - `immutable` will only make the read only query (which is only useful when you mark the original query as `mutable`)
|
|
/// be able to be iterated contiguously
|
|
#[derive(QueryData)]
|
|
#[query_data(derive(Debug), contiguous(all))]
|
|
struct CustomContiguousQuery<T: Component + Debug, P: Component + Debug> {
|
|
entity: Entity,
|
|
a: Ref<'static, ComponentA>,
|
|
b: Option<&'static ComponentB>,
|
|
generic: GenericQuery<T, P>,
|
|
}
|
|
|
|
fn print_components_contiguous_iter(query: Query<CustomContiguousQuery<ComponentC, ComponentD>>) {
|
|
println!("Print components (contiguous_iter):");
|
|
for e in query.contiguous_iter().unwrap() {
|
|
let e: CustomContiguousQueryContiguousItem<'_, '_, _, _> = e;
|
|
println!("Entity: {:?}", e.entity);
|
|
println!("A: {:?}", e.a);
|
|
println!("B: {:?}", e.b);
|
|
println!(
|
|
"Generic: {:?} {:?}",
|
|
e.generic.generic.0, e.generic.generic.1
|
|
);
|
|
}
|
|
}
|