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