use super::{Handle, HandleStore};
use std::sync::LazyLock;
const DEFAULT_BLOCK_SIZE: usize = 4096;
#[derive(Debug)]
pub struct PoolBlock {
data: Vec<u8>,
pos: usize,
}
impl PoolBlock {
fn new(size: usize) -> Self {
Self {
data: vec![0u8; size],
pos: 0,
}
}
fn available(&self) -> usize {
self.data.len() - self.pos
}
fn allocate(&mut self, size: usize, align: usize) -> Option<usize> {
let base_addr = self.data.as_ptr() as usize;
let current_addr = base_addr + self.pos;
let aligned_addr = (current_addr + align - 1) & !(align - 1);
let aligned_pos = aligned_addr - base_addr;
if aligned_pos + size <= self.data.len() {
let offset = aligned_pos;
self.pos = aligned_pos + size;
Some(offset)
} else {
None
}
}
}
#[derive(Debug)]
pub struct Pool {
pub block_size: usize,
pub blocks: Vec<PoolBlock>,
pub total_allocated: usize,
pub total_used: usize,
pub high_water: usize,
pub alloc_count: usize,
pub name: String,
}
impl Default for Pool {
fn default() -> Self {
Self {
block_size: DEFAULT_BLOCK_SIZE,
blocks: Vec::new(),
total_allocated: 0,
total_used: 0,
high_water: 0,
alloc_count: 0,
name: String::new(),
}
}
}
impl Pool {
fn alloc(&mut self, size: usize, align: usize) -> Option<*mut u8> {
if size == 0 {
return Some(std::ptr::null_mut());
}
let align = align.max(1);
for block in &mut self.blocks {
if let Some(offset) = block.allocate(size, align) {
self.total_used += size;
self.alloc_count += 1;
if self.total_used > self.high_water {
self.high_water = self.total_used;
}
return Some(block.data.as_mut_ptr().wrapping_add(offset));
}
}
let new_block_size = self.block_size.max(size + align);
let mut new_block = PoolBlock::new(new_block_size);
self.total_allocated += new_block_size;
if let Some(offset) = new_block.allocate(size, align) {
let ptr = new_block.data.as_mut_ptr().wrapping_add(offset);
self.blocks.push(new_block);
self.total_used += size;
self.alloc_count += 1;
if self.total_used > self.high_water {
self.high_water = self.total_used;
}
return Some(ptr);
}
None
}
fn reset(&mut self) {
for block in &mut self.blocks {
block.pos = 0;
}
self.total_used = 0;
self.alloc_count = 0;
}
fn shrink(&mut self) {
self.blocks.retain(|b| b.pos > 0);
self.total_allocated = self.blocks.iter().map(|b| b.data.len()).sum();
}
}
pub static POOLS: LazyLock<HandleStore<Pool>> = LazyLock::new(HandleStore::new);
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_pool(_ctx: Handle) -> Handle {
POOLS.insert(Pool::default())
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_pool_with_size(_ctx: Handle, block_size: usize) -> Handle {
let pool = Pool {
block_size: block_size.max(64),
..Default::default()
};
POOLS.insert(pool)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_pool_named(_ctx: Handle, name: *const std::ffi::c_char) -> Handle {
let pool_name = if name.is_null() {
String::new()
} else {
let c_str = unsafe { std::ffi::CStr::from_ptr(name) };
c_str.to_str().unwrap_or("").to_string()
};
let pool = Pool {
name: pool_name,
..Default::default()
};
POOLS.insert(pool)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_pool_alloc(_ctx: Handle, pool: Handle, size: usize) -> *mut u8 {
if let Some(p) = POOLS.get(pool) {
if let Ok(mut guard) = p.lock() {
return guard.alloc(size, 8).unwrap_or(std::ptr::null_mut());
}
}
std::ptr::null_mut()
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_pool_alloc_aligned(
_ctx: Handle,
pool: Handle,
size: usize,
align: usize,
) -> *mut u8 {
if let Some(p) = POOLS.get(pool) {
if let Ok(mut guard) = p.lock() {
return guard.alloc(size, align).unwrap_or(std::ptr::null_mut());
}
}
std::ptr::null_mut()
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_pool_calloc(_ctx: Handle, pool: Handle, count: usize, size: usize) -> *mut u8 {
let total_size = count.saturating_mul(size);
if let Some(p) = POOLS.get(pool) {
if let Ok(mut guard) = p.lock() {
if let Some(ptr) = guard.alloc(total_size, 8) {
return ptr;
}
}
}
std::ptr::null_mut()
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_pool_strdup(
_ctx: Handle,
pool: Handle,
s: *const std::ffi::c_char,
) -> *mut std::ffi::c_char {
if s.is_null() {
return std::ptr::null_mut();
}
let mut len = 0usize;
unsafe {
while *s.add(len) != 0 {
len += 1;
}
}
let size = len + 1;
if let Some(p) = POOLS.get(pool) {
if let Ok(mut guard) = p.lock() {
if let Some(ptr) = guard.alloc(size, 1) {
unsafe {
std::ptr::copy_nonoverlapping(s as *const u8, ptr, size);
}
return ptr as *mut std::ffi::c_char;
}
}
}
std::ptr::null_mut()
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_pool_reset(_ctx: Handle, pool: Handle) {
if let Some(p) = POOLS.get(pool) {
if let Ok(mut guard) = p.lock() {
guard.reset();
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_pool_shrink(_ctx: Handle, pool: Handle) {
if let Some(p) = POOLS.get(pool) {
if let Ok(mut guard) = p.lock() {
guard.shrink();
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_pool_set_block_size(_ctx: Handle, pool: Handle, size: usize) {
if let Some(p) = POOLS.get(pool) {
if let Ok(mut guard) = p.lock() {
guard.block_size = size.max(64);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_pool_allocated(_ctx: Handle, pool: Handle) -> usize {
if let Some(p) = POOLS.get(pool) {
if let Ok(guard) = p.lock() {
return guard.total_allocated;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_pool_used(_ctx: Handle, pool: Handle) -> usize {
if let Some(p) = POOLS.get(pool) {
if let Ok(guard) = p.lock() {
return guard.total_used;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_pool_high_water(_ctx: Handle, pool: Handle) -> usize {
if let Some(p) = POOLS.get(pool) {
if let Ok(guard) = p.lock() {
return guard.high_water;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_pool_alloc_count(_ctx: Handle, pool: Handle) -> usize {
if let Some(p) = POOLS.get(pool) {
if let Ok(guard) = p.lock() {
return guard.alloc_count;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_pool_block_count(_ctx: Handle, pool: Handle) -> usize {
if let Some(p) = POOLS.get(pool) {
if let Ok(guard) = p.lock() {
return guard.blocks.len();
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_pool_available(_ctx: Handle, pool: Handle) -> usize {
if let Some(p) = POOLS.get(pool) {
if let Ok(guard) = p.lock() {
return guard.blocks.iter().map(|b| b.available()).sum();
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_pool_fragmentation(_ctx: Handle, pool: Handle) -> f32 {
if let Some(p) = POOLS.get(pool) {
if let Ok(guard) = p.lock() {
if guard.total_allocated == 0 {
return 0.0;
}
let wasted = guard.total_allocated - guard.total_used;
return wasted as f32 / guard.total_allocated as f32;
}
}
0.0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_keep_pool(_ctx: Handle, pool: Handle) -> Handle {
POOLS.keep(pool)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_drop_pool(_ctx: Handle, pool: Handle) {
POOLS.remove(pool);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_pool() {
let pool = fz_new_pool(0);
assert!(pool > 0);
assert_eq!(fz_pool_allocated(0, pool), 0);
assert_eq!(fz_pool_used(0, pool), 0);
fz_drop_pool(0, pool);
}
#[test]
fn test_pool_alloc() {
let pool = fz_new_pool_with_size(0, 1024);
let ptr1 = fz_pool_alloc(0, pool, 100);
assert!(!ptr1.is_null());
let ptr2 = fz_pool_alloc(0, pool, 200);
assert!(!ptr2.is_null());
assert_eq!(fz_pool_alloc_count(0, pool), 2);
assert!(fz_pool_used(0, pool) >= 300);
fz_drop_pool(0, pool);
}
#[test]
fn test_pool_aligned_alloc() {
let pool = fz_new_pool(0);
let ptr = fz_pool_alloc_aligned(0, pool, 64, 64);
assert!(!ptr.is_null());
assert_eq!(ptr as usize % 64, 0);
fz_drop_pool(0, pool);
}
#[test]
fn test_pool_reset() {
let pool = fz_new_pool(0);
fz_pool_alloc(0, pool, 100);
fz_pool_alloc(0, pool, 200);
assert!(fz_pool_used(0, pool) >= 300);
fz_pool_reset(0, pool);
assert_eq!(fz_pool_used(0, pool), 0);
assert!(fz_pool_allocated(0, pool) > 0);
fz_drop_pool(0, pool);
}
#[test]
fn test_pool_shrink() {
let pool = fz_new_pool_with_size(0, 256);
for _ in 0..5 {
fz_pool_alloc(0, pool, 256);
}
let blocks_before = fz_pool_block_count(0, pool);
assert!(blocks_before >= 5);
fz_pool_reset(0, pool);
fz_pool_shrink(0, pool);
assert_eq!(fz_pool_block_count(0, pool), 0);
fz_drop_pool(0, pool);
}
#[test]
fn test_pool_calloc() {
let pool = fz_new_pool(0);
let ptr = fz_pool_calloc(0, pool, 10, 8);
assert!(!ptr.is_null());
let slice = unsafe { std::slice::from_raw_parts(ptr, 80) };
assert!(slice.iter().all(|&b| b == 0));
fz_drop_pool(0, pool);
}
#[test]
fn test_pool_strdup() {
let pool = fz_new_pool(0);
let s = c"Hello, World!";
let dup = fz_pool_strdup(0, pool, s.as_ptr());
assert!(!dup.is_null());
let dup_str = unsafe { std::ffi::CStr::from_ptr(dup) };
assert_eq!(dup_str.to_str().unwrap(), "Hello, World!");
fz_drop_pool(0, pool);
}
#[test]
fn test_pool_fragmentation() {
let pool = fz_new_pool_with_size(0, 1024);
fz_pool_alloc(0, pool, 10);
let frag = fz_pool_fragmentation(0, pool);
assert!(frag > 0.9);
fz_drop_pool(0, pool);
}
#[test]
fn test_named_pool() {
let name = c"test_pool";
let pool = fz_new_pool_named(0, name.as_ptr());
assert!(pool > 0);
fz_drop_pool(0, pool);
}
}