Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Headless runs and testing

Game::run_headless(ticks, setup) (C++: crabby::game::run_headless) runs the simulation with no window and no GPU, as fast as the CPU allows, then hands you the World.

Use it for:

  • tests of game logic in plain cargo test, including on CI machines without a GPU;
  • dedicated servers and batch runs;
  • benchmarks of the simulation alone.
#![allow(unused)]
fn main() {
use ponko::*;

struct Pos(f32);

fn fall(ctx: &mut Ctx) {
    let dt = ctx.dt();
    ctx.world.query::<&mut Pos>().for_each(|p| p.0 -= 10.0 * dt);
}

#[test]
fn things_fall_ten_units_per_second() {
    let mut world = Game::new("test", 800, 600)
        .sim_system(fall)
        .run_headless(60, |ctx| {
            ctx.world.spawn((Pos(100.0),));
        });
    let y = world.query::<&Pos>().iter().next().unwrap().0;
    assert!((y - 90.0).abs() < 1e-3);
}
}
struct Pos { float y; };

static void things_fall_ten_units_per_second() {
    crabby::entity thing = 0;
    auto world = crabby::game("test", 800, 600)
        .sim_system([](crabby::ctx& ctx) {
            const float dt = ctx.dt();
            ctx.world().each([dt](Pos& p) { p.y -= 10 * dt; });
        }, "fall")
        .run_headless(60, [&](crabby::ctx& ctx) { thing = ctx.world().spawn(Pos{100}); });
    const float y = world.get<Pos>(thing).y;
    if (std::fabs(y - 90) > 1e-3f) { std::fprintf(stderr, "fell to %f\n", double(y)); std::abort(); }
}

What runs

  • setup runs once. If it returns a frame closure, that closure is dropped.
  • Each tick runs every sim system once. Frame systems and drawing don’t run, because there are no frames.
  • Pause and sim speed are ignored; ctx.quit() stops early.
  • Textures load for their sizes but nothing is uploaded.

The same game can run windowed or headless: the boids example does both (HEADLESS=1 cargo run --release --example boids), and examples/headless.rs simulates a million agents this way.

Builds without a window

A simulation server or a CI job that only runs headless doesn’t need winit and wgpu. Turn the window feature off and keep the rest:

# Cargo.toml
[dependencies]
ponko = { version = "0.0.1", default-features = false, features = ["audio"] }
# Build the C library without a window
cargo build --release -p ponko_ffi \
  --no-default-features --features audio

That build has 82 crates instead of 142, builds from clean in 7 s instead of 15 s (release, M-series Mac) and leaves half the build output. Everything in this chapter works the same. Drawing calls do nothing, and Game::run (C: crabby_game_run) stops with a message that says to use run_headless.