Grid models¶
See Writing a CPU grid model for a tutorial.
GridModel is the authoring trait for a cellular automaton, a grid of u8 cells where every cell advances by one rule per tick.
It fits a world made of small integer states, each cell updating from what surrounds it.
You implement init, step_cell, stats and the consts, and the engine supplies everything else, including the parallel row-wise step.
fn step_cell(cell: u8, neighbors: &[u8], _params: &(), _rng: &mut u64) -> u8 {
let alive_count: u8 = neighbors.iter().sum();
match (cell, alive_count) {
(ALIVE, 2..=3) | (DEAD, 3) => ALIVE,
_ => DEAD,
}
}
Read game_of_life.rs and sir.rs alongside this page.
Game of Life is the smaller of the two and draws no random numbers during a step, which makes it the easier one to copy from when you begin.
Items you supply¶
Everything in the table below comes out of your impl.
| Item | Role |
|---|---|
NAME, ID, DESCRIPTION |
Identity, read by the app and by henad-cli --list |
PALETTE |
One RGBA colour per cell value. See palettes and views |
NEIGHBORHOOD |
Moore or VonNeumann, deciding the neighbours step_cell receives |
STATS |
The series the history chart plots. See statistics |
ACTIONS |
One-off changes to the state, one button each, empty by default. See actions |
type Params |
Hot parameters, rebuilt once a tick |
param_descriptors |
This model's own parameters. See parameters |
from_params |
Extracts Params from a value slice |
init |
Fills the grid, given the parameters and a seed |
step_cell |
The rule |
act |
Runs one entry of ACTIONS |
stats |
The reduction, in STATS order |
The rule¶
step_cell receives its neighbours already gathered, in the order your declared NEIGHBORHOOD fixes.
Keep the function pure apart from the rng it receives.
The engine steps rows in parallel and makes no guarantee about which row runs on which core.
Neighbours arrive row-major, with dy on the outer axis and dx on the inner.
This ordering is published API, and a test asserts it against the tables in authoring primitives.
dy runs south, matching the display's downward y axis.
The grid is a torus on both axes, and the engine wraps every coordinate before your rule runs, so step_cell never sees an edge.
Hot parameters¶
Your rule reads a &Self::Params.
Passing it the raw value slice would put a ParamValue match inside a loop that runs once per cell, millions of times a tick, and from_params avoids that by running once at the start of a tick.
Every cell of that tick then shares the result.
fn from_params(params: &[ParamValue]) -> SirParams {
SirParams {
infection_rate: extract_f32(params, INFECTION_RATE, 0.3),
recovery_rate: extract_f32(params, RECOVERY_RATE, 0.05),
}
}
A model with nothing to extract, such as Game of Life, sets type Params = () and writes an empty from_params.
Anything a rule would otherwise recompute per cell also belongs here, such as a squared radius or a reciprocal.
Width and height¶
The engine prepends grid width and height at indices 0 and 1, and no grid model declares them itself.
Its own parameters start at index 2, and the indices params! generates are already relative to the model's own slice.
init and from_params both receive that slice.
The engine can then add an engine parameter without shifting anything that a model reads.
The initial state¶
init runs once, sequentially, on the current side of a freshly allocated grid.
The rng argument is a plain u64 xorshift state, advanced by the random primitives.
A GPU port of the model calls this same function to seed its buffers, which keeps tick 0 bit-identical between the two backends. See porting a model to the GPU.
Left to the engine¶
With the trait implemented, the engine covers the rest:
- Allocates the grid and its second buffer, and swaps the two after every tick.
- Splits the step by row across rayon, on native and on the web alike.
- Wraps both axes, peeling the x wrap off the row loop so the interior runs without a modulo.
- Seeds an RNG per row per tick from the row index, never from anything a worker mutates.
- Stores the parameters, and rejects any edit to a reload-only parameter.
- Builds the grid view, the display texture, the history chart and the snapshots.
Next¶
- Writing a grid model builds one from an empty directory.
- Statistics explains the reduction and why it runs on publish rather than every tick.
- GPU grid models covers the same topology written in compute shaders.