grixy 0.6.0

Zero-cost 2D grids for embedded systems and graphics
Documentation
//! Provides a generic, 2D grid data structure backed by a linear buffer.
//!
//! The main type, [`GridBuf`], is highly generic, allowing it to act as a view over any buffer
//! that can be treated as a slice.
//!
//! # Examples
//!
//! Creating an owned `GridBuf` and accessing an element:
//! ```
//! use grixy::{core::Pos, buf::GridBuf, ops::GridRead};
//!
//! let grid = GridBuf::<u8, _, _>::new_filled(3, 4, 42);
//! assert_eq!(grid.get(Pos::new(2, 3)), Some(&42));
//! ```

use core::{
    fmt,
    marker::PhantomData,
    ops::{Index, IndexMut},
};

// IMPLEMENATIONS ----------------------------------------------------------------------------------

pub mod bits;

// TRAIT IMPLS -------------------------------------------------------------------------------------

use crate::ops::ExactSizeGrid as _;
pub use crate::ops::unchecked::TrustedSizeGrid as _;
use crate::{core::Pos, ops::layout};

mod impl_grid;
mod impl_new;
mod impl_resize;
mod impl_serde;
mod impl_slice;

/// A 2-dimensional grid implemented by a linear data buffer.
///
/// ## Layout
///
/// The grid is stored in a linear buffer, with elements accessed in an order defined by
/// [`Layout`].
///
/// [`Layout`]: layout::Layout
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GridBuf<T, B, L> {
    buffer: B,
    width: usize,
    height: usize,
    _layout: PhantomData<L>,
    _element: PhantomData<T>,
}

impl<T, B, L> GridBuf<T, B, L>
where
    L: layout::LinearLayout,
{
    /// Consumes the `GridBuf`, returning the underlying buffer, width, and height.
    #[must_use]
    pub fn into_inner(self) -> (B, usize, usize) {
        (self.buffer, self.width, self.height)
    }

    /// Returns a mutable reference to the element at `pos`, or `None` if out of bounds.
    #[must_use]
    pub fn get_mut(&mut self, pos: Pos) -> Option<&mut T>
    where
        B: AsMut<[T]>,
        L: layout::LinearLayout,
    {
        if self.contains(pos) {
            let idx = L::pos_to_index(pos, self.width);
            self.buffer.as_mut().get_mut(idx)
        } else {
            None
        }
    }
}

impl<T, B, L> Index<Pos> for GridBuf<T, B, L>
where
    L: layout::LinearLayout,
    B: AsRef<[T]>,
{
    type Output = T;

    fn index(&self, index: Pos) -> &Self::Output {
        &self.buffer.as_ref()[index.y * self.width + index.x]
    }
}

impl<T, B, L> IndexMut<Pos> for GridBuf<T, B, L>
where
    L: layout::LinearLayout,
    B: AsRef<[T]> + AsMut<[T]>,
{
    fn index_mut(&mut self, index: Pos) -> &mut Self::Output {
        &mut self.buffer.as_mut()[index.y * self.width + index.x]
    }
}

impl<T, B, L> fmt::Display for GridBuf<T, B, L>
where
    T: fmt::Display + Default + PartialEq,
    B: AsRef<[T]>,
    L: layout::LinearLayout,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        for row in 0..self.height {
            for col in 0..self.width {
                let pos = Pos::new(col, row);
                let elem = &self[pos];
                if *elem == T::default() {
                    f.write_str("·")?;
                } else {
                    write!(f, "{elem}")?;
                }
            }
            writeln!(f)?;
        }
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    extern crate alloc;
    use super::*;
    use crate::{
        core::{Pos, Rect},
        ops::{
            GridRead as _,
            layout::RowMajor,
            unchecked::{GridReadUnchecked as _, GridWriteUnchecked as _},
        },
    };
    use alloc::{format, vec, vec::Vec};

    #[test]
    fn into_inner() {
        let grid = GridBuf::<u8, _, _>::new(5, 4);
        let (buffer, width, height) = grid.into_inner();
        assert_eq!(buffer.len(), width * height);
    }

    #[test]
    fn impl_bounded_grid() {
        let grid = GridBuf::<u8, _, _>::new(5, 4);
        assert_eq!(grid.width(), 5);
        assert_eq!(grid.height(), 4);
    }

    #[test]
    fn impl_get_unchecked() {
        let grid = GridBuf::new_filled(5, 4, 42);
        let pos = Pos::new(2, 3);
        assert_eq!(*unsafe { grid.get_unchecked(pos) }, 42);
    }

    #[test]
    fn impl_set_unchecked() {
        let mut grid = GridBuf::new(5, 4);
        let pos = Pos::new(2, 3);
        unsafe { grid.set_unchecked(pos, 99) };
        assert_eq!(*unsafe { grid.get_unchecked(pos) }, 99);
    }

    #[test]
    fn with_buffer_col_major() {
        let buffer =
            GridBuf::<_, _, layout::ColumnMajor>::from_buffer(vec![1, 2, 3, 4, 5, 6, 7, 8, 9], 3);
        assert_eq!(buffer.width(), 3);
        assert_eq!(buffer.height(), 3);
        assert_eq!(buffer.get(Pos::new(0, 0)), Some(&1));
        assert_eq!(buffer.get(Pos::new(2, 2)), Some(&9));
    }

    #[test]
    fn rect_iter_unchecked() {
        #[rustfmt::skip]
        let buffer = GridBuf::<_, _, RowMajor>::from_buffer(vec![
            1, 2, 3,
            4, 5, 6,
            7, 8, 9,
        ], 3);

        assert_eq!(
            unsafe {
                buffer
                    .iter_rect_unchecked(Rect::from_ltwh(1, 1, 2, 1))
                    .collect::<Vec<_>>()
            },
            vec![&5, &6]
        );
        assert_eq!(
            unsafe {
                buffer
                    .iter_rect_unchecked(Rect::from_ltwh(0, 0, 3, 3))
                    .copied()
                    .collect::<Vec<_>>()
            },
            vec![1, 2, 3, 4, 5, 6, 7, 8, 9]
        );
    }

    #[test]
    fn fill_rect_iter_unchecked() {
        let mut grid = GridBuf::<_, _, RowMajor>::new(3, 3);
        unsafe {
            grid.fill_rect_iter_unchecked(Rect::from_ltwh(0, 0, 2, 2), vec![1, 2, 3, 4]);
        }
        #[rustfmt::skip]
        assert_eq!(grid.buffer.as_ref() as &[i32], &[
            1, 2, 0,
            3, 4, 0,
            0, 0, 0,
        ]);
    }

    #[test]
    fn fill_rect_solid_unchecked() {
        let mut grid = GridBuf::<_, _, RowMajor>::new(3, 3);
        unsafe {
            grid.fill_rect_solid_unchecked(Rect::from_ltwh(0, 0, 2, 2), 42);
        }
        #[rustfmt::skip]
        assert_eq!(grid.buffer.as_ref() as &[i32], &[
            42, 42, 0,
            42, 42, 0,
            0, 0, 0,
        ]);
    }

    #[test]
    fn display() {
        let grid = GridBuf::new_filled(2, 2, 0u8);
        let output = format!("{grid}");
        assert_eq!(output, "··\n··\n");
    }

    #[test]
    fn get_mut_in_bounds() {
        let mut grid = GridBuf::new_filled(3, 3, 0u8);
        let cell = grid.get_mut(Pos::new(1, 1));
        assert!(cell.is_some());
        *cell.unwrap() = 42;
        assert_eq!(grid[Pos::new(1, 1)], 42);
    }

    #[test]
    fn get_mut_out_of_bounds() {
        let mut grid = GridBuf::new_filled(3, 3, 0u8);
        assert!(grid.get_mut(Pos::new(3, 3)).is_none());
    }

    #[test]
    fn display_non_default() {
        let mut grid = GridBuf::new_filled(2, 2, 0u8);
        grid[Pos::new(0, 0)] = 42;
        let output = format!("{grid}");
        assert_eq!(output, "42·\n··\n");
    }

    #[test]
    fn index_ops() {
        let mut grid = GridBuf::<u8, _, _>::new(3, 3);
        grid[Pos::new(1, 1)] = 42;
        assert_eq!(grid[Pos::new(1, 1)], 42);
        assert_eq!(grid.get(Pos::new(1, 1)), Some(&42));
        assert_eq!(grid.get(Pos::new(3, 3)), None); // Out of bounds
    }
}