layer_proc_gen/
rolling_grid.rs1use 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
11pub type GridPoint<C> = crate::vec2::Point2d<GridIndex<C>>;
13
14pub(crate) struct RollingGrid<C: Chunk> {
17 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
39pub 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 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 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 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}