henad_core/authoring/model/field.rs
1//! The grid slot an agent model sits over, and the [`Extent`] of the world.
2//!
3//! A field layer owns cells, updates them once per tick and draws them.
4
5use crate::params::{ParamDescriptor, ParamValue};
6use crate::view::GridView;
7
8/// The world rectangle every display layer stretches to.
9///
10/// The whole model shares one extent. An agent layer and a field layer then cannot disagree about
11/// how big the world is.
12#[derive(Debug, Clone, Copy, PartialEq)]
13pub struct Extent {
14 /// Width in world units.
15 pub w: f32,
16 /// Height in world units.
17 pub h: f32,
18}
19
20impl Extent {
21 /// Returns the cell dimensions of a field that tiles this extent at one cell per unit.
22 pub fn cells(self) -> (u32, u32) {
23 (self.w.max(1.0) as u32, self.h.max(1.0) as u32)
24 }
25}
26
27/// A layer of cells stepped once per tick.
28///
29/// henad-compute implements it for `CaField`, a [`crate::authoring::model::grid_model::GridModel`] as a layer, and
30/// for `ScalarField`, scatter-plus-decay layers. An agent model sits over either layer, or over [`NoField`].
31pub trait FieldLayer: Send + 'static {
32 /// Whether the layer draws a grid. It must agree with [`Self::grid_view`].
33 const HAS_GRID: bool = true;
34
35 /// Name of this layer in the Model panel.
36 const KIND: &'static str;
37
38 /// Hot parameters, rebuilt once per tick.
39 type Params: Send + Sync;
40 /// The field as an agent kernel sees it.
41 type Read<'a>
42 where
43 Self: 'a;
44 /// Per agent deposit lanes, filled by the agent passes, or `()` for a field without deposit lanes.
45 type DepositLanes: Send + 'static;
46
47 /// Returns this layer's own parameters, listed after the model's parameters.
48 fn param_descriptors() -> Vec<ParamDescriptor>;
49 /// Extracts the hot parameters for one tick.
50 ///
51 /// `params` is this layer's own slice, so its indices are 0 based and do not move when the
52 /// model above it gains a parameter.
53 fn from_params(params: &[ParamValue]) -> Self::Params;
54 /// Creates the layer over `extent`, from this layer's own slice of the parameters.
55 fn new(extent: Extent, params: &[ParamValue]) -> Self;
56
57 /// Returns the field as an agent kernel reads it.
58 fn read(&self) -> Self::Read<'_>;
59 /// Allocates deposit lanes for `n` agents. The engine reuses them every tick.
60 fn alloc_deposits(&self, n: usize) -> Self::DepositLanes;
61 /// Advances the layer by one tick, with this tick's deposits.
62 fn update(&mut self, deposits: &Self::DepositLanes, p: &Self::Params, tick: u64);
63
64 /// Turns cells into palette indices. The engine calls it before each snapshot.
65 fn prepare_view(&mut self) {}
66
67 /// Returns the cells to draw, or `None` for a layer without a grid.
68 fn grid_view(&self) -> Option<GridView<'_>>;
69 /// Number of cells.
70 fn cell_count(&self) -> usize;
71 /// Heap memory held by the layer, in bytes.
72 fn heap_bytes(&self) -> usize;
73}
74
75/// The empty grid slot, for a model that is agents only.
76#[derive(Debug)]
77pub struct NoField;
78
79impl FieldLayer for NoField {
80 const HAS_GRID: bool = false;
81 const KIND: &'static str = "None";
82
83 type Params = ();
84 type Read<'a> = ();
85 type DepositLanes = ();
86
87 fn param_descriptors() -> Vec<ParamDescriptor> {
88 Vec::new()
89 }
90
91 fn from_params(_params: &[ParamValue]) {}
92
93 fn new(_extent: Extent, _params: &[ParamValue]) -> Self {
94 Self
95 }
96
97 fn read(&self) {}
98
99 fn alloc_deposits(&self, _n: usize) {}
100
101 fn update(&mut self, _deposits: &(), _p: &(), _tick: u64) {}
102
103 fn grid_view(&self) -> Option<GridView<'_>> {
104 None
105 }
106
107 fn cell_count(&self) -> usize {
108 0
109 }
110
111 fn heap_bytes(&self) -> usize {
112 0
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 #[test]
121 fn extent_rounds_down_to_whole_cells() {
122 assert_eq!(Extent { w: 200.0, h: 200.0 }.cells(), (200, 200));
123 assert_eq!(Extent { w: 10.9, h: 4.2 }.cells(), (10, 4));
124 }
125
126 /// A zero extent would give a field with no cells and divide-by-zero indexing.
127 #[test]
128 fn extent_never_collapses_below_one_cell() {
129 assert_eq!(Extent { w: 0.0, h: 0.0 }.cells(), (1, 1));
130 assert_eq!(Extent { w: -5.0, h: 0.4 }.cells(), (1, 1));
131 }
132}