Skip to main content

FrameArena

Struct FrameArena 

Source
pub struct FrameArena { /* private fields */ }
Expand description

A fixed-capacity bump allocator for per-frame data.

§Growth policy

The arena never grows. Exhaustion is a hard error: the allocating methods panic and the try_ variants return None. Two reasons:

  • A frame arena that grows on demand cannot be distinguished from a leak. The whole point is that per-frame allocation is bounded; if the bound is soft, the first system that forgets to clamp a list quietly turns into a memory-growth bug that only shows up as a stall days later.
  • Growth would invalidate outstanding slices, so a growing arena needs either chunk chaining or unsafe aliasing games. Neither is worth it at this layer.

The peak in FrameArena::stats is the tool for sizing: run the game, read the high-water mark, set the capacity with headroom. Systems that can legitimately degrade (a debug overlay, a particle burst) should use FrameArena::try_alloc_slice and drop work when the budget is gone rather than take the process down.

§Borrowing

Allocation takes &self and returns &mut [T] borrowed from the arena, so any number of allocations can be live at once — which is the actual usage: a frame system builds a visible-instance list and a draw-argument list and an index list, and holds all three until the encoder consumes them. FrameArena::reset takes &mut self, so the borrow checker is what proves no slice outlives its frame; the frame boundary needs no discipline beyond calling reset.

use crcbl_core::alloc::FrameArena;

let mut arena = FrameArena::with_capacity(4096);
let instances = arena.alloc_slice(3, [0.0f32; 4]);
let draw_args = arena.alloc_slice(2, 0u32);
instances[0][1] = 1.0;
draw_args[1] = 7;
// Both live at once; `reset` cannot be called until both are dropped.
assert_eq!(instances[0][1] + draw_args[1] as f32, 8.0);
arena.reset();

§Aliasing

This is the bumpalo model, and handing &mut out of &self rests on three facts:

  1. Regions are disjoint by construction. The bump offset only ever advances between resets, so no two allocations can return overlapping byte ranges. Every returned slice is the unique reference to its bytes.
  2. The block never moves and never grows. Capacity is fixed at construction and the backing Vec is never reborrowed afterwards — every allocation derives from base, a pointer taken once in with_capacity. (Calling Vec::as_mut_ptr again would create a fresh unique reference to the whole block and invalidate every slice already handed out.)
  3. reset and reset_peak take &mut self. An exclusive borrow of the arena cannot coexist with any slice borrowed from it, so memory can only be recycled once every reference to it is gone. T: Copy also means no allocation has drop glue to skip.

The arena is neither Send nor Sync: allocation through &self is unsynchronized, and the Cell bump offset makes that a compile error rather than a data race. A job system wants one arena per worker anyway.

Implementations§

Source§

impl FrameArena

Source

pub fn with_capacity(capacity: usize) -> Self

Creates an arena with at least capacity bytes, rounded up to a multiple of the 64-byte block size.

Source

pub fn used(&self) -> usize

Bytes handed out since the last FrameArena::reset.

Source

pub const fn capacity(&self) -> usize

Total bytes available per frame.

Source

pub fn remaining(&self) -> usize

Bytes still available this frame (before alignment padding).

Source

pub fn stats(&self) -> ArenaStats

Used / capacity / peak.

Source

pub fn reset(&mut self)

Frees everything allocated this frame. O(1); the memory is not touched, so the next frame reads whatever the last one wrote until it initializes its own data.

Takes &mut self: this is the frame boundary, and the exclusive borrow is what proves every slice from the last frame is already gone.

The high-water mark survives — that is the point of tracking it.

Source

pub fn reset_peak(&mut self)

Forgets the high-water mark (e.g. when entering a new level and the old peak stops being informative).

Source

pub fn alloc_slice<T: Copy>(&self, len: usize, value: T) -> &mut [T]

Allocates len copies of value.

§Panics

If the arena is exhausted — see the growth policy in the type docs — or if T needs more than 64-byte alignment.

Source

pub fn try_alloc_slice<T: Copy>(&self, len: usize, value: T) -> Option<&mut [T]>

Allocates len copies of value, or None if the arena is exhausted.

§Panics

If T needs more than 64-byte alignment — that is a programming error, not a budget overrun, so it is not reported through the Option.

Source

pub fn alloc_slice_copy<T: Copy>(&self, src: &[T]) -> &mut [T]

Copies src into the arena.

§Panics

As FrameArena::alloc_slice.

Source

pub fn try_alloc_slice_copy<T: Copy>(&self, src: &[T]) -> Option<&mut [T]>

Copies src into the arena, or returns None if it is exhausted.

§Panics

As FrameArena::try_alloc_slice.

Source

pub fn alloc<T: Copy>(&self, value: T) -> &mut T

Allocates a single value.

§Panics

As FrameArena::alloc_slice.

Trait Implementations§

Source§

impl Debug for FrameArena

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.