use std::cell::SyncUnsafeCell; use image::{GenericImageView, Rgb}; use tokio::sync::{RwLock, RwLockReadGuard}; use crate::Coordinate; pub trait Grid { fn get(&self, x: I, y: I) -> Option<&V>; #[allow(dead_code)] fn get_unchecked(&self, x: I, y: I) -> &V; fn set(&self, x: I, y: I, value: V); } pub struct Flut { size_x: usize, size_y: usize, cells: SyncUnsafeCell>, jpgbuf: RwLock> } impl Flut { pub fn init(size_x: usize, size_y: usize, value: T) -> Flut { let mut vec = Vec::with_capacity(size_x * size_y); for _ in 0..(size_x * size_y) { vec.push(value.clone()); } Flut { size_x, size_y, cells: vec.into(), jpgbuf: RwLock::new(Vec::new()) } } pub fn get_size(&self) -> (usize, usize) { (self.size_x, self.size_y) } } impl Flut { fn index(&self, x: Coordinate, y: Coordinate) -> Option { let x = x as usize; let y = y as usize; if x >= self.size_x || y >= self.size_y { return None; } Some((y * self.size_x) + x) } pub async fn read_jpg_buffer(&self) -> RwLockReadGuard<'_, Vec> { self.jpgbuf.read().await } } impl Grid for Flut { fn get(&self, x: Coordinate, y: Coordinate) -> Option<&T> { self.index(x, y) .map(|idx| unsafe { &(*self.cells.get())[idx] }) } fn set(&self, x: Coordinate, y: Coordinate, value: T) { match self.index(x, y) { None => (), Some(idx) => unsafe { (*self.cells.get())[idx] = value }, } } fn get_unchecked(&self, x: Coordinate, y: Coordinate) -> &T { let idx = y as usize * self.size_x + x as usize; unsafe { &(*self.cells.get())[idx] } } } impl GenericImageView for Flut { type Pixel = Rgb; fn dimensions(&self) -> (u32, u32) { let (x, y) = self.get_size(); (x as u32, y as u32) } fn get_pixel(&self, x: u32, y: u32) -> Self::Pixel { let pixel = self.get_unchecked(x as Coordinate, y as Coordinate); let [r, g, b, _a] = pixel.to_be_bytes(); Rgb::from([r, g, b]) } } impl Flut { pub async fn update_jpg_buffer(&self) { let mut jpgbuf = self.jpgbuf.write().await; jpgbuf.clear(); let encoder = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut *jpgbuf, 50); let subimage = self.view(0, 0, self.width(), self.height()).to_image(); match subimage.write_with_encoder(encoder) { Ok(_) => {} Err(err) => eprintln!("{}", err), } } } #[cfg(test)] #[allow(clippy::needless_return)] mod tests { use super::Flut; use super::Grid; #[tokio::test] async fn test_grid_init_values() { let grid = Flut::init(3, 3, 0); assert_eq!(grid.cells.into_inner(), vec![0, 0, 0, 0, 0, 0, 0, 0, 0]); } #[tokio::test] async fn test_grid_init_size() { let grid = Flut::init(800, 600, 0); assert_eq!(grid.size_x, 800); assert_eq!(grid.size_y, 600); } #[tokio::test] async fn test_grid_set() { let grid = Flut::init(3, 3, 0); grid.set(1, 1, 255); grid.set(2, 1, 256); assert_eq!(grid.cells.into_inner(), vec![0, 0, 0, 0, 255, 256, 0, 0, 0]); } #[tokio::test] async fn test_grid_set_out_of_range() { let grid = Flut::init(3, 3, 0); grid.set(1, 1, 255); grid.set(3, 1, 256); assert_eq!(grid.cells.into_inner(), vec![0, 0, 0, 0, 255, 0, 0, 0, 0]); } #[tokio::test] async fn test_grid_get() { let grid = Flut::init(3, 3, 0); grid.set(1, 2, 222); assert_eq!(grid.get(1, 2), Some(&222)); } #[tokio::test] async fn test_grid_get_out_of_range() { let grid = Flut::init(3, 3, 0); grid.set(3, 1, 256); assert_eq!(grid.get(3, 1), None); assert_eq!(grid.get(1, 2), Some(&0)); } }