Skip to main content

mtb_entity_slab/container/
list_base.rs

1//! Basic linked-list implementation for entity allocation, including
2//! header definitions, error handling, and range + iterator support.
3
4use crate::{IDBoundAlloc, IEntityAllocID, IPoliciedID};
5
6/// Lightweight head metadata stored on each list node.
7///
8/// This small struct holds the previous and next node IDs for a node that
9/// participates in either a linked list or a ring list. It is intended to be
10/// embedded in entity storage so that list linkage does not require separate
11/// allocation.
12///
13/// The `prev`/`next` options are `None` when the node is detached. In the
14/// ring-list sentinel case both fields point to the sentinel itself.
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub struct EntityListNodeHead<I: IPoliciedID> {
17    /// Previous node ID, if any.
18    pub prev: Option<I>,
19    /// Next node ID, if any.
20    pub next: Option<I>,
21}
22impl<I: IPoliciedID> EntityListNodeHead<I> {
23    /// Create a new empty (detached) node head.
24    pub fn none() -> Self {
25        Self {
26            prev: None,
27            next: None,
28        }
29    }
30    /// Create a new node head with given prev/next IDs.
31    pub fn new_full(prev: Option<I>, next: Option<I>) -> Self {
32        Self { prev, next }
33    }
34    /// Create a new node head from given prev/next IDs (both non-None).
35    pub fn from_id(prev: I, next: I) -> Self {
36        Self {
37            prev: Some(prev),
38            next: Some(next),
39        }
40    }
41    /// Decompose the node head into its prev/next IDs.
42    pub fn into_id(self) -> (Option<I>, Option<I>) {
43        (self.prev, self.next)
44    }
45
46    /// Get the previous node ID, if any.
47    pub fn get_prev(&self) -> Option<I> {
48        self.prev
49    }
50    /// Get the next node ID, if any.
51    pub fn get_next(&self) -> Option<I> {
52        self.next
53    }
54
55    fn prev(mut self, prev: Option<I>) -> Self {
56        self.prev = prev;
57        self
58    }
59    fn next(mut self, next: Option<I>) -> Self {
60        self.next = next;
61        self
62    }
63}
64
65/// Error kinds returned by `EntityList` operations.
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub enum EntityListError<I: IPoliciedID> {
68    /// The list is empty.
69    EmptyList,
70    /// The list structure is broken (inconsistent links).
71    ListBroken,
72    /// A sentinel node was used where a normal node was expected.
73    NodeIsSentinel,
74    /// The same node was added multiple times.
75    RepeatedNode,
76    /// Operations are done in the same list which is not allowed.
77    RepeatedList,
78    /// The node was expected to be attached but is not.
79    SelfNotAttached(I),
80    /// The node was expected to be detached but is attached.
81    ItemFalselyAttached(I),
82    /// The node was expected to be attached but is detached.
83    ItemFalselyDetached(I),
84}
85impl<I: IPoliciedID> std::fmt::Display for EntityListError<I> {
86    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87        std::fmt::Debug::fmt(&self, f)
88    }
89}
90impl<I: IPoliciedID> std::error::Error for EntityListError<I> {}
91
92/// Result type returned by list operations.
93///
94/// Uses `EntityListError<I>` as the error kind so callers can handle list
95/// specific failures (broken invariants, sentinel misuse, etc.).
96pub type EntityListRes<I, T = ()> = Result<T, EntityListError<I>>;
97
98/// Basic doublely-linked list ID concept. Provides basic header(`prev|next`) management,
99/// signals for list operations, and sentinel node support.
100pub trait IBasicEntityListID: IPoliciedID {
101    /// Load the `EntityListNodeHead` from the entity object.
102    fn obj_load_head(obj: &Self::ObjectT) -> EntityListNodeHead<Self>;
103    /// NOTE: Entity Lists are internally mutable
104    fn obj_store_head(obj: &Self::ObjectT, head: EntityListNodeHead<Self>);
105    /// Check if the entity object is a sentinel node.
106    fn obj_is_sentinel(obj: &Self::ObjectT) -> bool;
107    /// Create a new sentinel entity object.
108    fn new_sentinel_obj() -> Self::ObjectT;
109
110    /// Check if this node ID is a sentinel.
111    #[inline]
112    fn is_sentinel(self, alloc: &IDBoundAlloc<Self>) -> bool {
113        Self::obj_is_sentinel(self.deref_alloc(alloc))
114    }
115    /// Create a new sentinel node ID allocated from the given allocator.
116    fn new_sentinel(alloc: &IDBoundAlloc<Self>) -> Self {
117        let obj = Self::new_sentinel_obj();
118        Self::from_backend(Self::BackID::allocate_from(alloc, obj))
119    }
120
121    /// Signal: invoked when this node is being added after `prev` node.
122    fn on_push_prev(self, prev: Self, alloc: &IDBoundAlloc<Self>) -> EntityListRes<Self>;
123    /// Signal: invoked when this node is being added before `next` node.
124    fn on_push_next(self, next: Self, alloc: &IDBoundAlloc<Self>) -> EntityListRes<Self>;
125    /// Signal: invoked when this node is being unplugged from a list.
126    fn on_unplug(self, alloc: &IDBoundAlloc<Self>) -> EntityListRes<Self>;
127
128    /// Get the previous node ID, if any.
129    fn get_prev_id(self, alloc: &IDBoundAlloc<Self>) -> Option<Self> {
130        Self::obj_get_prev_id(self.deref_alloc(alloc))
131    }
132    /// Get the next node ID, if any.
133    fn get_next_id(self, alloc: &IDBoundAlloc<Self>) -> Option<Self> {
134        Self::obj_get_next_id(self.deref_alloc(alloc))
135    }
136    /// Check if this node is currently attached to a list.
137    fn is_attached(self, alloc: &IDBoundAlloc<Self>) -> bool {
138        Self::obj_is_attached(self.deref_alloc(alloc))
139    }
140}
141
142pub(super) trait NodeOps: IPoliciedID {
143    fn obj_get_prev_id(obj: &Self::ObjectT) -> Option<Self>;
144    fn obj_get_next_id(obj: &Self::ObjectT) -> Option<Self>;
145
146    fn obj_set_prev_id(obj: &Self::ObjectT, prev: Option<Self>);
147    fn obj_set_next_id(obj: &Self::ObjectT, next: Option<Self>);
148    fn obj_is_attached(obj: &Self::ObjectT) -> bool;
149
150    fn set_prev_id(self, alloc: &IDBoundAlloc<Self>, prev: Option<Self>) {
151        Self::obj_set_prev_id(self.deref_alloc(alloc), prev);
152    }
153    fn set_next_id(self, alloc: &IDBoundAlloc<Self>, next: Option<Self>) {
154        Self::obj_set_next_id(self.deref_alloc(alloc), next);
155    }
156}
157impl<I: IBasicEntityListID> NodeOps for I {
158    fn obj_get_prev_id(obj: &Self::ObjectT) -> Option<Self> {
159        Self::obj_load_head(obj).prev
160    }
161    fn obj_get_next_id(obj: &Self::ObjectT) -> Option<Self> {
162        Self::obj_load_head(obj).next
163    }
164
165    fn obj_set_prev_id(obj: &Self::ObjectT, prev: Option<Self>) {
166        let head = Self::obj_load_head(obj);
167        Self::obj_store_head(obj, head.prev(prev));
168    }
169    fn obj_set_next_id(obj: &Self::ObjectT, next: Option<Self>) {
170        let head = Self::obj_load_head(obj);
171        Self::obj_store_head(obj, head.next(next));
172    }
173
174    fn obj_is_attached(obj: &Self::ObjectT) -> bool {
175        let head = Self::obj_load_head(obj);
176        let is_sentinel = Self::obj_is_sentinel(obj);
177        let has_prev = head.prev.is_some();
178        let has_next = head.next.is_some();
179        if cfg!(debug_assertions) && !is_sentinel && has_prev != has_next {
180            panic!("EntityListNodeID: obj_is_attached: inconsistent attachment state detected");
181        }
182        has_prev || has_next
183    }
184}
185
186/// A partial-close range of an entity pointer list representing `[start, end)`.
187///
188/// - if `end is None`, the range is open-ended.
189/// - the `start` is non-nullable, meaning any range must have a valid start.
190#[derive(Debug, Clone, Copy, PartialEq, Eq)]
191pub struct EntityListRange<I: IBasicEntityListID> {
192    /// Start ID (inclusive).
193    pub start: I,
194    /// Optional end ID (exclusive). If `None`, the range is open-ended.
195    pub end: Option<I>,
196}
197impl<I: IBasicEntityListID> EntityListRange<I> {
198    /// Check if the range is empty.
199    pub fn is_empty(&self) -> bool {
200        Some(self.start) == self.end
201    }
202
203    /// Create an iterator over this range.
204    pub fn iter<'a>(&self, alloc: &'a IDBoundAlloc<I>) -> EntityListIter<'a, I> {
205        EntityListIter::new(*self, alloc)
206    }
207
208    /// count the length of this range.
209    pub fn count_len(&self, alloc: &IDBoundAlloc<I>) -> usize {
210        self.iter(alloc).count()
211    }
212
213    /// Create a debug view for this range.
214    pub fn debug<'a>(&self, alloc: &'a IDBoundAlloc<I>) -> EntityListRangeDebug<'a, I> {
215        EntityListRangeDebug {
216            alloc,
217            range: *self,
218        }
219    }
220}
221
222/// Iterator over a range of nodes in an `EntityList`.
223///
224/// Yields `(I, &I::ObjectT)` pairs where the ID identifies the node and the
225/// reference points to the underlying entity. The iterator borrows the
226/// `EntityAlloc` for the duration of the iteration and follows the `next`
227/// pointers stored inside node objects.
228pub struct EntityListIter<'alloc, I: IBasicEntityListID> {
229    pub(super) alloc: &'alloc IDBoundAlloc<I>,
230    pub(super) curr: Option<I>,
231    pub(super) end: Option<I>,
232}
233impl<'alloc, I: IBasicEntityListID> Iterator for EntityListIter<'alloc, I>
234where
235    I::ObjectT: 'alloc,
236{
237    type Item = (I, &'alloc I::ObjectT);
238
239    fn next(&mut self) -> Option<Self::Item> {
240        let curr = self.curr?;
241        if Some(curr) == self.end {
242            return None;
243        }
244        let obj = curr.deref_alloc(self.alloc);
245        self.curr = curr.get_next_id(self.alloc);
246        Some((curr, obj))
247    }
248}
249impl<'alloc, I: IBasicEntityListID> EntityListIter<'alloc, I> {
250    /// Create a new iterator over the given range.
251    pub fn new(range: EntityListRange<I>, alloc: &'alloc IDBoundAlloc<I>) -> Self {
252        Self {
253            alloc,
254            curr: Some(range.start),
255            end: range.end,
256        }
257    }
258}
259
260/// Debug view for an entity list range.
261pub struct EntityListRangeDebug<'a, I: IBasicEntityListID> {
262    /// Allocator reference.
263    pub alloc: &'a IDBoundAlloc<I>,
264    /// Range to debug.
265    pub range: EntityListRange<I>,
266}
267impl<'a, I> std::fmt::Debug for EntityListRangeDebug<'a, I>
268where
269    I: IBasicEntityListID,
270    I::ObjectT: std::fmt::Debug,
271{
272    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
273        let mut list_dbg = f.debug_list();
274        for (_id, obj) in self.range.iter(self.alloc) {
275            list_dbg.entry(obj);
276        }
277        list_dbg.finish()
278    }
279}