#[cfg(feature = "std")]
use linked_hash_map::LinkedHashMap;
#[cfg(not(feature = "std"))]
extern crate alloc;
#[cfg(not(feature = "std"))]
use alloc::collections::{BTreeSet, VecDeque};
use super::ReusableIdPoolError;
#[cfg(feature = "std")]
#[derive(Debug, Clone, PartialEq, Eq)]
struct LinkedHashSet(LinkedHashMap<u64, ()>);
#[cfg(feature = "std")]
impl LinkedHashSet {
fn new() -> Self {
LinkedHashSet(LinkedHashMap::new())
}
fn pop_front(&mut self) -> Option<u64> {
Some(self.0.pop_front()?.0)
}
fn contains(&self, value: &u64) -> bool {
self.0.contains_key(value)
}
fn insert(&mut self, value: u64) -> Option<u64> {
self.0.insert(value, ()).map(|_| value)
}
}
#[cfg(not(feature = "std"))]
#[derive(Debug, Clone, PartialEq, Eq)]
struct NoStdFreeList {
insertion_order: VecDeque<u64>,
free_tree: BTreeSet<u64>,
}
#[cfg(not(feature = "std"))]
impl NoStdFreeList {
fn new() -> Self {
Self {
insertion_order: VecDeque::new(),
free_tree: BTreeSet::new(),
}
}
fn pop_front(&mut self) -> Option<u64> {
match self.insertion_order.pop_front() {
Some(item) => {
self.free_tree.remove(&item);
Some(item)
}
None => None,
}
}
fn contains(&self, value: &u64) -> bool {
self.free_tree.contains(value)
}
fn insert(&mut self, value: u64) {
self.free_tree.insert(value);
self.insertion_order.push_back(value);
}
}
#[derive(Debug)]
pub struct ReusableIdPoolManual {
frontier: u64,
#[cfg(feature = "std")]
free_list: LinkedHashSet,
#[cfg(not(feature = "std"))]
free_list: NoStdFreeList,
}
impl Default for ReusableIdPoolManual {
fn default() -> Self {
ReusableIdPoolManual::new()
}
}
impl ReusableIdPoolManual {
pub fn new() -> Self {
ReusableIdPoolManual {
frontier: 0,
#[cfg(feature = "std")]
free_list: LinkedHashSet::new(),
#[cfg(not(feature = "std"))]
free_list: NoStdFreeList::new(),
}
}
pub fn allocate(&mut self) -> u64 {
self.try_allocate().unwrap()
}
pub fn try_allocate(&mut self) -> Result<u64, ReusableIdPoolError> {
if let Some(free_list_id) = self.free_list.pop_front() {
Ok(free_list_id)
} else if self.frontier == u64::MAX {
Err(ReusableIdPoolError::TooManyLiveIDs)
} else {
let frontier_id = self.frontier;
self.frontier += 1;
Ok(frontier_id)
}
}
pub fn release(&mut self, id: u64) {
if id >= self.frontier {
return;
}
if self.free_list.contains(&id) {
return;
}
self.free_list.insert(id);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn allocate_creates_ids() {
let mut reusable_id_pool_manual = ReusableIdPoolManual::new();
let id1 = reusable_id_pool_manual.allocate();
let id2 = reusable_id_pool_manual.allocate();
assert_eq!(0, id1);
assert_eq!(1, id2);
}
#[test]
fn allocate_reuses_released_ids() {
let mut reusable_id_pool_manual = ReusableIdPoolManual::new();
let _id1 = reusable_id_pool_manual.allocate();
let id2 = reusable_id_pool_manual.allocate();
let id3 = reusable_id_pool_manual.allocate();
reusable_id_pool_manual.release(id3);
reusable_id_pool_manual.release(id2);
let id4 = reusable_id_pool_manual.allocate();
let id5 = reusable_id_pool_manual.allocate();
assert_eq!(2, id4);
assert_eq!(1, id5);
}
#[test]
#[should_panic]
fn allocate_panics_if_all_ids_are_in_use() {
let mut reusable_id_pool_manual = ReusableIdPoolManual::new();
reusable_id_pool_manual.frontier = u64::MAX;
reusable_id_pool_manual.allocate();
}
#[test]
fn release_rejects_free_request_on_frontier_boundary() {
let mut reusable_id_pool_manual = ReusableIdPoolManual::new();
let _id1 = reusable_id_pool_manual.allocate();
let _id2 = reusable_id_pool_manual.allocate();
let old_free_list = reusable_id_pool_manual.free_list.clone();
reusable_id_pool_manual.release(2);
assert_eq!(old_free_list, reusable_id_pool_manual.free_list);
}
#[test]
fn release_rejects_free_requests_above_frontier() {
let mut reusable_id_pool_manual = ReusableIdPoolManual::new();
let _id1 = reusable_id_pool_manual.allocate();
let _id2 = reusable_id_pool_manual.allocate();
let old_free_list = reusable_id_pool_manual.free_list.clone();
reusable_id_pool_manual.release(10);
assert_eq!(old_free_list, reusable_id_pool_manual.free_list);
}
#[test]
fn release_rejects_double_free_requests() {
let mut reusable_id_pool_manual = ReusableIdPoolManual::new();
let _id1 = reusable_id_pool_manual.allocate();
let id2 = reusable_id_pool_manual.allocate();
let id3 = reusable_id_pool_manual.allocate();
reusable_id_pool_manual.release(id2);
reusable_id_pool_manual.release(id3);
let old_free_list = reusable_id_pool_manual.free_list.clone();
reusable_id_pool_manual.release(id3);
reusable_id_pool_manual.release(id2);
assert_eq!(old_free_list, reusable_id_pool_manual.free_list);
}
}