Skip to main content

sgl_core/
grid.rs

1//! Deterministic row-major grids.
2
3use crate::{CanonicalWrite, StateHasher};
4
5/// An invalid grid construction request.
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7pub enum GridError {
8    /// The supplied cells did not equal `width * height`.
9    IncorrectCellCount,
10}
11
12/// A row-major grid with fixed-width dimensions.
13#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct Grid2<T> {
15    width: u16,
16    height: u16,
17    cells: Vec<T>,
18}
19
20impl<T> Grid2<T> {
21    /// Constructs a grid when `cells` exactly matches its checked dimensions.
22    pub fn from_cells(width: u16, height: u16, cells: Vec<T>) -> Result<Self, GridError> {
23        let expected = Self::cell_count(width, height);
24        if cells.len() != expected {
25            return Err(GridError::IncorrectCellCount);
26        }
27        Ok(Self {
28            width,
29            height,
30            cells,
31        })
32    }
33
34    /// Returns the grid width.
35    #[must_use]
36    pub const fn width(&self) -> u16 {
37        self.width
38    }
39
40    /// Returns the grid height.
41    #[must_use]
42    pub const fn height(&self) -> u16 {
43        self.height
44    }
45
46    /// Returns a cell by coordinate.
47    #[must_use]
48    pub fn get(&self, x: u16, y: u16) -> Option<&T> {
49        self.cells.get(self.index(x, y)?)
50    }
51
52    /// Returns a mutable cell by coordinate.
53    #[must_use]
54    pub fn get_mut(&mut self, x: u16, y: u16) -> Option<&mut T> {
55        let index = self.index(x, y)?;
56        self.cells.get_mut(index)
57    }
58
59    /// Returns one row as a contiguous slice.
60    #[must_use]
61    pub fn row(&self, y: u16) -> Option<&[T]> {
62        let start = self.index(0, y)?;
63        self.cells
64            .get(start..start.checked_add(usize::from(self.width))?)
65    }
66
67    /// Returns all cells in deterministic row-major order.
68    #[must_use]
69    pub fn cells(&self) -> &[T] {
70        &self.cells
71    }
72
73    fn cell_count(width: u16, height: u16) -> usize {
74        let count = u32::from(width)
75            .checked_mul(u32::from(height))
76            .expect("u16 grid dimensions have a representable product");
77        usize::try_from(count).expect("u16 grid dimensions fit usize")
78    }
79
80    // This is the sole coordinate-to-linear conversion in this type.
81    fn index(&self, x: u16, y: u16) -> Option<usize> {
82        if y >= self.height || (x >= self.width && !(self.width == 0 && x == 0)) {
83            return None;
84        }
85        usize::try_from(u32::from(y) * u32::from(self.width) + u32::from(x)).ok()
86    }
87}
88
89impl<T: Clone> Grid2<T> {
90    /// Constructs a grid filled with clones of `value`.
91    #[must_use]
92    pub fn filled(width: u16, height: u16, value: T) -> Self {
93        Self {
94            width,
95            height,
96            cells: vec![value; Self::cell_count(width, height)],
97        }
98    }
99}
100
101impl<T: CanonicalWrite> CanonicalWrite for Grid2<T> {
102    fn canonical_write(&self, out: &mut StateHasher) {
103        out.u16(self.width);
104        out.u16(self.height);
105        out.sequence(&self.cells);
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112    use wasm_bindgen_test::wasm_bindgen_test;
113
114    /// #249: the dimension getters report the constructed size.
115    #[wasm_bindgen_test(unsupported = test)]
116    fn dimensions_are_reported() {
117        let grid = Grid2::filled(3, 5, 0u8);
118        assert_eq!((grid.width(), grid.height()), (3, 5));
119        assert_eq!(grid.cells().len(), 15);
120    }
121
122    #[wasm_bindgen_test(unsupported = test)]
123    fn construction_and_access_preserve_row_major_invariants() {
124        let mut grid = Grid2::from_cells(3, 2, vec![0, 1, 2, 3, 4, 5]).expect("matching shape");
125        assert_eq!(grid.get(2, 1), Some(&5));
126        assert_eq!(grid.row(1), Some(&[3, 4, 5][..]));
127        *grid.get_mut(1, 0).expect("in bounds") = 9;
128        assert_eq!(grid.cells(), &[0, 9, 2, 3, 4, 5]);
129        assert_eq!(grid.get(3, 0), None);
130        assert_eq!(grid.get(0, 2), None);
131        assert_eq!(
132            Grid2::from_cells(2, 2, vec![1, 2, 3]),
133            Err(GridError::IncorrectCellCount)
134        );
135    }
136
137    #[wasm_bindgen_test(unsupported = test)]
138    fn dimensions_have_checked_widened_size_arithmetic() {
139        assert_eq!(Grid2::<()>::cell_count(u16::MAX, u16::MAX), 4_294_836_225);
140    }
141
142    #[wasm_bindgen_test(unsupported = test)]
143    fn zero_width_rows_are_valid_and_empty() {
144        let grid = Grid2::from_cells(0, 2, Vec::<u8>::new()).expect("zero-width shape");
145        assert_eq!(grid.row(0), Some(&[][..]));
146        assert_eq!(grid.row(1), Some(&[][..]));
147        assert_eq!(grid.row(2), None);
148        assert_eq!(grid.get(0, 0), None);
149    }
150}