miniconf/
packed.rs

1use core::{
2    num::NonZero,
3    ops::{Deref, DerefMut},
4};
5
6use crate::{IntoKeys, Key, KeyLookup, Keys, Node, Transcode, Traversal, TreeKey};
7
8/// A bit-packed representation of multiple indices.
9///
10/// Given known bit width of each index, the bits are
11/// concatenated above a marker bit.
12///
13/// The value consists of (from storage MSB to LSB):
14///
15/// * Zero or more groups of variable bit length, concatenated, each containing
16///   one index. The first is aligned with the storage MSB.
17/// * A set bit to mark the end of the used bits.
18/// * Zero or more cleared bits corresponding to unused index space.
19///
20/// [`Packed::EMPTY`] has the marker at the MSB.
21/// During [`Packed::push_lsb()`] the indices are inserted with their MSB
22/// where the marker was and the marker moves toward the storage LSB.
23/// During [`Packed::pop_msb()`] the indices are removed with their MSB
24/// aligned with the storage MSB and the remaining bits and the marker move
25/// toward the storage MSB.
26///
27/// The representation is MSB aligned to make `PartialOrd`/`Ord` more natural and stable.
28/// The `Packed` key `Ord` matches the ordering of nodes in a horizontal leaf tree
29/// traversal. New nodes can be added/removed to the tree without changing the implicit
30/// encoding (and ordering!) as long no new bits need to be allocated/deallocated (
31/// as long as the number of child nodes of an internal node does not cross a
32/// power-of-two boundary).
33/// Under this condition the mapping between indices/paths and `Packed` representation
34/// is stable even if child nodes are added/removed.
35///
36/// "Small numbers" in LSB-aligned representation can be obtained through
37/// [`Packed::into_lsb()`]/[`Packed::from_lsb()`] but don't have the ordering
38/// and stability properties.
39///
40/// `Packed` can be used to uniquely identify
41/// nodes in a `TreeKey` using only a very small amount of bits.
42/// For many realistic `TreeKey`s a `u16` or even a `u8` is sufficient
43/// to hold a `Packed` in LSB notation. Together with the
44/// `postcard` `serde` format, this then gives access to any node in a nested
45/// heterogeneous `Tree` with just a `u16` or `u8` as compact key and `[u8]` as
46/// compact value.
47///
48/// ```
49/// use miniconf::Packed;
50///
51/// let mut p = Packed::EMPTY;
52/// let mut p_lsb = 0b1; // marker
53/// for (bits, value) in [(2, 0b11), (1, 0b0), (0, 0b0), (3, 0b101)] {
54///     p.push_lsb(bits, value).unwrap();
55///     p_lsb <<= bits;
56///     p_lsb |= value;
57/// }
58/// assert_eq!(p_lsb, 0b1_11_0__101);
59/// //                  ^ marker
60/// assert_eq!(p, Packed::from_lsb(p_lsb.try_into().unwrap()));
61/// assert_eq!(p.get(), 0b11_0__101_1 << (Packed::CAPACITY - p.len()));
62/// //                              ^ marker
63/// ```
64#[derive(
65    Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, serde::Serialize, serde::Deserialize,
66)]
67#[repr(transparent)]
68#[serde(transparent)]
69pub struct Packed(NonZero<usize>);
70
71impl Default for Packed {
72    #[inline]
73    fn default() -> Self {
74        Self::EMPTY
75    }
76}
77
78impl From<NonZero<usize>> for Packed {
79    #[inline]
80    fn from(value: NonZero<usize>) -> Self {
81        Self(value)
82    }
83}
84
85impl From<Packed> for NonZero<usize> {
86    #[inline]
87    fn from(value: Packed) -> Self {
88        value.0
89    }
90}
91
92impl Deref for Packed {
93    type Target = NonZero<usize>;
94
95    #[inline]
96    fn deref(&self) -> &Self::Target {
97        &self.0
98    }
99}
100
101impl DerefMut for Packed {
102    #[inline]
103    fn deref_mut(&mut self) -> &mut Self::Target {
104        &mut self.0
105    }
106}
107
108impl Packed {
109    /// Number of bits in the representation including the marker bit
110    pub const BITS: u32 = NonZero::<usize>::BITS;
111
112    /// The total number of bits this representation can store.
113    pub const CAPACITY: u32 = Self::BITS - 1;
114
115    /// The empty value
116    pub const EMPTY: Self = Self(
117        // Slightly cumbersome to generate it with `const`
118        NonZero::<usize>::MIN
119            .saturating_add(1)
120            .saturating_pow(Self::CAPACITY),
121    );
122
123    /// Create a new `Packed` from a `usize`.
124    ///
125    /// The value must not be zero.
126    #[inline]
127    pub const fn new(value: usize) -> Option<Self> {
128        match NonZero::new(value) {
129            Some(value) => Some(Self(value)),
130            None => None,
131        }
132    }
133
134    /// Create a new `Packed` from LSB aligned `usize`
135    ///
136    /// The value must not be zero.
137    #[inline]
138    pub const fn new_from_lsb(value: usize) -> Option<Self> {
139        match NonZero::new(value) {
140            Some(value) => Some(Self::from_lsb(value)),
141            None => None,
142        }
143    }
144
145    /// The value is empty.
146    #[inline]
147    pub const fn is_empty(&self) -> bool {
148        matches!(*self, Self::EMPTY)
149    }
150
151    /// Clear and discard all bits stored.
152    #[inline]
153    pub fn clear(&mut self) {
154        *self = Self::EMPTY;
155    }
156
157    /// Number of bits that can be stored.
158    #[inline]
159    pub const fn capacity(&self) -> u32 {
160        self.0.trailing_zeros()
161    }
162
163    /// Number of bits stored.
164    #[inline]
165    pub const fn len(&self) -> u32 {
166        Self::CAPACITY - self.capacity()
167    }
168
169    /// Return the representation aligned to the LSB with the marker bit
170    /// moved from the LSB to the MSB.
171    #[inline]
172    pub const fn into_lsb(self) -> NonZero<usize> {
173        match NonZero::new(((self.0.get() >> 1) | (1 << Self::CAPACITY)) >> self.0.trailing_zeros())
174        {
175            Some(v) => v,
176            // We ensure there is at least the marker bit set
177            None => unreachable!(),
178        }
179    }
180
181    /// Build a `Packed` from a LSB-aligned representation with the marker bit
182    /// moved from the MSB the LSB.
183    #[inline]
184    pub const fn from_lsb(value: NonZero<usize>) -> Self {
185        match Self::new(((value.get() << 1) | 1) << value.leading_zeros()) {
186            Some(v) => v,
187            // We ensure there is at least the marker bit set
188            None => unreachable!(),
189        }
190    }
191
192    /// Return the number of bits required to represent `num`.
193    ///
194    /// Ensures that at least one bit is allocated.
195    #[inline]
196    pub const fn bits_for(num: usize) -> u32 {
197        match usize::BITS - num.leading_zeros() {
198            0 => 1,
199            v => v,
200        }
201    }
202
203    /// Remove the given number of MSBs and return them.
204    ///
205    /// If the value does not contain sufficient bits
206    /// it is left unchanged and `None` is returned.
207    ///
208    /// # Args
209    /// * `bits`: Number of bits to pop. `bits <= Self::CAPACITY`
210    pub fn pop_msb(&mut self, bits: u32) -> Option<usize> {
211        let s = self.get();
212        // Remove value from self
213        Self::new(s << bits).map(|new| {
214            *self = new;
215            // Extract value from old self
216            // Done in two steps as bits + 1 can be Self::BITS which would wrap.
217            (s >> (Self::CAPACITY - bits)) >> 1
218        })
219    }
220
221    /// Push the given number `bits` of `value` as new LSBs.
222    ///
223    /// Returns the remaining number of unused bits on success.
224    ///
225    /// # Args
226    /// * `bits`: Number of bits to push. `bits <= Self::CAPACITY`
227    /// * `value`: Value to push. `value >> bits == 0`
228    pub fn push_lsb(&mut self, bits: u32, value: usize) -> Option<u32> {
229        debug_assert_eq!(value >> bits, 0);
230        let mut n = self.trailing_zeros();
231        let old_marker = 1 << n;
232        Self::new(old_marker >> bits).map(|new_marker| {
233            n -= bits;
234            // * Remove old marker
235            // * Add value at offset n + 1
236            //   Done in two steps as n + 1 can be Self::BITS, which would wrap.
237            // * Add new marker
238            self.0 = (self.get() ^ old_marker) | ((value << n) << 1) | new_marker.0;
239            n
240        })
241    }
242}
243
244impl core::fmt::Display for Packed {
245    #[inline]
246    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
247        self.0.fmt(f)
248    }
249}
250
251impl Keys for Packed {
252    #[inline]
253    fn next(&mut self, lookup: &KeyLookup) -> Result<usize, Traversal> {
254        let bits = Self::bits_for(lookup.len().get() - 1);
255        let index = self.pop_msb(bits).ok_or(Traversal::TooShort(0))?;
256        index.find(lookup)
257    }
258
259    #[inline]
260    fn finalize(&mut self) -> Result<(), Traversal> {
261        self.is_empty().then_some(()).ok_or(Traversal::TooLong(0))
262    }
263}
264
265impl IntoKeys for Packed {
266    type IntoKeys = Self;
267
268    #[inline]
269    fn into_keys(self) -> Self::IntoKeys {
270        self
271    }
272}
273
274impl Transcode for Packed {
275    fn transcode<M, K>(&mut self, keys: K) -> Result<Node, Traversal>
276    where
277        Self: Sized,
278        M: TreeKey + ?Sized,
279        K: IntoKeys,
280    {
281        M::traverse_by_key(keys.into_keys(), |index, _name, len| {
282            match self.push_lsb(Packed::bits_for(len.get() - 1), index) {
283                None => Err(()),
284                Some(_) => Ok(()),
285            }
286        })
287        .try_into()
288    }
289}
290
291#[cfg(test)]
292mod test {
293    use super::*;
294
295    #[test]
296    fn test() {
297        // Check path encoding round trip.
298        let t = [1usize, 3, 4, 0, 1];
299        let mut p = Packed::EMPTY;
300        for t in t {
301            let bits = Packed::bits_for(t);
302            p.push_lsb(bits, t).unwrap();
303        }
304        for t in t {
305            let bits = Packed::bits_for(t);
306            assert_eq!(p.pop_msb(bits).unwrap(), t);
307        }
308    }
309}