embedded_3dgfx/bsp/scratch.rs
1//! Per-frame mutable scratch memory for BSP traversal.
2//!
3//! Create once, pass `&mut BspScratch` into [`K3dengine::record_bsp`](crate::K3dengine::record_bsp) each
4//! frame. The frame counter wraps safely and the visframe array is zero-cost
5//! to "clear" — a wrapping increment of `frame` is the full reset.
6
7/// Per-frame scratch buffer used to de-duplicate faces during BSP traversal.
8///
9/// A face shared by multiple leaves (possible with Quake-style marksurface
10/// tables) is emitted at most once per frame. The caller owns the backing
11/// slice; length must equal `BspWorld::faces.len()`.
12pub struct BspScratch<'a> {
13 /// `face_visframe[i] == frame` iff face `i` was already emitted this frame.
14 pub face_visframe: &'a mut [u32],
15 /// Monotonically-increasing frame counter (wraps via `wrapping_add`).
16 pub frame: u32,
17}
18
19impl<'a> BspScratch<'a> {
20 /// Create from a caller-owned slice.
21 ///
22 /// The slice should be zero-initialised on first use; subsequent frames
23 /// require no manual clearing.
24 pub fn new(face_visframe: &'a mut [u32]) -> Self {
25 Self {
26 face_visframe,
27 frame: 0,
28 }
29 }
30
31 /// Advance to a new frame. Call once at the start of each `record_bsp`.
32 #[inline]
33 pub fn mark_new_frame(&mut self) {
34 self.frame = self.frame.wrapping_add(1);
35 }
36
37 /// Returns `true` if face `face_idx` was already emitted this frame.
38 #[inline]
39 pub fn is_marked(&self, face_idx: usize) -> bool {
40 self.face_visframe.get(face_idx).copied().unwrap_or(0) == self.frame
41 }
42
43 /// Mark face `face_idx` as emitted for the current frame.
44 #[inline]
45 pub fn mark(&mut self, face_idx: usize) {
46 if let Some(slot) = self.face_visframe.get_mut(face_idx) {
47 *slot = self.frame;
48 }
49 }
50}
51
52#[cfg(test)]
53mod tests {
54 extern crate std;
55 use super::*;
56
57 #[test]
58 fn new_frame_deduplication() {
59 let mut buf = [0u32; 4];
60 let mut s = BspScratch::new(&mut buf);
61
62 s.mark_new_frame();
63 assert!(!s.is_marked(0));
64 s.mark(0);
65 assert!(s.is_marked(0));
66 assert!(!s.is_marked(1));
67
68 // Advancing frame resets all marks
69 s.mark_new_frame();
70 assert!(!s.is_marked(0));
71 }
72
73 #[test]
74 fn out_of_bounds_mark_is_noop() {
75 let mut buf = [0u32; 2];
76 let mut s = BspScratch::new(&mut buf);
77 s.mark_new_frame();
78 s.mark(99); // out of bounds — should not panic
79 assert!(!s.is_marked(99));
80 }
81
82 #[test]
83 fn frame_wraps_safely() {
84 let mut buf = [0u32; 1];
85 let mut s = BspScratch::new(&mut buf);
86 s.frame = u32::MAX;
87 s.mark_new_frame();
88 assert_eq!(s.frame, 0);
89 }
90}