use bumpalo::collections::Vec as BumpVec;
use bumpalo::Bump;
use crate::crypto::Hash;
pub const DEFAULT_ARENA_CAPACITY: usize = 1024 * 1024;
pub struct BatchArena {
bump: Bump,
}
impl BatchArena {
#[inline]
pub fn new() -> Self {
Self {
bump: Bump::with_capacity(DEFAULT_ARENA_CAPACITY),
}
}
#[inline]
pub fn with_capacity(capacity: usize) -> Self {
Self {
bump: Bump::with_capacity(capacity),
}
}
#[inline]
pub fn for_events(count: usize) -> Self {
let capacity = count * 1024;
Self::with_capacity(capacity.max(DEFAULT_ARENA_CAPACITY))
}
#[inline]
pub fn for_hashes(count: usize) -> Self {
let capacity = count * 64;
Self::with_capacity(capacity.max(65536))
}
#[inline]
pub fn alloc_bytes(&self, bytes: &[u8]) -> &[u8] {
self.bump.alloc_slice_copy(bytes)
}
#[inline]
pub fn alloc_str(&self, s: &str) -> &str {
self.bump.alloc_str(s)
}
#[inline]
pub fn alloc<T>(&self, value: T) -> &mut T {
self.bump.alloc(value)
}
#[inline]
pub fn alloc_slice<T: Copy>(&self, slice: &[T]) -> &[T] {
self.bump.alloc_slice_copy(slice)
}
#[inline]
pub fn alloc_vec<T>(&self) -> BumpVec<'_, T> {
BumpVec::new_in(&self.bump)
}
#[inline]
pub fn alloc_vec_with_capacity<T>(&self, capacity: usize) -> BumpVec<'_, T> {
BumpVec::with_capacity_in(capacity, &self.bump)
}
#[inline]
pub fn alloc_slice_fill_default<T: Default + Clone>(&self, count: usize) -> &mut [T] {
self.bump.alloc_slice_fill_default(count)
}
#[inline]
pub fn alloc_hash_array(&self, count: usize) -> &mut [Hash] {
self.bump.alloc_slice_fill_default(count)
}
#[inline]
pub fn allocated_bytes(&self) -> usize {
self.bump.allocated_bytes()
}
#[inline]
pub fn reset(&mut self) {
self.bump.reset();
}
#[inline]
pub fn as_bump(&self) -> &Bump {
&self.bump
}
}
impl Default for BatchArena {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Debug for BatchArena {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BatchArena")
.field("allocated_bytes", &self.allocated_bytes())
.finish()
}
}
pub struct CanonicalBytesArena<'a> {
arena: &'a BatchArena,
items: BumpVec<'a, &'a [u8]>,
}
impl<'a> CanonicalBytesArena<'a> {
pub fn new(arena: &'a BatchArena, capacity: usize) -> Self {
Self {
arena,
items: arena.alloc_vec_with_capacity(capacity),
}
}
pub fn push(&mut self, bytes: &[u8]) {
let allocated = self.arena.alloc_bytes(bytes);
self.items.push(allocated);
}
pub fn as_slices(&self) -> &[&'a [u8]] {
&self.items
}
pub fn len(&self) -> usize {
self.items.len()
}
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::hash;
#[test]
fn test_arena_basic_allocation() {
let arena = BatchArena::new();
let bytes1 = arena.alloc_bytes(b"hello");
let bytes2 = arena.alloc_bytes(b"world");
assert_eq!(bytes1, b"hello");
assert_eq!(bytes2, b"world");
}
#[test]
fn test_arena_vector() {
let arena = BatchArena::new();
let mut vec = arena.alloc_vec::<u32>();
vec.push(1);
vec.push(2);
vec.push(3);
assert_eq!(vec.as_slice(), &[1, 2, 3]);
}
#[test]
fn test_arena_hash_array() {
let arena = BatchArena::new();
let hashes = arena.alloc_hash_array(4);
assert_eq!(hashes.len(), 4);
for h in hashes.iter() {
assert!(h.is_zero());
}
hashes[0] = hash(b"test");
assert!(!hashes[0].is_zero());
}
#[test]
fn test_arena_reset() {
let mut arena = BatchArena::new();
let first = arena.alloc_bytes(&[0u8; 10000]);
let first_ptr = first.as_ptr();
let before = arena.allocated_bytes();
assert!(before >= 10000);
arena.reset();
let second = arena.alloc_bytes(&[1u8; 10000]);
let second_ptr = second.as_ptr();
assert_eq!(second.len(), 10000);
let after = arena.allocated_bytes();
assert!(after <= before * 2, "Arena should reuse memory after reset");
let _ = (first_ptr, second_ptr);
}
#[test]
fn test_canonical_bytes_arena() {
let arena = BatchArena::new();
let mut collector = CanonicalBytesArena::new(&arena, 10);
collector.push(b"event 1 canonical bytes");
collector.push(b"event 2 canonical bytes");
collector.push(b"event 3 canonical bytes");
assert_eq!(collector.len(), 3);
let slices = collector.as_slices();
assert_eq!(slices[0], b"event 1 canonical bytes");
assert_eq!(slices[1], b"event 2 canonical bytes");
assert_eq!(slices[2], b"event 3 canonical bytes");
}
#[test]
fn test_arena_sizing() {
let arena = BatchArena::for_events(100);
assert!(arena.as_bump().chunk_capacity() >= 100 * 1024);
let arena = BatchArena::for_hashes(1000);
assert!(arena.as_bump().chunk_capacity() >= 64 * 1024);
}
#[test]
fn test_arena_many_small_allocations() {
let arena = BatchArena::new();
for i in 0..1000u32 {
let _ = arena.alloc(i);
}
assert!(arena.allocated_bytes() >= 4000);
}
#[test]
fn test_arena_slice_allocation() {
let arena = BatchArena::new();
let hashes: Vec<Hash> = (0..10u32).map(|i| hash(&i.to_le_bytes())).collect();
let allocated = arena.alloc_slice(&hashes);
assert_eq!(allocated.len(), 10);
for (i, h) in allocated.iter().enumerate() {
assert_eq!(*h, hash(&(i as u32).to_le_bytes()));
}
}
}