Skip to main content

Query

Struct Query 

Source
pub struct Query<'w, Q>
where Q: QueryData,
{ /* private fields */ }
Expand description

The ECS: entities, components, queries, resources, commands. A query over a World, created by World::query.

struct Pos(f32);
struct Vel(f32);
struct Frozen;

let mut world = World::new();
world.spawn((Pos(0.0), Vel(1.0)));
world.spawn((Pos(0.0), Vel(1.0), Frozen));

world.query::<(&mut Pos, &Vel)>().without::<Frozen>().for_each(|(p, v)| p.0 += v.0);

let moved = world.query::<&Pos>().iter().filter(|p| p.0 > 0.0).count();
assert_eq!(moved, 1);

Implementations§

Source§

impl<'w, Q> Query<'w, Q>
where Q: QueryData,

Source

pub fn with<T>(self) -> Query<'w, Q>
where T: Component,

Only match entities that also have component T (without fetching it).

§Panics

With more than 4 with filters on one query.

Examples found in repository?
examples/physics.rs (line 123)
96fn drop_things(ctx: &mut Ctx) {
97    let at = ctx.mouse_world();
98    if ctx.mouse_pressed(MouseButton::Left) {
99        ctx.world.spawn((
100            Transform2D::at(at),
101            RigidBody::dynamic(),
102            Collider::rect(vec2(30.0, 30.0)).friction(0.8),
103        ));
104    }
105    if ctx.mouse_pressed(MouseButton::Right) {
106        ctx.world.spawn((
107            Transform2D::at(at),
108            RigidBody::dynamic(),
109            Collider::circle(12.0).restitution(0.6),
110        ));
111    }
112    if ctx.mouse_pressed(MouseButton::Middle) {
113        let rock: Vec<Vec2> = (0..5)
114            .map(|i| Vec2::from_angle(i as f32 * std::f32::consts::TAU / 5.0) * 18.0)
115            .collect();
116        ctx.world.spawn((
117            Transform2D::at(at),
118            RigidBody::dynamic(),
119            Collider::polygon(&rock).friction(0.7),
120        ));
121    }
122    if ctx.key_pressed(Key::R) {
123        let all: Vec<Entity> = ctx.world.query::<Entity>().with::<RigidBody>().iter().collect();
124        for e in all {
125            ctx.world.despawn(e);
126        }
127        build_level(ctx);
128    }
129    if ctx.key_pressed(Key::Escape) {
130        ctx.quit();
131    }
132}
Source

pub fn without<T>(self) -> Query<'w, Q>
where T: Component,

Skip entities that have component T.

§Panics

With more than 4 without filters on one query.

Source

pub fn for_each(&mut self, f: impl FnMut(<Q as QueryData>::Item<'_>))

Runs f for every matching entity. The fastest way to loop.

Examples found in repository?
examples/3d.rs (lines 26-28)
24fn spin(ctx: &mut Ctx) {
25    let dt = ctx.dt();
26    ctx.world.query::<(&mut Transform3D, &Spin)>().for_each(|(t, s)| {
27        t.rotation *= Quat::from_rotation_y(s.0 * dt);
28    });
29}
More examples
Hide additional examples
examples/ribbons.rs (lines 9-11)
7fn fly(ctx: &mut Ctx) {
8    let t = ctx.time() as f32;
9    ctx.world.query::<(&mut Transform3D, &Orbiting)>().for_each(|(at, _)| {
10        at.position = vec3(t.cos() * 3.0, 1.0 + (t * 2.0).sin() * 0.5, t.sin() * 3.0);
11    });
12}
examples/tiles.rs (lines 130-134)
125fn paint(ctx: &mut Ctx) {
126    if !ctx.mouse_down(MouseButton::Left) || ctx.mouse_over_ui() {
127        return;
128    }
129    let at = ctx.mouse_world();
130    ctx.world.query::<(&Transform2D, &mut TileMap)>().for_each(|(t, map)| {
131        if let Some(tile) = map.tile_at(t.position, at) {
132            map.set(tile.x, tile.y, Some(STONE));
133        }
134    });
135}
examples/scene_viewer.rs (lines 19-22)
17fn extent(world: &mut World) -> (Vec3, f32) {
18    let (mut min, mut max) = (Vec3::splat(f32::MAX), Vec3::splat(f32::MIN));
19    world.query::<(&Transform3D, &Model)>().for_each(|(t, _)| {
20        min = min.min(t.position);
21        max = max.max(t.position);
22    });
23    if min.x > max.x {
24        return (Vec3::ZERO, 10.0);
25    }
26    ((min + max) * 0.5, (max - min).max_element().max(10.0))
27}
examples/ecs.rs (lines 52-62)
49fn movement(ctx: &mut Ctx) {
50    let dt = ctx.dt();
51    let bounds = ctx.window_size();
52    ctx.world.query::<(&mut Transform2D, &mut Vel)>().for_each(|(t, v)| {
53        t.position += v.0 * dt;
54        t.rotation = v.0.to_angle();
55        if t.position.x < 0.0 || t.position.x > bounds.x {
56            v.0.x = -v.0.x;
57        }
58        if t.position.y < 0.0 || t.position.y > bounds.y {
59            v.0.y = -v.0.y;
60        }
61        t.position = t.position.clamp(Vec2::ZERO, bounds);
62    });
63}
64
65/// Simulation: age, fade out, and despawn through commands.
66fn aging(ctx: &mut Ctx) {
67    let dt = ctx.dt();
68    let commands = &mut ctx.commands;
69    ctx.world
70        .query::<(Entity, &mut Life, &mut Sprite)>()
71        .for_each(|(e, life, sprite)| {
72            life.0 -= dt;
73            sprite.color = sprite.color.with_alpha((life.0 / LIFETIME).clamp(0.0, 1.0));
74            if life.0 <= 0.0 {
75                commands.despawn(e);
76            }
77        });
78}
examples/editor.rs (lines 43-54)
40fn walk(ctx: &mut Ctx) {
41    let dt = ctx.dt();
42    let bounds = ctx.window_size();
43    ctx.world.query::<(&mut Transform2D, &mut Walker)>().for_each(|(t, w)| {
44        let mut v = Vec2::from_angle(w.heading) * w.speed;
45        t.position += v * dt;
46        if !(0.0..=bounds.x).contains(&t.position.x) {
47            v.x = -v.x;
48        }
49        if !(0.0..=bounds.y).contains(&t.position.y) {
50            v.y = -v.y;
51        }
52        t.position = t.position.clamp(Vec2::ZERO, bounds);
53        w.heading = v.to_angle();
54    });
55}
Source

pub fn par_for_each( &mut self, f: impl Fn(<Q as QueryData>::Item<'_>) + Send + Sync, )

Like for_each, but spread over all CPU cores.

Each archetype is cut into chunks of rows, and the chunks run in parallel. Use it for heavy per-entity work over many entities; for a few thousand cheap updates, for_each is just as fast.

f can’t capture &mut state (it runs on many threads at once). To spawn or despawn, collect what you need and use commands afterwards.

struct Pos(f32);
struct Vel(f32);
let mut world = World::new();
world.spawn_batch((0..100_000).map(|i| (Pos(i as f32), Vel(1.0))));

world.query::<(&mut Pos, &Vel)>().par_for_each(|(p, v)| p.0 += v.0);
Examples found in repository?
examples/tiles.rs (line 121)
117fn wander(ctx: &mut Ctx) {
118    let dt = ctx.dt();
119    ctx.world
120        .query::<(&mut Transform2D, &Wander)>()
121        .par_for_each(|(t, w)| t.position += w.0 * dt);
122}
More examples
Hide additional examples
examples/headless.rs (lines 21-27)
17fn wander(ctx: &mut Ctx) {
18    let dt = ctx.dt();
19    ctx.world
20        .query::<(&mut Transform2D, &mut Agent)>()
21        .par_for_each(|(t, a)| {
22            let p = &mut t.position;
23            a.heading += (p.x * 0.013 + p.y * 0.007 + a.heading).sin() * 0.8 * dt;
24            p.x = (p.x + a.heading.cos() * a.speed * dt).rem_euclid(WORLD);
25            p.y = (p.y + a.heading.sin() * a.speed * dt).rem_euclid(WORLD);
26            t.rotation = a.heading;
27        });
28}
examples/flow.rs (lines 103-118)
96fn steer(ctx: &mut Ctx) {
97    let dt = ctx.dt();
98    let random = ctx.random();
99    // Taken out for the query, which borrows the world, then put back.
100    let paths = ctx.world.remove_resource::<Paths>().expect("paths");
101    ctx.world
102        .query::<(Entity, &mut Transform2D, &mut Agent)>()
103        .par_for_each(|(e, t, a)| {
104            let mut rng = random.of(e);
105            let cell = paths.nav.cell_at(t.position);
106            let slow = cell.and_then(|c| paths.nav.cost(c.x, c.y)).unwrap_or(1).max(1) as f32;
107            let want = paths.flow.direction_at(t.position) * (SPEED / slow);
108            a.vel = a.vel.lerp(want, 0.15) + rng.unit_vec2() * 3.0;
109            // Slide along walls instead of walking into them.
110            let next = t.position + a.vel * dt;
111            if paths.nav.walkable(next) {
112                t.position = next;
113            } else if paths.nav.walkable(vec2(next.x, t.position.y)) {
114                t.position.x = next.x;
115            } else if paths.nav.walkable(vec2(t.position.x, next.y)) {
116                t.position.y = next.y;
117            }
118        });
119    ctx.world.insert_resource(paths);
120}
examples/boids.rs (lines 45-65)
27fn flocking(bounds: Vec2) -> impl FnMut(&mut Ctx) {
28    let mut grid = SpatialGrid::with_data(RADIUS);
29    move |ctx: &mut Ctx| {
30        let dt = ctx.dt();
31        // Keep boids that are close in space close in memory, so neighbor
32        // lookups hit the cache (1M boids: 62 -> 40 ms/tick).
33        if ctx.sim_tick().is_multiple_of(SPATIAL_SORT_EVERY) {
34            ctx.world.sort_by::<Transform2D>(|t| spatial_key(t.position, RADIUS));
35        }
36        grid.rebuild_with_data(
37            ctx.world
38                .query::<(Entity, &Transform2D, &Vel)>()
39                .iter()
40                .map(|(e, t, v)| (e, t.position, v.0)),
41        );
42        let grid = &grid;
43        ctx.world
44            .query::<(Entity, &mut Transform2D, &mut Vel)>()
45            .par_for_each(|(me, t, v)| {
46                let (mut away, mut heading, mut center, mut n) = (Vec2::ZERO, Vec2::ZERO, Vec2::ZERO, 0.0);
47                grid.for_each_in_radius(t.position, RADIUS, |o| {
48                    if o.entity == me {
49                        return;
50                    }
51                    let d = t.position - o.position;
52                    away += d / d.length_squared().max(1.0);
53                    heading += o.data;
54                    center += o.position;
55                    n += 1.0;
56                });
57                let mut steer = away * 900.0;
58                if n > 0.0 {
59                    steer += (heading / n - v.0) * 1.5 + (center / n - t.position) * 1.2;
60                }
61                let speed = (v.0 + steer * dt).length().clamp(MIN_SPEED, MAX_SPEED);
62                v.0 = (v.0 + steer * dt).normalize_or(Vec2::X) * speed;
63                t.position = (t.position + v.0 * dt).rem_euclid(bounds);
64                t.rotation = v.0.to_angle();
65            });
66    }
67}
examples/ants.rs (lines 136-180)
122fn steer(bounds: Vec2) -> impl FnMut(&mut Ctx) {
123    let mut food_grid = SpatialGrid::new(SIGHT);
124    move |ctx: &mut Ctx| {
125        let (dt, random) = (ctx.dt(), ctx.random());
126        food_grid.rebuild_from(
127            ctx.world
128                .query::<(Entity, &Transform2D, &Food)>()
129                .iter()
130                .map(|(e, t, _)| (e, t.position)),
131        );
132        let field = ctx.world.remove_resource::<Field>().expect("field");
133        let (grid, f) = (&food_grid, &field);
134        ctx.world
135            .query::<(Entity, &mut Transform2D, &mut Ant)>()
136            .par_for_each(|(e, t, ant)| {
137                let mut rng = random.of(e);
138                let p = t.position;
139                let target = if ant.carrying {
140                    (p.distance(ant.home_at) < SIGHT * 3.0).then_some(ant.home_at)
141                } else {
142                    grid.nearest(p, SIGHT, |_| true).map(|food| food.position)
143                };
144                if let Some(target) = target {
145                    ant.heading = (target - p).to_angle();
146                } else {
147                    let layer = if ant.carrying { &f.to_home } else { &f.to_food };
148                    let smell = |angle: f32| {
149                        let at = p + Vec2::from_angle(ant.heading + angle) * SENSE_DIST;
150                        f.index(at).map_or(-1.0, |i| layer[i])
151                    };
152                    let (left, ahead, right) = (smell(-SENSE_ANGLE), smell(0.0), smell(SENSE_ANGLE));
153                    if ant.carrying && left.max(ahead).max(right) < FAINT {
154                        // No trail to follow: real ants keep track of where
155                        // home is (path integration) and head that way.
156                        let off = (ant.home_at - p).to_angle() - ant.heading;
157                        let off = (off + std::f32::consts::PI).rem_euclid(std::f32::consts::TAU) - std::f32::consts::PI;
158                        ant.heading += off.clamp(-2.0 * dt, 2.0 * dt);
159                    } else if left > ahead && left > right {
160                        ant.heading -= 4.0 * dt;
161                    } else if right > ahead && right > left {
162                        ant.heading += 4.0 * dt;
163                    }
164                }
165                ant.heading += rng.range(-2.5, 2.5) * dt;
166                let mut next = p + Vec2::from_angle(ant.heading) * SPEED * dt;
167                // Bounce off the edges.
168                if next.x < 0.0 || next.x >= bounds.x {
169                    ant.heading = std::f32::consts::PI - ant.heading;
170                    next.x = next.x.clamp(0.0, bounds.x - 0.01);
171                }
172                if next.y < 0.0 || next.y >= bounds.y {
173                    ant.heading = -ant.heading;
174                    next.y = next.y.clamp(0.0, bounds.y - 0.01);
175                }
176                t.position = next;
177                t.rotation = ant.heading;
178                ant.since += dt;
179                ant.energy -= dt;
180            });
181        ctx.world.insert_resource(field);
182    }
183}
184
185/// Every tick: ants mark where they walk. Searching ants lay "home is this
186/// way", carrying ants lay "food is this way"; fresher trails are stronger.
187fn lay_trails(ctx: &mut Ctx) {
188    let mut field = ctx.world.remove_resource::<Field>().expect("field");
189    ctx.world.query::<(&Transform2D, &Ant)>().for_each(|(t, ant)| {
190        if let Some(i) = field.index(t.position) {
191            let strength = (1.0 - ant.since / 30.0).max(0.0);
192            let layer = if ant.carrying {
193                &mut field.to_food
194            } else {
195                &mut field.to_home
196            };
197            layer[i] = layer[i].max(strength);
198        }
199    });
200    ctx.world.insert_resource(field);
201}
202
203/// Every 4th tick: trails fade.
204fn evaporate(ctx: &mut Ctx) {
205    let keep = 0.985f32.powf(ctx.dt() * 60.0);
206    let field = ctx.world.resource_mut::<Field>();
207    for v in field.to_food.iter_mut().chain(field.to_home.iter_mut()) {
208        *v *= keep;
209    }
210}
211
212/// Ants take turns (each every 2nd tick) picking up food and dropping it at
213/// their nest; starving ants die. Each ant decides on all cores and notes
214/// what happened in its `event`; one quick pass then applies the events.
215fn forage(ctx: &mut Ctx) {
216    let turn = ctx.turns(2);
217    let mut food_grid = SpatialGrid::new(8.0);
218    food_grid.rebuild_from(
219        ctx.world
220            .query::<(Entity, &Transform2D, &Food)>()
221            .iter()
222            .map(|(e, t, _)| (e, t.position)),
223    );
224    let grid = &food_grid;
225    ctx.world
226        .query::<(Entity, &Transform2D, &mut Ant)>()
227        .par_for_each(|(e, t, ant)| {
228            ant.event = Event::None;
229            if ant.energy <= 0.0 {
230                ant.event = Event::Starved;
231                return;
232            }
233            if !turn.includes(e) {
234                return;
235            }
236            let at_home = t.position.distance(ant.home_at) < NEST_RADIUS;
237            if ant.carrying {
238                if at_home {
239                    ant.carrying = false;
240                    ant.since = 0.0;
241                    ant.energy = LIFESPAN;
242                    ant.heading += std::f32::consts::PI;
243                    ant.event = Event::Delivered;
244                }
245            } else if at_home {
246                ant.since = 0.0;
247            } else if let Some(f) = grid.nearest(t.position, 4.0, |_| true) {
248                ant.carrying = true;
249                ant.since = 0.0;
250                ant.heading += std::f32::consts::PI;
251                ant.event = Event::PickedUp(f.entity);
252            }
253        });
254
255    let (mut picked, mut delivered) = (Vec::new(), Vec::new());
256    let cmds = &mut ctx.commands;
257    ctx.world
258        .query::<(Entity, &Ant)>()
259        .for_each(|(e, ant)| match ant.event {
260            Event::None => {}
261            Event::Starved => cmds.despawn(e),
262            Event::PickedUp(food) => picked.push(food),
263            Event::Delivered => delivered.push(ant.home),
264        });
265    for f in picked {
266        if let Some(food) = ctx.world.get_mut::<Food>(f) {
267            food.amount = food.amount.saturating_sub(1);
268            if food.amount == 0 {
269                ctx.world.despawn(f);
270            }
271        }
272    }
273    for nest in delivered {
274        if let Some(n) = ctx.world.get_mut::<Nest>(nest) {
275            n.stored += 1;
276            n.delivered += 1;
277        }
278    }
279}
examples/battle.rs (lines 252-307)
218fn fight() -> impl FnMut(&mut Ctx) {
219    // Per team: a fine grid for friends (only the closest few matter) and a
220    // coarse one for enemies (seen from afar). Marching armies far from the
221    // enemy then cost almost nothing to search, however packed they are.
222    let mut near = [
223        SpatialGrid::new(PERSONAL_SPACE * 2.0),
224        SpatialGrid::new(PERSONAL_SPACE * 2.0),
225    ];
226    let mut far = [SpatialGrid::new(SIGHT), SpatialGrid::new(SIGHT)];
227    let mut positions: Vec<(Entity, Vec2, Team)> = Vec::new();
228    move |ctx: &mut Ctx| {
229        let (dt, random, look) = (ctx.dt(), ctx.random(), ctx.turns(4));
230        let crowding = ctx.sim_tick().is_multiple_of(2);
231        ctx.world
232            .query::<(Entity, &Transform2D, &Unit)>()
233            .par_collect_into(&mut positions, |(e, t, u)| (e, t.position, u.team));
234        // The four grids are independent: build them at the same time.
235        let [near_red, near_blue] = &mut near;
236        let [far_red, far_blue] = &mut far;
237        let all = &positions;
238        let of = |team: Team| all.iter().filter(move |p| p.2 == team).map(|&(e, at, _)| (e, at));
239        rayon::scope(|s| {
240            if crowding {
241                s.spawn(|_| near_red.rebuild_from(of(Team::Red)));
242                s.spawn(|_| near_blue.rebuild_from(of(Team::Blue)));
243            }
244            s.spawn(|_| far_red.rebuild_from(of(Team::Red)));
245            s.spawn(|_| far_blue.rebuild_from(of(Team::Blue)));
246        });
247        let over = ctx.world.resource::<Score>().winner.is_some();
248        let battle = ctx.world.remove_resource::<Battle>().expect("battle");
249        let (near, far, b) = (&near, &far, &battle);
250        ctx.world
251            .query::<(Entity, &mut Transform2D, &mut Unit)>()
252            .par_for_each(|(e, t, unit)| {
253                let mut rng = random.of(e);
254                let me = t.position;
255                unit.hit = None;
256                unit.cooldown -= dt;
257                if crowding {
258                    let mut push = Vec2::ZERO;
259                    near[unit.team.index()].for_each_in_radius(me, PERSONAL_SPACE, |other| {
260                        if other.entity != e {
261                            let away = me - other.position;
262                            push += away / (away.length_squared() + 0.01);
263                        }
264                    });
265                    unit.push = push.clamp_length_max(1.0);
266                }
267                let mut nearest = None;
268                if unit.engaged || look.includes(e) {
269                    let enemies = &far[unit.team.enemy().index()];
270                    nearest = enemies
271                        .nearest(me, REACH, |_| true)
272                        .or_else(|| enemies.nearest(me, SIGHT, |_| true))
273                        .map(|g| (g.position.distance_squared(me), g.entity, g.position));
274                    unit.engaged = nearest.is_some();
275                }
276                let enemy_base = b.bases[unit.team.enemy().index()];
277                let mut want = match nearest {
278                    Some((d2, target, _)) if d2 < REACH * REACH => {
279                        if unit.cooldown <= 0.0 {
280                            unit.cooldown = COOLDOWN * rng.range(0.8, 1.2);
281                            unit.hit = Some(Hit::Unit(target));
282                        }
283                        Vec2::ZERO
284                    }
285                    Some((_, _, at)) => (at - me).normalize_or_zero() * SPEED,
286                    None if me.distance(enemy_base) < BASE_RADIUS + REACH => {
287                        if unit.cooldown <= 0.0 && !over {
288                            unit.cooldown = COOLDOWN;
289                            unit.hit = Some(Hit::Base);
290                        }
291                        Vec2::ZERO
292                    }
293                    None => b.fields[unit.team.index()].direction_at(me) * SPEED,
294                };
295                let slow = b.nav.cell_at(me).and_then(|c| b.nav.cost(c.x, c.y)).unwrap_or(1).max(1) as f32;
296                want = want / slow + unit.push * SPEED;
297                unit.vel = unit.vel.lerp(want, 0.2) + rng.unit_vec2() * 2.0;
298                // Slide along walls instead of walking into them.
299                let next = me + unit.vel * dt;
300                if b.nav.walkable(next) {
301                    t.position = next;
302                } else if b.nav.walkable(vec2(next.x, me.y)) {
303                    t.position.x = next.x;
304                } else if b.nav.walkable(vec2(me.x, next.y)) {
305                    t.position.y = next.y;
306                }
307            });
308        ctx.world.insert_resource(battle);
309    }
310}
Source

pub fn par_for_each_indexed( &mut self, f: impl Fn(usize, <Q as QueryData>::Item<'_>) + Send + Sync, )

Like par_for_each, but also passes each entity’s position in the query, 0..count(), in the same order as for_each and iter.

Use it to fill a buffer from all cores: every entity writes its own slot, so the result comes out in a stable order.

use std::sync::atomic::{AtomicU32, Ordering};

struct Pos(f32);
let mut world = World::new();
world.spawn_batch((0..10_000).map(|i| (Pos(i as f32),)));

let mut query = world.query::<&Pos>();
let out: Vec<AtomicU32> = (0..query.count()).map(|_| AtomicU32::new(0)).collect();
query.par_for_each_indexed(|i, p| out[i].store(p.0 as u32 * 2, Ordering::Relaxed));
assert_eq!(out[1234].load(Ordering::Relaxed), 2468);
Source

pub fn par_collect_into<R>( &mut self, out: &mut Vec<R>, f: impl Fn(<Q as QueryData>::Item<'_>) -> R + Send + Sync, )
where R: Send,

Replaces the contents of out with f of every matching entity, computed on all cores, in the same order as for_each. out keeps its memory between calls, so collecting every tick allocates nothing once it’s big enough.

struct Pos(f32);
let mut world = World::new();
world.spawn_batch((0..10_000).map(|i| (Pos(i as f32),)));

let mut doubled = Vec::new();
world.query::<&Pos>().par_collect_into(&mut doubled, |p| p.0 * 2.0);
assert_eq!(doubled[1234], 2468.0);
Examples found in repository?
examples/battle.rs (line 233)
218fn fight() -> impl FnMut(&mut Ctx) {
219    // Per team: a fine grid for friends (only the closest few matter) and a
220    // coarse one for enemies (seen from afar). Marching armies far from the
221    // enemy then cost almost nothing to search, however packed they are.
222    let mut near = [
223        SpatialGrid::new(PERSONAL_SPACE * 2.0),
224        SpatialGrid::new(PERSONAL_SPACE * 2.0),
225    ];
226    let mut far = [SpatialGrid::new(SIGHT), SpatialGrid::new(SIGHT)];
227    let mut positions: Vec<(Entity, Vec2, Team)> = Vec::new();
228    move |ctx: &mut Ctx| {
229        let (dt, random, look) = (ctx.dt(), ctx.random(), ctx.turns(4));
230        let crowding = ctx.sim_tick().is_multiple_of(2);
231        ctx.world
232            .query::<(Entity, &Transform2D, &Unit)>()
233            .par_collect_into(&mut positions, |(e, t, u)| (e, t.position, u.team));
234        // The four grids are independent: build them at the same time.
235        let [near_red, near_blue] = &mut near;
236        let [far_red, far_blue] = &mut far;
237        let all = &positions;
238        let of = |team: Team| all.iter().filter(move |p| p.2 == team).map(|&(e, at, _)| (e, at));
239        rayon::scope(|s| {
240            if crowding {
241                s.spawn(|_| near_red.rebuild_from(of(Team::Red)));
242                s.spawn(|_| near_blue.rebuild_from(of(Team::Blue)));
243            }
244            s.spawn(|_| far_red.rebuild_from(of(Team::Red)));
245            s.spawn(|_| far_blue.rebuild_from(of(Team::Blue)));
246        });
247        let over = ctx.world.resource::<Score>().winner.is_some();
248        let battle = ctx.world.remove_resource::<Battle>().expect("battle");
249        let (near, far, b) = (&near, &far, &battle);
250        ctx.world
251            .query::<(Entity, &mut Transform2D, &mut Unit)>()
252            .par_for_each(|(e, t, unit)| {
253                let mut rng = random.of(e);
254                let me = t.position;
255                unit.hit = None;
256                unit.cooldown -= dt;
257                if crowding {
258                    let mut push = Vec2::ZERO;
259                    near[unit.team.index()].for_each_in_radius(me, PERSONAL_SPACE, |other| {
260                        if other.entity != e {
261                            let away = me - other.position;
262                            push += away / (away.length_squared() + 0.01);
263                        }
264                    });
265                    unit.push = push.clamp_length_max(1.0);
266                }
267                let mut nearest = None;
268                if unit.engaged || look.includes(e) {
269                    let enemies = &far[unit.team.enemy().index()];
270                    nearest = enemies
271                        .nearest(me, REACH, |_| true)
272                        .or_else(|| enemies.nearest(me, SIGHT, |_| true))
273                        .map(|g| (g.position.distance_squared(me), g.entity, g.position));
274                    unit.engaged = nearest.is_some();
275                }
276                let enemy_base = b.bases[unit.team.enemy().index()];
277                let mut want = match nearest {
278                    Some((d2, target, _)) if d2 < REACH * REACH => {
279                        if unit.cooldown <= 0.0 {
280                            unit.cooldown = COOLDOWN * rng.range(0.8, 1.2);
281                            unit.hit = Some(Hit::Unit(target));
282                        }
283                        Vec2::ZERO
284                    }
285                    Some((_, _, at)) => (at - me).normalize_or_zero() * SPEED,
286                    None if me.distance(enemy_base) < BASE_RADIUS + REACH => {
287                        if unit.cooldown <= 0.0 && !over {
288                            unit.cooldown = COOLDOWN;
289                            unit.hit = Some(Hit::Base);
290                        }
291                        Vec2::ZERO
292                    }
293                    None => b.fields[unit.team.index()].direction_at(me) * SPEED,
294                };
295                let slow = b.nav.cell_at(me).and_then(|c| b.nav.cost(c.x, c.y)).unwrap_or(1).max(1) as f32;
296                want = want / slow + unit.push * SPEED;
297                unit.vel = unit.vel.lerp(want, 0.2) + rng.unit_vec2() * 2.0;
298                // Slide along walls instead of walking into them.
299                let next = me + unit.vel * dt;
300                if b.nav.walkable(next) {
301                    t.position = next;
302                } else if b.nav.walkable(vec2(next.x, me.y)) {
303                    t.position.x = next.x;
304                } else if b.nav.walkable(vec2(me.x, next.y)) {
305                    t.position.y = next.y;
306                }
307            });
308        ctx.world.insert_resource(battle);
309    }
310}
Source

pub fn iter(&mut self) -> QueryIter<'_, Q> ⓘ

An iterator over matching entities, for map/filter/sum and friends.

Convenient, but several times slower than for_each on tight math loops (the compiler can’t vectorize across archetypes). Use for_each for per-tick simulation work.

Examples found in repository?
examples/hotpatch.rs (line 18)
16fn spin(ctx: &mut Ctx) {
17    let dt = ctx.dt();
18    for t in ctx.world.query::<&mut Transform2D>().iter() {
19        t.rotation += -6.0 * dt;
20    }
21}
22// ANCHOR_END: spin
23
24/// Crabs breathe. Try a bigger amplitude, or a different shape.
25fn wobble(ctx: &mut Ctx) {
26    let time = ctx.sim_time() as f32;
27    for (t, crab) in ctx.world.query::<(&mut Transform2D, &Crab)>().iter() {
28        let s = 1.0 + 0.15 * (time * 3.0 + crab.0).sin();
29        t.scale = vec2(s, s);
30    }
31}
32
33/// Prints the first crab's rotation once a second, so you can see that
34/// state carries over a patch.
35fn report(ctx: &mut Ctx) {
36    let tick = ctx.sim_tick();
37    if tick.is_multiple_of(60)
38        && let Some(t) = ctx.world.query::<&Transform2D>().iter().next()
39    {
40        println!("tick {tick:>5}  rotation {:>7.2}", t.rotation);
41    }
42}
More examples
Hide additional examples
examples/physics.rs (line 65)
59fn ring_the_bell(ctx: &mut Ctx) {
60    let mut rang = false;
61    for c in ctx.events.read::<CollisionStarted>() {
62        rang |= c.sensor && [c.a, c.b].iter().any(|&e| ctx.world.has::<Bell>(e));
63    }
64    let dt = ctx.dt();
65    for bell in ctx.world.query::<&mut Bell>().iter() {
66        bell.rang = if rang { 1.0 } else { (bell.rang - dt * 2.0).max(0.0) };
67    }
68}
69// ANCHOR_END: collisions
70
71// ANCHOR: queries
72fn blast(ctx: &mut Ctx) {
73    if !ctx.key_pressed(Key::Space) {
74        return;
75    }
76    let center = ctx.mouse_world();
77    let mut near = Vec::new();
78    ctx.physics.entities_in_circle(center, 200.0, &mut near);
79    for e in near {
80        let Some(t) = ctx.world.get::<Transform2D>(e) else {
81            continue;
82        };
83        let away = (t.position - center).normalize_or(Vec2::NEG_Y);
84        if let Some(mass) = ctx.physics.mass(e) {
85            ctx.physics.apply_impulse(e, away * mass * 600.0);
86        }
87    }
88}
89
90/// What's under the mouse, straight down: a laser from the cursor.
91fn laser(ctx: &mut Ctx) -> Option<RayHit> {
92    ctx.physics.cast_ray(ctx.mouse_world(), vec2(0.0, 1.0), H)
93}
94// ANCHOR_END: queries
95
96fn drop_things(ctx: &mut Ctx) {
97    let at = ctx.mouse_world();
98    if ctx.mouse_pressed(MouseButton::Left) {
99        ctx.world.spawn((
100            Transform2D::at(at),
101            RigidBody::dynamic(),
102            Collider::rect(vec2(30.0, 30.0)).friction(0.8),
103        ));
104    }
105    if ctx.mouse_pressed(MouseButton::Right) {
106        ctx.world.spawn((
107            Transform2D::at(at),
108            RigidBody::dynamic(),
109            Collider::circle(12.0).restitution(0.6),
110        ));
111    }
112    if ctx.mouse_pressed(MouseButton::Middle) {
113        let rock: Vec<Vec2> = (0..5)
114            .map(|i| Vec2::from_angle(i as f32 * std::f32::consts::TAU / 5.0) * 18.0)
115            .collect();
116        ctx.world.spawn((
117            Transform2D::at(at),
118            RigidBody::dynamic(),
119            Collider::polygon(&rock).friction(0.7),
120        ));
121    }
122    if ctx.key_pressed(Key::R) {
123        let all: Vec<Entity> = ctx.world.query::<Entity>().with::<RigidBody>().iter().collect();
124        for e in all {
125            ctx.world.despawn(e);
126        }
127        build_level(ctx);
128    }
129    if ctx.key_pressed(Key::Escape) {
130        ctx.quit();
131    }
132}
133
134fn draw(ctx: &mut Ctx) {
135    ctx.clear(Color::hex(0x1d2330));
136    let shapes: Vec<(Transform2D, Collider, bool, f32)> = ctx
137        .world
138        .query::<(&Transform2D, &Collider, &RigidBody, Option<&Bell>)>()
139        .iter()
140        .map(|(t, c, b, bell)| (*t, *c, b.kind == BodyKind::Fixed, bell.map_or(0.0, |b| b.rang)))
141        .collect();
142    for (t, c, fixed, rang) in shapes {
143        let color = if c.sensor {
144            Color::rgba(1.0, 0.3 + 0.7 * (1.0 - rang), 0.3, 0.35)
145        } else if fixed {
146            Color::hex(0x4a5568)
147        } else {
148            Color::hex(0xf6ad55)
149        };
150        match c.shape {
151            Shape::Circle { radius } => {
152                ctx.draw_circle(t.position, radius, color);
153                // A spoke shows the spin.
154                let spoke = t.position + Vec2::from_angle(t.rotation) * radius;
155                ctx.draw_line(t.position, spoke, 2.0, Color::hex(0x1d2330));
156            }
157            Shape::Rect { size } => {
158                // Four corners turned with the body, drawn as edges.
159                let (sin, cos) = t.rotation.sin_cos();
160                let turn = |v: Vec2| t.position + vec2(v.x * cos - v.y * sin, v.x * sin + v.y * cos);
161                let h = size / 2.0;
162                let corners = [vec2(-h.x, -h.y), vec2(h.x, -h.y), vec2(h.x, h.y), vec2(-h.x, h.y)].map(turn);
163                if fixed || c.sensor {
164                    ctx.draw_rect(Rect::new(t.position.x - h.x, t.position.y - h.y, size.x, size.y), color);
165                } else {
166                    for i in 0..4 {
167                        ctx.draw_line(corners[i], corners[(i + 1) % 4], 3.0, color);
168                    }
169                }
170            }
171            Shape::Capsule { height, radius } => {
172                let up = Vec2::from_angle(t.rotation + std::f32::consts::FRAC_PI_2) * height / 2.0;
173                ctx.draw_line(t.position - up, t.position + up, radius * 2.0, color);
174            }
175            Shape::Polygon { .. } => {
176                let (sin, cos) = t.rotation.sin_cos();
177                let turn = |v: Vec2| t.position + vec2(v.x * cos - v.y * sin, v.x * sin + v.y * cos);
178                let corners: Vec<Vec2> = c.shape.points().iter().map(|&p| turn(p + c.offset)).collect();
179                for i in 0..corners.len() {
180                    ctx.draw_line(corners[i], corners[(i + 1) % corners.len()], 3.0, color);
181                }
182            }
183            _ => {} // shapes added in later versions
184        }
185    }
186    if let Some(hit) = laser(ctx) {
187        ctx.draw_line(ctx.mouse_world(), hit.point, 1.0, Color::rgba(1.0, 0.2, 0.2, 0.8));
188        ctx.draw_circle(hit.point, 4.0, Color::rgb(1.0, 0.2, 0.2));
189    }
190    let bodies = ctx.physics.body_count();
191    ctx.draw_text(
192        &format!(
193            "{bodies} bodies  |  left click: crate  right click: ball  middle click: rock  space: blast  R: reset"
194        ),
195        vec2(30.0, 30.0),
196        18.0,
197        Color::WHITE,
198    );
199}
examples/gamepad.rs (line 42)
31fn move_players(ctx: &mut Ctx) {
32    let dt = ctx.dt();
33    // Read the pads first: the query below borrows the world.
34    let mut sticks = [Vec2::ZERO; MAX_GAMEPADS];
35    let mut spins = [false; MAX_GAMEPADS];
36    let mut grow = [0.0; MAX_GAMEPADS];
37    for &pad in ctx.gamepads() {
38        sticks[pad.slot()] = ctx.gamepad_stick(pad, GamepadStick::Left);
39        spins[pad.slot()] = ctx.gamepad_pressed(pad, GamepadButton::South);
40        grow[pad.slot()] = ctx.gamepad_axis(pad, GamepadAxis::RightTrigger);
41    }
42    for (t, player) in ctx.world.query::<(&mut Transform2D, &mut Player)>().iter() {
43        let Some(slot) = player.slot else { continue };
44        t.position += sticks[slot] * SPEED * dt;
45        if spins[slot] {
46            player.spin = std::f32::consts::TAU;
47        }
48        t.scale = Vec2::splat(1.0 + 2.0 * grow[slot]);
49    }
50}
51// ANCHOR_END: gamepad
52
53/// Arrow keys move the keyboard's crab.
54fn move_keyboard_player(ctx: &mut Ctx) {
55    let dt = ctx.dt();
56    let mut dir = Vec2::ZERO;
57    for (key, d) in [
58        (Key::Left, -Vec2::X),
59        (Key::Right, Vec2::X),
60        (Key::Up, -Vec2::Y),
61        (Key::Down, Vec2::Y),
62    ] {
63        if ctx.key_down(key) {
64            dir += d;
65        }
66    }
67    let spin = ctx.key_pressed(Key::Space);
68    for (t, player) in ctx.world.query::<(&mut Transform2D, &mut Player)>().iter() {
69        if player.slot.is_none() {
70            t.position += dir.normalize_or_zero() * SPEED * dt;
71            if spin {
72                player.spin = std::f32::consts::TAU;
73            }
74        }
75    }
76}
77
78/// One crab per connected pad: spawn on plug-in, despawn on unplug.
79fn join_and_leave(ctx: &mut Ctx) {
80    let mut connected = [false; MAX_GAMEPADS];
81    for pad in ctx.gamepads() {
82        connected[pad.slot()] = true;
83    }
84    let mut has_crab = [false; MAX_GAMEPADS];
85    for (e, player) in ctx.world.query::<(Entity, &Player)>().iter() {
86        if let Some(slot) = player.slot {
87            if connected[slot] {
88                has_crab[slot] = true;
89            } else {
90                ctx.commands.despawn(e);
91            }
92        }
93    }
94    for slot in (0..MAX_GAMEPADS).filter(|&s| connected[s] && !has_crab[s]) {
95        let mut sprite = Sprite::new(ctx.load_texture("crab.png"));
96        sprite.color = PLAYER_COLORS[slot];
97        let at = vec2(200.0 + slot as f32 * 120.0, 360.0);
98        ctx.world.spawn((
99            Transform2D::at(at),
100            sprite,
101            Player {
102                slot: Some(slot),
103                spin: 0.0,
104            },
105        ));
106    }
107}
108
109/// Spins play out over half a second.
110fn spin(ctx: &mut Ctx) {
111    let dt = ctx.dt();
112    for (t, player) in ctx.world.query::<(&mut Transform2D, &mut Player)>().iter() {
113        let step = player.spin.min(4.0 * std::f32::consts::TAU * dt);
114        t.rotation += step;
115        player.spin -= step;
116    }
117}
examples/ffi_bench.rs (line 35)
16fn main() {
17    let mut world = World::new();
18    world.spawn_batch((0..1_000_000).map(|_| (Pos { x: 0.0, y: 0.0 }, Vel { x: 1.0, y: 2.0 })));
19    let mut samples = Vec::with_capacity(200);
20    for i in 0..210 {
21        let start = Instant::now();
22        world.query::<(&mut Pos, &Vel)>().for_each(|(p, v)| {
23            p.x += v.x;
24            p.y += v.y;
25        });
26        black_box(&world);
27        let elapsed = start.elapsed().as_secs_f64() * 1000.0;
28        if i >= 10 {
29            samples.push(elapsed);
30        }
31    }
32    samples.sort_by(f64::total_cmp);
33    let checksum: f64 = world
34        .query::<&Pos>()
35        .iter()
36        .map(|p| f64::from(p.x) + f64::from(p.y))
37        .sum();
38    assert_eq!(checksum, 630_000_000.0);
39    println!(
40        "Rust: 1000000 entities, median {:.6} ms/update, checksum {checksum:.0}",
41        (samples[99] + samples[100]) / 2.0
42    );
43}
examples/navmesh3d.rs (line 34)
12fn main() {
13    // ANCHOR: query
14    let nav = navigation();
15    let start = nav.sample_position(vec3(-8.0, 1.0, 0.0), 2.0).unwrap();
16    let destination = vec3(8.0, 0.0, 0.0);
17    let corners = nav.find_path(start, destination).unwrap();
18    assert!(corners.len() >= 3);
19    // ANCHOR_END: query
20
21    // ANCHOR: agents
22    let mut world = Game::new("Navigation server", 1, 1)
23        .fixed_hz(60)
24        .sim_system(step_navmesh3d)
25        .run_headless(600, |ctx| {
26            ctx.world.insert_resource(nav);
27            let mut agent = NavAgent3D::default();
28            agent.destination = Some(destination);
29            ctx.world.spawn((Transform3D::at(start), agent));
30            // World-space AABB; move its center or despawn it to reopen polygons.
31            ctx.world
32                .spawn((NavObstacle3D::new(vec3(0.0, 1.0, 8.0), vec3(1.0, 1.0, 1.0)),));
33        });
34    let position = world.query::<&Transform3D>().iter().next().unwrap().position;
35    assert!(position.distance(destination) < 0.3);
36    // ANCHOR_END: agents
37}
examples/lockstep.rs (line 62)
54fn move_crabs(ctx: &mut Ctx) {
55    let pads: Vec<Pad> = (0..ctx.net.players())
56        .map(|p| ctx.net.input::<Pad>(p).unwrap_or_default())
57        .collect();
58    let dt = ctx.dt();
59    let crabs: Vec<(Entity, Vec2, u32)> = ctx
60        .world
61        .query::<(Entity, &Transform2D, &Crab)>()
62        .iter()
63        .map(|(e, t, c)| (e, t.position, c.player))
64        .collect();
65    for &(e, at, player) in &crabs {
66        let pad = &pads[player as usize];
67        if pad.shove {
68            for &(other, other_at, _) in &crabs {
69                if other != e && at.distance(other_at) < 120.0 {
70                    ctx.world.get_mut::<Crab>(other).unwrap().push += (other_at - at).normalize_or_zero() * 900.0;
71                }
72            }
73        }
74        let crab = *ctx.world.get::<Crab>(e).unwrap();
75        let t = ctx.world.get_mut::<Transform2D>(e).unwrap();
76        t.position += (pad.dir.normalize_or_zero() * 250.0 + crab.push) * dt;
77        t.position = t.position.clamp(vec2(40.0, 40.0), vec2(760.0, 560.0));
78        ctx.world.get_mut::<Crab>(e).unwrap().push = crab.push * 0.9;
79    }
80}
81// ANCHOR_END: simulate
82
83fn draw(ctx: &mut Ctx) {
84    ctx.clear(Color::hex(0x20304a));
85    let crabs: Vec<(Vec2, u32)> = ctx
86        .world
87        .query::<(&Transform2D, &Crab)>()
88        .iter()
89        .map(|(t, c)| (t.position, c.player))
90        .collect();
91    for (at, player) in crabs {
92        let color = [Color::hex(0xf56565), Color::hex(0x63b3ed)][player as usize % 2];
93        ctx.draw_circle(at, 30.0, color);
94    }
95    let net = &ctx.net;
96    let status = match net.state() {
97        NetState::Connecting => "waiting for the other player...".to_string(),
98        NetState::Running => format!(
99            "you are player {} ({}), tick {}{}",
100            net.player() + 1,
101            ["red", "blue"][net.player() % 2],
102            ctx.sim_tick(),
103            net.desync().map_or(String::new(), |d| format!("  DESYNC: {d}"))
104        ),
105        NetState::Lost => format!("connection lost: {}", net.error().unwrap_or("?")),
106        NetState::Offline => "offline".into(),
107        _ => String::new(),
108    };
109    ctx.draw_text(&status, vec2(20.0, 20.0), 20.0, Color::WHITE);
110    #[cfg(feature = "steam")]
111    if ctx.net.state() == NetState::Connecting
112        && let Some(lobby) = ctx.net.steam_lobby()
113    {
114        let text = format!("Steam lobby {}: press I to invite a friend", lobby.raw());
115        ctx.draw_text(&text, vec2(20.0, 50.0), 20.0, Color::WHITE);
116        if ctx.key_pressed(Key::I) {
117            ctx.net.steam_invite();
118        }
119    }
120    if ctx.key_pressed(Key::Escape) {
121        ctx.quit();
122    }
123}
Source

pub fn count(&mut self) -> usize

Number of matching entities (doesn’t visit them one by one).

Examples found in repository?
examples/flow.rs (line 229)
225fn hud(ctx: &mut Ctx) {
226    let panel = Rect::new(12.0, 12.0, 260.0, 104.0);
227    ctx.ui_panel(panel);
228    let at = panel.min + ctx.ui_style.padding;
229    let agents = ctx.world.query::<&Agent>().count();
230    let ms = ctx.world.resource::<Paths>().compute_ms;
231    let text = format!(
232        "{agents} agents, {:.0} FPS\nfield: {}x{} cells, {ms:.2} ms\nclick: new goal   H: distances",
233        ctx.fps(),
234        MAP.x,
235        MAP.y
236    );
237    ctx.ui_label(at, &text);
238}
More examples
Hide additional examples
examples/ants.rs (line 305)
283fn grow(bounds: Vec2) -> impl FnMut(&mut Ctx) {
284    move |ctx: &mut Ctx| {
285        let mut rng = ctx.random().stream(0);
286        let sprite = ctx.world.resource::<AntLook>().0;
287        let mut counts = std::collections::HashMap::new();
288        ctx.world
289            .query::<&Ant>()
290            .for_each(|a| *counts.entry(a.home).or_insert(0u32) += 1);
291        let mut nests = Vec::new();
292        ctx.world
293            .query::<(Entity, &Transform2D, &mut Nest)>()
294            .for_each(|(e, t, n)| {
295                n.ants = counts.get(&e).copied().unwrap_or(0);
296                let born = n.stored.min(n.max_ants.saturating_sub(n.ants));
297                n.stored -= born;
298                nests.push((e, t.position, born));
299            });
300        for &(home, at, born) in &nests {
301            for _ in 0..born {
302                spawn_ant(&mut ctx.commands, sprite, home, at, rng.angle());
303            }
304        }
305        let piles = ctx.world.query::<&Food>().count();
306        let homes: Vec<Vec2> = nests.iter().map(|n| n.1).collect();
307        for _ in piles..food_piles(bounds) {
308            spawn_food(&mut ctx.commands, &mut rng, bounds, &homes);
309        }
310    }
311}
312
313// -------------------------------------------------------------------- setup
314
315fn food_piles(bounds: Vec2) -> usize {
316    ((bounds.x * bounds.y) / 60_000.0).ceil() as usize
317}
318
319fn spawn_ant(cmds: &mut Commands, sprite: Option<Texture>, home: Entity, at: Vec2, heading: f32) {
320    let ant = Ant {
321        home,
322        home_at: at,
323        heading,
324        carrying: false,
325        since: 0.0,
326        energy: LIFESPAN,
327        event: Event::None,
328    };
329    let t = Transform2D::at(at).rotated(heading);
330    match sprite {
331        Some(tex) => cmds.spawn((t, ant, Sprite::new(tex).with_color(Color::hex(0xD4553A)))),
332        None => cmds.spawn((t, ant)),
333    }
334}
335
336fn spawn_food(cmds: &mut Commands, rng: &mut Rng, bounds: Vec2, nests: &[Vec2]) {
337    // Not too close to any nest, so trails have to form.
338    let p = loop {
339        let p = vec2(rng.range(20.0, bounds.x - 20.0), rng.range(20.0, bounds.y - 20.0));
340        if nests.iter().all(|n| p.distance(*n) > 180.0) {
341            break p;
342        }
343    };
344    cmds.spawn((Transform2D::at(p), Food { amount: 400 }));
345}
346
347/// The simulation, shared by the windowed and headless runs.
348fn colonies(game: Game, bounds: Vec2) -> Game {
349    game.sim_system(steer(bounds))
350        .sim_system(lay_trails)
351        .sim_system(forage)
352        .sim_system_every(4, evaporate)
353        .sim_system_every(30, grow(bounds))
354}
355
356/// Nests on a grid, one per `NEST_AREA`; ants and food around them.
357fn setup(ctx: &mut Ctx, bounds: Vec2, ants: usize, sprite: Option<Texture>) {
358    ctx.world.reflect::<Ant>();
359    ctx.world.reflect::<Nest>();
360    ctx.world.reflect::<Food>();
361    ctx.world.insert_resource(Field::new(bounds));
362    ctx.world.insert_resource(AntLook(sprite));
363    let mut rng = ctx.random().stream(1);
364    let (cols, rows) = ((bounds.x / NEST_AREA.x) as usize, (bounds.y / NEST_AREA.y) as usize);
365    let (cols, rows) = (cols.max(1), rows.max(1));
366    let per_nest = (ants / (cols * rows)).max(1);
367    let mut homes = Vec::new();
368    for row in 0..rows {
369        for col in 0..cols {
370            let at = (vec2(col as f32, row as f32) + 0.5) * bounds / vec2(cols as f32, rows as f32);
371            let nest = Nest {
372                stored: 0,
373                delivered: 0,
374                max_ants: per_nest as u32 * 2,
375                ants: per_nest as u32,
376            };
377            let home = ctx.world.spawn((Transform2D::at(at), nest));
378            homes.push(at);
379            for _ in 0..per_nest {
380                spawn_ant(&mut ctx.commands, sprite, home, at, rng.angle());
381            }
382        }
383    }
384    for _ in 0..food_piles(bounds) {
385        spawn_food(&mut ctx.commands, &mut rng, bounds, &homes);
386    }
387}
388
389fn delivered(world: &mut World) -> u64 {
390    world.query::<&Nest>().iter().map(|n| n.delivered).sum()
391}
392
393// ------------------------------------------------------------------ drawing
394
395fn controls(ctx: &mut Ctx) {
396    if ctx.key_pressed(Key::Space) {
397        if ctx.is_paused() { ctx.resume() } else { ctx.pause() }
398    }
399    if ctx.key_pressed(Key::Period) {
400        ctx.step();
401    }
402    for (key, speed) in [(Key::Num1, 1.0), (Key::Num2, 4.0), (Key::Num3, 16.0), (Key::Num4, 64.0)] {
403        if ctx.key_pressed(key) {
404            ctx.set_sim_speed(speed);
405        }
406    }
407    if ctx.key_pressed(Key::Escape) {
408        ctx.quit();
409    }
410}
411
412/// Colony stats and sim controls, in a panel on top of the world.
413fn hud(ctx: &mut Ctx) {
414    let ants = ctx.world.query::<&Ant>().count();
415    let nests = ctx.world.query::<&Nest>().count();
416    let food = delivered(&mut ctx.world);
417    let panel = Rect::new(12.0, 12.0, 260.0, 156.0);
418    ctx.ui_panel(panel);
419    let mut at = panel.min + ctx.ui_style.padding;
420    let speed = if ctx.is_paused() {
421        "paused".to_string()
422    } else {
423        format!("{}x", ctx.sim_speed())
424    };
425    let stats = format!(
426        "{ants} ants in {nests} colonies\n{food} food delivered\nspeed {speed}, {:.0} FPS",
427        ctx.fps()
428    );
429    at.y += ctx.ui_label(at, &stats).y + 8.0;
430    let pause = if ctx.is_paused() { "Run" } else { "Pause" };
431    if ctx.ui_button(Rect::new(at.x, at.y, 72.0, 30.0), pause) {
432        if ctx.is_paused() { ctx.resume() } else { ctx.pause() }
433    }
434    for (i, speed) in [1.0f32, 4.0, 16.0].into_iter().enumerate() {
435        let r = Rect::new(at.x + 80.0 + i as f32 * 54.0, at.y, 48.0, 30.0);
436        if ctx.ui_button(r, &format!("{speed}x")) {
437            ctx.set_sim_speed(speed);
438            ctx.resume();
439        }
440    }
441}
442
443/// Draws the pheromone field (one texture, updated every frame), the food
444/// piles and the nests. Ants are sprites, drawn by the engine.
445fn draw_world(bounds: Vec2) -> impl FnMut(&mut Ctx) {
446    let mut pixels = Vec::new();
447    let mut dots: Vec<(Vec2, f32, Color)> = Vec::new();
448    let mut field_texture = None;
449    move |ctx: &mut Ctx| {
450        let (w, h) = {
451            let f = ctx.world.resource::<Field>();
452            (f.w, f.h)
453        };
454        let field_tex =
455            *field_texture.get_or_insert_with(|| ctx.create_texture(w as u32, h as u32, &vec![0; w * h * 4]));
456        let field = ctx.world.resource::<Field>();
457        pixels.clear();
458        pixels.extend(field.to_food.iter().zip(&field.to_home).flat_map(|(&food, &home)| {
459            let g = (food.sqrt() * 200.0) as u8;
460            let b = (home.sqrt() * 120.0) as u8;
461            [g / 3, g, b.max(g / 4), 255]
462        }));
463        ctx.update_texture(&field_tex, &pixels);
464        ctx.clear(Color::BLACK);
465        let scale = bounds / vec2(w as f32, h as f32);
466        ctx.draw_ex(
467            &field_tex,
468            bounds * 0.5,
469            DrawParams {
470                scale,
471                z: -10.0,
472                ..Default::default()
473            },
474        );
475
476        dots.clear();
477        ctx.world.query::<(&Transform2D, &Food)>().for_each(|(t, f)| {
478            dots.push((t.position, 2.0 + (f.amount as f32).sqrt() / 2.0, Color::hex(0x7BD389)));
479        });
480        ctx.world.query::<(&Transform2D, &Nest)>().for_each(|(t, _)| {
481            dots.push((t.position, NEST_RADIUS, Color::hex(0x8E5A3C)));
482        });
483        for &(p, radius, color) in &dots {
484            ctx.draw_circle(p, radius, color);
485        }
486    }
487}
488
489fn main() {
490    let ants = env_number("ANTS", ANTS_PER_NEST as f64) as usize;
491    let seed = env_number("SEED", DEFAULT_SEED as f64) as u64;
492    // One colony's worth of space per ANTS_PER_NEST ants.
493    let nests = (ants as f32 / ANTS_PER_NEST as f32).max(1.0);
494    let side = nests.sqrt().ceil();
495    let bounds = NEST_AREA * vec2(side, (nests / side).ceil());
496
497    if env_number("HEADLESS", 0.0) != 0.0 {
498        let ticks = env_number("TICKS", 600.0) as u64;
499        let mut start = Instant::now();
500        let mut world = colonies(Game::new("ants", 1, 1).seed(seed), bounds).run_headless(ticks, |ctx| {
501            setup(ctx, bounds, ants, None);
502            start = Instant::now();
503        });
504        let secs = start.elapsed().as_secs_f64();
505        println!(
506            "{ticks} ticks: {:.2} ms/tick ({:.0} ticks/s). {} ants, {} food delivered",
507            secs * 1e3 / ticks as f64,
508            ticks as f64 / secs,
509            world.query::<&Ant>().count(),
510            delivered(&mut world)
511        );
512        return;
513    }
514
515    colonies(Game::new("Crabby Engine: ants", 1280, 720).seed(seed), bounds)
516        .system(controls)
517        .system(camera_controls)
518        .system(draw_world(bounds))
519        .system(hud)
520        .run(move |ctx| {
521            let sprite = ctx.create_texture(3, 2, &[255; 3 * 2 * 4]);
522            setup(ctx, bounds, ants, Some(sprite));
523            // Start with the whole world in view.
524            let fit = ctx.window_size() / bounds;
525            ctx.camera.zoom = fit.x.min(fit.y);
526            ctx.camera.look_at(bounds / 2.0);
527            let mut next = 1.0;
528            move |ctx: &mut Ctx| {
529                if ctx.time() >= next {
530                    next += 1.0;
531                    let sim_ms: f32 = ctx.system_timings().iter().filter(|t| t.sim).map(|t| t.avg_ms).sum();
532                    println!(
533                        "ants: {}  delivered: {}  fps: {:.0}  sim: {sim_ms:.2} ms/frame  tick: {}",
534                        ctx.world.query::<&Ant>().count(),
535                        delivered(&mut ctx.world),
536                        ctx.fps(),
537                        ctx.sim_tick()
538                    );
539                }
540            }
541        });
542}
Source

pub fn get(&mut self, e: Entity) -> Option<<Q as QueryData>::Item<'_>>

The query’s item for one entity, or None if it’s dead or doesn’t match.

Auto Trait Implementations§

§

impl<'w, Q> !RefUnwindSafe for Query<'w, Q>

§

impl<'w, Q> !UnwindSafe for Query<'w, Q>

§

impl<'w, Q> Forget for Query<'w, Q>

§

impl<'w, Q> Freeze for Query<'w, Q>
where <Q as QueryData>::Ids: Freeze,

§

impl<'w, Q> Send for Query<'w, Q>
where <Q as QueryData>::Ids: Send,

§

impl<'w, Q> Sync for Query<'w, Q>
where <Q as QueryData>::Ids: Sync,

§

impl<'w, Q> Unpin for Query<'w, Q>
where <Q as QueryData>::Ids: Unpin,

§

impl<'w, Q> UnsafeUnpin for Query<'w, Q>
where <Q as QueryData>::Ids: UnsafeUnpin,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> Component for T
where T: Send + Sync + 'static,

Source§

impl<T> Downcast for T
where T: Any,

Source§

fn into_any(self: Box<T>) -> Box<dyn Any>

Convert Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.
Source§

fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>

Convert Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be further downcast into Rc<ConcreteType> where ConcreteType implements Trait.
Source§

fn as_any(&self) -> &(dyn Any + 'static)

Convert &Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &Any’s vtable from &Trait’s.
Source§

fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)

Convert &mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &mut Any’s vtable from &mut Trait’s.
Source§

impl<T> Downcast for T
where T: Any,

Source§

fn into_any(self: Box<T>) -> Box<dyn Any>

Converts Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>, which can then be downcast into Box<dyn ConcreteType> where ConcreteType implements Trait.
Source§

fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>

Converts Rc<Trait> (where Trait: Downcast) to Rc<Any>, which can then be further downcast into Rc<ConcreteType> where ConcreteType implements Trait.
Source§

fn as_any(&self) -> &(dyn Any + 'static)

Converts &Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &Any’s vtable from &Trait’s.
Source§

fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)

Converts &mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &mut Any’s vtable from &mut Trait’s.
Source§

impl<T> Downcast<T> for T

Source§

fn downcast(&self) -> &T

Source§

impl<T> DowncastSend for T
where T: Any + Send,

Source§

fn into_any_send(self: Box<T>) -> Box<dyn Any + Send>

Converts Box<Trait> (where Trait: DowncastSend) to Box<dyn Any + Send>, which can then be downcast into Box<ConcreteType> where ConcreteType implements Trait.
Source§

impl<T> DowncastSync for T
where T: Any + Send + Sync,

Source§

fn into_any_arc(self: Arc<T>) -> Arc<dyn Any + Send + Sync> ⓘ

Convert Arc<Trait> (where Trait: Downcast) to Arc<Any>. Arc<Any> can then be further downcast into Arc<ConcreteType> where ConcreteType implements Trait.
Source§

impl<T> DowncastSync for T
where T: Any + Send + Sync,

Source§

fn into_any_sync(self: Box<T>) -> Box<dyn Any + Send + Sync>

Converts Box<Trait> (where Trait: DowncastSync) to Box<dyn Any + Send + Sync>, which can then be downcast into Box<ConcreteType> where ConcreteType implements Trait.
Source§

fn into_any_arc(self: Arc<T>) -> Arc<dyn Any + Send + Sync> ⓘ

Converts Arc<Trait> (where Trait: DowncastSync) to Arc<Any>, which can then be downcast into Arc<ConcreteType> where ConcreteType implements Trait.
Source§

impl<S, T> Duplex<S> for T
where T: FromSample<S> + ToSample<S>,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<S> FromSample<S> for S

Source§

fn from_sample_(s: S) -> S

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<F, T> IntoSample<T> for F
where T: FromSample<F>,

Source§

fn into_sample(self) -> T

Source§

impl<T> MaybeSync for T
where T: Sync + ?Sized,

Source§

impl<T> MaybeSync for T
where T: Sync + ?Sized,

Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, S> SimdFrom<T, S> for T
where S: Simd,

Source§

fn simd_from(_simd: S, value: T) -> T

Source§

impl<F, T, S> SimdInto<T, S> for F
where T: SimdFrom<F, S>, S: Simd,

Source§

fn simd_into(self, simd: S) -> T

Source§

impl<SS, SP> SupersetOf<SS> for SP
where SS: SubsetOf<SP>,

Source§

fn to_subset(&self) -> Option<SS>

The inverse inclusion map: attempts to construct self from the equivalent element of its superset. Read more
Source§

fn is_in_subset(&self) -> bool

Checks if self is actually part of its subset T (and can be converted to it).
Source§

fn to_subset_unchecked(&self) -> SS

Use with care! Same as self.to_subset but without any property checks. Always succeeds.
Source§

fn from_subset(element: &SS) -> SP

The inclusion map: converts self to the equivalent element of its superset.
Source§

impl<T, U> ToSample<U> for T
where U: FromSample<T>,

Source§

fn to_sample_(self) -> U

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> Upcast<T> for T

Source§

fn upcast(&self) -> Option<&T>

Source§

impl<T> WasmNotSend for T
where T: Send,

Source§

impl<T> WasmNotSendSync for T

Source§

impl<T> WasmNotSync for T
where T: Sync,

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more