#![warn(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
#![deny(missing_docs)]
use std::{borrow::Borrow, ops::Deref};
use debug::DynLayer;
use rolling_grid::RollingGrid;
pub use rolling_grid::{GridIndex, GridPoint};
pub use vec2::{Bounds, Point2d};
pub mod debug;
pub mod generic_layers;
#[macro_export]
macro_rules! deps {
($(#[$meta:meta])* struct $name:ident {$($field:ident: $ty:ty,)*}) => {
$(#[$meta])*
struct $name {
$($field: Layer<$ty>,)*
}
impl $crate::Dependencies for $name {
fn debug(&self) -> Vec<&dyn $crate::debug::DynLayer> {
let $name {
$($field,)*
} = self;
vec![ $($field,)*]
}
}
}
}
pub trait Dependencies {
fn debug(&self) -> Vec<&dyn DynLayer>;
}
impl Dependencies for () {
fn debug(&self) -> Vec<&dyn DynLayer> {
vec![]
}
}
impl<C: Chunk + debug::Debug> Dependencies for Layer<C> {
fn debug(&self) -> Vec<&dyn DynLayer> {
vec![self]
}
}
pub struct Layer<C: Chunk> {
layer: Store<C>,
}
impl<C: Chunk> Deref for Layer<C> {
type Target = C::Dependencies;
fn deref(&self) -> &Self::Target {
&self.layer.borrow().1
}
}
impl<C: Chunk> Default for Layer<C>
where
C::Dependencies: Default,
{
fn default() -> Self {
Self::new(Default::default())
}
}
impl<C: Chunk> Layer<C> {
pub fn new(value: C::Dependencies) -> Self {
Layer {
layer: Store::<C>::from((RollingGrid::default(), value)),
}
}
}
impl<C: Chunk> Clone for Layer<C>
where
Store<C>: Clone,
{
fn clone(&self) -> Self {
Self {
layer: self.layer.clone(),
}
}
}
#[expect(type_alias_bounds)]
type Store<C: Chunk> = C::LayerStore<Tuple<C>>;
#[expect(type_alias_bounds)]
type Tuple<C: Chunk> = (RollingGrid<C>, C::Dependencies);
impl<C: Chunk> Layer<C> {
#[track_caller]
pub fn ensure_loaded_in_bounds(&self, chunk_bounds: Bounds) {
let indices = C::bounds_to_grid(chunk_bounds);
let mut create_indices: Vec<_> = indices.iter().collect();
let center = indices.center();
create_indices.sort_by_cached_key(|&index| index.dist_squared(center));
for index in create_indices {
self.get_or_compute(index);
}
}
pub fn clear(&self, chunk_bounds: Bounds) {
for index in C::bounds_to_grid(chunk_bounds).iter() {
self.layer.borrow().0.clear(index, self)
}
}
pub fn incoherent_override_cache(&self, index: GridPoint<C>, val: C) {
self.layer.borrow().0.incoherent_override_cache(index, val)
}
pub fn get_or_compute(&self, index: GridPoint<C>) -> C {
self.layer.borrow().0.get_or_compute(index, self)
}
pub fn get_range(&self, range: Bounds) -> impl Iterator<Item = C> + '_ {
let range = C::bounds_to_grid(range);
self.get_grid_range(range)
}
pub fn get_grid_range(&self, range: Bounds<GridIndex<C>>) -> impl Iterator<Item = C> + '_ {
range.iter().map(move |pos| self.get_or_compute(pos))
}
pub fn get_moore_neighborhood(&self, index: GridPoint<C>) -> [[C; 3]; 3] {
C::moore_neighborhood(index).map(|line| line.map(|index| self.get_or_compute(index)))
}
}
pub trait Chunk: Sized + Default + Clone + 'static {
const GRID_SIZE: Point2d<u8> = Point2d::splat(5);
const GRID_OVERLAP: u8 = 3;
type LayerStore<T>: Borrow<T> + From<T>;
const SIZE: Point2d<u8> = Point2d::splat(8);
fn compute(layer: &Self::Dependencies, index: GridPoint<Self>) -> Self;
fn clear(layer: &Self::Dependencies, index: GridPoint<Self>);
fn bounds(index: GridPoint<Self>) -> Bounds {
let size = Self::SIZE.map(|i| 1 << i);
let min = index.map(|i| i.0) * size;
Bounds {
min,
max: min + size,
}
}
fn bounds_to_grid(bounds: Bounds) -> Bounds<GridIndex<Self>> {
bounds.map(Self::pos_to_grid)
}
fn pos_to_grid(point: Point2d) -> GridPoint<Self> {
RollingGrid::<Self>::pos_to_grid_pos(point)
}
fn vision_range(bounds: Bounds) -> Bounds {
bounds.pad(Self::SIZE.map(|i| 1 << i))
}
fn moore_neighborhood(index: GridPoint<Self>) -> [[GridPoint<Self>; 3]; 3] {
let p = |x, y| index + GridPoint::new(GridIndex::from_raw(x), GridIndex::from_raw(y));
[
[p(-1, -1), p(0, -1), p(1, -1)],
[p(-1, 0), p(0, 0), p(1, 0)],
[p(-1, 1), p(0, 1), p(1, 1)],
]
}
type Dependencies: Dependencies;
}
mod rolling_grid;
pub mod vec2;