Skip to main content

mtb_entity_slab/container/
entity_list.rs

1//! Doubly-linked list over entities stored in an entity allocator.
2//!
3//! This module provides doubly linked list structure that operate on entities
4//! managed by `EntityAlloc`. The list support various operations such as insertion,
5//! removal, and traversal.
6//!
7//! The list is generic over entity ID types that implement the
8//! `IEntityListNodeID` trait, allowing flexible integration with different
9//! entity types (PtrID, IndexedID or anything defined by yourself).
10//!
11//! The list also support signaling mechanisms to notify entities
12//! when they are added to or removed from a list, enabling custom
13//! behaviors during these operations.
14//!
15//! # Safety
16//!
17//! These lists rely on the correctness of the entity allocator
18//! and the entity ID implementations. Improper use may lead to
19//! undefined behavior, such as broken lists or memory corruption.
20
21use std::cell::Cell;
22
23use crate::{
24    EntityListError, EntityListIter, EntityListNodeHead, EntityListRange, EntityListRes,
25    IBasicEntityListID, IDBoundAlloc, container::list_base::NodeOps,
26};
27
28/// Trait required for an entity ID to be used as a node in `EntityList`.
29///
30/// Implementors must provide accessors to load/store the embedded
31/// `EntityListNodeHead` inside the entity object, create sentinel objects,
32/// and handle optional push/unplug signals. This trait lets the list logic
33/// remain generic over different ID representations (pointer-based or
34/// index-based).
35pub trait IEntityListNodeID: IBasicEntityListID {
36    /// Check whether this node comes before another node in the same list.
37    fn comes_before(self, latter: Self, alloc: &IDBoundAlloc<Self>) -> EntityListRes<Self, bool> {
38        let mut node = self;
39        while let Some(next) = node.get_next_id(alloc) {
40            if next == latter {
41                return Ok(true);
42            }
43            node = next;
44        }
45        if node.is_sentinel(alloc) {
46            Ok(false)
47        } else {
48            Err(EntityListError::ListBroken)
49        }
50    }
51}
52
53/// Doubly-linked list view over entities stored in an `EntityAlloc`.
54///
55/// Logical structure: the list uses head/tail sentinel nodes (allocated via
56/// the entity allocator) and maintains `EntityListNodeHead` inside each
57/// entity to chain nodes. Physical structure: no extra heap nodes are
58/// allocated—linkage is stored inline within entity storage.
59///
60/// The list invokes signals (`on_push_*`, `on_unplug`) on nodes before
61/// modifying links; these signals may veto operations by returning an error.
62/// Many operations assume the node is not already attached to another list;
63/// violating that assumption may lead to an inconsistent list (reported as
64/// `ListBroken`).
65pub struct EntityList<I: IEntityListNodeID> {
66    /// Head sentinel node ID.
67    pub head: I,
68    /// Tail sentinel node ID.
69    pub tail: I,
70    /// Number of nodes in the list (excluding sentinels).
71    pub len: Cell<usize>,
72}
73
74impl<I: IEntityListNodeID> EntityList<I> {
75    /// create a new empty list with head and tail sentinels linked
76    /// with each other.
77    pub fn new(alloc: &IDBoundAlloc<I>) -> Self {
78        let head = I::new_sentinel(alloc);
79        let tail = I::new_sentinel(alloc);
80        head.set_next_id(alloc, Some(tail));
81        tail.set_prev_id(alloc, Some(head));
82        Self {
83            head,
84            tail,
85            len: Cell::new(0),
86        }
87    }
88
89    /// get the number of nodes in the list.
90    #[inline]
91    pub fn len(&self) -> usize {
92        self.len.get()
93    }
94    /// check if the list is empty.
95    #[inline]
96    pub fn is_empty(&self) -> bool {
97        self.len() == 0
98    }
99
100    /// try to get the front node ID, or None if the list is empty
101    #[inline]
102    pub fn get_front_id(&self, alloc: &IDBoundAlloc<I>) -> Option<I> {
103        let next = self.head.get_next_id(alloc)?;
104        if next == self.tail { None } else { Some(next) }
105    }
106    /// try to get the back node ID, or None if the list is empty
107    #[inline]
108    pub fn get_back_id(&self, alloc: &IDBoundAlloc<I>) -> Option<I> {
109        let prev = self.tail.get_prev_id(alloc)?;
110        if prev == self.head { None } else { Some(prev) }
111    }
112
113    /// Add a new node after an existing node linked to `this` list.
114    ///
115    /// If the current node is linked to another list, then the operation is undefined.
116    /// You'll get a broken list without reporting an error.
117    ///
118    /// ### Signal invocation
119    ///
120    /// `node_add_next` will invoke `on_push_next` on `node` before modifying any links.
121    /// If the signal returns an error, the operation is aborted without modifying any links.
122    pub fn node_add_next(&self, node: I, new_node: I, alloc: &IDBoundAlloc<I>) -> EntityListRes<I> {
123        if node == new_node {
124            return Err(EntityListError::RepeatedNode);
125        }
126        if node == self.tail {
127            return Err(EntityListError::NodeIsSentinel);
128        }
129        node.on_push_next(new_node, alloc)?;
130
131        let node_obj = node.deref_alloc(alloc);
132        let new_node_obj = new_node.deref_alloc(alloc);
133
134        debug_assert!(
135            I::obj_is_attached(node_obj),
136            "Cannot add next to a detached node"
137        );
138        debug_assert!(
139            !I::obj_is_attached(new_node_obj),
140            "Cannot add a node that is already attached"
141        );
142
143        let Some(old_next) = I::obj_get_next_id(node_obj) else {
144            return Err(EntityListError::ListBroken);
145        };
146        I::obj_set_next_id(node_obj, Some(new_node));
147        I::obj_store_head(new_node_obj, EntityListNodeHead::from_id(node, old_next));
148        old_next.set_prev_id(alloc, Some(new_node));
149        self.len.set(self.len.get() + 1);
150        Ok(())
151    }
152
153    /// Add a new node before an existing node linked to `this` list.
154    ///
155    /// If the current node is linked to another list, then the operation is undefined.
156    /// You'll get a broken list without reporting an error.
157    ///
158    /// ### Signal invocation
159    ///
160    /// `node_add_prev` will invoke `on_push_prev` on `node` before modifying any links.
161    /// If the signal returns an error, the operation is aborted without modifying any links.
162    pub fn node_add_prev(&self, node: I, new_node: I, alloc: &IDBoundAlloc<I>) -> EntityListRes<I> {
163        if node == new_node {
164            return Err(EntityListError::RepeatedNode);
165        }
166        if node == self.head {
167            return Err(EntityListError::NodeIsSentinel);
168        }
169        node.on_push_prev(new_node, alloc)?;
170
171        let node_obj = node.deref_alloc(alloc);
172        let new_node_obj = new_node.deref_alloc(alloc);
173
174        debug_assert!(
175            I::obj_is_attached(node_obj),
176            "Cannot add prev to a detached node"
177        );
178        debug_assert!(
179            !I::obj_is_attached(new_node_obj),
180            "Cannot add a node that is already attached"
181        );
182
183        let Some(old_prev) = I::obj_get_prev_id(node_obj) else {
184            return Err(EntityListError::ListBroken);
185        };
186        NodeOps::obj_set_prev_id(node_obj, Some(new_node));
187        I::obj_store_head(new_node_obj, EntityListNodeHead::from_id(old_prev, node));
188        old_prev.set_next_id(alloc, Some(new_node));
189        self.len.set(self.len.get() + 1);
190        Ok(())
191    }
192
193    /// Unplug a node from the list.
194    ///
195    /// ### Signal invocation
196    ///
197    /// `node_unplug` will invoke `on_unplug` on `node` before modifying any links.
198    /// If the signal returns an error, the operation is aborted without modifying any links.
199    pub fn node_unplug(&self, node: I, alloc: &IDBoundAlloc<I>) -> EntityListRes<I> {
200        if node == self.head || node == self.tail {
201            return Err(EntityListError::NodeIsSentinel);
202        }
203        node.on_unplug(alloc)?;
204
205        let node_obj = node.deref_alloc(alloc);
206
207        debug_assert!(
208            I::obj_is_attached(node_obj),
209            "Cannot unplug a detached node"
210        );
211
212        let Some(prev) = I::obj_get_prev_id(node_obj) else {
213            return Err(EntityListError::ListBroken);
214        };
215        let Some(next) = I::obj_get_next_id(node_obj) else {
216            return Err(EntityListError::ListBroken);
217        };
218        prev.set_next_id(alloc, Some(next));
219        next.set_prev_id(alloc, Some(prev));
220        I::obj_store_head(node_obj, EntityListNodeHead::none());
221        self.len.set(self.len.get() - 1);
222        Ok(())
223    }
224
225    /// Push a new node to the back of the list.
226    #[inline]
227    pub fn push_back_id(&self, new_node: I, alloc: &IDBoundAlloc<I>) -> EntityListRes<I> {
228        self.node_add_prev(self.tail, new_node, alloc)
229    }
230
231    /// Push a new node to the front of the list.
232    #[inline]
233    pub fn push_front_id(&self, new_node: I, alloc: &IDBoundAlloc<I>) -> EntityListRes<I> {
234        self.node_add_next(self.head, new_node, alloc)
235    }
236
237    /// Pop a node from the back of the list.
238    pub fn pop_back(&self, alloc: &IDBoundAlloc<I>) -> EntityListRes<I, I> {
239        let back_id = self.get_back_id(alloc).ok_or(EntityListError::EmptyList)?;
240        self.node_unplug(back_id, alloc)?;
241        Ok(back_id)
242    }
243
244    /// Pop a node from the front of the list.
245    pub fn pop_front(&self, alloc: &IDBoundAlloc<I>) -> EntityListRes<I, I> {
246        let front_id = self.get_front_id(alloc).ok_or(EntityListError::EmptyList)?;
247        self.node_unplug(front_id, alloc)?;
248        Ok(front_id)
249    }
250
251    /// Get a range covering all nodes in the list (excluding sentinels).
252    pub fn get_range(&self, alloc: &IDBoundAlloc<I>) -> EntityListRange<I> {
253        EntityListRange {
254            start: self.head.get_next_id(alloc).unwrap(),
255            end: Some(self.tail),
256        }
257    }
258    /// Get a range covering all nodes in the list (including sentinels).
259    pub fn get_range_with_sentinels(&self) -> EntityListRange<I> {
260        EntityListRange {
261            start: self.head,
262            end: None,
263        }
264    }
265
266    /// Create an iterator over all nodes in the list (excluding sentinels).
267    pub fn iter<'alloc>(&self, alloc: &'alloc IDBoundAlloc<I>) -> EntityListIter<'alloc, I> {
268        EntityListIter::new(self.get_range(alloc), alloc)
269    }
270    /// Create an iterator over all nodes in the list (including sentinels).
271    pub fn iter_with_sentinels<'alloc>(
272        &self,
273        alloc: &'alloc IDBoundAlloc<I>,
274    ) -> EntityListIter<'alloc, I> {
275        EntityListIter::new(self.get_range_with_sentinels(), alloc)
276    }
277
278    /// Apply a function to all nodes in the list (including sentinels).
279    pub fn forall_with_sentinel(
280        &self,
281        alloc: &IDBoundAlloc<I>,
282        mut f: impl FnMut(I, &I::ObjectT) -> EntityListRes<I>,
283    ) -> EntityListRes<I, ()> {
284        let mut curr = self.head;
285        loop {
286            f(curr, curr.deref_alloc(alloc))?;
287            if curr == self.tail {
288                break;
289            }
290            let next = curr.get_next_id(alloc).ok_or(EntityListError::ListBroken)?;
291            curr = next;
292        }
293        Ok(())
294    }
295}