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