use std::collections::{HashSet, VecDeque};
pub struct PageAllocator {
freelist: VecDeque<u32>,
preferred: VecDeque<u32>,
next_extend: u32,
used: HashSet<u32>,
}
impl PageAllocator {
pub fn new(freelist: VecDeque<u32>, next_extend: u32) -> Self {
let mut alloc = Self {
freelist,
preferred: VecDeque::new(),
next_extend,
used: HashSet::new(),
};
let max_free = alloc.freelist.iter().copied().max().unwrap_or(0);
if max_free + 1 > alloc.next_extend {
alloc.next_extend = max_free + 1;
}
alloc
}
pub fn set_preferred(&mut self, mut pool: Vec<u32>) {
pool.sort_unstable();
pool.dedup();
pool.retain(|p| !self.used.contains(p));
self.preferred = VecDeque::from(pool);
}
pub fn finish_preferred(&mut self) {
while let Some(p) = self.preferred.pop_front() {
if !self.used.contains(&p) {
self.freelist.push_back(p);
}
}
}
pub fn allocate(&mut self) -> u32 {
let page = if let Some(p) = self.preferred.pop_front() {
p
} else if let Some(p) = self.freelist.pop_front() {
p
} else {
let p = self.next_extend;
self.next_extend += 1;
p
};
if page >= self.next_extend {
self.next_extend = page + 1;
}
debug_assert!(
!self.used.contains(&page),
"PageAllocator handed out page {page} twice"
);
self.used.insert(page);
page
}
pub fn add_to_freelist(&mut self, pages: impl IntoIterator<Item = u32>) {
for p in pages {
if !self.used.contains(&p) && !self.freelist.contains(&p) {
self.freelist.push_back(p);
if p + 1 > self.next_extend {
self.next_extend = p + 1;
}
}
}
}
pub fn high_water(&self) -> u32 {
self.next_extend
}
pub fn used(&self) -> &HashSet<u32> {
&self.used
}
pub fn drain_freelist(&mut self) -> Vec<u32> {
let mut v: Vec<u32> = self.freelist.drain(..).collect();
v.sort_unstable();
v.dedup();
v
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn allocate_extends_when_pools_empty() {
let mut a = PageAllocator::new(VecDeque::new(), 1);
assert_eq!(a.allocate(), 1);
assert_eq!(a.allocate(), 2);
assert_eq!(a.allocate(), 3);
assert_eq!(a.high_water(), 4);
}
#[test]
fn preferred_pool_drains_first() {
let mut a = PageAllocator::new(VecDeque::from([8, 9]), 1);
a.set_preferred(vec![3, 4]);
assert_eq!(a.allocate(), 3);
assert_eq!(a.allocate(), 4);
assert_eq!(a.allocate(), 8);
assert_eq!(a.allocate(), 9);
assert_eq!(a.allocate(), 10);
}
#[test]
fn freelist_drains_after_preferred() {
let mut a = PageAllocator::new(VecDeque::from([5, 7]), 1);
assert_eq!(a.allocate(), 5);
assert_eq!(a.allocate(), 7);
assert_eq!(a.allocate(), 8); }
#[test]
fn finish_preferred_returns_leftovers_to_freelist() {
let mut a = PageAllocator::new(VecDeque::new(), 1);
a.set_preferred(vec![3, 4, 5]);
assert_eq!(a.allocate(), 3); a.finish_preferred();
assert_eq!(a.allocate(), 4);
assert_eq!(a.allocate(), 5);
}
#[test]
fn add_to_freelist_skips_already_used() {
let mut a = PageAllocator::new(VecDeque::new(), 1);
let p = a.allocate(); a.add_to_freelist([p, 5, 6]);
let drained = a.drain_freelist();
assert!(
!drained.contains(&p),
"used page should not land on freelist"
);
assert_eq!(drained, vec![5, 6]);
}
#[test]
fn next_extend_respects_max_free() {
let mut a = PageAllocator::new(VecDeque::from([100]), 1);
assert_eq!(a.allocate(), 100);
assert_eq!(a.allocate(), 101);
}
}