Skip to content

Conversation

@Jenya705
Copy link
Contributor

@Jenya705 Jenya705 commented Nov 30, 2025

Objective

Enables accessing slices from tables directly via Queries.

This pr is a draft to get feedback on the design.

Fixes: #21861

Solution

Two new traits:

  • 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))
  • ContiguousQueryFilter allows to filter whole tables at once (I am not sure whether it is actually helpful (With and Without are the only default filters which implement it, yet, but they always return true anyway), but I added it to match the QueryFilter trait)

And a method next_contiguous in QueryIter making possible to iterate using these traits.

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

Example

let mut world = World::new();
let mut query = world.query::<(&Velocity, &mut Position)>();
let mut iter = query.iter_mut(&mut world);
// velocity's type is &[Velocity]
// position's type is &mut [Position]
// ticks's type is ContiguousComponentTicks
while let Some((velocity, (position, mut ticks))) = iter.next_contiguous() {
    for (v, p) in velocity.iter().zip(position.iter_mut()) {
        p.0 += v.0;
    }
    // sets changed ticks
    let tick = ticks.this_run();
    for t in ticks.get_changed_ticks_mut() {
        *t = tick;
    }
}

Benchmarks

Code for base benchmark:

#[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 one 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

Things to think about

  • The neediness of offset parameter in ContiguousQueryData and ContiguousQueryFilter
  • If it is not needed, won't it be more efficient to introduce an update tick value setting which lazily propagates to all other entities in the same table

@Jenya705 Jenya705 marked this pull request as draft November 30, 2025 15:04
@Jondolf Jondolf added C-Feature A new feature, making something new possible A-ECS Entities, components, systems, and events C-Performance A change motivated by improving speed, memory usage or compile times S-Needs-Review Needs reviewer attention (from anyone!) to move forward D-Complex Quite challenging from either a design or technical perspective. Ask for help! D-Unsafe Touches with unsafe code in some way labels Nov 30, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-ECS Entities, components, systems, and events C-Feature A new feature, making something new possible C-Performance A change motivated by improving speed, memory usage or compile times D-Complex Quite challenging from either a design or technical perspective. Ask for help! D-Unsafe Touches with unsafe code in some way S-Needs-Review Needs reviewer attention (from anyone!) to move forward

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Raw table iteration to improve query iteration speed by bypassing change ticks

2 participants