use std::collections::HashMap;
use std::sync::atomic::AtomicBool;
use super::graph::Graph;
use super::{WriteError, WriteLimits, check};
use crate::ObjectKind;
pub(super) struct Closures<'graph, 'ids> {
graph: &'graph Graph<'ids>,
inverse: &'graph [usize],
remaining: usize,
cached: HashMap<usize, Vec<u64>>,
retained_bytes: usize,
max_bytes: usize,
}
impl<'graph, 'ids> Closures<'graph, 'ids> {
pub fn new(graph: &'graph Graph<'ids>, inverse: &'graph [usize], limits: WriteLimits) -> Self {
Self {
graph,
inverse,
remaining: limits.max_visits,
cached: HashMap::new(),
retained_bytes: 0,
max_bytes: limits.max_bytes,
}
}
pub fn reachable(&mut self, root: usize, cancel: &AtomicBool) -> Result<Vec<u64>, WriteError> {
let before = self.remaining;
let mut bitmap = vec![0_u64; self.graph.kinds.len().div_ceil(64)];
let mut stack = vec![root];
while let Some(index) = stack.pop() {
check(cancel)?;
charge(&mut self.remaining)?;
let bit = self.inverse[index];
let word = &mut bitmap[bit / 64];
let mask = 1 << (bit % 64);
if *word & mask != 0 {
continue;
}
*word |= mask;
if self.graph.kinds[index] == ObjectKind::Commit
&& let Some(cached) = self.cached.get(&index)
{
for (word, &cached) in bitmap.iter_mut().zip(cached) {
check(cancel)?;
charge(&mut self.remaining)?;
*word |= cached;
}
continue;
}
for &target in self.graph.targets(index) {
check(cancel)?;
charge(&mut self.remaining)?;
let bit = self.inverse[target];
if bitmap[bit / 64] & (1 << (bit % 64)) == 0 {
stack.push(target);
}
}
}
let work = before - self.remaining;
if work.saturating_sub(1) > bitmap.len()
&& let Some(bytes) = bitmap
.len()
.checked_mul(size_of::<u64>())
.and_then(|bytes| self.retained_bytes.checked_add(bytes))
.filter(|&bytes| bytes <= self.max_bytes)
{
self.cached.insert(root, bitmap.clone());
self.retained_bytes = bytes;
}
Ok(bitmap)
}
}
fn charge(budget: &mut usize) -> Result<(), WriteError> {
*budget = budget
.checked_sub(1)
.ok_or(WriteError::Limit("reachability visits"))?;
Ok(())
}