use std::collections::{HashMap, HashSet};
use crate::sdf::Path;
use super::index::{ArcType, PrimIndex};
#[derive(Debug, Default)]
pub(super) struct Dependencies {
per_layer: Vec<HashMap<Path, Vec<Path>>>,
by_prim: HashMap<Path, Vec<(usize, Path)>>,
}
impl Dependencies {
fn ensure_layer_capacity(&mut self, layer_count: usize) {
if self.per_layer.len() < layer_count {
self.per_layer.resize_with(layer_count, HashMap::new);
}
}
pub(super) fn add(&mut self, prim_index_path: &Path, index: &PrimIndex, layer_count: usize) {
self.ensure_layer_capacity(layer_count);
self.remove(prim_index_path);
let mut seen: HashSet<(usize, Path)> = HashSet::new();
let mut registered: Vec<(usize, Path)> = Vec::new();
for node in index.nodes() {
if node.arc == ArcType::Root && node.path == *prim_index_path {
continue;
}
let key = (node.layer_index, node.path.clone());
if !seen.insert(key.clone()) {
continue;
}
registered.push(key);
self.per_layer[node.layer_index]
.entry(node.path.clone())
.or_default()
.push(prim_index_path.clone());
}
for li in 0..layer_count {
let key = (li, prim_index_path.clone());
if !seen.insert(key.clone()) {
continue;
}
registered.push(key);
self.per_layer[li]
.entry(prim_index_path.clone())
.or_default()
.push(prim_index_path.clone());
}
self.by_prim.insert(prim_index_path.clone(), registered);
}
pub(super) fn remove(&mut self, prim_index_path: &Path) {
let Some(sites) = self.by_prim.remove(prim_index_path) else {
return;
};
for (li, site) in sites {
if let Some(map) = self.per_layer.get_mut(li) {
if let Some(deps) = map.get_mut(&site) {
deps.retain(|p| p != prim_index_path);
if deps.is_empty() {
map.remove(&site);
}
}
}
}
}
pub(super) fn clear(&mut self) {
for m in &mut self.per_layer {
m.clear();
}
self.by_prim.clear();
}
pub(super) fn lookup_with_ancestors(&self, layer_index: usize, site_path: &Path) -> Vec<Path> {
let Some(map) = self.per_layer.get(layer_index) else {
return Vec::new();
};
let mut out: Vec<Path> = Vec::new();
let mut seen: HashSet<Path> = HashSet::new();
let mut cursor = Some(site_path.clone());
while let Some(p) = cursor {
if let Some(deps) = map.get(&p) {
for d in deps {
if seen.insert(d.clone()) {
out.push(d.clone());
}
}
}
if p.is_abs_root() {
break;
}
cursor = p.parent();
}
out
}
pub(super) fn subtree_lookup(&self, layer_index: usize, prefix: &Path) -> Vec<Path> {
let Some(map) = self.per_layer.get(layer_index) else {
return Vec::new();
};
let mut out: Vec<Path> = Vec::new();
let mut seen: HashSet<Path> = HashSet::new();
for (site, deps) in map {
if site.has_prefix(prefix) {
for d in deps {
if seen.insert(d.clone()) {
out.push(d.clone());
}
}
}
}
out
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pcp::index::Node;
use crate::pcp::mapping::MapFunction;
fn p(s: &str) -> Path {
Path::new(s).expect("valid path")
}
fn make_index(prim_path: &Path, nodes: Vec<(ArcType, usize, Path)>) -> PrimIndex {
let mut idx = PrimIndex::default();
for (arc, layer_index, node_path) in nodes {
let map = MapFunction::from_pair_identity(node_path.clone(), prim_path.clone());
idx.push_node(Node {
layer_index,
path: node_path,
arc,
map_to_parent: map.clone(),
map_to_root: map,
introduced_by_specialize: false,
});
}
idx
}
#[test]
fn synthetic_self_registration_covers_empty_index() {
let mut deps = Dependencies::default();
let foo = p("/Foo");
let index = make_index(&foo, vec![(ArcType::Root, 0, foo.clone())]);
deps.add(&foo, &index, 2);
assert_eq!(deps.lookup_with_ancestors(0, &foo), vec![foo.clone()]);
assert_eq!(deps.lookup_with_ancestors(1, &foo), vec![foo.clone()]);
}
#[test]
fn reference_arc_registers_dependency() {
let mut deps = Dependencies::default();
let here = p("/World/Inst");
let there = p("/Model");
let index = make_index(
&here,
vec![(ArcType::Root, 0, here.clone()), (ArcType::Reference, 1, there.clone())],
);
deps.add(&here, &index, 2);
assert_eq!(deps.lookup_with_ancestors(1, &there), vec![here.clone()]);
}
#[test]
fn ancestor_walk_finds_dep() {
let mut deps = Dependencies::default();
let here = p("/A/B");
let arc_site = p("/X/Y");
let index = make_index(
&here,
vec![
(ArcType::Root, 0, here.clone()),
(ArcType::Inherit, 0, arc_site.clone()),
],
);
deps.add(&here, &index, 1);
assert_eq!(deps.lookup_with_ancestors(0, &p("/X/Y/Child")), vec![here.clone()]);
}
#[test]
fn subtree_lookup_finds_descendants() {
let mut deps = Dependencies::default();
let a = p("/A");
let b = p("/B");
let xy = p("/X/Y");
let xy_child = p("/X/Y/Child");
deps.add(&a, &make_index(&a, vec![(ArcType::Reference, 0, xy.clone())]), 1);
deps.add(&b, &make_index(&b, vec![(ArcType::Reference, 0, xy_child.clone())]), 1);
let mut found = deps.subtree_lookup(0, &p("/X"));
found.sort();
assert_eq!(found, vec![a.clone(), b.clone()]);
}
#[test]
fn remove_drops_all_entries() {
let mut deps = Dependencies::default();
let here = p("/A");
let there = p("/X");
deps.add(
&here,
&make_index(&here, vec![(ArcType::Reference, 0, there.clone())]),
1,
);
assert!(!deps.lookup_with_ancestors(0, &there).is_empty());
deps.remove(&here);
assert!(deps.lookup_with_ancestors(0, &there).is_empty());
}
}