use crate::error::{Error, Result};
#[derive(Debug, Default, Clone)]
pub struct FreeSpaceManager {
free_list: Vec<(u64, u64)>,
}
impl FreeSpaceManager {
pub fn new() -> Self {
Self { free_list: vec![] }
}
pub fn free_list(&self) -> &[(u64, u64)] {
&self.free_list
}
pub fn total_free_bytes(&self) -> u64 {
self.free_list.iter().map(|(_, s)| *s).sum()
}
pub fn deallocate(&mut self, offset: u64, size: u64) -> Result<()> {
if size == 0 {
return Ok(());
}
let end = offset
.checked_add(size)
.ok_or_else(|| Error::InvalidFormat("free range overflow".into()))?;
self.free_list.push((offset, size));
self.free_list.sort_by_key(|(o, _)| *o);
let mut out: Vec<(u64, u64)> = Vec::with_capacity(self.free_list.len());
for (o, s) in self.free_list.drain(..) {
let e = o
.checked_add(s)
.ok_or_else(|| Error::InvalidFormat("free range overflow".into()))?;
if let Some((last_o, last_s)) = out.last_mut() {
let last_e = last_o
.checked_add(*last_s)
.ok_or_else(|| Error::InvalidFormat("free range overflow".into()))?;
if o <= last_e {
let new_e = last_e.max(e);
*last_s = new_e - *last_o;
continue;
}
}
out.push((o, s));
}
self.free_list = out;
let _ = end;
Ok(())
}
pub fn allocate(&mut self, size: u64) -> Option<u64> {
if size == 0 {
return None;
}
for i in 0..self.free_list.len() {
let (off, sz) = self.free_list[i];
if sz >= size {
let alloc_off = off;
let remaining = sz - size;
if remaining == 0 {
self.free_list.remove(i);
} else {
self.free_list[i] = (off + size, remaining);
}
return Some(alloc_off);
}
}
None
}
pub fn defragment(&mut self) -> Result<()> {
let mut out = FreeSpaceManager::new();
for (o, s) in std::mem::take(&mut self.free_list) {
out.deallocate(o, s)?;
}
self.free_list = out.free_list;
Ok(())
}
}
#[cfg(all(test, not(target_arch = "wasm32")))]
mod tests {
use super::*;
#[test]
fn allocate_and_deallocate_coalesces() {
let mut f = FreeSpaceManager::new();
f.deallocate(100, 50).unwrap();
f.deallocate(150, 50).unwrap(); assert_eq!(f.free_list(), &[(100, 100)]);
let off = f.allocate(30).unwrap();
assert_eq!(off, 100);
assert_eq!(f.free_list(), &[(130, 70)]);
f.deallocate(0, 10).unwrap();
assert_eq!(f.free_list(), &[(0, 10), (130, 70)]);
f.deallocate(10, 120).unwrap(); assert_eq!(f.free_list(), &[(0, 200)]);
}
#[test]
fn allocate_returns_none_when_insufficient() {
let mut f = FreeSpaceManager::new();
f.deallocate(0, 10).unwrap();
assert_eq!(f.allocate(11), None);
}
#[test]
fn allocate_rejects_zero_size() {
let mut f = FreeSpaceManager::new();
f.deallocate(100, 10).unwrap();
assert_eq!(f.allocate(0), None);
assert_eq!(f.free_list(), &[(100, 10)]);
}
}