use crate::pager::Pager;
use crate::pager::format::page_kind::PageKind;
use crate::vfs::Vfs;
use crate::{PagedbError, RealmId, Result};
use super::layout::{decode_chain_header, decode_entry};
pub struct ChainWalk {
tortoise: u64,
hare: u64,
entries: Vec<(u64, u64)>,
decode_entries: bool,
last_count: usize,
}
impl ChainWalk {
#[must_use]
pub fn new(head: u64) -> Self {
Self {
tortoise: head,
hare: head,
entries: Vec::new(),
decode_entries: true,
last_count: 0,
}
}
#[must_use]
pub fn headers_only(head: u64) -> Self {
Self {
decode_entries: false,
..Self::new(head)
}
}
#[must_use]
pub fn last_count(&self) -> usize {
self.last_count
}
#[must_use]
pub fn tail(&self) -> u64 {
self.tortoise
}
#[must_use]
pub fn entries(&self) -> &[(u64, u64)] {
&self.entries
}
pub async fn advance<V: Vfs + Clone>(
&mut self,
pager: &Pager<V>,
realm_id: RealmId,
) -> Result<Option<u64>> {
self.entries.clear();
let page_id = self.tortoise;
if page_id == 0 {
return Ok(None);
}
let next = {
let guard = pager
.read_main_page(page_id, realm_id, PageKind::Free)
.await?;
let body = guard.body_ref();
let (next, count) = decode_chain_header(body)?;
self.last_count = count;
if self.decode_entries {
self.entries.reserve(count);
for index in 0..count {
self.entries.push(decode_entry(body, index)?);
}
}
next
};
self.tortoise = next;
for _ in 0..2 {
if self.hare == 0 {
break;
}
let (hare_next, _) = read_chain_link(pager, realm_id, self.hare).await?;
self.hare = hare_next;
}
if self.hare != 0 && self.hare == self.tortoise {
return Err(PagedbError::page_chain_cycle("free_list", self.hare));
}
Ok(Some(page_id))
}
}
async fn read_chain_link<V: Vfs + Clone>(
pager: &Pager<V>,
realm_id: RealmId,
page_id: u64,
) -> Result<(u64, usize)> {
let guard = pager
.read_main_page(page_id, realm_id, PageKind::Free)
.await?;
decode_chain_header(guard.body_ref())
}
#[derive(Debug)]
pub struct ChainPrefix {
pub entries: Vec<(u64, u64)>,
pub chain_pages: Vec<u64>,
pub tail: u64,
}
pub async fn read_chain_prefix<V: Vfs + Clone>(
pager: &Pager<V>,
realm_id: RealmId,
head: u64,
max_pages: usize,
) -> Result<ChainPrefix> {
let mut walk = ChainWalk::new(head);
let mut entries = Vec::new();
let mut chain_pages = Vec::new();
while chain_pages.len() < max_pages {
let Some(page_id) = walk.advance(pager, realm_id).await? else {
break;
};
entries.extend_from_slice(walk.entries());
chain_pages.push(page_id);
}
Ok(ChainPrefix {
entries,
chain_pages,
tail: walk.tail(),
})
}
pub async fn read_chain<V: Vfs + Clone>(
pager: &Pager<V>,
realm_id: RealmId,
head: u64,
) -> Result<(Vec<(u64, u64)>, Vec<u64>)> {
let prefix = read_chain_prefix(pager, realm_id, head, usize::MAX).await?;
debug_assert_eq!(
prefix.tail, 0,
"an unbounded prefix walk must consume the whole chain"
);
Ok((prefix.entries, prefix.chain_pages))
}
pub async fn count_chain<V: Vfs + Clone>(
pager: &Pager<V>,
realm_id: RealmId,
head: u64,
) -> Result<u64> {
let mut walk = ChainWalk::headers_only(head);
let mut total: u64 = 0;
while walk.advance(pager, realm_id).await?.is_some() {
total = total.saturating_add(walk.last_count() as u64);
}
Ok(total)
}
pub async fn count_at_or_above_floor<V: Vfs + Clone>(
pager: &Pager<V>,
realm_id: RealmId,
head: u64,
floor: u64,
) -> Result<u64> {
let mut walk = ChainWalk::new(head);
let mut total: u64 = 0;
while walk.advance(pager, realm_id).await?.is_some() {
let stuck = walk
.entries()
.iter()
.filter(|(commit_id, _)| *commit_id >= floor)
.count() as u64;
total = total.saturating_add(stuck);
}
Ok(total)
}