#![allow(dead_code)]
use std::collections::HashMap;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct AllocId(u64);
impl fmt::Display for AllocId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "alloc#{}", self.0)
}
}
#[derive(Debug, Clone, Copy)]
pub struct AllocRecord {
pub offset: usize,
pub size: usize,
pub alignment: usize,
pub id: AllocId,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AllocStrategy {
Linear,
BestFit,
}
#[derive(Debug, Clone, Default)]
pub struct ArenaStats {
pub capacity: usize,
pub used: usize,
pub peak_used: usize,
pub alloc_count: u64,
pub reset_count: u64,
}
impl ArenaStats {
#[must_use]
#[allow(clippy::cast_precision_loss)]
pub fn utilization(&self) -> f64 {
if self.capacity == 0 {
return 0.0;
}
self.used as f64 / self.capacity as f64
}
#[must_use]
#[allow(clippy::cast_precision_loss)]
pub fn peak_utilization(&self) -> f64 {
if self.capacity == 0 {
return 0.0;
}
self.peak_used as f64 / self.capacity as f64
}
#[must_use]
pub fn free_bytes(&self) -> usize {
self.capacity.saturating_sub(self.used)
}
}
pub struct MemoryArena {
capacity: usize,
cursor: usize,
next_id: u64,
records: HashMap<AllocId, AllocRecord>,
strategy: AllocStrategy,
stats: ArenaStats,
}
impl MemoryArena {
#[must_use]
pub fn new(capacity: usize) -> Self {
Self {
capacity,
cursor: 0,
next_id: 0,
records: HashMap::new(),
strategy: AllocStrategy::Linear,
stats: ArenaStats {
capacity,
..ArenaStats::default()
},
}
}
#[must_use]
pub fn with_strategy(capacity: usize, strategy: AllocStrategy) -> Self {
let mut arena = Self::new(capacity);
arena.strategy = strategy;
arena
}
#[must_use]
pub fn capacity(&self) -> usize {
self.capacity
}
#[must_use]
pub fn used(&self) -> usize {
self.cursor
}
#[must_use]
pub fn remaining(&self) -> usize {
self.capacity.saturating_sub(self.cursor)
}
#[must_use]
pub fn strategy(&self) -> AllocStrategy {
self.strategy
}
#[must_use]
pub fn live_alloc_count(&self) -> usize {
self.records.len()
}
pub fn allocate(&mut self, size: usize, alignment: usize) -> Option<AllocRecord> {
let align = alignment.max(1);
let aligned_offset = (self.cursor + align - 1) & !(align - 1);
let end = aligned_offset.checked_add(size)?;
if end > self.capacity {
return None;
}
let id = AllocId(self.next_id);
self.next_id += 1;
let record = AllocRecord {
offset: aligned_offset,
size,
alignment: align,
id,
};
self.cursor = end;
self.records.insert(id, record);
self.stats.alloc_count += 1;
self.stats.used = self.cursor;
if self.cursor > self.stats.peak_used {
self.stats.peak_used = self.cursor;
}
Some(record)
}
pub fn allocate_unaligned(&mut self, size: usize) -> Option<AllocRecord> {
self.allocate(size, 1)
}
#[must_use]
pub fn get_record(&self, id: AllocId) -> Option<&AllocRecord> {
self.records.get(&id)
}
pub fn reset(&mut self) {
self.cursor = 0;
self.records.clear();
self.stats.used = 0;
self.stats.reset_count += 1;
}
#[must_use]
pub fn stats(&self) -> &ArenaStats {
&self.stats
}
pub fn resize(&mut self, new_capacity: usize) {
self.capacity = new_capacity;
self.stats.capacity = new_capacity;
if self.cursor > new_capacity {
self.cursor = new_capacity;
self.stats.used = new_capacity;
}
}
}
impl fmt::Debug for MemoryArena {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MemoryArena")
.field("capacity", &self.capacity)
.field("used", &self.cursor)
.field("live_allocs", &self.records.len())
.field("strategy", &self.strategy)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_alloc_id_display() {
let id = AllocId(42);
assert_eq!(id.to_string(), "alloc#42");
}
#[test]
fn test_new_arena() {
let arena = MemoryArena::new(1024);
assert_eq!(arena.capacity(), 1024);
assert_eq!(arena.used(), 0);
assert_eq!(arena.remaining(), 1024);
}
#[test]
fn test_simple_allocation() {
let mut arena = MemoryArena::new(256);
let rec = arena.allocate(64, 1).unwrap();
assert_eq!(rec.offset, 0);
assert_eq!(rec.size, 64);
assert_eq!(arena.used(), 64);
assert_eq!(arena.remaining(), 192);
}
#[test]
fn test_aligned_allocation() {
let mut arena = MemoryArena::new(256);
arena.allocate(10, 1).unwrap(); let rec = arena.allocate(32, 16).unwrap(); assert_eq!(rec.offset, 16);
assert_eq!(rec.size, 32);
}
#[test]
fn test_allocation_overflow() {
let mut arena = MemoryArena::new(64);
assert!(arena.allocate(65, 1).is_none());
assert_eq!(arena.live_alloc_count(), 0);
}
#[test]
fn test_multiple_allocations() {
let mut arena = MemoryArena::new(1024);
for i in 0..10 {
let rec = arena.allocate(32, 1).unwrap();
assert_eq!(rec.offset, i * 32);
}
assert_eq!(arena.live_alloc_count(), 10);
assert_eq!(arena.used(), 320);
}
#[test]
fn test_reset() {
let mut arena = MemoryArena::new(256);
arena.allocate(100, 1).unwrap();
arena.allocate(50, 1).unwrap();
arena.reset();
assert_eq!(arena.used(), 0);
assert_eq!(arena.live_alloc_count(), 0);
assert_eq!(arena.stats().reset_count, 1);
}
#[test]
fn test_peak_tracking() {
let mut arena = MemoryArena::new(512);
arena.allocate(200, 1).unwrap();
arena.allocate(100, 1).unwrap();
assert_eq!(arena.stats().peak_used, 300);
arena.reset();
arena.allocate(50, 1).unwrap();
assert_eq!(arena.stats().peak_used, 300);
}
#[test]
fn test_get_record() {
let mut arena = MemoryArena::new(256);
let rec = arena.allocate(16, 1).unwrap();
let found = arena.get_record(rec.id).unwrap();
assert_eq!(found.offset, 0);
assert_eq!(found.size, 16);
}
#[test]
fn test_stats_utilization() {
let mut arena = MemoryArena::new(100);
arena.allocate(50, 1).unwrap();
let s = arena.stats();
assert!((s.utilization() - 0.5).abs() < 1e-9);
assert_eq!(s.free_bytes(), 50);
}
#[test]
fn test_strategy_hint() {
let arena = MemoryArena::with_strategy(1024, AllocStrategy::BestFit);
assert_eq!(arena.strategy(), AllocStrategy::BestFit);
}
#[test]
fn test_resize_larger() {
let mut arena = MemoryArena::new(100);
arena.allocate(80, 1).unwrap();
arena.resize(200);
assert_eq!(arena.capacity(), 200);
assert_eq!(arena.remaining(), 120);
}
#[test]
fn test_resize_smaller_than_cursor() {
let mut arena = MemoryArena::new(200);
arena.allocate(150, 1).unwrap();
arena.resize(100);
assert_eq!(arena.capacity(), 100);
assert_eq!(arena.used(), 100);
}
#[test]
fn test_allocate_unaligned() {
let mut arena = MemoryArena::new(64);
let rec = arena.allocate_unaligned(10).unwrap();
assert_eq!(rec.alignment, 1);
assert_eq!(rec.offset, 0);
}
}