Skip to main content

mtb_entity_slab/
id.rs

1use std::{
2    fmt::{Debug, Formatter, LowerHex, Pointer, UpperHex},
3    hash::{Hash, Hasher},
4    marker::PhantomData,
5    ptr::NonNull,
6};
7
8use crate::{EntityAlloc, IAllocPolicy, chunk::Unit, gen_index::GenIndex};
9
10/// Trait for ID wrappers that carry an associated object type and allocation policy.
11///
12/// This trait provides a uniform interface for ID types used by the crate's
13/// APIs. An implementor represents an ID type that is bound to a concrete
14/// `ObjectT` and `PolicyT`. The `BackID` associated type is a backend ID that
15/// actually implements the conversion and dereference logic (for example
16/// `PtrID` or `IndexedID`).
17///
18/// Implementations must be `Copy` and `Eq`. The trait exposes helpers to
19/// convert to/from the backend representation and to dereference given an
20/// `IDBoundAlloc` (an `EntityAlloc` specialized to the ID's object/policy).
21///
22/// 该 trait 用于将不同的 ID 表示(指针型或索引型)抽象为统一的接口,绑定对象类型与分配策略,
23/// 便于上层 API 泛化处理 ID。
24pub trait IPoliciedID: Copy + Eq + Debug {
25    /// The object type this ID references.
26    type ObjectT: Sized;
27    /// The allocation policy this ID is associated with.
28    type PolicyT: IAllocPolicy;
29    /// The backend ID type that implements the actual logic.
30    type BackID: IEntityAllocID<Self::ObjectT, Self::PolicyT>;
31
32    /// Create this ID from its backend representation.
33    fn from_backend(ptr: Self::BackID) -> Self;
34    /// Convert this ID into its backend representation.
35    fn into_backend(self) -> Self::BackID;
36
37    /// Try to dereference the object this ID points to, returning `None` if invalid.
38    fn try_deref_alloc(self, alloc: &IDBoundAlloc<Self>) -> Option<&Self::ObjectT> {
39        self.into_backend().try_deref(alloc)
40    }
41    /// Try to mutably dereference the object this ID points to, returning `None` if invalid.
42    fn try_deref_alloc_mut(self, alloc: &mut IDBoundAlloc<Self>) -> Option<&mut Self::ObjectT> {
43        self.into_backend().try_deref_mut(alloc)
44    }
45
46    /// Dereference the object this ID points to, panicking if invalid.
47    fn deref_alloc(self, alloc: &IDBoundAlloc<Self>) -> &Self::ObjectT {
48        self.into_backend().deref(alloc)
49    }
50    /// Mutably dereference the object this ID points to, panicking if invalid.
51    fn deref_alloc_mut(self, alloc: &mut IDBoundAlloc<Self>) -> &mut Self::ObjectT {
52        self.into_backend().deref_mut(alloc)
53    }
54}
55
56/// Alias for `EntityAlloc` specialized to the object and policy types of an ID.
57///
58/// 常用作函数签名中把 `ID` 与对应的 `EntityAlloc` 进行绑定,避免每次都写出复杂的关联类型。
59pub type IDBoundAlloc<I> = EntityAlloc<<I as IPoliciedID>::ObjectT, <I as IPoliciedID>::PolicyT>;
60
61/// Trait implemented by concrete ID representations that can reference
62/// entities inside an `EntityAlloc`.
63///
64/// Responsibilities:
65/// - Convert from/to `PtrID` and `IndexedID` where possible.
66/// - Provide fallible dereference operations `try_deref` / `try_deref_mut`.
67/// - Provide allocation (`allocate_from`) and deallocation (`free`) helpers
68///   that tie the ID lifecycle to an `EntityAlloc` instance.
69///
70/// 中文说明:实现此 trait 的类型可作为对 `EntityAlloc` 中实体的引用,
71/// 支持在指针/索引与自身之间转换并提供(可选)解引用与生命周期绑定方法。
72pub trait IEntityAllocID<E, P: IAllocPolicy>: Sized + Copy {
73    /// Create an ID from a pointer-backed ID, validating generation/index.
74    fn from_ptr(alloc: &EntityAlloc<E, P>, ptr: PtrID<E, P>) -> Option<Self>;
75
76    /// Create an ID from an index-backed ID, validating generation/index.
77    fn from_index(alloc: &EntityAlloc<E, P>, indexed: IndexedID<E, P>) -> Option<Self>;
78
79    /// Try to dereference the entity this ID points to, returning `None` if invalid.
80    fn try_deref(self, alloc: &EntityAlloc<E, P>) -> Option<&E>;
81
82    /// Try to mutably dereference the entity this ID points to, returning `None` if invalid.
83    fn try_deref_mut(self, alloc: &mut EntityAlloc<E, P>) -> Option<&mut E>;
84
85    /// Convert this ID to an index-backed ID, validating generation/index.
86    fn to_index(self, alloc: &EntityAlloc<E, P>) -> Option<IndexedID<E, P>>;
87
88    /// Convert this ID to a pointer-backed ID, validating generation/index.
89    fn to_ptr(self, alloc: &EntityAlloc<E, P>) -> Option<PtrID<E, P>>;
90
91    /// Directly dereference the entity this ID points to, panicking if invalid.
92    #[inline]
93    fn deref(self, alloc: &EntityAlloc<E, P>) -> &E {
94        self.try_deref(alloc).expect("UAF detected!")
95    }
96    /// Directly mutably dereference the entity this ID points to, panicking if invalid.
97    #[inline]
98    fn deref_mut(self, alloc: &mut EntityAlloc<E, P>) -> &mut E {
99        self.try_deref_mut(alloc).expect("UAF detected!")
100    }
101
102    /// Allocate a new entity in the given allocator and return its ID.
103    /// NOTE that `MTB::Entity` library is ID-driven; allocation and deallocation
104    /// must be done through IDs.
105    fn allocate_from(alloc: &EntityAlloc<E, P>, val: E) -> Self;
106
107    /// Free the referenced entity and return its owned value if successful.
108    ///
109    /// 释放当前 ID 指向的实体并返回其值(若释放成功)。
110    fn free(self, alloc: &mut EntityAlloc<E, P>) -> Option<E>;
111}
112
113/// Pointer-backed ID referencing a `Unit<E>` inside an `EntityAlloc`.
114///
115/// `PtrID` is a thin, non-null wrapper around a raw `NonNull<Unit<E>>`. It is
116/// intended for fast, pointer-based access to entities. The struct carries a
117/// `PhantomData<P>` to associate the allocation policy at the type level.
118///
119/// Safety / 注意事项:
120/// - `PtrID` assumes the pointer points to a properly initialized `Unit<E>`.
121/// - Many operations are `unsafe` without validity checks; prefer the safe
122///   conversion methods in `IEntityAllocID`/`EntityAlloc` that validate
123///   generation/index when necessary.
124pub struct PtrID<E, P> {
125    pub(crate) ptr: NonNull<Unit<E>>,
126    _marker: PhantomData<P>,
127}
128impl<E, P> From<NonNull<Unit<E>>> for PtrID<E, P> {
129    fn from(ptr: NonNull<Unit<E>>) -> Self {
130        Self {
131            ptr,
132            _marker: PhantomData,
133        }
134    }
135}
136impl<E, P> From<&Unit<E>> for PtrID<E, P> {
137    fn from(unit: &Unit<E>) -> Self {
138        Self {
139            ptr: NonNull::from(unit),
140            _marker: PhantomData,
141        }
142    }
143}
144impl<E, P> Copy for PtrID<E, P> {}
145impl<E, P> Clone for PtrID<E, P> {
146    fn clone(&self) -> Self {
147        *self
148    }
149}
150impl<E, P> Debug for PtrID<E, P> {
151    #[inline]
152    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
153        Debug::fmt(&self.ptr, f)
154    }
155}
156impl<E, P> Pointer for PtrID<E, P> {
157    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
158        Pointer::fmt(&self.ptr, f)
159    }
160}
161
162impl<E, P> PartialEq for PtrID<E, P> {
163    #[inline]
164    fn eq(&self, other: &Self) -> bool {
165        self.ptr == other.ptr
166    }
167}
168impl<E, P> Eq for PtrID<E, P> {}
169impl<E, P> PartialOrd for PtrID<E, P> {
170    #[inline]
171    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
172        Some(self.cmp(other))
173    }
174}
175impl<E, P> Ord for PtrID<E, P> {
176    #[inline]
177    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
178        self.ptr.cmp(&other.ptr)
179    }
180}
181impl<E, P> Hash for PtrID<E, P> {
182    #[inline]
183    fn hash<H: Hasher>(&self, state: &mut H) {
184        self.ptr.hash(state);
185    }
186}
187unsafe impl<E: Send, P> Send for PtrID<E, P> {}
188unsafe impl<E: Sync, P> Sync for PtrID<E, P> {}
189impl<E, P: IAllocPolicy> IEntityAllocID<E, P> for PtrID<E, P> {
190    fn from_ptr(_: &EntityAlloc<E, P>, ptr: PtrID<E, P>) -> Option<Self> {
191        Some(ptr)
192    }
193    fn from_index(alloc: &EntityAlloc<E, P>, indexed: IndexedID<E, P>) -> Option<Self> {
194        let unit_ptr = alloc.unit_ptr_of_indexed(indexed.indexed).ok()?;
195        Some(Self::from(unit_ptr))
196    }
197    fn try_deref(self, alloc: &EntityAlloc<E, P>) -> Option<&E> {
198        if cfg!(debug_assertions) {
199            // 在 debug 模式下检查指针有效性
200            alloc.check_unit_validity(self.ptr.as_ptr()).ok()?;
201        }
202        unsafe { self.ptr.as_ref().as_init_ref() }
203    }
204    fn try_deref_mut(mut self, alloc: &mut EntityAlloc<E, P>) -> Option<&mut E> {
205        if cfg!(debug_assertions) {
206            // 在 debug 模式下检查指针有效性
207            alloc.check_unit_validity(self.ptr.as_ptr()).ok()?;
208        }
209        unsafe { self.ptr.as_mut().as_init_mut() }
210    }
211    fn to_index(self, alloc: &EntityAlloc<E, P>) -> Option<IndexedID<E, P>> {
212        let gen_index = alloc.index_of_unit_ptr(self.ptr.as_ptr()).ok()?;
213        Some(IndexedID::from(gen_index))
214    }
215    fn to_ptr(self, _alloc: &EntityAlloc<E, P>) -> Option<PtrID<E, P>> {
216        Some(self)
217    }
218
219    fn allocate_from(alloc: &EntityAlloc<E, P>, val: E) -> Self {
220        let ptr = match alloc.try_allocate_unit(val) {
221            Ok(ptr) => NonNull::new(ptr as *mut _).unwrap(),
222            Err(..) => panic!("Allocation failed in IEntityAllocID::allocate_from"),
223        };
224        PtrID {
225            ptr,
226            _marker: PhantomData,
227        }
228    }
229    fn free(self, alloc: &mut EntityAlloc<E, P>) -> Option<E> {
230        alloc.free_unit_ptr(self.ptr.as_ptr())
231    }
232}
233impl<E, P: IAllocPolicy> IPoliciedID for PtrID<E, P> {
234    type ObjectT = E;
235    type PolicyT = P;
236    type BackID = PtrID<E, P>;
237
238    fn from_backend(ptr: Self::BackID) -> Self {
239        ptr
240    }
241    fn into_backend(self) -> Self::BackID {
242        self
243    }
244}
245impl<E, P: IAllocPolicy> PtrID<E, P> {
246    /// Check whether this pointer currently refers to a valid allocated unit.
247    ///
248    /// Returns `true` if the pointer can be resolved to a live `GenIndex` in
249    /// the provided `alloc`. This performs the same generation/index check used
250    /// by other conversion helpers.
251    ///
252    /// 检查此指针在给定的 `EntityAlloc` 中是否仍然有效(未被释放或越界)。
253    pub fn check_validity(&self, alloc: &EntityAlloc<E, P>) -> bool {
254        alloc.index_of_unit_ptr(self.ptr.as_ptr()).is_ok()
255    }
256
257    /// Directly dereference the underlying unit, without validity check.
258    ///
259    /// # Safety
260    /// The caller must ensure the pointer is valid and the unit is initialized.
261    /// 不做有效性检查,调用者必须保证指针有效且单元已初始化。
262    pub unsafe fn direct_deref<'a>(self) -> Option<&'a E> {
263        unsafe { self.ptr.as_ref().as_init_ref() }
264    }
265
266    /// Directly dereference the underlying unit mutably, without validity check.
267    ///
268    /// # Safety
269    /// The caller must ensure exclusive access and that the pointer is valid.
270    /// 不做有效性检查,调用者必须保证对该内存拥有唯一可变访问权。
271    pub unsafe fn direct_deref_mut<'a>(mut self) -> Option<&'a mut E> {
272        unsafe { self.ptr.as_mut().as_init_mut() }
273    }
274}
275
276/// Index-backed ID storing a `GenIndex` (real index + generation).
277///
278/// `IndexedID` is a compact representation suitable for serialization,
279/// hashing, and comparisons. It stores the `GenIndex` produced by the allocator
280/// and keeps a `PhantomData` to tie the ID to the element and policy types.
281///
282/// 中文说明:索引型 ID,包含 `GenIndex` 用于表达槽位与世代信息,适合哈希、比较与持久化场景。
283#[repr(C)]
284pub struct IndexedID<E, P> {
285    /// The underlying `GenIndex` representing the indexed entity.
286    pub indexed: GenIndex,
287    _marker: PhantomData<(E, P)>,
288}
289impl<E, P> From<GenIndex> for IndexedID<E, P> {
290    fn from(indexed: GenIndex) -> Self {
291        Self {
292            indexed,
293            _marker: PhantomData,
294        }
295    }
296}
297impl<E, P> Copy for IndexedID<E, P> {}
298impl<E, P> Clone for IndexedID<E, P> {
299    fn clone(&self) -> Self {
300        *self
301    }
302}
303impl<E, P> Debug for IndexedID<E, P> {
304    #[inline]
305    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
306        let (real, gene) = self.indexed.tear();
307        write!(f, "IndexedID({real:x} gen {gene})")
308    }
309}
310impl<E, P> Pointer for IndexedID<E, P> {
311    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
312        write!(f, "{:#x}", u64::from(self.indexed))
313    }
314}
315impl<E, P> LowerHex for IndexedID<E, P> {
316    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
317        LowerHex::fmt(&u64::from(self.indexed), f)
318    }
319}
320impl<E, P> UpperHex for IndexedID<E, P> {
321    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
322        UpperHex::fmt(&u64::from(self.indexed), f)
323    }
324}
325impl<E, P> PartialEq for IndexedID<E, P> {
326    #[inline]
327    fn eq(&self, other: &Self) -> bool {
328        self.indexed == other.indexed
329    }
330}
331impl<E, P> Eq for IndexedID<E, P> {}
332impl<E, P> PartialOrd for IndexedID<E, P> {
333    #[inline]
334    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
335        Some(self.cmp(other))
336    }
337}
338impl<E, P> Ord for IndexedID<E, P> {
339    #[inline]
340    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
341        self.indexed.cmp(&other.indexed)
342    }
343}
344impl<E, P> Hash for IndexedID<E, P> {
345    #[inline]
346    fn hash<H: Hasher>(&self, state: &mut H) {
347        self.indexed.hash(state);
348    }
349}
350#[cfg(feature = "serde")]
351/// Indexed ID Serialize Syntax: string literal `{ID:x}:{Gen:x}`
352impl<E, P> serde_core::Serialize for IndexedID<E, P> {
353    fn serialize<S: serde_core::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
354        use std::fmt::Write;
355        #[derive(Default)]
356        struct Buffer {
357            buf: [u8; 32],
358            len: usize,
359        }
360        impl Buffer {
361            fn as_str(&self) -> &str {
362                // We only ever write valid UTF-8 via `write_str`, so this is safe.
363                unsafe { std::str::from_utf8_unchecked(&self.buf[..self.len]) }
364            }
365        }
366        impl Write for Buffer {
367            fn write_str(&mut self, s: &str) -> std::fmt::Result {
368                let bytes = s.as_bytes();
369                let avail = self.buf.len().saturating_sub(self.len);
370                if bytes.len() > avail {
371                    return Err(std::fmt::Error);
372                }
373                let dst = &mut self.buf[self.len..self.len + bytes.len()];
374                dst.copy_from_slice(bytes);
375                self.len += bytes.len();
376                Ok(())
377            }
378        }
379
380        let (real, gene) = self.indexed.tear();
381        let mut buf = Buffer::default();
382        write!(&mut buf, "{real:x}:{gene:x}").unwrap();
383        serializer.serialize_str(buf.as_str())
384    }
385}
386#[cfg(feature = "serde")]
387impl<'de, E, P> serde_core::Deserialize<'de> for IndexedID<E, P> {
388    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
389    where
390        D: serde_core::Deserializer<'de>,
391    {
392        use serde_core::de::Error as _;
393
394        let s = <&str>::deserialize(deserializer)?;
395        let mut parts = s.splitn(2, ':');
396        let real_str = parts
397            .next()
398            .ok_or_else(|| D::Error::custom("missing real index"))?;
399        let gen_str = parts
400            .next()
401            .ok_or_else(|| D::Error::custom("missing generation"))?;
402
403        let real_u64 = u64::from_str_radix(real_str, 16)
404            .map_err(|e| D::Error::custom(format!("invalid real index: {e}")))?;
405        let gen_u64 = u64::from_str_radix(gen_str, 16)
406            .map_err(|e| D::Error::custom(format!("invalid generation: {e}")))?;
407
408        if gen_u64 > u16::MAX as u64 {
409            return Err(D::Error::custom("generation out of range"));
410        }
411
412        let real_usize = real_u64 as usize;
413        let generation = gen_u64 as u16;
414
415        Ok(IndexedID::from(GenIndex::compose(real_usize, generation)))
416    }
417}
418
419impl<E, P: IAllocPolicy> IEntityAllocID<E, P> for IndexedID<E, P> {
420    fn from_ptr(alloc: &EntityAlloc<E, P>, ptr: PtrID<E, P>) -> Option<Self> {
421        let gen_index = alloc.index_of_unit_ptr(ptr.ptr.as_ptr()).ok()?;
422        Some(IndexedID::from(gen_index))
423    }
424    fn from_index(_: &EntityAlloc<E, P>, indexed: IndexedID<E, P>) -> Option<Self> {
425        Some(indexed)
426    }
427
428    fn try_deref(self, alloc: &EntityAlloc<E, P>) -> Option<&E> {
429        let unit_ptr = alloc.unit_ptr_of_indexed(self.indexed).ok()?;
430        unsafe { unit_ptr.as_ref().as_init_ref() }
431    }
432    fn try_deref_mut(self, alloc: &mut EntityAlloc<E, P>) -> Option<&mut E> {
433        let mut unit_ptr = alloc.unit_ptr_of_indexed(self.indexed).ok()?;
434        unsafe { unit_ptr.as_mut().as_init_mut() }
435    }
436    fn to_index(self, _: &EntityAlloc<E, P>) -> Option<IndexedID<E, P>> {
437        Some(self)
438    }
439    fn to_ptr(self, alloc: &EntityAlloc<E, P>) -> Option<PtrID<E, P>> {
440        let unit_ptr = alloc.unit_ptr_of_indexed(self.indexed).ok()?;
441        Some(PtrID::from(unit_ptr))
442    }
443
444    fn allocate_from(alloc: &EntityAlloc<E, P>, val: E) -> Self {
445        let Ok(unit) = alloc.try_allocate_unit(val) else {
446            panic!("Allocation failed in IEntityAllocID::allocate_from");
447        };
448        unsafe { Self::from(unit.as_ref().unwrap().indexed) }
449    }
450    fn free(self, alloc: &mut EntityAlloc<E, P>) -> Option<E> {
451        alloc.free_gen_index(self.indexed)
452    }
453}
454impl<E, P: IAllocPolicy> IPoliciedID for IndexedID<E, P> {
455    type ObjectT = E;
456    type PolicyT = P;
457    type BackID = IndexedID<E, P>;
458
459    fn from_backend(ptr: Self::BackID) -> Self {
460        ptr
461    }
462    fn into_backend(self) -> Self::BackID {
463        self
464    }
465}
466impl<E, P: IAllocPolicy> IndexedID<E, P> {
467    /// Create an `IndexedID` that represents the next index the allocator would allocate.
468    ///
469    /// 注意:这只是对分配器内部 `next_index` 的封装,实际分配仍需调用分配接口。
470    pub fn next_to_alloc(alloc: &EntityAlloc<E, P>) -> Self {
471        Self::from(alloc.next_index())
472    }
473
474    /// Return the generation stored in this `IndexedID`.
475    ///
476    /// 返回索引中的世代号(用于检测过期的引用)。
477    pub fn get_generation(self) -> u16 {
478        self.indexed.generation()
479    }
480
481    /// Return the real-order/index portion of this `IndexedID`.
482    ///
483    /// 返回索引的真实位置,用于定位在分配器内部的槽位。
484    pub fn get_order(self) -> usize {
485        self.indexed.real_index()
486    }
487}