Skip to main content

mtb_entity_slab/
gen_index.rs

1use std::num::{NonZeroU16, NonZeroU64};
2
3/// Generation + index combined identifier used by the allocator.
4///
5/// The value stores a 16-bit generation in the low bits and a platform-sized
6/// real index in the high bits. Concretely this crate places the generation in
7/// the lower 16 bits and the real index in the upper bits of the `u64`
8/// storage. `GenIndex` is a compact handle that allows fast comparison while
9/// also providing a generation counter to detect use-after-free or stale
10/// references.
11///
12/// Key points:
13/// - Lower 16 bits: generation - to differentiate entities at the same slot across different lifetimes.
14/// - Upper bits: real index - the actual index (48 bits on 64-bit platforms)
15/// - `is_invalid()` checks if the index is a special invalid value (`INVALID_INDEX`).
16/// - `to_invalid()` replaces the real index with `INVALID_INDEX`, preserving generation info.
17/// - 使用 `generation_inc()` 在释放并重新分配同一槽位时递增 generation,避免 ABA 问题。
18///
19/// 注意:真实索引必须小于或等于 `INDEX_MASK`,否则行为未定义;该类型提供了 `compose`
20/// 和 `replace_index` 等安全构造器/替换方法。
21///
22/// 用例示例(简要):
23/// ```ignore
24/// let idx = GenIndex::compose(1234, 1);
25/// let bumped = idx.generation_inc();
26/// assert!(!idx.generation_match(bumped));
27/// ```
28#[repr(transparent)]
29#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
30pub struct GenIndex(pub NonZeroU64);
31impl From<NonZeroU64> for GenIndex {
32    fn from(value: NonZeroU64) -> Self {
33        GenIndex(value)
34    }
35}
36impl From<GenIndex> for NonZeroU64 {
37    fn from(value: GenIndex) -> Self {
38        value.0
39    }
40}
41impl From<GenIndex> for u64 {
42    fn from(value: GenIndex) -> Self {
43        value.0.get()
44    }
45}
46impl GenIndex {
47    #[cfg(not(any(target_pointer_width = "64", target_pointer_width = "32")))]
48    compile_error!("MTB::Entity only supports 32-bit and 64-bit platform.");
49
50    /// Bit shift amount for generation bits. ( = 0 )
51    pub const GEN_SHIFT: u32 = 0;
52
53    /// Bit mask for generation bits. ( = 0xFFFF )
54    pub const GEN_MASK: u64 = 0xFFFF;
55
56    /// Generation value representing the first generation (1).
57    /// Generation 0 is reserved to represent invalid indices.
58    pub const GEN_1: NonZeroU16 = NonZeroU16::new(1).unwrap();
59
60    /// Bit mask for real index bits.
61    pub const INDEX_MASK: u64 = !Self::GEN_MASK;
62
63    /// Bit shift amount for real index bits. ( = 16 )
64    pub const INDEX_SHIFT: u32 = Self::GEN_SHIFT + 16;
65
66    /// INVALID_INDEX is a special real index value (usize::MAX) used to
67    /// represent an invalid or null reference.
68    #[cfg(target_pointer_width = "32")]
69    pub const INVALID_INDEX: usize = usize::MAX;
70
71    /// On 64-bit platforms, we can use the full 48 bits for real index,
72    /// so INVALID_INDEX is 0xFFFFFFFFFFFF.
73    #[cfg(target_pointer_width = "64")]
74    pub const INVALID_INDEX: usize = 0xFFFF_FFFF_FFFF;
75
76    pub(crate) const fn do_generation_inc(gene: NonZeroU16) -> NonZeroU16 {
77        let gene = gene.get();
78        let gene = match gene.wrapping_add(1) {
79            0 => 1, // Skip zero to maintain non-zero invariant
80            other => other,
81        };
82        NonZeroU16::new(gene).expect("Generation must be non-zero")
83    }
84
85    /// Compose a `GenIndex` from a raw `real_index` and `generation`.
86    ///
87    /// 构造函数:从真实索引和世代值组合出 `GenIndex`。`real_index` 会被掩码截断,
88    /// 因此传入值应保证在 `INDEX_MASK` 范围内以避免信息丢失。
89    pub const fn compose(real_index: usize, generation: NonZeroU16) -> Self {
90        let real_index = real_index as u64;
91        let generation = generation.get() as u64;
92        let value = real_index << Self::INDEX_SHIFT | generation;
93        let value = NonZeroU64::new(value).expect("GenIndex value must be non-zero");
94        GenIndex(value)
95    }
96
97    /// Return the generation.
98    ///
99    /// 返回世代号
100    pub const fn generation(self) -> NonZeroU16 {
101        let gene = (self.0.get() & Self::GEN_MASK) as u16;
102        NonZeroU16::new(gene).expect("GenIndex generation must be non-zero")
103    }
104
105    /// Return the real index (high bits masked by `INDEX_MASK`).
106    ///
107    /// 返回真实索引(高位,已掩码)。
108    pub const fn real_index(self) -> usize {
109        ((self.0.get() & Self::INDEX_MASK) >> Self::INDEX_SHIFT) as usize
110    }
111    /// Decompose into `(real_index, generation)` tuple.
112    ///
113    /// 拆分为 `(真实索引, 世代)`。
114    pub const fn tear(self) -> (usize, NonZeroU16) {
115        (self.real_index(), self.generation())
116    }
117
118    /// Return a new `GenIndex` with generation incremented by one.
119    ///
120    /// 世代递增(使用 wrapping add),用于在槽位复用时更新世代号以避免允许旧引用访问。
121    pub const fn generation_inc(self) -> Self {
122        let (real_index, generation) = self.tear();
123        let new_generation = Self::do_generation_inc(generation);
124        Self::compose(real_index, new_generation)
125    }
126
127    /// Check whether two `GenIndex` values have the same generation.
128    ///
129    /// 比较两个索引的世代是否相同(不关心真实索引部分)。
130    pub const fn generation_match(self, other: Self) -> bool {
131        let lgene = self.0.get() & Self::GEN_MASK;
132        let rgene = other.0.get() & Self::GEN_MASK;
133        lgene == rgene
134    }
135
136    /// Replace the real-index portion while preserving the generation bits.
137    ///
138    /// 替换真实索引部分,同时保留世代位。这在需要重映射索引但保留
139    /// 当前世代信息时非常有用。`new_index` 将被掩码截断以适配位宽。
140    pub const fn replace_index(self, new_index: usize) -> Self {
141        Self::compose(new_index, self.generation())
142    }
143
144    /// Check whether this `GenIndex` represents an invalid sentinel index.
145    ///
146    /// 判定是否为特殊无效索引(`INVALID_INDEX`),通常用于表示无效/空引用。
147    pub const fn is_invalid(self) -> bool {
148        self.real_index() == Self::INVALID_INDEX
149    }
150
151    /// Convert this `GenIndex` into an invalid-index while preserving generation bits.
152    ///
153    /// 将真实索引替换为 `INVALID_INDEX`,保留世代信息。
154    pub const fn to_invalid(self) -> Self {
155        self.replace_index(Self::INVALID_INDEX)
156    }
157
158    /// Create a canonical invalid `GenIndex` with generation `1`.
159    ///
160    /// 返回一个基本的无效索引(世代 = 1)。
161    pub const fn new_basic_invalid() -> Self {
162        GenIndex::compose(Self::INVALID_INDEX, Self::GEN_1)
163    }
164}