Skip to main content

layer_proc_gen/
rolling_grid.rs

1use crate::{
2    Chunk,
3    vec2::{Abs, Num, Point2d},
4};
5use std::{
6    cell::{Cell, RefCell},
7    marker::PhantomData,
8    ops::{Div, DivAssign, Neg},
9};
10
11/// The x and y positions of a chunk in the number of chunks, not in world coordinates.
12pub type GridPoint<C> = crate::vec2::Point2d<GridIndex<C>>;
13
14// TODO: avoid the box when generic const exprs allow for it
15// The Layer that contains it will already get put into an `Arc`
16pub(crate) struct RollingGrid<C: Chunk> {
17    /// The inner slice contains to `L::OVERLAP` entries,
18    /// some of which are `None` if they have nevef been used
19    /// so far.
20    grid: Box<[Box<[ActiveCell<C>]>]>,
21    time: Cell<u64>,
22}
23
24impl<C: Chunk> Default for RollingGrid<C> {
25    fn default() -> Self {
26        Self {
27            grid: std::iter::repeat_with(|| {
28                std::iter::repeat_with(Default::default)
29                    .take(C::GRID_OVERLAP.into())
30                    .collect()
31            })
32            .take((1 << C::GRID_SIZE.x) << C::GRID_SIZE.y)
33            .collect(),
34            time: Cell::new(1),
35        }
36    }
37}
38
39/// An x or y index in chunk coordinates, not world coordinates.
40pub struct GridIndex<C>(pub i64, PhantomData<C>);
41
42impl<C> Abs for GridIndex<C> {
43    fn abs(self) -> Self {
44        Self::from_raw(self.0.abs())
45    }
46}
47
48impl<C> Div for GridIndex<C> {
49    type Output = Self;
50
51    fn div(mut self, rhs: Self) -> Self::Output {
52        self /= rhs;
53        self
54    }
55}
56
57impl<C> DivAssign for GridIndex<C> {
58    fn div_assign(&mut self, rhs: Self) {
59        self.0 /= rhs.0;
60    }
61}
62
63impl<C> std::ops::SubAssign for GridIndex<C> {
64    fn sub_assign(&mut self, rhs: Self) {
65        self.0 -= rhs.0;
66    }
67}
68
69impl<C> std::ops::Sub for GridIndex<C> {
70    type Output = Self;
71
72    fn sub(mut self, rhs: Self) -> Self::Output {
73        self -= rhs;
74        self
75    }
76}
77
78impl<C> std::ops::Mul for GridIndex<C> {
79    type Output = Self;
80
81    fn mul(mut self, rhs: Self) -> Self::Output {
82        self *= rhs;
83        self
84    }
85}
86
87impl<C> std::ops::MulAssign for GridIndex<C> {
88    fn mul_assign(&mut self, rhs: Self) {
89        self.0 *= rhs.0;
90    }
91}
92
93impl<C> std::ops::Add for GridIndex<C> {
94    type Output = Self;
95
96    fn add(mut self, rhs: Self) -> Self::Output {
97        self += rhs;
98        self
99    }
100}
101
102impl<C> std::ops::AddAssign for GridIndex<C> {
103    fn add_assign(&mut self, rhs: Self) {
104        self.0 += rhs.0;
105    }
106}
107
108impl<C> Ord for GridIndex<C> {
109    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
110        self.0.cmp(&other.0)
111    }
112}
113
114impl<C> PartialOrd for GridIndex<C> {
115    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
116        Some(self.0.cmp(&other.0))
117    }
118}
119
120impl<C> Eq for GridIndex<C> {}
121
122impl<C> PartialEq for GridIndex<C> {
123    fn eq(&self, other: &Self) -> bool {
124        self.0 == other.0
125    }
126}
127
128impl<C> std::fmt::Debug for GridIndex<C> {
129    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
130        f.debug_tuple("GridIndex").field(&self.0).finish()
131    }
132}
133
134impl<C> Copy for GridIndex<C> {}
135
136impl<C> Clone for GridIndex<C> {
137    fn clone(&self) -> Self {
138        *self
139    }
140}
141
142impl<C> GridIndex<C> {
143    /// Create a [GridIndex] for any chunk.
144    /// Useful if you are doing some custom math to convert from a
145    /// [Chunk]'s coordinates to another [Chunk]'s coordinates without
146    /// going through world coordinates.
147    pub const fn from_raw(i: i64) -> Self {
148        Self(i, PhantomData)
149    }
150}
151
152impl<C> Neg for GridIndex<C> {
153    type Output = Self;
154
155    fn neg(mut self) -> Self::Output {
156        self.0 = self.0.neg();
157        self
158    }
159}
160
161impl<C> Num for GridIndex<C> {
162    const ZERO: Self = Self::from_raw(0);
163    const ONE: Self = Self::from_raw(1);
164    const TWO: Self = Self::from_raw(2);
165
166    fn iter_range(mut range: std::ops::Range<Self>) -> impl Iterator<Item = Self> {
167        std::iter::from_fn(move || {
168            if range.start == range.end {
169                None
170            } else {
171                let i = range.start;
172                range.start.0 += 1;
173                Some(i)
174            }
175        })
176    }
177
178    fn as_u64(self) -> u64 {
179        self.0.as_u64()
180    }
181}
182
183impl<C> Div<i64> for GridIndex<C> {
184    type Output = Self;
185    fn div(mut self, rhs: i64) -> Self::Output {
186        self /= rhs;
187        self
188    }
189}
190
191impl<C> DivAssign<i64> for GridIndex<C> {
192    fn div_assign(&mut self, rhs: i64) {
193        self.0 /= rhs;
194    }
195}
196
197impl<C: Chunk> GridPoint<C> {
198    /// When two [Chunk]s have the same size, all their coordinates are trivially
199    /// the same and we can convert them with just a compile-time check.
200    pub fn into_same_chunk_size<D: Chunk>(self) -> GridPoint<D> {
201        const { assert!(C::SIZE.x == D::SIZE.x && C::SIZE.y == D::SIZE.y) };
202        GridPoint {
203            x: GridIndex::from_raw(self.x.0),
204            y: GridIndex::from_raw(self.y.0),
205        }
206    }
207}
208
209struct ActiveCell<C: Chunk> {
210    pos: Cell<GridPoint<C>>,
211    chunk: RefCell<C>,
212    last_access: Cell<u64>,
213}
214
215impl<C: Chunk> Default for ActiveCell<C> {
216    fn default() -> Self {
217        Self {
218            pos: GridPoint::splat(GridIndex::from_raw(i64::MIN)).into(),
219            chunk: Default::default(),
220            last_access: Cell::new(0),
221        }
222    }
223}
224
225impl<C: Chunk> RollingGrid<C> {
226    #[track_caller]
227    /// If the position is already occupied with a block,
228    /// debug assert that it's the same that we'd generate.
229    /// Otherwise just increment the user count for that block.
230    pub fn get_or_compute(&self, pos: GridPoint<C>, layer: &C::Dependencies) -> C {
231        let now = self.time.get();
232        self.time.set(now.checked_add(1).unwrap());
233        let free = match self.find_free_or_entry(pos, now) {
234            Ok(value) => value,
235            Err(p) => return p.chunk.borrow().clone(),
236        };
237        let chunk = C::compute(layer, pos);
238        free.pos.set(pos);
239        free.chunk.replace(chunk.clone());
240        free.last_access.set(now);
241        chunk
242    }
243
244    fn find_free_or_entry(
245        &self,
246        pos: Point2d<GridIndex<C>>,
247        now: u64,
248    ) -> Result<&ActiveCell<C>, &ActiveCell<C>> {
249        let (mut free, mut rest) = self.access(pos).split_first().unwrap();
250        while let Some((p, r)) = rest.split_first() {
251            rest = r;
252            if p.last_access.get() == 0 {
253            } else if p.pos.get() == pos {
254                p.last_access.set(now);
255                return Err(p);
256            } else if free.last_access < p.last_access {
257                continue;
258            }
259            free = p;
260        }
261        Ok(free)
262    }
263
264    pub fn clear(&self, pos: GridPoint<C>, layer: &C::Dependencies) {
265        for cell in self.access(pos) {
266            if cell.pos.get() == pos {
267                cell.last_access.set(0);
268                cell.chunk.replace(Default::default());
269            }
270        }
271        C::clear(layer, pos)
272    }
273
274    pub fn incoherent_override_cache(&self, pos: GridPoint<C>, val: C) {
275        let now = self.time.get();
276        self.time.set(now.checked_add(1).unwrap());
277        let (Ok(v) | Err(v)) = self.find_free_or_entry(pos, now);
278        v.chunk.replace(val);
279        v.pos.set(pos);
280    }
281
282    pub const fn pos_to_grid_pos(pos: Point2d) -> GridPoint<C> {
283        GridPoint {
284            x: GridIndex::from_raw(pos.x >> C::SIZE.x),
285            y: GridIndex::from_raw(pos.y >> C::SIZE.y),
286        }
287    }
288
289    const fn index_of_point(point: GridPoint<C>) -> usize {
290        const { assert!((C::GRID_SIZE.x as u32) < usize::BITS) }
291        const { assert!((C::GRID_SIZE.y as u32) < usize::BITS) }
292        #[expect(
293            clippy::cast_possible_truncation,
294            reason = "checked above that remainder op will alway fit in usize"
295        )]
296        let x = point.x.0.rem_euclid(1 << C::GRID_SIZE.x) as usize;
297        #[expect(
298            clippy::cast_possible_truncation,
299            reason = "checked above that remainder op will alway fit in usize"
300        )]
301        let y = point.y.0.rem_euclid(1 << C::GRID_SIZE.y) as usize;
302        x + (y << C::GRID_SIZE.x)
303    }
304
305    #[track_caller]
306    fn access(&self, pos: GridPoint<C>) -> &[ActiveCell<C>] {
307        self.grid
308            .get(Self::index_of_point(pos))
309            .unwrap_or_else(|| panic!("grid position {pos:?} out of bounds"))
310    }
311
312    pub fn iter_all_loaded(&self) -> impl Iterator<Item = (GridPoint<C>, C)> + '_ {
313        self.grid
314            .iter()
315            .flatten()
316            .filter(|cell| cell.last_access.get() != 0)
317            .map(|cell| (cell.pos.get(), cell.chunk.borrow().clone()))
318    }
319}