Skip to main content

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}