use std::collections::{HashMap, HashSet};
use anyhow::Result;
use crate::sdf::schema::FieldKey;
use crate::sdf::{Path, PathElement, Value};
use super::index_cache::IndexCache;
use super::layer_graph::LayerGraph;
use super::prim_graph::ArcType;
use super::prim_index::PrimIndex;
use super::LayerId;
#[derive(Default)]
pub(super) struct PrototypeRegistry {
by_root: HashMap<Path, Prototype>,
by_key: HashMap<InstanceKey, Path>,
count: usize,
}
struct Prototype {
index: usize,
canonical: Path,
instances: Vec<Path>,
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub(super) struct InstanceKey {
arcs: Vec<InstanceArc>,
selections: Vec<(String, String)>,
}
#[derive(Clone, PartialEq, Eq, Hash)]
struct InstanceArc {
arc: u8,
layer: LayerId,
path: String,
offset_bits: u64,
scale_bits: u64,
}
impl PrototypeRegistry {
fn register(&mut self, key: InstanceKey, instance: &Path) -> (Path, Path, bool) {
if let Some(root) = self.by_key.get(&key) {
let root = root.clone();
let prototype = self.by_root.get_mut(&root).expect("key index points to a prototype");
if !prototype.instances.contains(instance) {
prototype.instances.push(instance.clone());
}
return (prototype.canonical.clone(), root, false);
}
let index = self.count;
let path = Path::new(&format!("/__Prototype_{index}")).expect("synthetic prototype path is valid");
self.count += 1;
self.by_key.insert(key, path.clone());
self.by_root.insert(
path.clone(),
Prototype {
index,
canonical: instance.clone(),
instances: vec![instance.clone()],
},
);
(instance.clone(), path, true)
}
#[cfg(test)]
fn canonical_of(&self, prototype: &Path) -> Option<Path> {
self.by_root.get(prototype).map(|p| p.canonical.clone())
}
fn instances_of(&self, prototype: &Path) -> Vec<Path> {
let mut instances = self.instances_unsorted(prototype).to_vec();
instances.sort();
instances
}
fn instances_unsorted(&self, prototype: &Path) -> &[Path] {
self.by_root.get(prototype).map_or(&[], |p| &p.instances)
}
fn roots(&self) -> Vec<Path> {
let mut roots: Vec<(&Path, &Prototype)> = self.by_root.iter().collect();
roots.sort_by_key(|(_, prototype)| prototype.index);
roots.into_iter().map(|(root, _)| root.clone()).collect()
}
fn is_root(&self, path: &Path) -> bool {
self.by_root.contains_key(path)
}
fn is_in_prototype(&self, path: &Path) -> bool {
self.enclosing_root(path.parent()).is_some()
}
fn enclosing_root(&self, start: Option<Path>) -> Option<Path> {
let mut node = start;
while let Some(current) = node {
if self.by_root.contains_key(¤t) {
return Some(current);
}
node = current.parent();
}
None
}
pub(super) fn clear(&mut self) {
self.by_root.clear();
self.by_key.clear();
}
fn remove_affected(&mut self, changed: &[Path]) -> Vec<Path> {
let affected: HashSet<Path> = self
.by_root
.iter()
.filter(|(root, prototype)| {
changed.iter().any(|p| {
p.is_nested_with(root) || prototype.instances.iter().any(|instance| p.is_nested_with(instance))
})
})
.map(|(root, _)| root.clone())
.collect();
self.by_root.retain(|root, _| !affected.contains(root));
self.by_key.retain(|_, root| !affected.contains(root));
affected.into_iter().collect()
}
}
fn instance_key(index: &PrimIndex, instance_depth: u16) -> InstanceKey {
let local = index.instance_local_nodes(instance_depth);
let mut arcs = Vec::new();
let mut selections = Vec::new();
for (id, node) in index.nodes_with_ids() {
if local[id.idx()] || node.is_culled() {
continue;
}
if node.arc == ArcType::Variant {
if let Some(PathElement::Variant { set, selection }) = node.path.last_element() {
selections.push((set.to_string(), selection.to_string()));
}
}
let offset = node.map_to_root.time_offset();
arcs.push(InstanceArc {
arc: node.arc as u8,
layer: node.layer_id(),
path: node.path.strip_all_variant_selections().to_string(),
offset_bits: offset.offset.to_bits(),
scale_bits: offset.scale.to_bits(),
});
}
InstanceKey { arcs, selections }
}
impl IndexCache {
pub(crate) fn invalidate_prototypes(&mut self, changed: &[Path]) {
for root in self.prototypes.remove_affected(changed) {
self.drop_index_subtree(&root);
}
self.redirected_prims.clear();
}
pub(crate) fn is_instance(&mut self, graph: &LayerGraph, path: &Path) -> Result<bool> {
if path.is_abs_root() {
return Ok(false);
}
let composed = self.effective_path(graph, path)?;
self.ensure_index(graph, &composed)?;
let index = &self.indices[&composed];
if !index.has_composition_arc() {
return Ok(false);
}
Ok(matches!(
index.resolve_field(FieldKey::Instanceable.as_str(), graph, None)?,
Some(Value::Bool(true))
))
}
fn register_prototype(&mut self, graph: &LayerGraph, instance: &Path) -> Result<(Path, Path)> {
self.ensure_index(graph, instance)?;
let key = instance_key(&self.indices[instance], instance.prim_element_count() as u16);
let (canonical, prototype, minted) = self.prototypes.register(key, instance);
if minted {
self.drop_index_subtree(&prototype);
self.redirected_prims.retain(|path, _| !path.has_prefix(&prototype));
self.materialize_prototype(graph, &canonical, &prototype);
}
Ok((canonical, prototype))
}
fn materialize_prototype(&mut self, graph: &LayerGraph, canonical: &Path, prototype: &Path) {
let mut index = self.indices[canonical].clone();
index.mark_instance_local_inert(canonical.prim_element_count() as u16);
index.rebase_root(canonical, prototype);
let mut context = index.context_for_children(graph, &self.root_parent_context());
context.instance_depth = None;
debug_assert!(
!matches!(
index.resolve_field(FieldKey::Instanceable.as_str(), graph, None),
Ok(Some(Value::Bool(true)))
),
"materialized prototype root's instanceable must be inert",
);
self.cache_index(graph, prototype, index, context);
}
pub(crate) fn prototype_of(&mut self, graph: &LayerGraph, instance: &Path) -> Result<Option<Path>> {
if !self.is_instance(graph, instance)? {
return Ok(None);
}
Ok(Some(self.register_prototype(graph, instance)?.1))
}
pub(crate) fn instances_of(&self, prototype: &Path) -> Vec<Path> {
self.prototypes.instances_of(prototype)
}
pub(crate) fn prototypes(&self) -> Vec<Path> {
self.prototypes.roots()
}
pub(crate) fn is_prototype(&self, path: &Path) -> bool {
self.prototypes.is_root(path)
}
pub(crate) fn is_in_prototype(&self, path: &Path) -> bool {
self.prototypes.is_in_prototype(path)
}
pub(crate) fn prototype_root_of(&self, path: &Path) -> Option<Path> {
self.prototypes.enclosing_root(Some(path.clone()))
}
pub(crate) fn prototype_instances(&self, root: &Path) -> &[Path] {
self.prototypes.instances_unsorted(root)
}
pub(crate) fn is_instance_proxy(&mut self, graph: &LayerGraph, path: &Path) -> Result<bool> {
if path.is_abs_root() || self.is_prototype(path) {
return Ok(false);
}
if self.enclosing_instance(graph, path)?.is_none() {
return Ok(false);
}
self.has_spec(graph, path)
}
pub(crate) fn prim_in_prototype(&mut self, graph: &LayerGraph, path: &Path) -> Result<Option<Path>> {
if !self.is_instance_proxy(graph, path)? {
return Ok(None);
}
let instance = self
.enclosing_instance(graph, path)?
.expect("an instance proxy has an enclosing instance");
let (_canonical, prototype) = self.register_prototype(graph, &instance)?;
Ok(path.replace_prefix(&instance, &prototype))
}
fn enclosing_instance(&mut self, graph: &LayerGraph, path: &Path) -> Result<Option<Path>> {
let mut ancestor = path.parent();
while let Some(current) = ancestor {
if current.is_abs_root() {
break;
}
if self.is_instance(graph, ¤t)? {
return Ok(Some(current));
}
ancestor = current.parent();
}
Ok(None)
}
fn redirect_prim(&mut self, graph: &LayerGraph, prim: &Path) -> Result<Path> {
match self.redirect_anchor(graph, prim)? {
Some((origin, target)) => Ok(prim.replace_prefix(&origin, &target).unwrap_or_else(|| prim.clone())),
None => Ok(prim.clone()),
}
}
pub(super) fn redirect_anchor(&mut self, graph: &LayerGraph, prim: &Path) -> Result<Option<(Path, Path)>> {
if let Some(instance) = self.enclosing_instance(graph, prim)? {
let (_canonical, prototype) = self.register_prototype(graph, &instance)?;
return Ok(Some((instance, prototype)));
}
Ok(None)
}
pub(super) fn effective_path(&mut self, graph: &LayerGraph, path: &Path) -> Result<Path> {
let prim = path.prim_path();
let redirected = match self.redirected_prims.get(&prim) {
Some(hit) => hit.clone(),
None => {
let redirected = self.redirect_prim(graph, &prim)?;
self.redirected_prims.insert(prim.clone(), redirected.clone());
redirected
}
};
if redirected == prim {
return Ok(path.clone());
}
Ok(path.replace_prefix(&prim, &redirected).unwrap_or(redirected))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn path(s: &str) -> Path {
Path::new(s).expect("valid test path")
}
fn key(tag: &str) -> InstanceKey {
InstanceKey {
arcs: Vec::new(),
selections: vec![(tag.to_string(), tag.to_string())],
}
}
#[test]
fn remove_affected_targets_touched() {
let mut reg = PrototypeRegistry::default();
let (_, p0, minted0) = reg.register(key("p"), &path("/A"));
reg.register(key("p"), &path("/B"));
let (_, p1, minted1) = reg.register(key("q"), &path("/C"));
assert!(minted0 && minted1);
assert_ne!(p0, p1);
let dropped = reg.remove_affected(&[path("/C/Child")]);
assert_eq!(dropped, vec![p1.clone()]);
assert_eq!(reg.canonical_of(&p0), Some(path("/A")));
assert!(reg.canonical_of(&p1).is_none());
let (_, p1b, minted) = reg.register(key("q"), &path("/C"));
assert!(minted);
assert_ne!(p1b, p1);
}
#[test]
fn remove_affected_keeps_unrelated() {
let mut reg = PrototypeRegistry::default();
let (_, p0, _) = reg.register(key("p"), &path("/A"));
let (_, p1, _) = reg.register(key("q"), &path("/C"));
assert!(reg.remove_affected(&[path("/Extra")]).is_empty());
assert!(reg.canonical_of(&p0).is_some());
assert!(reg.canonical_of(&p1).is_some());
}
#[test]
fn remove_affected_ancestor_and_root() {
let mut reg = PrototypeRegistry::default();
let (_, p0, _) = reg.register(key("p"), &path("/Group/A"));
assert_eq!(reg.remove_affected(std::slice::from_ref(&p0)), vec![p0.clone()]);
let (_, p0b, _) = reg.register(key("p"), &path("/Group/A"));
assert_eq!(reg.remove_affected(&[path("/Group")]), vec![p0b]);
}
}