use std::borrow::Cow;
use std::collections::{HashMap, HashSet};
use crate::sdf;
use crate::sdf::schema::FieldKey;
use crate::sdf::{Path, PathElement, Value};
use crate::tf::Token;
use super::diagnostics::Diagnostics;
use super::index_cache::IndexCache;
use super::layer_graph::LayerGraph;
use super::load_rules::LoadRules;
use super::population_mask::PopulationMask;
use super::prim_graph::ArcType;
use super::prim_index::PrimIndex;
use super::prim_indexer::ExprVarDeps;
use super::{LayerId, QueryError};
#[derive(Default)]
pub(super) struct PrototypeRegistry {
by_root: sdf::PathTable<Prototype>,
by_instance: sdf::PathTable<Path>,
by_key: HashMap<InstanceKey, Path>,
count: usize,
}
pub(super) struct Prototype {
index: usize,
instances: Vec<Path>,
pub(super) relative_load_rules: LoadRules,
pub(super) relative_mask: PopulationMask,
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub(super) struct InstanceKey {
arcs: Vec<InstanceArc>,
selections: Vec<(String, String)>,
load_rules: LoadRules,
mask: PopulationMask,
}
#[derive(Clone, PartialEq, Eq, Hash)]
struct InstanceArc {
arc: u8,
layer: LayerId,
path: String,
layer_offset_bits: (u64, u64),
}
impl PrototypeRegistry {
fn register(&mut self, key: InstanceKey, composed: &Path) -> (Path, bool) {
if let Some(root) = self.by_key.get(&key) {
let root = root.clone();
if self.by_instance.get(composed) != Some(&root) {
let prototype = self.by_root.get_mut(&root).expect("key index points to a prototype");
prototype.instances.push(composed.clone());
self.by_instance.insert(composed.clone(), root.clone());
}
return (root, false);
}
let index = self.count;
let path = Path::new(&format!("/{PROTOTYPE_PREFIX}{index}")).expect("synthetic prototype path is valid");
self.count += 1;
let relative_load_rules = key.load_rules.clone();
let relative_mask = key.mask.clone();
self.by_key.insert(key, path.clone());
self.by_root.insert(
path.clone(),
Prototype {
index,
instances: vec![composed.clone()],
relative_load_rules,
relative_mask,
},
);
self.by_instance.insert(composed.clone(), path.clone());
(path, true)
}
fn get(&self, prototype: &Path) -> Option<&Prototype> {
self.by_root.get(prototype)
}
#[cfg(test)]
fn canonical_of(&self, prototype: &Path) -> Option<Path> {
self.by_root.get(prototype).and_then(|p| p.instances.first().cloned())
}
fn instances_of(&self, prototype: &Path) -> Vec<Path> {
let mut instances = self
.by_root
.get(prototype)
.map_or_else(Vec::new, |p| p.instances.clone());
instances.sort();
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 enclosing_root(&self, path: &Path) -> Option<Path> {
self.by_root.nearest_ancestor(path).map(|(root, _)| root.clone())
}
fn remove_affected(&mut self, changed: &[Path]) -> Vec<Path> {
let mut affected: HashSet<Path> = HashSet::new();
let mut worklist: Vec<&Path> = changed.iter().collect();
while let Some(p) = worklist.pop() {
let roots = self.by_root.ancestors(p).chain(self.by_root.subtree(p));
let instances = self.by_instance.ancestors(p).chain(self.by_instance.subtree(p));
let touched = roots.map(|(root, _)| root).chain(instances.map(|(_, root)| root));
for root in touched {
if !affected.contains(root) {
affected.insert(root.clone());
worklist.push(root);
}
}
}
for root in &affected {
if let Some(prototype) = self.by_root.remove(root) {
for instance in &prototype.instances {
self.by_instance.remove(instance);
}
}
}
self.by_key.retain(|_, root| !affected.contains(root));
let mut dropped: Vec<Path> = affected.into_iter().collect();
dropped.sort();
dropped
}
}
const PROTOTYPE_PREFIX: &str = "__Prototype_";
pub(crate) fn is_prototype_namespace(path: &Path) -> bool {
path.is_abs()
&& path
.root_prim_name()
.is_some_and(|name| name.starts_with(PROTOTYPE_PREFIX))
}
fn instance_key(index: &PrimIndex, instance_depth: u16, load_rules: LoadRules, mask: PopulationMask) -> InstanceKey {
let local = index.instance_local_nodes(instance_depth, 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
&& let Some(PathElement::Variant { set, selection }) = node.path.last_element()
{
selections.push((set.to_string(), selection.to_string()));
}
arcs.push(InstanceArc {
arc: node.arc as u8,
layer: node.layer_id(),
path: node.path.strip_all_variant_selections().to_string(),
layer_offset_bits: node.map_to_root.time_offset().to_bits(),
});
}
InstanceKey {
arcs,
selections,
load_rules,
mask,
}
}
impl IndexCache {
pub(crate) fn invalidate_prototypes(&mut self, changed: &[Path]) -> Vec<Path> {
self.clear_population_memos();
let retired = self.prototypes.remove_affected(changed);
for root in &retired {
self.drop_index_subtree(root);
}
retired
}
pub(crate) fn is_populated(&mut self, graph: &LayerGraph, path: &Path) -> Result<bool, QueryError> {
if path.is_abs_root() {
return Ok(true);
}
if let Some(hit) = self.populated_prims.get(path) {
return Ok(*hit);
}
let pending_before = self.pending_loads.len();
let parent = path.parent().expect("a non-root path has a parent");
let populated = self.mask_includes(path) && self.is_populated(graph, &parent)? && {
let composed = self.effective_path(graph, path)?;
self.has_spec_at(graph, &composed)? && self.active_at(graph, &composed)?
};
if !self.provisional(path, pending_before) {
self.populated_prims.insert(path.clone(), populated);
}
Ok(populated)
}
pub(super) fn provisional(&self, path: &Path, pending_before: usize) -> bool {
self.pending_loads.len() != pending_before || self.in_unregistered_prototype(path)
}
pub(super) fn in_unregistered_prototype(&self, path: &Path) -> bool {
is_prototype_namespace(path) && self.prototype_root_of(path).is_none()
}
pub(crate) fn is_instance(&mut self, graph: &LayerGraph, path: &Path) -> Result<bool, QueryError> {
if path.is_abs_root() || self.is_prototype(path) {
return Ok(false);
}
if !self.is_populated(graph, path)? {
return Ok(false);
}
let composed = self.effective_path(graph, path)?;
self.ensure_index(graph, &composed)?;
let index = self.cached(&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, QueryError> {
let composed = self.effective_path(graph, instance)?;
self.ensure_index(graph, &composed)?;
let relative_load_rules = {
let (rules, relative_instance) = self.scoped_load_rules(&composed);
rules.make_relative_to(&relative_instance)
};
let relative_mask = {
let (mask, relative_instance) = self.scoped_mask(&composed);
mask.make_relative_to(&relative_instance)
};
let key = instance_key(
self.cached(&composed),
composed.prim_element_count() as u16,
relative_load_rules,
relative_mask,
);
let (prototype, minted) = self.prototypes.register(key, &composed);
if minted {
self.materialize_prototype(graph, &composed, &prototype);
}
Ok(prototype)
}
fn materialize_prototype(&mut self, graph: &LayerGraph, canonical: &Path, prototype: &Path) {
let mut index = self.cached(canonical).clone();
let depth = canonical.prim_element_count() as u16;
index.mark_instance_local_inert(depth, depth);
index.rebase_root(canonical, prototype);
let (mut context, _) = index.context_for_children(graph, &self.root_parent_context());
context.instance_depth = None;
self.cache_index(
graph,
prototype,
index,
context,
Diagnostics::default(),
ExprVarDeps::default(),
);
}
pub(crate) fn prototype_of(&mut self, graph: &LayerGraph, instance: &Path) -> Result<Option<Path>, QueryError> {
if !self.is_instance(graph, instance)? {
return Ok(None);
}
Ok(Some(self.register_prototype(graph, instance)?))
}
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.enclosing_root(path).is_some()
}
pub(crate) fn prototype_root_of(&self, path: &Path) -> Option<Path> {
if is_prototype_namespace(path) {
self.prototypes.enclosing_root(path)
} else {
None
}
}
pub(super) fn scoped<'a, T>(
&'a self,
path: &'a Path,
global: &'a T,
stored: impl FnOnce(&'a Prototype) -> &'a T,
) -> (&'a T, Cow<'a, Path>) {
let Some(root) = self.prototype_root_of(path) else {
return (global, Cow::Borrowed(path));
};
let relative = path
.replace_prefix(&root, &Path::abs_root())
.unwrap_or_else(Path::abs_root);
let prototype = self
.prototypes
.get(&root)
.expect("a registered prototype root has stored tables");
(stored(prototype), Cow::Owned(relative))
}
pub(super) fn scoped_mask<'a>(&'a self, path: &'a Path) -> (&'a PopulationMask, Cow<'a, Path>) {
self.scoped(path, &self.population_mask, |p| &p.relative_mask)
}
pub(crate) fn mask_includes(&self, path: &Path) -> bool {
let (mask, relative) = self.scoped_mask(path);
mask.includes(&relative)
}
pub(crate) fn population_mask(&self) -> &PopulationMask {
&self.population_mask
}
pub(crate) fn filter_child_names(&self, parent: &Path, children: Vec<Token>) -> Vec<Token> {
let (mask, relative) = self.scoped_mask(parent);
if mask.includes_subtree(&relative) {
return children;
}
children
.into_iter()
.filter(|name| {
relative
.append_path(name.as_str())
.is_ok_and(|child| mask.includes(&child))
})
.collect()
}
pub(crate) fn is_instance_proxy(&mut self, graph: &LayerGraph, path: &Path) -> Result<bool, QueryError> {
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>, QueryError> {
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 prototype = self.register_prototype(graph, &instance)?;
Ok(path.replace_prefix(&instance, &prototype))
}
fn enclosing_instance(&mut self, graph: &LayerGraph, path: &Path) -> Result<Option<Path>, QueryError> {
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, QueryError> {
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)>, QueryError> {
if let Some(instance) = self.enclosing_instance(graph, prim)? {
let 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, QueryError> {
let prim = path.prim_path();
let redirected = if let Some(hit) = self.redirected_prims.get(&prim) {
hit.clone()
} else {
let pending_before = self.pending_loads.len();
let redirected = self.redirect_prim(graph, &prim)?;
if !self.provisional(&prim, pending_before) {
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())],
load_rules: LoadRules::default(),
mask: PopulationMask::all(),
}
}
#[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_cascades() {
let mut reg = PrototypeRegistry::default();
let (p0, _) = reg.register(key("outer"), &path("/A"));
let (p1, _) = reg.register(key("mid"), &path(&format!("{p0}/Inner")));
let (p2, _) = reg.register(key("inner"), &path(&format!("{p1}/Nested")));
let dropped = reg.remove_affected(&[path("/A")]);
for root in [&p0, &p1, &p2] {
assert!(dropped.contains(root), "{root} must be dropped");
assert!(reg.canonical_of(root).is_none(), "{root} must be unmapped");
}
}
#[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]);
}
}