1use henad_core::action::action_seed;
5use henad_core::authoring::model::field::{Extent, FieldLayer as _};
6use henad_core::authoring::model::grid_model::GridModel;
7use henad_core::helpers::{extract_u32, u32_param};
8use henad_core::model::SimState;
9use henad_core::params::{ParamDescriptor, ParamStore, ParamValue};
10use henad_core::view::{GridView, StatEntry, stat_entries};
11
12use crate::cpu::field::CaField;
13
14pub use crate::cpu::field::{GRID_INIT_SEED, grid_init_rng};
15
16pub struct GridModelState<M: GridModel> {
18 field: CaField<M>,
19 params: ParamStore,
20 action_seed: u64,
22 tick: u64,
23}
24
25impl<M: GridModel> std::fmt::Debug for GridModelState<M> {
26 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27 f.debug_struct("GridModelState")
28 .field("model", &M::ID)
29 .field("tick", &self.tick)
30 .field("params", &self.params)
31 .finish_non_exhaustive()
32 }
33}
34
35impl<M: GridModel> GridModelState<M> {
36 pub fn from_params(params: &[ParamValue]) -> Self {
38 Self::from_params_seeded(params, None)
39 }
40
41 pub fn from_params_seeded(params: &[ParamValue], seed: Option<u64>) -> Self {
43 let extent = Extent {
44 w: extract_u32(params, GRID_WIDTH, 1024) as f32,
45 h: extract_u32(params, GRID_HEIGHT, 1024) as f32,
46 };
47 Self {
48 field: CaField::with_seed(extent, own_params(params), seed),
49 params: ParamStore::new(&grid_model_param_descriptors::<M>(), params),
50 action_seed: action_seed(seed),
51 tick: 0,
52 }
53 }
54
55 pub fn from_cells(params: &[ParamValue], cells: &[u8]) -> Option<Self> {
59 let extent = Extent {
60 w: extract_u32(params, GRID_WIDTH, 1024) as f32,
61 h: extract_u32(params, GRID_HEIGHT, 1024) as f32,
62 };
63 Some(Self {
64 field: CaField::from_cells(extent, cells)?,
65 params: ParamStore::new(&grid_model_param_descriptors::<M>(), params),
66 action_seed: action_seed(None),
67 tick: 0,
68 })
69 }
70}
71
72pub const GRID_WIDTH: usize = 0;
75pub const GRID_HEIGHT: usize = 1;
77
78pub const GRID_PARAM_BASE: usize = 2;
80
81fn own_params(params: &[ParamValue]) -> &[ParamValue] {
83 ¶ms[GRID_PARAM_BASE.min(params.len())..]
84}
85
86pub fn grid_model_param_descriptors<M: GridModel>() -> Vec<ParamDescriptor> {
88 let mut descs = vec![
89 u32_param("grid_width", "Grid Width", 1024, 1, 10_000).on_reload(),
90 u32_param("grid_height", "Grid Height", 1024, 1, 10_000).on_reload(),
91 ];
92 descs.extend(M::param_descriptors());
93 descs
94}
95
96impl<M: GridModel> SimState for GridModelState<M> {
97 fn step(&mut self) {
98 let hot = M::from_params(own_params(self.params.values()));
99 self.field.update(&(), &hot, self.tick);
100 self.tick += 1;
101 }
102
103 fn tick(&self) -> u64 {
104 self.tick
105 }
106
107 fn grid_view(&self) -> Option<GridView<'_>> {
108 self.field.grid_view()
109 }
110
111 fn stats(&self) -> Vec<StatEntry> {
112 stat_entries(M::STATS, M::stats(self.field.grid()))
113 }
114
115 fn set_param(&mut self, index: usize, value: &ParamValue) -> bool {
116 self.params.set(index, value)
117 }
118
119 fn act(&mut self, index: usize) -> bool {
120 if index >= M::ACTIONS.len() {
121 return false;
122 }
123 let Self {
125 field,
126 params,
127 action_seed,
128 ..
129 } = self;
130 M::act(index, field.grid_mut(), own_params(params.values()), action_seed);
131 true
132 }
133
134 fn population(&self) -> u64 {
135 self.field.cell_count() as u64
136 }
137
138 fn heap_bytes(&self) -> usize {
139 self.field.heap_bytes()
140 }
141
142 fn parallel_jobs(&self) -> Option<usize> {
143 Some(self.field.parallel_jobs())
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use super::GridModelState;
150 use henad_core::authoring::model::grid_model::GridModel;
151 use henad_core::authoring::primitives::rng::xorshift64;
152 use henad_core::grid::Grid2D;
153 use henad_core::model::SimState as _;
154 use henad_core::params::{ParamDescriptor, ParamValue};
155 use henad_core::topology::NeighborhoodKind;
156 use henad_core::view::{StatDescriptor, StatValue};
157
158 fn live_neighbors(neighbors: &[u8]) -> u8 {
159 neighbors.iter().filter(|&&n| n != 0).count() as u8
160 }
161
162 macro_rules! counting_model {
165 ($ty:ident, $id:literal, $kind:expr) => {
166 struct $ty;
167
168 impl GridModel for $ty {
169 const NAME: &'static str = $id;
170 const ID: &'static str = $id;
171 const DESCRIPTION: &'static str = $id;
172 const PALETTE: &'static [[u8; 4]] = &[[0, 0, 0, 255]; 9];
173 const NEIGHBORHOOD: NeighborhoodKind = $kind;
174 const STATS: &'static [StatDescriptor] = &[];
175 type Params = ();
176
177 fn param_descriptors() -> Vec<ParamDescriptor> {
178 Vec::new()
179 }
180
181 fn from_params(_params: &[ParamValue]) -> Self::Params {}
182
183 fn init(grid: &mut Grid2D<u8>, _params: &[ParamValue], rng: &mut u64) {
184 for cell in grid.current_mut() {
185 *rng = xorshift64(*rng);
186 *cell = (*rng & 1) as u8;
187 }
188 }
189
190 fn step_cell(_cell: u8, neighbors: &[u8], _params: &Self::Params, _rng: &mut u64) -> u8 {
191 live_neighbors(neighbors)
192 }
193
194 fn stats(_grid: &Grid2D<u8>) -> Vec<StatValue> {
195 Vec::new()
196 }
197 }
198 };
199 }
200
201 counting_model!(MooreCount, "moore_count", NeighborhoodKind::Moore);
202 counting_model!(VnCount, "vn_count", NeighborhoodKind::VonNeumann);
203
204 struct Noisy;
207
208 impl GridModel for Noisy {
209 const NAME: &'static str = "noisy";
210 const ID: &'static str = "noisy";
211 const DESCRIPTION: &'static str = "noisy";
212 const PALETTE: &'static [[u8; 4]] = &[[0, 0, 0, 255]; 2];
213 const NEIGHBORHOOD: NeighborhoodKind = NeighborhoodKind::Moore;
214 const STATS: &'static [StatDescriptor] = &[];
215 type Params = ();
216
217 fn param_descriptors() -> Vec<ParamDescriptor> {
218 Vec::new()
219 }
220
221 fn from_params(_params: &[ParamValue]) -> Self::Params {}
222
223 fn init(grid: &mut Grid2D<u8>, params: &[ParamValue], rng: &mut u64) {
224 MooreCount::init(grid, params, rng);
225 }
226
227 fn step_cell(_cell: u8, neighbors: &[u8], _params: &Self::Params, rng: &mut u64) -> u8 {
228 *rng = xorshift64(*rng);
229 (live_neighbors(neighbors) + (*rng >> 63) as u8) & 1
230 }
231
232 fn stats(_grid: &Grid2D<u8>) -> Vec<StatValue> {
233 Vec::new()
234 }
235 }
236
237 fn reference(cells: &[u8], w: usize, h: usize, moore: bool) -> Vec<u8> {
239 let mut out = vec![0u8; cells.len()];
240 for y in 0..h {
241 let (ym, yp) = ((y + h - 1) % h, (y + 1) % h);
242 for x in 0..w {
243 let (xm, xp) = ((x + w - 1) % w, (x + 1) % w);
244 let neighbors = if moore {
245 vec![
246 cells[ym * w + xm],
247 cells[ym * w + x],
248 cells[ym * w + xp],
249 cells[y * w + xm],
250 cells[y * w + xp],
251 cells[yp * w + xm],
252 cells[yp * w + x],
253 cells[yp * w + xp],
254 ]
255 } else {
256 vec![
257 cells[ym * w + x],
258 cells[y * w + xm],
259 cells[y * w + xp],
260 cells[yp * w + x],
261 ]
262 };
263 out[y * w + x] = live_neighbors(&neighbors);
264 }
265 }
266 out
267 }
268
269 const SIZES: [(u32, u32); 7] = [(1, 1), (1, 6), (2, 2), (3, 4), (6, 1), (7, 9), (65, 3)];
271
272 fn cells<M: GridModel>(state: &GridModelState<M>) -> Vec<u8> {
273 state
274 .grid_view()
275 .expect("a grid model always has a grid view")
276 .cells
277 .to_vec()
278 }
279
280 #[test]
281 fn moore_gather_wraps_like_the_reference() {
282 for (w, h) in SIZES {
283 let params = vec![ParamValue::U32(w), ParamValue::U32(h)];
284 let mut state = GridModelState::<MooreCount>::from_params(¶ms);
285 let before = cells(&state);
286 state.step();
287 assert_eq!(
288 cells(&state),
289 reference(&before, w as usize, h as usize, true),
290 "{w}x{h}"
291 );
292 }
293 }
294
295 #[test]
296 fn von_neumann_gather_wraps_like_the_reference() {
297 for (w, h) in SIZES {
298 let params = vec![ParamValue::U32(w), ParamValue::U32(h)];
299 let mut state = GridModelState::<VnCount>::from_params(¶ms);
300 let before = cells(&state);
301 state.step();
302 assert_eq!(
303 cells(&state),
304 reference(&before, w as usize, h as usize, false),
305 "{w}x{h}"
306 );
307 }
308 }
309
310 #[test]
312 fn seeds_produce_independent_replicates() {
313 let params = vec![ParamValue::U32(64), ParamValue::U32(64)];
314 let run = |seed: Option<u64>| -> Vec<u8> {
315 let mut state = GridModelState::<MooreCount>::from_params_seeded(¶ms, seed);
316 state.step();
317 cells(&state)
318 };
319
320 assert_eq!(run(None), run(None), "the default must be reproducible");
321 assert_ne!(run(Some(1)), run(Some(2)), "different seeds must give different runs");
322 assert_eq!(run(Some(7)), run(Some(7)), "a seed must still be reproducible");
323 assert_ne!(run(None), run(Some(1)), "a user seed must not land on the default");
324
325 let zero = run(Some(0));
327 assert!(
328 zero.iter().any(|&c| c != zero[0]),
329 "seed 0 produced a uniform grid, so its RNG state was stuck"
330 );
331 }
332
333 #[test]
337 fn results_do_not_depend_on_the_thread_count() {
338 let params = vec![ParamValue::U32(128), ParamValue::U32(1024)];
340 let run = |threads: usize| -> Vec<u8> {
341 let pool = rayon::ThreadPoolBuilder::new()
342 .num_threads(threads)
343 .build()
344 .expect("rayon pool");
345 pool.install(|| {
346 let mut state = GridModelState::<Noisy>::from_params(¶ms);
347 for _ in 0..20 {
348 state.step();
349 }
350 cells(&state)
351 })
352 };
353 assert_eq!(run(1), run(7), "grid contents depend on the thread count");
354 }
355}