use std::marker::PhantomData;
use henad_core::authoring::model::field::{Extent, FieldLayer};
use henad_core::authoring::model::grid_model::GridModel;
use henad_core::grid::Grid2D;
use henad_core::params::{ParamDescriptor, ParamValue};
use henad_core::topology::NeighborhoodKind;
use henad_core::view::GridView;
use crate::cpu::primitives::chunked::{advance_tick_seed, chunk_seed};
use crate::for_each_chunk_mut;
pub const GRID_INIT_SEED: u64 = 0xDEAD_BEEF_CAFE_1234;
pub fn grid_init_rng(seed: Option<u64>) -> u64 {
seed.map_or(GRID_INIT_SEED, henad_core::authoring::primitives::rng::mix_seed)
}
pub struct CaField<M: GridModel> {
grid: Grid2D<u8>,
seed: u64,
_marker: PhantomData<M>,
}
impl<M: GridModel> std::fmt::Debug for CaField<M> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CaField")
.field("model", &M::NAME)
.field("grid", &self.grid)
.finish_non_exhaustive()
}
}
impl<M: GridModel> CaField<M> {
pub fn grid(&self) -> &Grid2D<u8> {
&self.grid
}
pub fn grid_mut(&mut self) -> &mut Grid2D<u8> {
&mut self.grid
}
pub fn parallel_jobs(&self) -> usize {
let width = self.grid.width() as usize;
let height = self.grid.height() as usize;
height.div_ceil(rows_per_leaf(width, height))
}
pub fn with_seed(extent: Extent, params: &[ParamValue], seed: Option<u64>) -> Self {
let (width, height) = extent.cells();
let mut grid = Grid2D::new(width, height);
let mut seed = grid_init_rng(seed);
M::init(&mut grid, params, &mut seed);
Self {
grid,
seed,
_marker: PhantomData,
}
}
pub fn from_cells(extent: Extent, cells: &[u8]) -> Option<Self> {
let (width, height) = extent.cells();
if cells.len() != width as usize * height as usize {
return None;
}
let mut grid = Grid2D::new(width, height);
grid.current_mut().copy_from_slice(cells);
Some(Self {
grid,
seed: GRID_INIT_SEED,
_marker: PhantomData,
})
}
}
impl<M: GridModel> FieldLayer for CaField<M> {
const KIND: &'static str = "Cellular automaton";
type Params = M::Params;
type Read<'a> = &'a [u8];
type DepositLanes = ();
fn param_descriptors() -> Vec<ParamDescriptor> {
M::param_descriptors()
}
fn from_params(params: &[ParamValue]) -> M::Params {
M::from_params(params)
}
fn new(extent: Extent, params: &[ParamValue]) -> Self {
Self::with_seed(extent, params, None)
}
fn read(&self) -> &[u8] {
self.grid.current()
}
fn alloc_deposits(&self, _n: usize) {}
fn update(&mut self, (): &(), p: &M::Params, tick: u64) {
step_grid::<M>(&mut self.grid, p, self.seed, tick);
self.seed = advance_tick_seed(self.seed, tick);
self.grid.swap();
}
fn grid_view(&self) -> Option<GridView<'_>> {
Some(GridView {
width: self.grid.width(),
height: self.grid.height(),
cells: self.grid.current(),
palette: M::PALETTE,
})
}
fn cell_count(&self) -> usize {
self.grid.len()
}
fn heap_bytes(&self) -> usize {
self.grid.heap_bytes()
}
}
const MIN_LEAF_CELLS: usize = 8_192;
fn rows_per_leaf(width: usize, height: usize) -> usize {
MIN_LEAF_CELLS.div_ceil(width.max(1)).min(height).max(1)
}
fn step_grid<M: GridModel>(grid: &mut Grid2D<u8>, hot: &M::Params, seed: u64, tick: u64) {
let h = grid.height();
let ws = grid.width() as usize;
let leaf = rows_per_leaf(ws, h as usize);
let (current, next) = grid.current_and_next_mut();
match M::NEIGHBORHOOD {
NeighborhoodKind::Moore => {
for_each_chunk_mut!(next, ws, min_leaf leaf, |y, _base, next_row| {
let mut rng = chunk_seed(seed, tick, y);
step_row_moore::<M>(neighbor_rows(current, ws, y, h), next_row, hot, &mut rng);
});
}
NeighborhoodKind::VonNeumann => {
for_each_chunk_mut!(next, ws, min_leaf leaf, |y, _base, next_row| {
let mut rng = chunk_seed(seed, tick, y);
step_row_vn::<M>(neighbor_rows(current, ws, y, h), next_row, hot, &mut rng);
});
}
}
}
#[inline]
fn neighbor_rows(current: &[u8], ws: usize, y: usize, h: u32) -> [&[u8]; 3] {
let hs = h as usize;
let ym = if y == 0 { hs - 1 } else { y - 1 };
let yp = if y + 1 == hs { 0 } else { y + 1 };
[
¤t[ym * ws..ym * ws + ws],
¤t[y * ws..y * ws + ws],
¤t[yp * ws..yp * ws + ws],
]
}
#[inline(always)]
fn moore_cell<M: GridModel>(rows: [&[u8]; 3], xm: usize, x: usize, xp: usize, hot: &M::Params, rng: &mut u64) -> u8 {
let [up, mid, down] = rows;
let neighbors = [up[xm], up[x], up[xp], mid[xm], mid[xp], down[xm], down[x], down[xp]];
M::step_cell(mid[x], &neighbors, hot, rng)
}
#[inline(always)]
fn vn_cell<M: GridModel>(rows: [&[u8]; 3], xm: usize, x: usize, xp: usize, hot: &M::Params, rng: &mut u64) -> u8 {
let [up, mid, down] = rows;
let neighbors = [up[x], mid[xm], mid[xp], down[x]];
M::step_cell(mid[x], &neighbors, hot, rng)
}
#[inline(always)]
fn step_row_moore<M: GridModel>(rows: [&[u8]; 3], next_row: &mut [u8], hot: &M::Params, rng: &mut u64) {
let Some(last) = next_row.len().checked_sub(1) else {
return;
};
next_row[0] = moore_cell::<M>(rows, last, 0, last.min(1), hot, rng);
if let Some(interior) = next_row.get_mut(1..last) {
for (i, out) in interior.iter_mut().enumerate() {
let x = i + 1;
*out = moore_cell::<M>(rows, x - 1, x, x + 1, hot, rng);
}
}
if last > 0 {
next_row[last] = moore_cell::<M>(rows, last - 1, last, 0, hot, rng);
}
}
#[inline(always)]
fn step_row_vn<M: GridModel>(rows: [&[u8]; 3], next_row: &mut [u8], hot: &M::Params, rng: &mut u64) {
let Some(last) = next_row.len().checked_sub(1) else {
return;
};
next_row[0] = vn_cell::<M>(rows, last, 0, last.min(1), hot, rng);
if let Some(interior) = next_row.get_mut(1..last) {
for (i, out) in interior.iter_mut().enumerate() {
let x = i + 1;
*out = vn_cell::<M>(rows, x - 1, x, x + 1, hot, rng);
}
}
if last > 0 {
next_row[last] = vn_cell::<M>(rows, last - 1, last, 0, hot, rng);
}
}
#[cfg(test)]
mod tests {
use henad_core::authoring::primitives::space::{MOORE_ROW_MAJOR, VON_NEUMANN};
use henad_core::view::{StatDescriptor, StatValue};
use super::*;
fn encode(dx: i32, dy: i32) -> u8 {
((dx + 1) * 3 + (dy + 1)) as u8
}
const CENTER: u8 = 4;
macro_rules! order_probe {
($name:ident, $kind:expr, $table:expr) => {
struct $name;
impl GridModel for $name {
const NAME: &'static str = "order probe";
const ID: &'static str = "order_probe";
const DESCRIPTION: &'static str = "";
const PALETTE: &'static [[u8; 4]] = &[[0, 0, 0, 255]];
const NEIGHBORHOOD: NeighborhoodKind = $kind;
const STATS: &'static [StatDescriptor] = &[];
type Params = ();
fn param_descriptors() -> Vec<ParamDescriptor> {
Vec::new()
}
fn from_params(_params: &[ParamValue]) {}
fn init(_grid: &mut Grid2D<u8>, _params: &[ParamValue], _rng: &mut u64) {}
fn step_cell(cell: u8, neighbors: &[u8], (): &(), _rng: &mut u64) -> u8 {
if cell == CENTER {
let want: Vec<u8> = $table.iter().map(|&(dx, dy)| encode(dx, dy)).collect();
assert_eq!(
neighbors,
want.as_slice(),
"step_cell's neighbour order must match the published table"
);
}
cell
}
fn stats(_grid: &Grid2D<u8>) -> Vec<StatValue> {
Vec::new()
}
}
};
}
order_probe!(MooreProbe, NeighborhoodKind::Moore, MOORE_ROW_MAJOR);
order_probe!(VonNeumannProbe, NeighborhoodKind::VonNeumann, VON_NEUMANN);
fn probe_grid() -> Grid2D<u8> {
let mut grid = Grid2D::new(3, 3);
let cells = grid.current_mut();
for dy in -1..=1 {
for dx in -1..=1 {
cells[((dy + 1) * 3 + (dx + 1)) as usize] = encode(dx, dy);
}
}
grid
}
#[test]
fn the_gather_order_matches_the_published_moore_table() {
step_grid::<MooreProbe>(&mut probe_grid(), &(), 1, 0);
}
#[test]
fn the_gather_order_matches_the_published_von_neumann_table() {
step_grid::<VonNeumannProbe>(&mut probe_grid(), &(), 1, 0);
}
#[test]
fn a_grid_under_the_leaf_floor_is_one_job() {
let small = CaField::<MooreProbe>::with_seed(Extent { w: 64.0, h: 64.0 }, &[], None);
assert_eq!(small.parallel_jobs(), 1);
let wide = CaField::<MooreProbe>::with_seed(Extent { w: 4096.0, h: 100.0 }, &[], None);
assert_eq!(wide.parallel_jobs(), 50);
}
#[test]
fn the_field_prints_its_size_for_any_model() {
let field = CaField::<MooreProbe>::with_seed(Extent { w: 64.0, h: 32.0 }, &[], None);
assert_eq!(
format!("{field:?}"),
"CaField { model: \"order probe\", grid: Grid2D { width: 64, height: 32, .. }, .. }"
);
}
}