Skip to main content
← PSoXide SDK guides and examples · SDK f2fec1c301f1 · All Cargo features enabled; check feature gates before use.

psx_pack/
visibility.rs

1//! Allocation-free Quake/GoldSrc visibility RLE.
2//!
3//! Nonzero bytes are literals; zero followed by a count advances over invisible
4//! leaves. The caller selects strict container validation or GoldSrc's bounded
5//! best-effort policy, and replacement or OR merging, without a second row buffer.
6
7/// Decode exactly one row, rejecting truncation, zero runs and oversized runs.
8/// Bytes already decoded remain in `output` on failure.
9#[inline]
10pub fn decode_strict(input: &[u8], offset: usize, output: &mut [u8]) -> bool {
11    decode::<false, true>(input, offset, output)
12}
13
14/// OR one strict row into an existing row. Invisible runs leave it unchanged.
15/// Bytes already merged remain in `output` on failure.
16#[inline]
17pub fn merge_strict(input: &[u8], offset: usize, output: &mut [u8]) -> bool {
18    decode::<true, true>(input, offset, output)
19}
20
21/// Decode a bounded GoldSrc row, clipping oversized runs and leaving a
22/// truncated tail invisible. Zero-length runs are consumed, not rejected.
23#[inline]
24pub fn decode_clamped(input: &[u8], offset: usize, output: &mut [u8]) {
25    output.fill(0);
26    let _ = decode::<false, false>(input, offset, output);
27}
28
29/// OR a bounded GoldSrc row into an existing row, clipping oversized runs and
30/// preserving the destination after truncated input or invisible runs.
31#[inline]
32pub fn merge_clamped(input: &[u8], offset: usize, output: &mut [u8]) {
33    let _ = decode::<true, false>(input, offset, output);
34}
35
36#[inline]
37fn decode<const MERGE: bool, const STRICT: bool>(
38    input: &[u8],
39    offset: usize,
40    output: &mut [u8],
41) -> bool {
42    let mut source = offset;
43    let mut destination = 0usize;
44    while destination < output.len() {
45        let Some(&value) = input.get(source) else {
46            return false;
47        };
48        source += 1;
49        if value != 0 {
50            if MERGE {
51                output[destination] |= value;
52            } else {
53                output[destination] = value;
54            }
55            destination += 1;
56            continue;
57        }
58        let Some(&run) = input.get(source) else {
59            return false;
60        };
61        source += 1;
62        let available = output.len() - destination;
63        if STRICT && (run == 0 || run as usize > available) {
64            return false;
65        }
66        let end = destination + (run as usize).min(available);
67        if !MERGE {
68            output[destination..end].fill(0);
69        }
70        destination = end;
71    }
72    true
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78
79    #[test]
80    fn literal_zero_runs_and_or_merging_preserve_rows() {
81        let input = [0xaa, 0x11, 0, 2, 0x80];
82        let mut row = [0xcc; 4];
83        assert!(decode_strict(&input, 1, &mut row));
84        assert_eq!(row, [0x11, 0, 0, 0x80]);
85        row = [0x22; 4];
86        assert!(merge_strict(&input, 1, &mut row));
87        assert_eq!(row, [0x33, 0x22, 0x22, 0xa2]);
88    }
89
90    #[test]
91    fn strict_failures_preserve_the_decoded_prefix_and_unwritten_tail() {
92        for input in [&[0x11][..], &[0x11, 0], &[0x11, 0, 0], &[0x11, 0, 4]] {
93            let mut row = [0xcc; 4];
94            assert!(!decode_strict(input, 0, &mut row));
95            assert_eq!(row, [0x11, 0xcc, 0xcc, 0xcc]);
96            let mut merged = [0x22; 4];
97            assert!(!merge_strict(input, 0, &mut merged));
98            assert_eq!(merged, [0x33, 0x22, 0x22, 0x22]);
99        }
100        assert!(decode_strict(&[], usize::MAX, &mut []));
101        assert!(!decode_strict(&[], usize::MAX, &mut [0]));
102    }
103
104    #[test]
105    fn goldsrc_clips_runs_accepts_empty_runs_and_zeroes_truncated_tail() {
106        for input in [&[0x11][..], &[0x11, 0], &[0x11, 0, 0], &[0x11, 0, 99]] {
107            let mut row = [0xcc; 4];
108            decode_clamped(input, 0, &mut row);
109            assert_eq!(row, [0x11, 0, 0, 0]);
110            let mut merged = [0x22; 4];
111            merge_clamped(input, 0, &mut merged);
112            assert_eq!(merged, [0x33, 0x22, 0x22, 0x22]);
113        }
114        let mut row = [0xcc; 2];
115        decode_clamped(&[0, 0, 0, 0, 0x11, 0x80], 0, &mut row);
116        assert_eq!(row, [0x11, 0x80]);
117        decode_clamped(&[], usize::MAX, &mut row);
118        assert_eq!(row, [0, 0]);
119    }
120}