cjc_runtime/
object_slab.rs1use std::any::Any;
16use std::cell::RefCell;
17use std::fmt;
18use std::rc::Rc;
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
25pub struct SlabRef {
26 pub index: usize,
27}
28
29struct SlabObject {
31 value: Rc<RefCell<Box<dyn Any>>>,
32}
33
34impl fmt::Debug for SlabObject {
35 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36 f.debug_struct("SlabObject")
37 .field("rc_count", &Rc::strong_count(&self.value))
38 .field("value", &"<dyn Any>")
39 .finish()
40 }
41}
42
43#[derive(Debug)]
49pub struct ObjectSlab {
50 objects: Vec<Option<SlabObject>>,
51 pub free_list: Vec<usize>,
53 alloc_count: usize,
55}
56
57impl ObjectSlab {
58 pub fn new() -> Self {
60 ObjectSlab {
61 objects: Vec::new(),
62 free_list: Vec::new(),
63 alloc_count: 0,
64 }
65 }
66
67 pub fn alloc<T: Any + 'static>(&mut self, value: T) -> SlabRef {
71 let obj = SlabObject {
72 value: Rc::new(RefCell::new(Box::new(value) as Box<dyn Any>)),
73 };
74
75 let index = if let Some(idx) = self.free_list.pop() {
76 self.objects[idx] = Some(obj);
77 idx
78 } else {
79 self.objects.push(Some(obj));
80 self.objects.len() - 1
81 };
82
83 self.alloc_count += 1;
84 SlabRef { index }
85 }
86
87 pub fn get<T: Any + 'static>(&self, slab_ref: SlabRef) -> Option<&T> {
90 self.objects
91 .get(slab_ref.index)
92 .and_then(|slot| slot.as_ref())
93 .and_then(|obj| {
94 let borrowed = obj.value.try_borrow().ok()?;
98 let any_ref: &dyn Any = &**borrowed;
101 let ptr = any_ref as *const dyn Any;
105 unsafe { &*ptr }.downcast_ref::<T>()
106 })
107 }
108
109 pub fn get_mut<T: Any + 'static>(&self, slab_ref: SlabRef) -> Option<std::cell::RefMut<'_, Box<dyn Any>>> {
111 self.objects
112 .get(slab_ref.index)
113 .and_then(|slot| slot.as_ref())
114 .and_then(|obj| obj.value.try_borrow_mut().ok())
115 }
116
117 pub fn live_count(&self) -> usize {
119 self.objects.iter().filter(|s| s.is_some()).count()
120 }
121
122 pub fn capacity(&self) -> usize {
124 self.objects.len()
125 }
126
127 pub fn alloc_count(&self) -> usize {
129 self.alloc_count
130 }
131
132 pub fn free(&mut self, slab_ref: SlabRef) {
137 if let Some(slot) = self.objects.get_mut(slab_ref.index) {
138 if slot.is_some() {
139 *slot = None;
140 self.free_list.push(slab_ref.index);
141 }
142 }
143 }
144
145 pub fn collect_noop(&self) {
151 }
153}
154
155impl Default for ObjectSlab {
156 fn default() -> Self {
157 Self::new()
158 }
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164
165 #[test]
166 fn alloc_and_read_back() {
167 let mut slab = ObjectSlab::new();
168 let r = slab.alloc(42i64);
169 assert_eq!(slab.get::<i64>(r), Some(&42));
170 assert_eq!(slab.live_count(), 1);
171 }
172
173 #[test]
174 fn free_and_slot_reuse() {
175 let mut slab = ObjectSlab::new();
176 let r1 = slab.alloc(1i64);
177 let r2 = slab.alloc(2i64);
178 assert_eq!(slab.capacity(), 2);
179
180 slab.free(r2);
182 assert_eq!(slab.live_count(), 1);
183 assert_eq!(slab.free_list.len(), 1);
184
185 let r3 = slab.alloc(3i64);
187 assert_eq!(r3.index, r2.index, "LIFO reuse");
188 assert_eq!(slab.capacity(), 2, "no new slots");
189 assert_eq!(slab.get::<i64>(r3), Some(&3));
190 assert_eq!(slab.get::<i64>(r1), Some(&1));
191 }
192
193 #[test]
194 fn type_mismatch_returns_none() {
195 let mut slab = ObjectSlab::new();
196 let r = slab.alloc(42i64);
197 assert_eq!(slab.get::<String>(r), None);
198 assert_eq!(slab.get::<i64>(r), Some(&42));
199 }
200
201 #[test]
202 fn collect_noop_is_harmless() {
203 let mut slab = ObjectSlab::new();
204 let r1 = slab.alloc(1i64);
205 slab.collect_noop();
206 assert_eq!(slab.live_count(), 1);
208 assert_eq!(slab.get::<i64>(r1), Some(&1));
209 }
210
211 #[test]
212 fn deterministic_slot_order() {
213 let mut slab1 = ObjectSlab::new();
215 let mut slab2 = ObjectSlab::new();
216
217 let a1 = slab1.alloc(10i64);
218 let a2 = slab1.alloc(20i64);
219 let a3 = slab1.alloc(30i64);
220 slab1.free(a2);
221 let a4 = slab1.alloc(40i64);
222
223 let b1 = slab2.alloc(10i64);
224 let b2 = slab2.alloc(20i64);
225 let b3 = slab2.alloc(30i64);
226 slab2.free(b2);
227 let b4 = slab2.alloc(40i64);
228
229 assert_eq!(a1.index, b1.index);
230 assert_eq!(a2.index, b2.index);
231 assert_eq!(a3.index, b3.index);
232 assert_eq!(a4.index, b4.index, "LIFO reuse deterministic");
233 }
234
235 #[test]
236 fn alloc_count_tracking() {
237 let mut slab = ObjectSlab::new();
238 assert_eq!(slab.alloc_count(), 0);
239 let _ = slab.alloc(1i64);
240 let _ = slab.alloc(2i64);
241 assert_eq!(slab.alloc_count(), 2);
242 }
243}