1
#![doc = include_str!("../README.md")]
2
#![no_std]
3
#![forbid(unsafe_code)]
4
#![warn(missing_docs, clippy::pedantic)]
5

            
6
extern crate alloc;
7

            
8
/// An ordered collection of values, accessible by [`LotId`] or index.
9
pub mod ordered;
10
/// An unordered collection of values, accessible by [`LotId`].
11
pub mod unordered;
12

            
13
use core::array;
14
use core::fmt::{Debug, Write};
15
use core::num::{NonZeroU16, NonZeroUsize};
16

            
17
pub use ordered::OrderedLots;
18
pub use unordered::Lots;
19

            
20
/// A `LotId` is a single `usize`, encoding generation information in the top
21
/// 1/4 of the bits, and index information in the remaining bits. This table
22
/// shows the breakdown for supported target platforms:
23
///
24
/// | `target_pointer_width` | generation bits | index bits |
25
/// |------------------------|-----------------|------------|
26
/// | 16                     | 4               | 12         |
27
/// | 32                     | 8               | 24         |
28
/// | 64                     | 16              | 48         |
29
///
30
/// Each time a lot is allocated, its generation is incremented. When retrieving
31
/// values using a `LotId`, the generation is validated as a safe guard against
32
/// returning a value. Because the generation isn't significantly wide, the
33
/// generation can wrap and is not a perfect protection against stale data,
34
/// although the likelihood of improper access is greatly reduced.
35
#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd)]
36
pub struct LotId(NonZeroUsize);
37

            
38
impl Debug for LotId {
39
5
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
40
5
        f.write_str("LotId(")?;
41
5
        self.index().fmt(f)?;
42
5
        f.write_char('g')?;
43
5
        self.generation().fmt(f)?;
44
5
        f.write_char(')')
45
5
    }
46
}
47

            
48
#[test]
49
1
fn lot_id_debug() {
50
1
    let lot = LotId::new(Generation::first(), 0).unwrap();
51
1
    assert_eq!(alloc::format!("{lot:?}"), "LotId(0g1)");
52
1
}
53

            
54
#[cfg(not(any(
55
    target_pointer_width = "16",
56
    target_pointer_width = "32",
57
    target_pointer_width = "64"
58
)))]
59
compile_error!("LotId currently only supports 16, 32 and 64 bit architectures.");
60

            
61
impl LotId {
62
    #[allow(clippy::cast_possible_truncation)]
63
    const GENERATION_MAX: u16 = (usize::MAX >> Self::INDEX_BITS) as u16;
64
    const INDEX_BITS: u32 = usize::BITS / 4 * 3;
65
    const INDEX_MASK: usize = 2_usize.pow(Self::INDEX_BITS) - 1;
66

            
67
    #[cfg_attr(target_pointer_width = "64", allow(clippy::absurd_extreme_comparisons))]
68
    #[inline]
69
75
    fn new(generation: Generation, index: usize) -> Option<Self> {
70
75
        if index <= Self::INDEX_MASK && generation.get() <= Self::GENERATION_MAX {
71
74
            Some(Self(
72
74
                NonZeroUsize::new((generation.0.get() as usize) << Self::INDEX_BITS | index)
73
74
                    .expect("generation is non-zero"),
74
74
            ))
75
        } else {
76
1
            None
77
        }
78
75
    }
79

            
80
    #[inline]
81
    #[must_use]
82
198
    const fn index(self) -> usize {
83
198
        self.0.get() & Self::INDEX_MASK
84
198
    }
85

            
86
    #[inline]
87
    #[must_use]
88
    #[allow(clippy::cast_possible_truncation)]
89
167
    fn generation(self) -> Generation {
90
167
        Generation(
91
167
            NonZeroU16::new((self.0.get() >> Self::INDEX_BITS) as u16).expect("invalid Lot id"),
92
167
        )
93
167
    }
94

            
95
    /// Returns this ID as bytes. To decode the resulting bytes, use
96
    /// [`from_bytes()`](Self::from_bytes).
97
    ///
98
    /// The result of this fuction changes size based on the width of `usize`.
99
    #[must_use]
100
1
    pub const fn as_bytes(self) -> [u8; (usize::BITS / 8) as usize] {
101
1
        self.0.get().to_be_bytes()
102
1
    }
103

            
104
    /// Decodes `bytes` that were previously encoded with
105
    /// [`as_bytes()`](Self::as_bytes) and returns a `LotId` if it appears to be
106
    /// a valid ID.
107
    ///
108
    /// This function will "upgrade" previously encoded `LotId`s from
109
    /// architectures where `usize` is smaller than the current architecture.
110
    #[must_use]
111
5
    pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
112
5
        let usize = match bytes.try_into() {
113
2
            Ok(bytes) => usize::from_be_bytes(bytes),
114
3
            Err(_) => match bytes.len() {
115
1
                2 if usize::BITS >= 16 => {
116
2
                    expand_from_shorter::<16>(u16::from_be_bytes(array::from_fn(|index| {
117
2
                        bytes[index]
118
2
                    })) as usize)
119
                }
120
1
                4 if usize::BITS >= 32 => {
121
4
                    expand_from_shorter::<32>(u32::from_be_bytes(array::from_fn(|index| {
122
4
                        bytes[index]
123
4
                    })) as usize)
124
                }
125
1
                _ => return None,
126
            },
127
        };
128

            
129
4
        if usize >> Self::INDEX_BITS == 0 {
130
1
            None
131
        } else {
132
3
            Some(Self(NonZeroUsize::new(usize).assert("generation checked")))
133
        }
134
5
    }
135
}
136

            
137
trait Assert {
138
    type Unwrapped;
139
    fn assert(self, msg: &str) -> Self::Unwrapped;
140
}
141

            
142
impl<T, E> Assert for Result<T, E>
143
where
144
    E: Debug,
145
{
146
    type Unwrapped = T;
147

            
148
    fn assert(self, msg: &str) -> Self::Unwrapped {
149
        self.expect(msg)
150
    }
151
}
152

            
153
impl<T> Assert for Option<T> {
154
    type Unwrapped = T;
155

            
156
70
    fn assert(self, msg: &str) -> Self::Unwrapped {
157
70
        self.expect(msg)
158
70
    }
159
}
160

            
161
#[inline]
162
2
fn expand_from_shorter<const BITS: usize>(value: usize) -> usize {
163
2
    let index_bits = BITS / 4 * 3;
164
2
    let generation = value >> index_bits;
165
2
    let index = value & ((1 << index_bits) - 1);
166
2

            
167
2
    (generation << LotId::INDEX_BITS) | index
168
2
}
169

            
170
#[test]
171
1
fn invalid_ids() {
172
1
    assert!(LotId::new(Generation::first(), usize::MAX).is_none());
173
1
}
174

            
175
#[test]
176
1
fn lot_id_bytes() {
177
1
    let decoded =
178
1
        LotId::from_bytes(&LotId::new(Generation::first(), 2).unwrap().as_bytes()).unwrap();
179
1
    assert_eq!(decoded.generation().get(), 1);
180
1
    assert_eq!(decoded.index(), 2);
181

            
182
1
    let expanded = LotId::from_bytes(&0xF001_u16.to_be_bytes()).unwrap();
183
1
    assert_eq!(expanded.generation().get(), 15);
184
1
    assert_eq!(expanded.index(), 1);
185
1
    let expanded = LotId::from_bytes(&0xFF00_0001_u32.to_be_bytes()).unwrap();
186
1
    assert_eq!(expanded.generation().get(), 255);
187
1
    assert_eq!(expanded.index(), 1);
188

            
189
1
    assert_eq!(LotId::from_bytes(&[]), None);
190
1
    assert_eq!(LotId::from_bytes(&[0; (usize::BITS / 8) as usize]), None);
191
1
}
192

            
193
#[derive(Clone, Copy, Eq, PartialEq)]
194
struct Generation(NonZeroU16);
195

            
196
impl Generation {
197
    #[cfg(test)]
198
    const MAX: Self = Self(match NonZeroU16::new(LotId::GENERATION_MAX) {
199
        Some(nz) => nz,
200
        None => unreachable!(),
201
    });
202

            
203
    #[inline]
204
    #[must_use]
205
59
    pub const fn first() -> Self {
206
59
        Self(match NonZeroU16::new(1) {
207
59
            Some(one) => one,
208
            None => unreachable!(),
209
        })
210
59
    }
211

            
212
    #[inline]
213
    #[must_use]
214
    #[cfg_attr(target_pointer_width = "64", allow(clippy::absurd_extreme_comparisons))]
215
14
    pub const fn next(self) -> Self {
216
14
        match self.0.checked_add(1) {
217
13
            Some(next) if next.get() <= LotId::GENERATION_MAX => Self(next),
218
1
            _ => Self::first(),
219
        }
220
14
    }
221

            
222
    #[inline]
223
    #[must_use]
224
80
    pub const fn get(self) -> u16 {
225
80
        self.0.get()
226
80
    }
227
}
228

            
229
impl Debug for Generation {
230
5
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
231
5
        self.0.fmt(f)
232
5
    }
233
}
234

            
235
#[test]
236
1
fn generation_wrapping() {
237
1
    let max = Generation::MAX;
238
1
    assert_eq!(max.next(), Generation::first());
239
1
}