1use std::any::Any;
21use std::fmt;
22
23use crate::object_slab::{ObjectSlab, SlabRef};
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
29pub struct GcRef {
30 pub index: usize,
31}
32
33impl GcRef {
34 fn from_slab(sr: SlabRef) -> Self {
35 GcRef { index: sr.index }
36 }
37
38 fn to_slab(self) -> SlabRef {
39 SlabRef { index: self.index }
40 }
41}
42
43pub struct GcHeap {
48 slab: ObjectSlab,
49 collection_count: u64,
51}
52
53impl fmt::Debug for GcHeap {
54 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55 f.debug_struct("GcHeap")
56 .field("live_count", &self.slab.live_count())
57 .field("capacity", &self.slab.capacity())
58 .field("collection_count", &self.collection_count)
59 .finish()
60 }
61}
62
63impl GcHeap {
64 pub fn new(_collection_threshold: usize) -> Self {
67 GcHeap {
68 slab: ObjectSlab::new(),
69 collection_count: 0,
70 }
71 }
72
73 pub fn alloc<T: Any + 'static>(&mut self, value: T) -> GcRef {
75 GcRef::from_slab(self.slab.alloc(value))
76 }
77
78 pub fn alloc_auto<T: Any + 'static>(&mut self, value: T, _roots: &[GcRef]) -> GcRef {
81 self.alloc(value)
82 }
83
84 pub fn get<T: Any + 'static>(&self, gc_ref: GcRef) -> Option<&T> {
86 self.slab.get::<T>(gc_ref.to_slab())
87 }
88
89 pub fn collect(&mut self, _roots: &[GcRef]) {
91 self.collection_count += 1;
92 self.slab.collect_noop();
93 }
94
95 pub fn live_count(&self) -> usize {
97 self.slab.live_count()
98 }
99
100 pub fn capacity(&self) -> usize {
102 self.slab.capacity()
103 }
104
105 pub fn free_list(&self) -> &[usize] {
107 &self.slab.free_list
108 }
109
110 pub fn free(&mut self, gc_ref: GcRef) {
112 self.slab.free(gc_ref.to_slab());
113 }
114}
115
116impl Default for GcHeap {
117 fn default() -> Self {
118 Self::new(1024)
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125
126 #[test]
127 fn alloc_and_read_back() {
128 let mut heap = GcHeap::new(1024);
129 let r = heap.alloc(42i64);
130 assert_eq!(heap.get::<i64>(r), Some(&42));
131 assert_eq!(heap.live_count(), 1);
132 }
133
134 #[test]
135 fn collect_is_noop_objects_survive() {
136 let mut heap = GcHeap::new(1024);
137 let r1 = heap.alloc(10i64);
138 let r2 = heap.alloc(20i64);
139 heap.collect(&[r1]);
141 assert_eq!(heap.live_count(), 2, "RC keeps all objects alive");
142 assert_eq!(heap.get::<i64>(r1), Some(&10));
143 assert_eq!(heap.get::<i64>(r2), Some(&20));
144 }
145
146 #[test]
147 fn explicit_free_and_slot_reuse() {
148 let mut heap = GcHeap::new(1024);
149 let r1 = heap.alloc(1i64);
150 let r2 = heap.alloc(2i64);
151 let _r3 = heap.alloc(3i64);
152
153 heap.free(r2);
155 assert_eq!(heap.free_list().len(), 1);
156
157 let r4 = heap.alloc(4i64);
159 assert_eq!(r4.index, r2.index, "LIFO slot reuse");
160 assert_eq!(heap.get::<i64>(r4), Some(&4));
161 assert_eq!(heap.get::<i64>(r1), Some(&1));
162 }
163
164 #[test]
165 fn type_mismatch_returns_none() {
166 let mut heap = GcHeap::new(1024);
167 let r = heap.alloc(42i64);
168 assert_eq!(heap.get::<String>(r), None);
169 assert_eq!(heap.get::<i64>(r), Some(&42));
170 }
171
172 #[test]
173 fn alloc_auto_compat() {
174 let mut heap = GcHeap::new(2);
175 let r1 = heap.alloc(1i64);
176 let _ = heap.alloc(2i64);
177 let r3 = heap.alloc_auto(3i64, &[r1]);
179 assert_eq!(heap.get::<i64>(r1), Some(&1));
180 assert_eq!(heap.get::<i64>(r3), Some(&3));
181 assert_eq!(heap.live_count(), 3);
183 }
184
185 #[test]
186 fn deterministic_slot_order() {
187 let mut h1 = GcHeap::new(1024);
188 let mut h2 = GcHeap::new(1024);
189
190 let a1 = h1.alloc(10i64);
191 let a2 = h1.alloc(20i64);
192 h1.free(a1);
193 let a3 = h1.alloc(30i64);
194
195 let b1 = h2.alloc(10i64);
196 let b2 = h2.alloc(20i64);
197 h2.free(b1);
198 let b3 = h2.alloc(30i64);
199
200 assert_eq!(a1.index, b1.index);
201 assert_eq!(a2.index, b2.index);
202 assert_eq!(a3.index, b3.index, "LIFO reuse deterministic");
203 }
204}