use std::{collections::VecDeque, sync::Arc};
use super::state::TaskStateBlock;
pub(super) enum BlockLookup<'directory> {
Retired,
Live(&'directory Arc<TaskStateBlock>),
Absent,
}
#[derive(Debug, Default)]
pub(super) struct BlockDirectory {
base: usize,
entries: VecDeque<Option<Arc<TaskStateBlock>>>,
resident: usize,
sweep_queue: VecDeque<usize>,
}
impl BlockDirectory {
pub(super) const fn new() -> Self {
Self {
base: 0,
entries: VecDeque::new(),
resident: 0,
sweep_queue: VecDeque::new(),
}
}
pub(super) fn end(&self) -> usize {
self.base + self.entries.len()
}
pub(super) fn resident(&self) -> usize {
self.resident
}
#[cfg(test)]
pub(super) fn span(&self) -> usize {
self.entries.len()
}
pub(super) fn lookup(&self, block_index: usize) -> BlockLookup<'_> {
let Some(offset) = block_index.checked_sub(self.base) else {
return BlockLookup::Retired;
};
match self.entries.get(offset) {
Some(Some(block)) => BlockLookup::Live(block),
Some(None) => BlockLookup::Retired,
None => BlockLookup::Absent,
}
}
pub(super) fn resident_blocks(&self) -> impl Iterator<Item = &Arc<TaskStateBlock>> {
self.entries.iter().flatten()
}
pub(super) fn ensure(&mut self, block_index: usize) -> Option<(Arc<TaskStateBlock>, bool)> {
let offset = block_index.checked_sub(self.base)?;
let mut created = false;
while self.entries.len() <= offset {
self.sweep_queue.push_back(self.end());
self.entries
.push_back(Some(Arc::new(TaskStateBlock::new())));
self.resident += 1;
created = true;
}
let block = self.entries[offset].as_ref()?;
Some((Arc::clone(block), created))
}
pub(super) fn resident_indexed(&self) -> impl Iterator<Item = (usize, &Arc<TaskStateBlock>)> {
(self.base..)
.zip(&self.entries)
.filter_map(|(index, entry)| Some((index, entry.as_ref()?)))
}
#[cfg(test)]
pub(super) fn queued(&self) -> usize {
self.sweep_queue.len()
}
pub(super) fn check_out<const N: usize>(&mut self) -> SweepWindow<N> {
let mut blocks = [const { None }; N];
for slot in &mut blocks {
let Some(index) = self.sweep_queue.pop_front() else {
break;
};
let block = self
.entries
.get(index - self.base)
.and_then(Option::as_ref)
.expect("invariant: a queued index names a resident block");
*slot = Some((index, Arc::clone(block)));
}
SweepWindow {
blocks,
resident: self.resident,
}
}
pub(super) fn requeue(&mut self, block_index: usize) {
if matches!(self.lookup(block_index), BlockLookup::Live(_)) {
self.sweep_queue.push_back(block_index);
}
}
pub(super) fn retire(&mut self, block_index: usize) -> Option<Arc<TaskStateBlock>> {
let entry = block_index
.checked_sub(self.base)
.and_then(|offset| self.entries.get_mut(offset))?;
let retired = entry.take()?;
self.resident -= 1;
while matches!(self.entries.front(), Some(None)) {
self.entries.pop_front();
self.base += 1;
}
Some(retired)
}
pub(super) fn cleanup(&mut self, block_indices: &[usize]) -> Vec<Arc<TaskStateBlock>> {
let retired: Vec<_> = block_indices
.iter()
.filter_map(|&index| self.retire(index))
.collect();
let (base, entries) = (self.base, &self.entries);
self.sweep_queue.retain(|&index| {
index
.checked_sub(base)
.is_some_and(|offset| matches!(entries.get(offset), Some(Some(_))))
});
retired
}
}
pub(super) struct SweepWindow<const N: usize> {
pub(super) blocks: [Option<(usize, Arc<TaskStateBlock>)>; N],
pub(super) resident: usize,
}