use core::ptr::NonNull;
use crate::lock::Mutex;
use crate::{Align, Alloc, Allocator, Brand, Error, Layout};
const DEFAULT_CHUNK_SIZE: usize = 1024 * 1024;
pub struct Arena<'alloc> {
pub allocator: &'alloc dyn Allocator,
inner: Mutex<ArenaInner<'alloc>>,
brand: Brand,
}
struct ArenaInner<'alloc> {
chunk_size: usize,
buf: Option<Alloc<'alloc, u8>>,
next: usize,
count: usize,
}
#[cfg(feature = "global")]
impl Arena<'static> {
pub fn new() -> Option<Self> {
crate::global::get().map(Self::new_in)
}
}
impl<'alloc> Arena<'alloc> {
pub fn new_in(allocator: &'alloc dyn Allocator) -> Self {
Self {
allocator,
inner: Mutex::new(ArenaInner {
chunk_size: DEFAULT_CHUNK_SIZE,
buf: None,
next: 0,
count: 0,
}),
brand: Brand::new(),
}
}
pub fn chunk_size(&self) -> usize {
self.inner.lock().chunk_size
}
pub fn set_chunk_size(&mut self, chunk_size: usize) {
let this = self.inner.get_mut();
if chunk_size > this.chunk_size {
this.chunk_size = chunk_size;
}
}
pub fn dealloc_inner(&mut self) {
let this = self.inner.get_mut();
if let Some(buf) = this.buf.take() {
buf.dealloc();
this.chunk_size = DEFAULT_CHUNK_SIZE;
}
}
}
unsafe impl Allocator for Arena<'_> {
fn brand(&self) -> &Brand {
&self.brand
}
fn alloc_bytes(&self, layout: Layout) -> Result<Alloc<'_, u8>, Error> {
let mut this_lock = self.inner.lock();
let this = &mut *this_lock;
let buf = match &mut this.buf {
Some(b) => b,
None => {
let layout = Layout {
size: this.chunk_size,
align: Align::new(1).unwrap(),
};
let alloc = self.allocator.alloc_bytes(layout)?;
this.buf.insert(alloc)
}
};
if layout.size > this.chunk_size - this.next {
let mut chunk_size = this.chunk_size * 2;
while layout.size > chunk_size - this.next {
chunk_size *= 2;
}
let layout = Layout {
size: chunk_size,
align: Align::new(1).unwrap(),
};
*buf = self.allocator.alloc_bytes(layout)?;
this.chunk_size = chunk_size;
}
let align = layout.align.align();
let start = unsafe { buf.as_ptr().add(this.next) };
let start = start.map_addr(|addr| addr.div_ceil(align) * align);
let end = start as usize + layout.size;
debug_assert!(start as usize % align == 0);
debug_assert!(end <= buf.as_ptr() as usize + this.chunk_size);
this.next = end - buf.as_ptr() as usize;
this.count += 1;
drop(this_lock);
let ptr = NonNull::new(start).unwrap();
Ok(unsafe { Alloc::new(ptr, layout, self) })
}
fn dealloc_bytes<'this>(&'this self, alloc: Alloc<'this, u8>) {
if alloc.allocator().brand() != &self.brand {
return;
}
let mut this = self.inner.lock();
this.count -= 1;
if this.count == 0 {
this.next = 0;
}
}
fn dangling(&self, align: Align) -> Alloc<'_, u8> {
let layout = Layout { size: 0, align };
unsafe {
Alloc::new(
NonNull::new(align.align() as *mut u8).unwrap(),
layout,
self,
)
}
}
fn total_size(&self) -> Option<usize> {
self.allocator.total_size()
}
fn remaining_size(&self) -> Option<usize> {
self.allocator.remaining_size()
}
}