1use bumpalo::Bump;
29
30pub type BumpVec<'a, T> = bumpalo::collections::Vec<'a, T>;
35
36pub const DEFAULT_ARENA_CAPACITY: usize = 256 * 1024;
38
39#[derive(Debug)]
58pub struct FrameArena {
59 bump: Bump,
60}
61
62impl FrameArena {
63 pub fn new(capacity: usize) -> Self {
69 Self {
70 bump: Bump::with_capacity(capacity),
71 }
72 }
73
74 pub fn with_default_capacity() -> Self {
76 Self::new(DEFAULT_ARENA_CAPACITY)
77 }
78
79 pub fn reset(&mut self) {
85 self.bump.reset();
86 }
87
88 pub fn alloc_str(&self, s: &str) -> &str {
93 self.bump.alloc_str(s)
94 }
95
96 pub fn alloc_fmt(&self, args: std::fmt::Arguments<'_>) -> &str {
101 use core::fmt::Write;
102 let mut s = bumpalo::collections::String::new_in(&self.bump);
103 s.write_fmt(args).expect("formatting into arena string");
104 s.into_bump_str()
105 }
106
107 pub fn alloc_slice<T: Copy>(&self, slice: &[T]) -> &[T] {
112 self.bump.alloc_slice_copy(slice)
113 }
114
115 pub fn alloc_with<T, F: FnOnce() -> T>(&self, f: F) -> &mut T {
120 self.bump.alloc_with(f)
121 }
122
123 pub fn alloc<T>(&self, val: T) -> &mut T {
128 self.bump.alloc(val)
129 }
130
131 pub fn alloc_iter<T, I>(&self, iter: I) -> &mut [T]
136 where
137 I: IntoIterator<Item = T>,
138 {
139 let mut vec = bumpalo::collections::Vec::new_in(&self.bump);
140 vec.extend(iter);
141 vec.into_bump_slice_mut()
142 }
143
144 pub fn new_vec<T>(&self) -> BumpVec<'_, T> {
149 bumpalo::collections::Vec::new_in(&self.bump)
150 }
151
152 pub fn new_vec_with_capacity<T>(&self, capacity: usize) -> BumpVec<'_, T> {
154 bumpalo::collections::Vec::with_capacity_in(capacity, &self.bump)
155 }
156
157 pub fn allocated_bytes(&self) -> usize {
159 self.bump.allocated_bytes()
160 }
161
162 pub fn allocated_bytes_including_metadata(&self) -> usize {
167 self.bump.allocated_bytes_including_metadata()
168 }
169
170 pub fn as_bump(&self) -> &Bump {
175 &self.bump
176 }
177}
178
179impl Default for FrameArena {
180 fn default() -> Self {
181 Self::with_default_capacity()
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188 use proptest::prelude::*;
189 use std::cell::Cell as DropCounter;
190 use std::mem::align_of;
191 use std::rc::Rc;
192
193 #[derive(Clone)]
194 struct DropSpy {
195 drops: Rc<DropCounter<usize>>,
196 }
197
198 impl Drop for DropSpy {
199 fn drop(&mut self) {
200 self.drops.set(self.drops.get() + 1);
201 }
202 }
203
204 #[test]
205 fn alloc_fmt_formats_into_arena() {
206 let arena = FrameArena::new(4096);
207 let s = arena.alloc_fmt(format_args!("hello {} {}", 42, "world"));
208 assert_eq!(s, "hello 42 world");
209 }
210
211 #[test]
212 fn alloc_iter_collects_to_arena() {
213 let arena = FrameArena::new(4096);
214 let data: Vec<u32> = (0..10).collect();
215 let slice = arena.alloc_iter(data.iter().copied());
216 assert_eq!(slice, &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
217 }
218
219 #[test]
220 fn alloc_iter_empty() {
221 let arena = FrameArena::new(4096);
222 let slice: &mut [u8] = arena.alloc_iter(std::iter::empty());
223 assert!(slice.is_empty());
224 }
225
226 #[test]
227 fn new_vec_push_and_read() {
228 let arena = FrameArena::new(4096);
229 let mut v = arena.new_vec::<u32>();
230 v.push(1);
231 v.push(2);
232 v.push(3);
233 assert_eq!(v.as_slice(), &[1, 2, 3]);
234 }
235
236 #[test]
237 fn new_vec_with_capacity_preallocates() {
238 let arena = FrameArena::new(4096);
239 let v = arena.new_vec_with_capacity::<u64>(100);
240 assert!(v.capacity() >= 100);
241 assert!(v.is_empty());
242 }
243
244 #[test]
245 fn new_creates_arena_with_capacity() {
246 let arena = FrameArena::new(1024);
247 let _s = arena.alloc_str("hello");
249 }
250
251 #[test]
252 fn default_uses_256kb() {
253 let arena = FrameArena::default();
254 let _s = arena.alloc_str("test");
255 }
256
257 #[test]
258 fn alloc_str_returns_correct_content() {
259 let arena = FrameArena::new(4096);
260 let s = arena.alloc_str("hello, world!");
261 assert_eq!(s, "hello, world!");
262 }
263
264 #[test]
265 fn alloc_str_empty() {
266 let arena = FrameArena::new(4096);
267 let s = arena.alloc_str("");
268 assert_eq!(s, "");
269 }
270
271 #[test]
272 fn alloc_str_unicode() {
273 let arena = FrameArena::new(4096);
274 let s = arena.alloc_str("こんにちは 🎉");
275 assert_eq!(s, "こんにちは 🎉");
276 }
277
278 #[test]
279 fn alloc_slice_copies_correctly() {
280 let arena = FrameArena::new(4096);
281 let data = [1u32, 2, 3, 4, 5];
282 let slice = arena.alloc_slice(&data);
283 assert_eq!(slice, &[1, 2, 3, 4, 5]);
284 }
285
286 #[test]
287 fn alloc_slice_empty() {
288 let arena = FrameArena::new(4096);
289 let slice: &[u8] = arena.alloc_slice(&[]);
290 assert!(slice.is_empty());
291 }
292
293 #[test]
294 fn alloc_slice_u8() {
295 let arena = FrameArena::new(4096);
296 let data = b"ANSI escape";
297 let slice = arena.alloc_slice(data.as_slice());
298 assert_eq!(slice, b"ANSI escape");
299 }
300
301 #[test]
302 fn alloc_with_constructs_value() {
303 let arena = FrameArena::new(4096);
304 let val = arena.alloc_with(|| 42u64);
305 assert_eq!(*val, 42);
306 }
307
308 #[test]
309 fn alloc_returns_mutable_ref() {
310 let arena = FrameArena::new(4096);
311 let val = arena.alloc(100i32);
312 assert_eq!(*val, 100);
313 *val = 200;
314 assert_eq!(*val, 200);
315 }
316
317 #[test]
318 fn reset_allows_reuse() {
319 let mut arena = FrameArena::new(4096);
320 let _s1 = arena.alloc_str("first frame data");
321 let bytes_before = arena.allocated_bytes();
322 assert!(bytes_before > 0);
323
324 arena.reset();
325
326 let _s2 = arena.alloc_str("second frame data");
328 }
329
330 #[test]
331 fn multiple_allocations_coexist() {
332 let arena = FrameArena::new(4096);
333 let s1 = arena.alloc_str("hello");
334 let s2 = arena.alloc_str("world");
335 let slice = arena.alloc_slice(&[1u32, 2, 3]);
336 let val = arena.alloc(42u64);
337
338 assert_eq!(s1, "hello");
340 assert_eq!(s2, "world");
341 assert_eq!(slice, &[1, 2, 3]);
342 assert_eq!(*val, 42);
343 }
344
345 #[test]
346 fn arena_grows_beyond_initial_capacity() {
347 let arena = FrameArena::new(64); let large = "a]".repeat(100);
350 let s = arena.alloc_str(&large);
351 assert_eq!(s, large);
352 }
353
354 #[test]
355 fn default_capacity_grows_beyond_256kb_without_panic() {
356 let arena = FrameArena::default();
357 let large = vec![0xAB; DEFAULT_ARENA_CAPACITY + 64 * 1024];
358 let s = arena.alloc_slice(&large);
359 assert_eq!(s.len(), large.len());
360 assert_eq!(s[0], 0xAB);
361 assert!(arena.allocated_bytes() >= DEFAULT_ARENA_CAPACITY);
362 }
363
364 #[test]
365 fn allocated_bytes_tracks_usage() {
366 let arena = FrameArena::new(4096);
367 let initial = arena.allocated_bytes();
368 let _s = arena.alloc_str("some text for tracking");
369 assert!(arena.allocated_bytes() >= initial);
370 }
371
372 #[test]
373 fn as_bump_provides_access() {
374 let arena = FrameArena::new(4096);
375 let bump = arena.as_bump();
376 let val = bump.alloc(99u32);
378 assert_eq!(*val, 99);
379 }
380
381 #[test]
382 fn reset_then_heavy_reuse() {
383 let mut arena = FrameArena::new(4096);
384 for frame in 0..100 {
385 let s = arena.alloc_str(&format!("frame {frame}"));
386 assert!(s.starts_with("frame "));
387 let data: Vec<u32> = (0..50).collect();
388 let slice = arena.alloc_slice(&data);
389 assert_eq!(slice.len(), 50);
390 arena.reset();
391 }
392 }
393
394 #[test]
395 fn allocations_respect_alignment_requirements() {
396 let arena = FrameArena::new(4096);
397
398 let p_u8 = arena.alloc(1u8) as *mut u8 as usize;
399 let p_u32 = arena.alloc(2u32) as *mut u32 as usize;
400 let p_u64 = arena.alloc(3u64) as *mut u64 as usize;
401 let p_u128 = arena.alloc(4u128) as *mut u128 as usize;
402
403 assert_eq!(p_u8 % align_of::<u8>(), 0);
404 assert_eq!(p_u32 % align_of::<u32>(), 0);
405 assert_eq!(p_u64 % align_of::<u64>(), 0);
406 assert_eq!(p_u128 % align_of::<u128>(), 0);
407 }
408
409 #[test]
410 fn reset_reuses_existing_chunks_without_extra_growth() {
411 let mut arena = FrameArena::new(128);
412 let payload = vec![7u8; 32 * 1024];
413
414 let first = arena.alloc_slice(&payload);
415 assert_eq!(first.len(), payload.len());
416 let grown = arena.allocated_bytes_including_metadata();
417 assert!(grown > 128);
418
419 arena.reset();
420
421 let second = arena.alloc_slice(&payload);
422 assert_eq!(second.len(), payload.len());
423 let after = arena.allocated_bytes_including_metadata();
424 assert!(
425 after <= grown + 1024,
426 "arena should reuse existing chunks after reset: before={grown}, after={after}"
427 );
428 }
429
430 #[test]
431 fn reset_does_not_run_drop_glue_for_allocated_values() {
432 let drops = Rc::new(DropCounter::new(0));
433 {
434 let mut arena = FrameArena::new(1024);
435 let _spy = arena.alloc(DropSpy {
436 drops: Rc::clone(&drops),
437 });
438 arena.reset();
439 assert_eq!(
440 drops.get(),
441 0,
442 "reset() must not run Drop for bump allocations"
443 );
444 }
445 assert_eq!(
446 drops.get(),
447 0,
448 "dropping arena must not run Drop for bump allocations"
449 );
450 }
451
452 #[test]
453 fn debug_impl() {
454 let arena = FrameArena::new(1024);
455 let debug = format!("{arena:?}");
456 assert!(debug.contains("FrameArena"));
457 }
458
459 proptest! {
460 #[test]
461 fn proptest_random_alloc_reset_sequences_never_panic(ops in prop::collection::vec((0u8..=3, 0u16..1024), 1..300)) {
462 let mut arena = FrameArena::new(256);
463 for (op, size_hint) in ops {
464 match op {
465 0 => {
466 let len = (size_hint as usize % 256) + 1;
467 let s = "x".repeat(len);
468 let alloc = arena.alloc_str(&s);
469 prop_assert_eq!(alloc.len(), len);
470 }
471 1 => {
472 let len = (size_hint as usize % 128) + 1;
473 let data = vec![size_hint as u32; len];
474 let alloc = arena.alloc_slice(&data);
475 prop_assert_eq!(alloc.len(), len);
476 }
477 2 => {
478 let value = arena.alloc(size_hint as u64);
479 prop_assert_eq!(*value, size_hint as u64);
480 }
481 _ => {
482 arena.reset();
483 }
484 }
485 }
486 }
487 }
488}