Skip to main content

vortex_buffer/bit/
meta.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use std::ops::Bound;
5use std::ops::RangeBounds;
6
7use vortex_error::VortexExpect;
8
9/// In-memory metadata describing a packed bitset: a normalized bit `offset` (always `< 8`) and a
10/// logical bit `len`.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12pub struct BitBufferMeta {
13    offset: usize,
14    len: usize,
15}
16
17impl BitBufferMeta {
18    /// Create metadata for a bitset starting at bit `offset` with `len` bits.
19    ///
20    /// Panics if `offset >= 8`. Use [`from_raw_offset`](Self::from_raw_offset) to normalize a
21    /// larger offset.
22    #[inline]
23    pub fn new(offset: usize, len: usize) -> Self {
24        assert!(offset < 8, "BitBufferMeta offset must be < 8, got {offset}");
25        Self { offset, len }
26    }
27
28    /// Normalize a raw bit `offset` into a whole-byte offset plus metadata whose `offset` is
29    /// `< 8`.
30    ///
31    /// Returns `(byte_offset, meta)` so the caller can slice its backing buffer by `byte_offset`
32    /// and store the remaining sub-byte offset in `meta`.
33    #[inline]
34    pub fn from_raw_offset(offset: usize, len: usize) -> (usize, Self) {
35        (
36            offset / 8,
37            Self {
38                offset: offset % 8,
39                len,
40            },
41        )
42    }
43
44    /// Return the leading byte offset and normalized metadata for a logical slice.
45    ///
46    /// # Panics
47    ///
48    /// Panics if the range is out of bounds or its end precedes its start.
49    #[inline]
50    pub fn slice(&self, range: impl RangeBounds<usize>) -> (usize, Self) {
51        let start = match range.start_bound() {
52            Bound::Included(&start) => start,
53            Bound::Excluded(&start) => start
54                .checked_add(1)
55                .vortex_expect("excluded slice start must not overflow"),
56            Bound::Unbounded => 0,
57        };
58        let end = match range.end_bound() {
59            Bound::Included(&end) => end
60                .checked_add(1)
61                .vortex_expect("included slice end must not overflow"),
62            Bound::Excluded(&end) => end,
63            Bound::Unbounded => self.len,
64        };
65
66        assert!(start <= end);
67        assert!(start <= self.len);
68        assert!(end <= self.len);
69
70        Self::from_raw_offset(self.offset + start, end - start)
71    }
72
73    /// The sub-byte bit offset. Always `< 8`.
74    #[inline(always)]
75    pub fn offset(&self) -> usize {
76        self.offset
77    }
78
79    /// The logical length of the bitset in bits.
80    #[inline(always)]
81    pub fn len(&self) -> usize {
82        self.len
83    }
84
85    /// Returns `true` if the bitset is empty.
86    #[inline(always)]
87    pub fn is_empty(&self) -> bool {
88        self.len == 0
89    }
90
91    /// The number of backing bytes required to hold `offset + len` bits.
92    #[inline]
93    pub fn byte_len(&self) -> usize {
94        (self.offset + self.len).div_ceil(8)
95    }
96}