Skip to main content

headless/
headless.rs

1//! Headless simulation: 1M wandering agents, no window, no GPU.
2//! Prints how many simulation ticks per second the machine sustains.
3//!
4//!   `cargo run --release --example headless`
5//!   `AGENTS=10000000 TICKS=300 cargo run --release --example headless`
6
7use ponko::*;
8
9struct Agent {
10    heading: f32,
11    speed: f32,
12}
13
14const WORLD: f32 = 4096.0;
15
16/// Wander steering on all cores: the per-agent work of a typical sim tick.
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}
29
30fn env_number(name: &str, default: f64) -> f64 {
31    std::env::var(name).ok().and_then(|v| v.parse().ok()).unwrap_or(default)
32}
33
34fn main() {
35    let agents = env_number("AGENTS", 1_000_000.0) as usize;
36    let ticks = env_number("TICKS", 600.0) as u64;
37
38    let spawn_start = Instant::now();
39    let mut setup_done = Instant::now();
40    let world = Game::new("headless", 1280, 720)
41        .sim_system(wander)
42        .run_headless(ticks, |ctx| {
43            let tex = ctx.load_texture("crab.png");
44            ctx.world.spawn_batch((0..agents).map(|i| {
45                let pos = vec2((i % 4096) as f32, (i / 4096 % 4096) as f32);
46                (
47                    Transform2D::at(pos),
48                    Sprite::new(tex),
49                    Agent {
50                        heading: i as f32,
51                        speed: 30.0,
52                    },
53                )
54            }));
55            println!(
56                "spawned {agents} agents in {:.0} ms",
57                spawn_start.elapsed().as_secs_f64() * 1e3
58            );
59            setup_done = Instant::now();
60        });
61    let secs = setup_done.elapsed().as_secs_f64();
62    println!(
63        "{ticks} ticks in {:.2} s: {:.0} ticks/s, {:.2} ms/tick ({} entities, {:.0} bytes/entity allocated)",
64        secs,
65        ticks as f64 / secs,
66        secs * 1e3 / ticks as f64,
67        world.len(),
68        world.allocated_bytes() as f64 / world.len().max(1) as f64
69    );
70}