1use crate::engine::tiles::TileKey;
15use crate::kernels::pyramid::assemble_parent;
16use std::collections::BTreeMap;
17
18pub fn level_count(width: f64, height: f64, size: usize) -> u32 {
22 let max_dimension = width.max(height);
23 let mut levels = 0;
24 let mut scale = size as f64;
25 while scale < max_dimension {
26 levels += 1;
27 scale *= 2.0;
28 }
29 levels
30}
31
32pub fn queue_key(x: i32, y: i32) -> String {
35 format!("{x}_{y}")
36}
37
38#[derive(Clone, Debug, PartialEq)]
39pub enum Step {
40 NeedIndex {
42 level: u32,
43 },
44 NeedChildren {
47 level: u32,
48 x: i32,
49 y: i32,
50 children: [TileKey; 4],
51 },
52 Parent {
55 level: u32,
56 x: i32,
57 y: i32,
58 pixels: Option<Vec<u8>>,
59 },
60 LevelDone {
62 level: u32,
63 },
64 Done,
65}
66
67#[derive(Clone, Copy, Debug, PartialEq, Eq)]
68pub enum PyramidError {
69 BadSize,
71 Unexpected,
73 BadChild,
75}
76
77#[derive(Clone, Debug, PartialEq)]
78enum Phase {
79 Start(u32),
81 Indexing(u32),
83 Parents(u32),
85 Children(u32, i32, i32),
87 Provided(u32, i32, i32),
89 Done,
90}
91
92pub struct PyramidSession {
94 canvas: String,
95 size: usize,
96 levels: u32,
97 phase: Phase,
98 queue: BTreeMap<String, (i32, i32)>,
99 children: [Option<Vec<u8>>; 4],
100}
101
102impl PyramidSession {
103 pub fn new(canvas: &str, width: f64, height: f64, size: usize) -> Result<Self, PyramidError> {
105 if size == 0 || !size.is_multiple_of(2) {
106 return Err(PyramidError::BadSize);
107 }
108 let levels = level_count(width, height, size);
109 Ok(Self {
110 canvas: canvas.to_string(),
111 size,
112 levels,
113 phase: if levels == 0 {
114 Phase::Done
115 } else {
116 Phase::Start(1)
117 },
118 queue: BTreeMap::new(),
119 children: [None, None, None, None],
120 })
121 }
122
123 pub fn levels(&self) -> u32 {
125 self.levels
126 }
127
128 pub fn queue(&mut self, x: i32, y: i32) -> Result<(i32, i32), PyramidError> {
131 if !matches!(self.phase, Phase::Indexing(_)) {
132 return Err(PyramidError::Unexpected);
133 }
134 let parent = (x.div_euclid(2), y.div_euclid(2));
135 self.queue.insert(queue_key(parent.0, parent.1), parent);
136 Ok(parent)
137 }
138
139 pub fn provide(&mut self, children: [Option<Vec<u8>>; 4]) -> Result<(), PyramidError> {
141 let Phase::Children(level, x, y) = self.phase else {
142 return Err(PyramidError::Unexpected);
143 };
144 let n = self.size * self.size * 4;
145 if children.iter().flatten().any(|c| c.len() != n) {
146 return Err(PyramidError::BadChild);
147 }
148 self.children = children;
149 self.phase = Phase::Provided(level, x, y);
150 Ok(())
151 }
152
153 pub fn step(&mut self) -> Step {
154 match self.phase.clone() {
155 Phase::Start(level) => {
156 self.phase = Phase::Indexing(level);
157 Step::NeedIndex { level }
158 }
159 Phase::Indexing(level) | Phase::Parents(level) => match self.queue.pop_first() {
160 Some((_, (x, y))) => {
161 self.phase = Phase::Children(level, x, y);
162 let child = |dx: i32, dy: i32| {
163 TileKey::new(&self.canvas, level - 1, x * 2 + dx, y * 2 + dy)
164 };
165 Step::NeedChildren {
166 level,
167 x,
168 y,
169 children: [child(0, 0), child(1, 0), child(0, 1), child(1, 1)],
170 }
171 }
172 None => {
173 self.phase = if level < self.levels {
174 Phase::Start(level + 1)
175 } else {
176 Phase::Done
177 };
178 Step::LevelDone { level }
179 }
180 },
181 Phase::Children(level, x, y) => {
183 let child = |dx: i32, dy: i32| {
184 TileKey::new(&self.canvas, level - 1, x * 2 + dx, y * 2 + dy)
185 };
186 Step::NeedChildren {
187 level,
188 x,
189 y,
190 children: [child(0, 0), child(1, 0), child(0, 1), child(1, 1)],
191 }
192 }
193 Phase::Provided(level, x, y) => {
194 self.phase = Phase::Parents(level);
195 let children = std::mem::take(&mut self.children);
196 let pixels = children.iter().any(Option::is_some).then(|| {
197 let mut out = vec![0u8; self.size * self.size * 4];
198 let [a, b, c, d] = &children;
199 assemble_parent(
200 [a.as_deref(), b.as_deref(), c.as_deref(), d.as_deref()],
201 self.size,
202 &mut out,
203 );
204 out
205 });
206 Step::Parent {
207 level,
208 x,
209 y,
210 pixels,
211 }
212 }
213 Phase::Done => Step::Done,
214 }
215 }
216}
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221
222 type Written = Vec<(u32, i32, i32, [u8; 4])>;
223
224 fn run(
227 size: usize,
228 width: f64,
229 height: f64,
230 level0: &[(i32, i32, u8)],
231 ) -> (Written, Vec<u32>, usize) {
232 let mut session = PyramidSession::new("c", width, height, size).unwrap();
233 let mut store: BTreeMap<(u32, i32, i32), Vec<u8>> = BTreeMap::new();
234 for &(x, y, v) in level0 {
235 let mut px = vec![v; size * size * 4];
236 px.iter_mut().skip(3).step_by(4).for_each(|a| *a = 255);
237 store.insert((0, x, y), px);
238 }
239 let (mut parents, mut done, mut queued) = (Vec::new(), Vec::new(), 0);
240 loop {
241 match session.step() {
242 Step::NeedIndex { level } => {
243 let mut rows: Vec<(String, i32, i32)> = store
245 .keys()
246 .filter(|k| k.0 == level - 1)
247 .map(|&(_, x, y)| (queue_key(x, y), x, y))
248 .collect();
249 rows.sort();
250 for (_, x, y) in rows {
251 session.queue(x, y).unwrap();
252 queued += 1;
253 }
254 }
255 Step::NeedChildren { children, .. } => {
256 let got = children
257 .clone()
258 .map(|k| store.get(&(k.level, k.x, k.y)).cloned());
259 session.provide(got).unwrap();
260 }
261 Step::Parent {
262 level,
263 x,
264 y,
265 pixels,
266 } => {
267 if let Some(p) = pixels {
268 parents.push((level, x, y, [p[0], p[1], p[2], p[3]]));
269 store.insert((level, x, y), p);
270 }
271 }
272 Step::LevelDone { level } => done.push(level),
273 Step::Done => return (parents, done, queued),
274 }
275 }
276 }
277
278 #[test]
279 fn level_count_matches_the_doubling_table() {
280 let size = 64;
281 for (width, expected) in [
282 (1.0, 0),
283 (63.0, 0),
284 (64.0, 0),
285 (65.0, 1),
286 (128.0, 1),
287 (129.0, 2),
288 (256.0, 2),
289 (257.0, 3),
290 ] {
291 assert_eq!(level_count(width, 1.0, size), expected, "width {width}");
292 assert_eq!(level_count(1.0, width, size), expected, "height {width}");
293 }
294 }
295
296 #[test]
297 fn a_canvas_within_one_tile_has_no_levels_and_asks_for_nothing() {
298 let mut s = PyramidSession::new("c", 10.0, 10.0, 64).unwrap();
299 assert_eq!(s.levels(), 0);
300 assert_eq!(s.step(), Step::Done);
301 }
302
303 #[test]
304 fn an_odd_or_zero_tile_size_is_refused() {
305 assert_eq!(
306 PyramidSession::new("c", 1.0, 1.0, 0).err(),
307 Some(PyramidError::BadSize)
308 );
309 assert_eq!(
310 PyramidSession::new("c", 1.0, 1.0, 15).err(),
311 Some(PyramidError::BadSize)
312 );
313 }
314
315 #[test]
316 fn queue_and_provide_out_of_turn_are_refused() {
317 let mut s = PyramidSession::new("c", 200.0, 10.0, 64).unwrap();
318 assert_eq!(s.queue(0, 0), Err(PyramidError::Unexpected));
319 assert_eq!(
320 s.provide([None, None, None, None]),
321 Err(PyramidError::Unexpected)
322 );
323 assert_eq!(s.step(), Step::NeedIndex { level: 1 });
324 assert_eq!(
325 s.provide([None, None, None, None]),
326 Err(PyramidError::Unexpected)
327 );
328 s.queue(0, 0).unwrap();
329 assert!(matches!(s.step(), Step::NeedChildren { .. }));
330 assert_eq!(s.queue(0, 0), Err(PyramidError::Unexpected));
331 assert_eq!(
332 s.provide([Some(vec![0; 3]), None, None, None]),
333 Err(PyramidError::BadChild)
334 );
335 }
336
337 #[test]
338 fn parents_floor_negative_coordinates_and_run_in_storage_key_order() {
339 let mut s = PyramidSession::new("c", 4096.0, 64.0, 64).unwrap();
340 assert_eq!(s.step(), Step::NeedIndex { level: 1 });
341 assert_eq!(s.queue(-1, 0).unwrap(), (-1, 0));
343 assert_eq!(s.queue(20, 0).unwrap(), (10, 0));
344 assert_eq!(s.queue(21, 1).unwrap(), (10, 0));
345 assert_eq!(s.queue(2, 0).unwrap(), (1, 0));
346 let mut order = Vec::new();
347 loop {
348 match s.step() {
349 Step::NeedChildren { x, y, children, .. } => {
350 order.push((x, y));
351 assert_eq!(children[1], TileKey::new("c", 0, x * 2 + 1, y * 2));
352 assert_eq!(children[2], TileKey::new("c", 0, x * 2, y * 2 + 1));
353 s.provide([None, None, None, None]).unwrap();
354 }
355 Step::Parent { pixels, .. } => assert!(pixels.is_none()),
356 Step::LevelDone { level } => {
357 assert_eq!(level, 1);
358 break;
359 }
360 other => panic!("unexpected {other:?}"),
361 }
362 }
363 assert_eq!(order, vec![(-1, 0), (10, 0), (1, 0)]);
365 }
366
367 #[test]
368 fn a_two_level_canvas_builds_each_level_from_the_one_below() {
369 let (parents, done, queued) = run(
371 64,
372 256.0,
373 64.0,
374 &[(0, 0, 10), (1, 0, 20), (2, 0, 30), (3, 0, 40)],
375 );
376 assert_eq!(done, vec![1, 2]);
377 assert_eq!(
379 parents.iter().map(|p| (p.0, p.1, p.2)).collect::<Vec<_>>(),
380 vec![(1, 0, 0), (1, 1, 0), (2, 0, 0)]
381 );
382 assert_eq!(parents[0].3, [10, 10, 10, 255]);
384 assert_eq!(parents[1].3, [30, 30, 30, 255]);
385 assert_eq!(parents[2].3, [10, 10, 10, 255]);
386 assert_eq!(queued, 4 + 2);
387 }
388
389 #[test]
390 fn a_missing_quadrant_stays_transparent() {
391 let (parents, ..) = run(64, 128.0, 128.0, &[(1, 1, 50)]);
392 assert_eq!(parents.len(), 1);
393 assert_eq!(parents[0].3, [0, 0, 0, 0]);
395 }
396}