use std::collections::HashMap;
use std::sync::Arc;
pub const TILE_SIZE: usize = 64;
pub const TILE_BYTES: usize = TILE_SIZE * TILE_SIZE * 4;
pub trait TileFormat: 'static + Send + Sync {
type Data: Clone + Default + Send + Sync + 'static;
}
#[derive(Clone, Copy)]
pub struct Rgba;
impl TileFormat for Rgba {
type Data = RgbaData;
}
#[derive(Clone, Copy)]
pub struct AlphaF32;
impl TileFormat for AlphaF32 {
type Data = AlphaF32Data;
}
#[derive(Clone)]
#[repr(transparent)]
pub struct RgbaData(pub [u8; TILE_BYTES]);
unsafe impl bytemuck::Zeroable for RgbaData {}
impl Default for RgbaData {
fn default() -> Self {
RgbaData([0u8; TILE_BYTES])
}
}
impl RgbaData {
pub fn pixel_mut(&mut self, x: usize, y: usize) -> &mut [u8; 4] {
debug_assert!(x < TILE_SIZE && y < TILE_SIZE);
let offset = (y * TILE_SIZE + x) * 4;
(&mut self.0[offset..offset + 4]).try_into().unwrap()
}
pub fn pixel(&self, x: usize, y: usize) -> &[u8; 4] {
debug_assert!(x < TILE_SIZE && y < TILE_SIZE);
let offset = (y * TILE_SIZE + x) * 4;
self.0[offset..offset + 4].try_into().unwrap()
}
}
pub const MASK_TILE_PIXELS: usize = TILE_SIZE * TILE_SIZE;
#[derive(Clone)]
#[repr(transparent)]
pub struct AlphaF32Data(pub [f32; MASK_TILE_PIXELS]);
unsafe impl bytemuck::Zeroable for AlphaF32Data {}
impl Default for AlphaF32Data {
fn default() -> Self {
AlphaF32Data([0.0f32; MASK_TILE_PIXELS])
}
}
impl AlphaF32Data {
pub fn get(&self, x: usize, y: usize) -> f32 {
debug_assert!(x < TILE_SIZE && y < TILE_SIZE);
self.0[y * TILE_SIZE + x]
}
pub fn set(&mut self, x: usize, y: usize, value: f32) {
debug_assert!(x < TILE_SIZE && y < TILE_SIZE);
self.0[y * TILE_SIZE + x] = value;
}
}
#[derive(Clone)]
pub struct Tile<F: TileFormat> {
data: Arc<F::Data>,
}
impl<F: TileFormat> Tile<F> {
pub fn new_empty() -> Self {
Tile {
data: Arc::new(F::Data::default()),
}
}
pub fn data(&self) -> &F::Data {
&self.data
}
pub fn write(&mut self) -> &mut F::Data {
Arc::make_mut(&mut self.data)
}
pub fn is_shared(&self) -> bool {
Arc::strong_count(&self.data) > 1
}
}
impl Tile<Rgba> {
pub fn empty() -> Self {
use std::sync::LazyLock;
static EMPTY: LazyLock<Arc<RgbaData>> = LazyLock::new(|| Arc::new(RgbaData::default()));
Tile {
data: Arc::clone(&EMPTY),
}
}
}
impl Tile<AlphaF32> {
pub fn empty() -> Self {
use std::sync::LazyLock;
static EMPTY: LazyLock<Arc<AlphaF32Data>> =
LazyLock::new(|| Arc::new(AlphaF32Data::default()));
Tile {
data: Arc::clone(&EMPTY),
}
}
pub fn full() -> Self {
use std::sync::LazyLock;
static FULL: LazyLock<Arc<AlphaF32Data>> =
LazyLock::new(|| Arc::new(AlphaF32Data([1.0f32; MASK_TILE_PIXELS])));
Tile {
data: Arc::clone(&FULL),
}
}
}
#[derive(Clone)]
pub struct TileStore<F: TileFormat> {
tiles: HashMap<(i32, i32), Tile<F>>,
}
impl<F: TileFormat> TileStore<F> {
pub fn new() -> Self {
TileStore {
tiles: HashMap::new(),
}
}
pub fn get(&self, tx: i32, ty: i32) -> Option<&Tile<F>> {
self.tiles.get(&(tx, ty))
}
pub fn get_or_create(&mut self, tx: i32, ty: i32) -> &mut Tile<F> {
self.tiles.entry((tx, ty)).or_insert_with(Tile::new_empty)
}
pub fn tile_coords_for_pixel(x: i32, y: i32) -> (i32, i32) {
(
x.div_euclid(TILE_SIZE as i32),
y.div_euclid(TILE_SIZE as i32),
)
}
pub fn iter(&self) -> impl Iterator<Item = ((i32, i32), &Tile<F>)> {
self.tiles.iter().map(|(&k, v)| (k, v))
}
pub fn len(&self) -> usize {
self.tiles.len()
}
pub fn is_empty(&self) -> bool {
self.tiles.is_empty()
}
}
impl TileStore<AlphaF32> {
pub fn get_or_create_full(&mut self, tx: i32, ty: i32) -> &mut Tile<AlphaF32> {
self.tiles.entry((tx, ty)).or_insert_with(Tile::full)
}
}
impl<F: TileFormat> Default for TileStore<F> {
fn default() -> Self {
Self::new()
}
}
pub type AlphaMask = TileStore<AlphaF32>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cow_clone_on_write() {
let t1 = Tile::<Rgba>::empty();
let mut t2 = t1.clone();
t2.write()
.pixel_mut(0, 0)
.copy_from_slice(&[255, 0, 0, 255]);
assert_eq!(t1.data().pixel(0, 0), &[0, 0, 0, 0]);
assert_eq!(t2.data().pixel(0, 0), &[255, 0, 0, 255]);
}
#[test]
fn tile_store_get_or_create() {
let mut store = TileStore::<Rgba>::new();
assert!(store.get(0, 0).is_none());
let tile = store.get_or_create(0, 0);
tile.write()
.pixel_mut(5, 5)
.copy_from_slice(&[0, 255, 0, 255]);
assert!(store.get(0, 0).is_some());
assert_eq!(
store.get(0, 0).unwrap().data().pixel(5, 5),
&[0, 255, 0, 255]
);
}
#[test]
fn tile_coords_for_pixel() {
let ts = TILE_SIZE as i32;
assert_eq!(TileStore::<Rgba>::tile_coords_for_pixel(0, 0), (0, 0));
assert_eq!(
TileStore::<Rgba>::tile_coords_for_pixel(ts - 1, ts - 1),
(0, 0)
);
assert_eq!(TileStore::<Rgba>::tile_coords_for_pixel(ts, 0), (1, 0));
assert_eq!(TileStore::<Rgba>::tile_coords_for_pixel(-1, -1), (-1, -1));
assert_eq!(TileStore::<Rgba>::tile_coords_for_pixel(-ts, 0), (-1, 0));
assert_eq!(
TileStore::<Rgba>::tile_coords_for_pixel(-ts - 1, 0),
(-2, 0)
);
}
#[test]
fn alpha_mask_cow() {
let t1 = Tile::<AlphaF32>::empty();
let mut t2 = t1.clone();
t2.write().set(0, 0, 1.0);
assert_eq!(t1.data().get(0, 0), 0.0);
assert_eq!(t2.data().get(0, 0), 1.0);
}
}