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