use std::{hash::Hash, mem};
use nalgebra::{Vector2, vector};
use crate::{
TileIndex, TileIndexOffset,
tile_grid::{self, Empty, TileGrid},
tile_rect::TileRect,
};
#[derive(Clone, Debug)]
pub struct MultiTileGrid<T: Tile, S: TileShape> {
data: TileGrid<TileEntry<(T, S)>>,
}
impl<T: Tile, S: TileShape> Default for MultiTileGrid<T, S> {
fn default() -> Self {
Self {
data: TileGrid::default(),
}
}
}
#[derive(Clone, Copy, Default, Debug)]
enum TileEntry<T> {
#[default]
Empty,
Origin(T),
Offset(Vector2<i8>),
}
impl<T: 'static> Empty for TileEntry<T> {
fn empty() -> &'static Self {
&TileEntry::Empty
}
fn is_empty(&self) -> bool {
matches!(self, Self::Empty)
}
}
impl<T> TileEntry<T> {
fn offset(&self) -> Option<TileIndexOffset> {
match self {
TileEntry::Empty => None,
TileEntry::Origin(_) => Some(vector![0, 0]),
TileEntry::Offset(offset) => Some(offset.map(isize::from)),
}
}
}
pub trait Tile: 'static {}
impl<T: 'static> Tile for T {}
pub trait TileShape: 'static {
fn offsets(&self) -> impl Iterator<Item = TileIndexOffset>;
fn bounds_hint(&self) -> Option<TileRect> {
None
}
}
impl<T: Tile, S: TileShape> MultiTileGrid<T, S> {
pub fn shift(&mut self, offset: TileIndexOffset) {
self.data.shift(offset)
}
pub fn bounds(&self) -> TileRect {
self.data.bounds()
}
pub fn expand_to_fit_index(&mut self, index: TileIndex) -> bool {
self.data.expand_to_fit_index(index)
}
pub fn expand_to_fit_bounds(&mut self, new_bounds: TileRect) -> bool {
self.data.expand_to_fit_bounds(new_bounds)
}
pub fn offset_of(&self, index: TileIndex) -> Option<TileIndexOffset> {
self.data[index].offset()
}
pub fn origin_of(&self, index: TileIndex) -> Option<TileIndex> {
Some(index + self.offset_of(index)?)
}
pub fn get_tile(&self, index: TileIndex) -> Option<IndexedTile<'_, T, S>> {
if let Some(origin) = self.origin_of(index) {
let TileEntry::Origin((tile, shape)) = &self.data[origin] else {
unreachable!("Origin index should contain an origin entry")
};
Some(IndexedTile {
tile,
shape,
origin,
})
} else {
None
}
}
pub fn get_tile_mut(&mut self, index: TileIndex) -> Option<IndexedTileMut<'_, T, S>> {
if let Some(origin) = self.origin_of(index) {
let TileEntry::Origin((tile, shape)) = &mut self.data[origin] else {
unreachable!("Origin index should contain an origin entry")
};
Some(IndexedTileMut {
tile,
shape,
origin,
})
} else {
None
}
}
pub fn can_insert_tile(&self, origin: TileIndex, shape: S) -> bool {
for offset in shape.offsets().chain([vector![0, 0]]) {
let index = origin + offset;
if !self.data[index].is_empty() {
return false;
}
}
true
}
pub fn insert_tile(
&mut self,
origin: TileIndex,
tile: T,
shape: S,
) -> Result<(), TileInsertError> {
for offset in shape.offsets().chain([vector![0, 0]]) {
let index = origin + offset;
if !self.data[index].is_empty() {
return Err(TileInsertError::Overlap { conflict: index });
}
}
unsafe { self.insert_tile_unchecked(origin, tile, shape) };
Ok(())
}
pub fn insert_tile_overwriting(
&mut self,
origin: TileIndex,
tile: T,
shape: S,
) -> Vec<RemovedTile<T, S>> {
let mut removed_tiles = Vec::new();
for offset in shape.offsets().chain([vector![0, 0]]) {
let index = origin + offset;
if let Some(removed_tile) = self.remove_tile(index) {
removed_tiles.push(removed_tile);
}
}
unsafe { self.insert_tile_unchecked(origin, tile, shape) };
removed_tiles
}
pub unsafe fn insert_tile_unchecked(&mut self, origin: TileIndex, tile: T, shape: S) {
if let Some(bounds) = shape.bounds_hint() {
self.data
.expand_to_fit_bounds(bounds.translate(origin.coords));
}
for offset in shape.offsets() {
self.data[origin + offset] =
TileEntry::Offset(-offset.map(|x| i8::try_from(x).expect("Shape is too big")));
}
self.data[origin] = TileEntry::Origin((tile, shape));
}
pub fn remove_tile(&mut self, index: TileIndex) -> Option<RemovedTile<T, S>> {
let origin = self.origin_of(index)?;
let entry = mem::take(&mut self.data[origin]);
let TileEntry::Origin((tile, shape)) = entry else {
unreachable!("Origin index should contain an origin entry")
};
for offset in shape.offsets() {
let index = origin + offset;
self.data[index] = TileEntry::Empty;
}
Some(RemovedTile {
tile,
shape,
origin,
})
}
pub fn shrink_to_fit(&mut self) {
self.data.shrink_to_fit();
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct RemovedTile<T: Tile, S: TileShape> {
pub tile: T,
pub shape: S,
pub origin: TileIndex,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct IndexedTile<'a, T: Tile, S: TileShape> {
pub tile: &'a T,
pub shape: &'a S,
pub origin: TileIndex,
}
#[derive(PartialEq, Eq, Hash, Debug)]
pub struct IndexedTileMut<'a, T: Tile, S: TileShape> {
pub tile: &'a mut T,
pub shape: &'a S,
pub origin: TileIndex,
}
#[derive(Clone, Copy, Debug)]
pub enum TileInsertError {
Overlap { conflict: TileIndex },
}
#[derive(Clone, Debug)]
pub struct IntoIter<T: Tile, S: TileShape> {
inner: tile_grid::IntoIter<TileEntry<(T, S)>>,
}
impl<T: Tile, S: TileShape> Iterator for IntoIter<T, S> {
type Item = RemovedTile<T, S>;
fn next(&mut self) -> Option<Self::Item> {
while let Some((origin, entry)) = self.inner.next() {
match entry {
TileEntry::Origin((tile, shape)) => {
return Some(RemovedTile {
tile,
shape,
origin,
});
}
TileEntry::Empty | TileEntry::Offset(_) => (),
}
}
None
}
}
impl<T: Tile, S: TileShape> IntoIterator for MultiTileGrid<T, S> {
type Item = RemovedTile<T, S>;
type IntoIter = IntoIter<T, S>;
fn into_iter(self) -> Self::IntoIter {
IntoIter {
inner: self.data.into_iter(),
}
}
}
#[derive(Clone, Debug)]
pub struct Iter<'a, T: Tile, S: TileShape> {
inner: tile_grid::Iter<'a, TileEntry<(T, S)>>,
}
impl<'a, T: Tile, S: TileShape> Iterator for Iter<'a, T, S> {
type Item = IndexedTile<'a, T, S>;
fn next(&mut self) -> Option<Self::Item> {
while let Some((origin, entry)) = self.inner.next() {
match entry {
TileEntry::Origin((tile, shape)) => {
return Some(IndexedTile {
tile,
shape,
origin,
});
}
TileEntry::Empty | TileEntry::Offset(_) => (),
}
}
None
}
}
impl<T: Tile, S: TileShape> MultiTileGrid<T, S> {
pub fn iter(&self) -> Iter<'_, T, S> {
Iter {
inner: self.data.iter(),
}
}
}
impl<'a, T: Tile, S: TileShape> IntoIterator for &'a MultiTileGrid<T, S> {
type Item = IndexedTile<'a, T, S>;
type IntoIter = Iter<'a, T, S>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
#[derive(Debug)]
pub struct IterMut<'a, T: Tile, S: TileShape> {
inner: tile_grid::IterMut<'a, TileEntry<(T, S)>>,
}
impl<'a, T: Tile, S: TileShape> Iterator for IterMut<'a, T, S> {
type Item = IndexedTileMut<'a, T, S>;
fn next(&mut self) -> Option<Self::Item> {
while let Some((origin, entry)) = self.inner.next() {
match entry {
TileEntry::Origin((tile, shape)) => {
return Some(IndexedTileMut {
tile,
shape,
origin,
});
}
TileEntry::Empty | TileEntry::Offset(_) => (),
}
}
None
}
}
impl<T: Tile, S: TileShape> MultiTileGrid<T, S> {
pub fn iter_mut(&mut self) -> IterMut<'_, T, S> {
IterMut {
inner: self.data.iter_mut(),
}
}
}
impl<'a, T: Tile, S: TileShape> IntoIterator for &'a mut MultiTileGrid<T, S> {
type Item = IndexedTileMut<'a, T, S>;
type IntoIter = IterMut<'a, T, S>;
fn into_iter(self) -> Self::IntoIter {
self.iter_mut()
}
}
impl<T: Tile + Hash, S: TileShape + Hash> Hash for MultiTileGrid<T, S> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
for (i, tile) in self.iter().enumerate() {
(i, tile).hash(state);
}
}
}
impl<T: Tile + Eq, S: TileShape + Eq> PartialEq for MultiTileGrid<T, S> {
fn eq(&self, other: &Self) -> bool {
self.iter().zip(other.iter()).all(|(a, b)| a == b)
}
}
impl<T: Tile + Eq, S: TileShape + Eq> Eq for MultiTileGrid<T, S> {}