use std::borrow::Cow;
use std::collections::hash_map::Entry;
use std::collections::{HashMap, HashSet};
use std::{iter, mem};
use crate::sdf::expr;
use crate::sdf::schema::{ChildrenKey, FieldKey};
use crate::sdf::{self, LayerOffset, Path, Value};
use super::compose_site::evaluate_expression;
use super::diagnostics::Diagnostics;
use super::index_store::PrimRevision;
use super::layer_stack::LayerStackId;
use super::mapping::MapFunction;
use super::prim_graph::{ArcType, Node, NodeFlags, NodeId, PrimIndexGraph, RelocateKind, SpecSite};
use super::prim_indexer::{BuildResult, ExprVarDeps};
use super::{ExpressionContext, LayerGraph, LayerId, VariantFallbackMap};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum SiteScope {
SpecStack,
EveryLayer,
}
#[derive(Debug, Clone, Default)]
pub struct PrimIndex {
graph: PrimIndexGraph,
spec_stack: Vec<SpecSite>,
authors_clips: bool,
path_order: Vec<NodeId>,
}
pub(crate) struct PrimEntry {
pub index: PrimIndex,
pub context: CompositionContext,
pub errors: Diagnostics,
pub property_errors: Diagnostics,
pub resolved_targets: HashMap<TargetMemoKey, TargetMemo>,
pub(super) revision: PrimRevision,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub(crate) enum PropertyTargetKind {
Relationship,
Connection,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub(crate) struct TargetMemoKey {
pub kind: PropertyTargetKind,
pub property_suffix: String,
}
#[derive(Clone)]
pub(crate) struct TargetMemo {
pub targets: Vec<Path>,
pub errors: Diagnostics,
}
#[derive(Debug, Default, Clone, Copy)]
pub(crate) struct SpecRefresh {
pub contributing: bool,
pub needs_rebuild: bool,
}
#[derive(Debug, Default)]
pub(crate) struct NodeRuns(Vec<(NodeId, Vec<SpecSite>)>);
impl NodeRuns {
fn record_with(&mut self, node: NodeId, run: impl FnOnce() -> Vec<SpecSite>) -> Option<&[SpecSite]> {
let at = self.0.binary_search_by_key(&node.idx(), |(id, _)| id.idx()).err()?;
self.0.insert(at, (node, run()));
Some(&self.0[at].1)
}
fn take(&mut self, node: NodeId) -> Option<Vec<SpecSite>> {
let at = self.0.binary_search_by_key(&node.idx(), |(id, _)| id.idx()).ok()?;
Some(self.0.remove(at).1)
}
fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
#[derive(Debug, Clone)]
pub(crate) struct Demand {
pub asset_path: String,
pub context: LayerStackId,
}
impl PrimIndex {
pub fn is_empty(&self) -> bool {
!self
.graph
.iter()
.any(|node| !node.is_inert() && !node.is_culled() && node.has_specs())
}
pub(crate) fn has_composition_arc(&self) -> bool {
self.arena()
.iter()
.any(|node| node.arc != ArcType::Root && !node.is_inert() && !node.is_culled() && node.has_specs())
}
pub fn nodes(&self) -> impl DoubleEndedIterator<Item = &Node> + Clone {
self.all_nodes().filter(|node| !node.is_culled())
}
pub fn all_nodes(&self) -> impl DoubleEndedIterator<Item = &Node> + Clone {
self.ordered_nodes().filter(|node| !node.is_inert())
}
pub(crate) fn dependency_nodes(&self) -> impl Iterator<Item = &Node> {
self.ordered_nodes().filter(|node| tracks_dependency(node))
}
pub(crate) fn dependency_nodes_at(
&self,
prim_path: &Path,
layer: LayerId,
path: &Path,
graph: &LayerGraph,
out: &mut Vec<NodeId>,
) {
out.clear();
let rank = |id: NodeId| self.graph.strength_order.iter().position(|&s| s == id);
out.extend(self.nodes_at(path).iter().copied().filter(|&id| {
let node = &self.graph.nodes[id.idx()];
registers_site(node, prim_path) && graph.stack_contains(node.layer_stack_id(), layer) && rank(id).is_some()
}));
out.sort_by_cached_key(|&id| rank(id).expect("filtered to ranked nodes"));
}
pub(crate) fn translate_dependency_path(&self, node: NodeId, changed: &Path) -> Option<Path> {
let mut id = node;
let mut path = Cow::Borrowed(changed);
while self.node(id).relocate_kind() == Some(RelocateKind::Propagated) {
let placeholder = self.node(id);
let parent = placeholder.parent?;
path = Cow::Owned(path.replace_prefix(&placeholder.path, &self.node(parent).path)?);
id = parent;
}
self.node(id).map_to_root().translate_to_target(&path)
}
pub(crate) fn muted_external_targets(&self) -> &[LayerId] {
self.graph.non_site_deps().map_or(&[][..], |d| &d.muted_external)
}
pub(crate) fn muted_unloaded_targets(&self) -> &[String] {
self.graph.non_site_deps().map_or(&[][..], |d| &d.muted_unloaded)
}
pub(crate) fn default_prim_layers(&self) -> &[LayerId] {
self.graph.non_site_deps().map_or(&[][..], |d| &d.default_prim)
}
pub(crate) fn live_spec_sites(&self) -> impl Iterator<Item = (&SpecSite, &Node)> {
self.spec_stack.iter().filter_map(|site| {
let node = self.node(site.node);
(!node.is_inert() && !node.is_culled()).then_some((site, node))
})
}
pub(crate) fn live_sites<'a>(
&'a self,
graph: &'a LayerGraph,
scope: SiteScope,
) -> impl Iterator<Item = (&'a Node, LayerId, LayerOffset)> {
let specs = (scope == SiteScope::SpecStack).then(|| {
self.live_spec_sites()
.map(|(site, node)| (node, site.layer, site.offset))
});
let every = (scope == SiteScope::EveryLayer).then(|| {
self.nodes().flat_map(|node| {
let arc_offset = node.map_to_root.time_offset();
graph
.layer_stack(node.layer_stack)
.iter()
.map(move |&(layer, sub)| (node, layer, arc_offset.concatenate(&sub)))
})
});
specs.into_iter().flatten().chain(every.into_iter().flatten())
}
fn ordered_nodes(&self) -> impl DoubleEndedIterator<Item = &Node> + Clone {
let nodes = &self.graph.nodes;
self.graph.strength_order.iter().map(move |id| &nodes[id.idx()])
}
pub(crate) fn nodes_with_ids(&self) -> impl DoubleEndedIterator<Item = (NodeId, &Node)> {
let nodes = &self.graph.nodes;
self.graph
.strength_order
.iter()
.map(move |&id| (id, &nodes[id.idx()]))
.filter(|(_, node)| !node.is_inert())
}
pub(crate) fn arena(&self) -> &[Node] {
&self.graph.nodes
}
pub(crate) fn graph(&self) -> &PrimIndexGraph {
&self.graph
}
pub fn root(&self) -> Option<NodeId> {
self.graph.root.is_valid().then_some(self.graph.root)
}
pub(crate) fn refresh_has_specs_at(
&mut self,
layer: LayerId,
path: &Path,
graph: &LayerGraph,
runs: &mut NodeRuns,
) -> SpecRefresh {
let mut refresh = SpecRefresh::default();
for id in self.nodes_at(path).to_vec() {
let node = &mut self.graph.nodes[id.idx()];
let stack = node.layer_stack;
if !graph.layer_stack(stack).iter().any(|&(li, _)| li == layer) {
continue;
}
refresh.contributing |= !node.is_culled();
let Some(run) = runs.record_with(id, || {
let mut run = Vec::new();
push_node_run(graph, node, id, &mut run, None);
run
}) else {
continue;
};
let has_specs = !run.is_empty();
if node.is_culled() {
refresh.needs_rebuild |= has_specs && !node.has_specs;
} else {
let lost_last_spec = node.has_specs && !has_specs;
let cullable = node.arc != ArcType::Root && !node.is_inert();
refresh.needs_rebuild |= lost_last_spec && cullable;
}
node.has_specs = has_specs;
}
refresh
}
fn nodes_at(&self, path: &Path) -> &[NodeId] {
let nodes = &self.graph.nodes;
let lo = self.path_order.partition_point(|&id| nodes[id.idx()].path < *path);
let hi = self.path_order.partition_point(|&id| nodes[id.idx()].path <= *path);
&self.path_order[lo..hi]
}
fn finalize_spec_stack(&mut self, graph: &LayerGraph) {
let (stack, authors_clips) = self.build_spec_stack(graph);
self.spec_stack = stack;
self.authors_clips = authors_clips;
}
pub(crate) fn respec_nodes(&mut self, mut runs: NodeRuns) {
if runs.is_empty() {
return;
}
let old_len = self.spec_stack.len();
let mut old = mem::take(&mut self.spec_stack).into_iter().peekable();
let mut stack = Vec::with_capacity(old_len);
for &id in &self.graph.strength_order {
let old_run = iter::from_fn(|| old.next_if(|site| site.node == id));
match runs.take(id) {
Some(run) => {
old_run.for_each(drop);
stack.extend(run);
}
None => stack.extend(old_run),
}
}
let stranded_old = old.count();
debug_assert_eq!(stranded_old, 0, "spec stack held entries for an unordered node");
debug_assert!(runs.is_empty(), "a refreshed node is missing from the strength order");
self.spec_stack = stack;
}
fn build_spec_stack(&self, graph: &LayerGraph) -> (Vec<SpecSite>, bool) {
let mut stack = Vec::new();
let mut authors_clips = false;
for &id in &self.graph.strength_order {
let node = &self.graph.nodes[id.idx()];
if !node.has_specs {
continue;
}
push_node_run(graph, node, id, &mut stack, Some(&mut authors_clips));
}
(stack, authors_clips)
}
pub(super) fn spec_stack_matches_rebuild(&self, graph: &LayerGraph) -> bool {
let (rebuilt, authors_clips) = self.build_spec_stack(graph);
self.authors_clips == authors_clips
&& self.spec_stack.len() == rebuilt.len()
&& self
.spec_stack
.iter()
.zip(&rebuilt)
.all(|(a, b)| a.node == b.node && a.layer == b.layer && a.offset.to_bits() == b.offset.to_bits())
}
pub(crate) fn authors_clips(&self) -> bool {
self.authors_clips
}
fn build_path_order(&mut self) {
let nodes = &self.graph.nodes;
let mut order: Vec<NodeId> = (0..nodes.len() as u32).map(NodeId).collect();
order.sort_by(|&a, &b| nodes[a.idx()].path.cmp(&nodes[b.idx()].path));
self.path_order = order;
}
pub(crate) fn instance_local_nodes(&self, prim_depth: u16, instance_depth: u16) -> Vec<bool> {
let below_instance = prim_depth.saturating_sub(instance_depth);
let nodes = &self.graph.nodes;
let mut local = vec![false; nodes.len()];
for (i, node) in nodes.iter().enumerate() {
debug_assert!(
node.parent.is_none_or(|p| p.idx() < i),
"instance partition requires every node's parent to precede it in the arena"
);
local[i] = match node.arc {
ArcType::Root => true,
ArcType::Reference | ArcType::Payload => {
self.graph.depth_below_introduction(NodeId(i as u32)) > below_instance
&& node.parent.is_some_and(|p| local[p.idx()])
}
_ => false,
};
}
local
}
pub(crate) fn mark_instance_local_inert(&mut self, prim_depth: u16, instance_depth: u16) {
let local = self.instance_local_nodes(prim_depth, instance_depth);
for (node, &is_local) in self.graph.nodes.iter_mut().zip(local.iter()) {
if is_local {
node.flags |= NodeFlags::INERT;
}
}
}
pub(crate) fn rebase_root(&mut self, from: &Path, to: &Path) {
for node in &mut self.graph.nodes {
node.map_to_root = node.map_to_root.rebase_target(from, to);
node.map_to_parent = node.map_to_parent.rebase_target(from, to);
}
}
pub fn node(&self, id: NodeId) -> &Node {
&self.graph.nodes[id.idx()]
}
pub fn parent(&self, id: NodeId) -> Option<NodeId> {
self.node(id).parent
}
pub fn children(&self, id: NodeId) -> &[NodeId] {
&self.node(id).children
}
pub fn dump_to_string(&self) -> String {
use std::fmt::Write as _;
let mut rank = vec![0usize; self.graph.nodes.len()];
for (r, id) in self.graph.strength_order.iter().enumerate() {
rank[id.idx()] = r;
}
let mut out = String::new();
let mut stack: Vec<(NodeId, usize)> = self
.graph
.strength_order
.iter()
.rev()
.filter(|id| self.node(**id).parent.is_none())
.map(|&id| (id, 0))
.collect();
while let Some((id, depth)) = stack.pop() {
let node = self.node(id);
let _ = write!(
out,
"{:indent$}{:?} [{:?}] {} #{}",
"",
node.arc,
node.layer_id(),
node.path,
rank[id.idx()],
indent = depth * 4
);
let offset = node.map_to_root.time_offset();
if !offset.is_identity() {
let _ = write!(out, " offset=({},{})", offset.offset, offset.scale);
}
if let Some(origin) = node.origin
&& Some(origin) != node.parent
{
let _ = write!(out, " origin={}", origin.0);
}
if !node.flags.is_empty() {
let _ = write!(out, " {:?}", node.flags);
}
out.push('\n');
for &child in node.children.iter().rev() {
stack.push((child, depth + 1));
}
}
out
}
#[cfg(test)]
pub(crate) fn push_node(&mut self, node: Node) {
let id = NodeId(self.graph.nodes.len() as u32);
let at = self
.path_order
.partition_point(|&nid| self.graph.nodes[nid.idx()].path <= node.path);
self.graph.nodes.push(node);
self.graph.strength_order.push(id);
self.path_order.insert(at, id);
}
#[cfg(test)]
pub(crate) fn build_with_context(path: &Path, stack: &LayerGraph, ctx: &CompositionContext) -> BuildResult<Self> {
Self::build_with_cache(path, stack, ctx, &sdf::PathTable::new(), true)
.map(|(index, _errors, _pending, _deps)| index)
}
pub(crate) fn build_with_cache(
path: &Path,
stack: &LayerGraph,
ctx: &CompositionContext,
cached_indices: &sdf::PathTable<PrimEntry>,
load_payloads: bool,
) -> BuildResult<(Self, Diagnostics, Vec<Demand>, ExprVarDeps)> {
Self::build_with_cache_in(path, stack, ctx, cached_indices, LayerStackId::ROOT, load_payloads)
}
pub(crate) fn build_with_cache_in(
path: &Path,
stack: &LayerGraph,
ctx: &CompositionContext,
cached_indices: &sdf::PathTable<PrimEntry>,
ambient: LayerStackId,
load_payloads: bool,
) -> BuildResult<(Self, Diagnostics, Vec<Demand>, ExprVarDeps)> {
if ambient == LayerStackId::ROOT
&& let Some(cached) = cached_indices.get(path)
{
return Ok((
cached.index.clone(),
Diagnostics::default(),
Vec::new(),
ExprVarDeps::default(),
));
}
let indexer = super::prim_indexer::Indexer::new(stack, ctx, cached_indices, ambient, load_payloads);
let super::prim_indexer::BuildOutput {
graph,
errors,
pending_loads,
expr_var_deps,
hit_cycle: _,
} = indexer.build(path)?;
let mut index = PrimIndex {
graph: graph.unwrap_or_default(),
spec_stack: Vec::new(),
authors_clips: false,
path_order: Vec::new(),
};
index.finalize_spec_stack(stack);
index.build_path_order();
Ok((index, errors, pending_loads, expr_var_deps))
}
pub(crate) fn context_for_children(
&self,
stack: &LayerGraph,
parent_ctx: &CompositionContext,
) -> (CompositionContext, ExprVarDeps) {
let mut expr_var_deps = ExprVarDeps::default();
let selections = resolve_variant_selections_in(
self.nodes(),
stack,
&parent_ctx.variant_fallbacks,
&parent_ctx.selections,
&mut expr_var_deps,
);
let mut ancestor_arcs = parent_ctx.ancestor_arcs.clone();
for (_, node) in self.nodes_with_ids() {
if node.arc != ArcType::Root {
ancestor_arcs.push(AncestorArc {
map: node.map_to_root.clone(),
});
}
}
let mut merged_selections = parent_ctx.selections.clone();
for (k, v) in selections {
merged_selections.entry(k).or_insert(v);
}
let context = CompositionContext {
selections: merged_selections,
ancestor_arcs,
variant_fallbacks: parent_ctx.variant_fallbacks.clone(),
may_have_clips: parent_ctx.may_have_clips || self.authors_clips,
instance_depth: parent_ctx.instance_depth,
};
(context, expr_var_deps)
}
pub(crate) fn variant_selections(&self) -> Vec<(String, String)> {
let mut selections: HashMap<String, String> = HashMap::new();
for node in self.all_nodes() {
if !node.path.is_prim_variant_selection_path() {
continue;
}
if let Some(sdf::PathElement::Variant { set, selection }) = node.path.last_element() {
selections
.entry(set.to_string())
.or_insert_with(|| selection.to_string());
}
}
let mut out: Vec<(String, String)> = selections.into_iter().collect();
out.sort();
out
}
}
#[derive(Debug, Clone)]
pub(crate) struct CompositionContext {
pub selections: HashMap<String, String>,
pub ancestor_arcs: Vec<AncestorArc>,
pub variant_fallbacks: VariantFallbackMap,
pub may_have_clips: bool,
pub instance_depth: Option<u16>,
}
impl Default for CompositionContext {
fn default() -> Self {
Self {
selections: HashMap::new(),
ancestor_arcs: Vec::new(),
variant_fallbacks: VariantFallbackMap::new(),
may_have_clips: false,
instance_depth: None,
}
}
}
impl CompositionContext {
pub fn within_instance(&self) -> bool {
self.instance_depth.is_some()
}
}
#[derive(Debug, Clone)]
pub(crate) struct AncestorArc {
pub map: MapFunction,
}
fn push_node_run(graph: &LayerGraph, node: &Node, id: NodeId, out: &mut Vec<SpecSite>, mut clips: Option<&mut bool>) {
let arc_offset = node.map_to_root.time_offset();
for &(layer, sub) in graph.layer_stack(node.layer_stack).iter() {
let data = graph.layer(layer).data();
if !data.has_spec(&node.path) {
continue;
}
out.push(SpecSite {
node: id,
layer,
offset: arc_offset.concatenate(&sub),
});
if let Some(authors_clips) = clips.as_deref_mut()
&& !*authors_clips
{
*authors_clips = super::clip::CLIP_FIELDS
.iter()
.any(|key| data.has_field(&node.path, key.as_str()));
}
}
}
fn tracks_dependency(node: &Node) -> bool {
!node.is_inert() || node.is_relocate_source()
}
pub(super) fn registers_site(node: &Node, prim_path: &Path) -> bool {
tracks_dependency(node) && !(node.arc == ArcType::Root && node.path == *prim_path)
}
pub(super) fn stack_has_spec(graph: &LayerGraph, stack: LayerStackId, path: &Path) -> bool {
graph
.layer_stack(stack)
.iter()
.any(|&(li, _)| graph.layer(li).data().has_spec(path))
}
fn resolve_variant_selections_in<'a>(
nodes: impl Iterator<Item = &'a Node> + Clone,
graph: &LayerGraph,
variant_fallbacks: &VariantFallbackMap,
seed: &HashMap<String, String>,
expr_var_deps: &mut ExprVarDeps,
) -> HashMap<String, String> {
let mut selections: HashMap<String, String> = HashMap::new();
for (set_name, selection) in seed {
selections.entry(set_name.clone()).or_insert_with(|| selection.clone());
}
let mut ordered: Vec<&Node> = nodes.collect();
ordered.sort_by_key(|n| n.arc);
for node in &ordered {
for &(layer, _) in graph.layer_stack(node.layer_stack_id()).iter() {
if let Ok(value) = graph
.layer(layer)
.data()
.get_field(&node.path, FieldKey::VariantSelection.as_str())
&& let Value::VariantSelectionMap(map) = value.into_owned()
{
for (set_name, selection) in map {
let Entry::Vacant(entry) = selections.entry(set_name) else {
continue;
};
let selection = if expr::is_expression(&selection) {
let vars = graph.stack_expression_variables(node.layer_stack_id());
let mut used_vars = HashSet::new();
let evaluated = evaluate_expression(
&selection,
vars,
ExpressionContext::Variant,
graph.identifier(layer),
&node.path,
None,
Some(&mut used_vars),
);
expr_var_deps.record(node.layer_stack_id(), used_vars);
match evaluated.into_selection() {
Some(selection) => selection,
None => continue,
}
} else {
selection
};
entry.insert(selection);
}
}
}
}
for node in &ordered {
for &(layer, _) in graph.layer_stack(node.layer_stack_id()).iter() {
let data = graph.layer(layer).data();
let Ok(value) = data.get_field(&node.path, ChildrenKey::VariantSetChildren.as_str()) else {
continue;
};
let Value::TokenVec(set_names) = value.into_owned() else {
continue;
};
for set_name in set_names {
let set_name = String::from(set_name);
if selections.get(&set_name).is_some_and(|sel| !sel.is_empty()) {
continue;
}
let Ok(set_path) = node.path.append_variant_selection(&set_name, "") else {
continue;
};
let Ok(val) = data.get_field(&set_path, ChildrenKey::VariantChildren.as_str()) else {
continue;
};
let Value::TokenVec(variants) = val.into_owned() else {
continue;
};
let fallbacks = variant_fallbacks.get(&set_name);
if let Some(fb) = fallbacks.iter().find(|fb| variants.iter().any(|v| v == fb.as_str())) {
selections.insert(set_name, fb.clone());
}
}
}
}
selections
}
#[cfg(test)]
impl PrimIndex {
pub(crate) fn push_node_strongest(&mut self, node: Node) {
self.push_node(node);
let id = self.graph.strength_order.pop().expect("just pushed");
self.graph.strength_order.insert(0, id);
}
pub(crate) fn record_default_prim(&mut self, layer: LayerId) {
self.graph.non_site_deps_mut().default_prim.push(layer);
}
}
#[cfg(test)]
pub(crate) mod tests {
use std::cmp::Ordering;
use super::*;
use crate::pcp::CompositionDiagnostic;
use crate::Result;
use crate::sdf::LayerOffset;
const VENDOR_COMPOSITION: &str = concat!(
env!("CARGO_WORKSPACE_DIR"),
"vendor/usd-wg-assets/test_assets/foundation/stage_composition"
);
fn manifest_dir() -> String {
std::env::var("CARGO_MANIFEST_DIR").unwrap()
}
fn composition_path(relative: &str) -> String {
format!("{VENDOR_COMPOSITION}/{relative}")
}
fn fixture_path(relative: &str) -> String {
format!("{}/fixtures/{relative}", manifest_dir())
}
fn load_layers(path: &str) -> Result<Vec<sdf::Layer>> {
Ok(sdf::LayerRegistry::default().collect_with_arcs(path)?)
}
fn build(stack: &mut LayerGraph, prim: &str) -> PrimIndex {
build_with_fallbacks(stack, prim, VariantFallbackMap::new())
}
pub(crate) fn build_with_fallbacks(stack: &mut LayerGraph, prim: &str, fallbacks: VariantFallbackMap) -> PrimIndex {
let path = Path::new(prim).unwrap();
let mut chain: Vec<Path> = Vec::new();
let mut p = Some(path.clone());
while let Some(pp) = p {
if pp == Path::abs_root() {
break;
}
chain.push(pp.clone());
p = pp.parent();
}
chain.reverse();
loop {
let mut cache: sdf::PathTable<PrimEntry> = sdf::PathTable::new();
let mut parent_ctx = CompositionContext {
variant_fallbacks: fallbacks.clone(),
..CompositionContext::default()
};
let mut last = None;
let mut pending: Vec<Demand> = Vec::new();
for ancestor in &chain {
let (index, _errors, demands, _deps) =
PrimIndex::build_with_cache(ancestor, stack, &parent_ctx, &cache, true)
.expect("index build failed");
pending.extend(demands);
parent_ctx = index.context_for_children(stack, &parent_ctx).0;
cache.insert(
ancestor.clone(),
PrimEntry {
index: index.clone(),
context: CompositionContext::default(),
errors: Diagnostics::default(),
property_errors: Diagnostics::default(),
resolved_targets: HashMap::new(),
revision: PrimRevision::placeholder(),
},
);
last = Some(index);
}
if !stack.intern_demanded(&pending) {
return last.expect("a non-empty namespace chain");
}
}
}
fn load_stack(path: &str) -> Result<LayerGraph> {
let layers = load_layers(path)?;
Ok(LayerGraph::from_layers(layers, 0, sdf::LayerRegistry::default()))
}
fn one_layer_stack(root: Box<dyn sdf::AbstractData>) -> LayerGraph {
let layers = vec![sdf::Layer::new("root.usd", root)];
LayerGraph::from_layers(layers, 0, sdf::LayerRegistry::default())
}
fn two_layer_stack(root: Box<dyn sdf::AbstractData>, refl: Box<dyn sdf::AbstractData>) -> LayerGraph {
let layers = vec![sdf::Layer::new("root.usd", root), sdf::Layer::new("ref.usd", refl)];
LayerGraph::from_layers(layers, 0, sdf::LayerRegistry::default())
}
#[test]
fn single_layer_root_node() -> Result<()> {
let mut stack = load_stack(&composition_path("active.usda"))?;
let index = build(&mut stack, "/World");
assert_eq!(index.nodes().count(), 1);
assert_eq!(index.nodes().next().unwrap().layer_id(), stack.all_ids()[0]);
assert_eq!(index.nodes().next().unwrap().arc, ArcType::Root);
Ok(())
}
#[test]
fn sublayer_site_layer_order() -> Result<()> {
let mut stack = load_stack(&fixture_path("sublayer_override.usda"))?;
let index = build(&mut stack, "/World");
let ns: Vec<_> = index.nodes().collect();
assert_eq!(ns.len(), 1, "one per-site node spans both sublayers");
assert_eq!(ns[0].arc, ArcType::Root);
let layers: Vec<LayerId> = stack
.layer_stack(ns[0].layer_stack_id())
.iter()
.map(|&(li, _)| li)
.collect();
let expected: Vec<LayerId> = stack.root_layer_stack().iter().map(|&(id, _)| id).collect();
assert_eq!(layers, expected, "stronger sublayer first");
Ok(())
}
#[test]
fn prim_only_in_stronger_layer() -> Result<()> {
let mut stack = load_stack(&fixture_path("sublayer_override.usda"))?;
let index = build(&mut stack, "/World/Sphere");
assert_eq!(index.nodes().count(), 1);
assert_eq!(index.nodes().next().unwrap().layer_id(), stack.all_ids()[0]);
Ok(())
}
#[test]
fn nonexistent_prim_empty_index() -> Result<()> {
let mut stack = load_stack(&composition_path("active.usda"))?;
let index = build(&mut stack, "/DoesNotExist");
assert!(index.is_empty());
Ok(())
}
#[test]
fn reference_arc_present() -> Result<()> {
let mut stack = load_stack(&fixture_path("ref_external.usda"))?;
let index = build(&mut stack, "/World/MyPrim");
assert!(index.nodes().any(|n| n.arc == ArcType::Reference));
Ok(())
}
#[test]
fn path_order_resolves_sites() -> Result<()> {
let root = parse_usda("#usda 1.0\ndef \"A\" ( references = @base.usd@</B> ) {\n custom double x = 1\n}\n");
let base = parse_usda("#usda 1.0\ndef \"B\" { custom double y = 2 }\n");
let layers = vec![sdf::Layer::new("root.usd", root), sdf::Layer::new("base.usd", base)];
let mut stack = LayerGraph::from_layers(layers, 0, sdf::LayerRegistry::default());
let index = build(&mut stack, "/A");
for p in ["/A", "/B"].into_iter().map(|s| Path::new(s).unwrap()) {
let mut expected: Vec<NodeId> = index
.arena()
.iter()
.enumerate()
.filter(|(_, n)| n.path == p)
.map(|(i, _)| NodeId(i as u32))
.collect();
let mut got = index.nodes_at(&p).to_vec();
expected.sort();
got.sort();
assert_eq!(got, expected, "site index resolves {p} to its arena nodes");
}
assert!(
index.nodes_at(&Path::new("/A").unwrap()).len() >= 2,
"the prim's own site carries the synthetic root and the local root"
);
assert!(
index
.nodes_at(&Path::new("/B").unwrap())
.iter()
.any(|&id| index.node(id).arc == ArcType::Reference),
"the reference node is indexed at its target path"
);
assert!(index.nodes_at(&Path::new("/Nope").unwrap()).is_empty());
Ok(())
}
#[test]
fn empty_reference_target_culled() -> Result<()> {
let root = parse_usda("#usda 1.0\ndef \"A\" ( references = @base.usd@</Empty> ) {\n custom double x = 1\n}\n");
let base = parse_usda("#usda 1.0\ndef \"Other\" {}\n");
let layers = vec![sdf::Layer::new("root.usd", root), sdf::Layer::new("base.usd", base)];
let mut stack = LayerGraph::from_layers(layers, 0, sdf::LayerRegistry::default());
let index = build(&mut stack, "/A");
assert!(
index.all_nodes().any(|n| n.arc == ArcType::Reference && n.is_culled()),
"empty reference target kept as a culled node"
);
assert!(
index.nodes().all(|n| n.arc != ArcType::Reference),
"culled reference contributes no opinion to resolution"
);
assert!(
!index.has_composition_arc(),
"an empty reference does not compose the prim"
);
assert!(!index.is_empty(), "the prim's own opinions remain");
Ok(())
}
#[test]
fn empty_inherit_target_culled() -> Result<()> {
let root = parse_usda("#usda 1.0\ndef \"A\" ( inherits = </_class_Missing> ) {\n custom double x = 1\n}\n");
let index = build(&mut one_layer_stack(root), "/A");
assert!(
index.all_nodes().any(|n| n.arc == ArcType::Inherit && n.is_culled()),
"empty inherit target kept as a culled node"
);
assert!(
index.nodes().all(|n| n.arc != ArcType::Inherit),
"culled inherit contributes no opinion to resolution"
);
assert!(
!index.has_composition_arc(),
"an empty inherit does not compose the prim"
);
assert!(!index.is_empty(), "the prim's own opinions remain");
Ok(())
}
#[test]
fn empty_specialize_target_culled() -> Result<()> {
let root = parse_usda("#usda 1.0\ndef \"A\" ( specializes = </_class_Missing> ) {\n custom double x = 1\n}\n");
let index = build(&mut one_layer_stack(root), "/A");
assert!(
index.all_nodes().any(|n| n.arc == ArcType::Specialize && n.is_culled()),
"empty specialize target kept as a culled node"
);
assert!(
index.nodes().all(|n| n.arc != ArcType::Specialize),
"culled specialize contributes no opinion to resolution"
);
assert!(
!index.has_composition_arc(),
"an empty specialize does not compose the prim"
);
assert!(!index.is_empty(), "the prim's own opinions remain");
Ok(())
}
#[test]
fn empty_variant_target_culled() -> Result<()> {
let root = parse_usda(
"#usda 1.0\ndef \"A\" (\n variantSets = \"v\"\n variants = { string v = \"missing\" }\n) {\n custom double own = 1\n variantSet \"v\" = {\n \"present\" { custom double x = 1 }\n }\n}\n",
);
let index = build(&mut one_layer_stack(root), "/A");
assert!(
index.all_nodes().any(|n| n.arc == ArcType::Variant && n.is_culled()),
"empty variant target kept as a culled node"
);
assert!(
index.nodes().all(|n| n.arc != ArcType::Variant),
"culled variant contributes no opinion to resolution"
);
assert!(
!index.has_composition_arc(),
"an empty variant does not compose the prim"
);
assert!(!index.is_empty(), "the prim's own opinions remain");
Ok(())
}
#[test]
fn implied_class_flagged() -> Result<()> {
let root = parse_usda(
"#usda 1.0\ndef \"Model\" ( references = @ref.usd@</Ref> ) {\n over \"Class\" { custom string x = \"rootimplied\" }\n}\n",
);
let refl = parse_usda(
"#usda 1.0\ndef \"Ref\" {\n def \"Sub\" ( inherits = </Ref/Class> ) {}\n class \"Class\" { custom string x = \"ref\" }\n}\n",
);
let layers = vec![sdf::Layer::new("root.usd", root), sdf::Layer::new("ref.usd", refl)];
let mut stack = LayerGraph::from_layers(layers, 0, sdf::LayerRegistry::default());
let index = build(&mut stack, "/Model/Sub");
let implied = index
.arena()
.iter()
.find(|n| n.path.as_str() == "/Model/Class")
.expect("implied class node in the referencing namespace");
assert!(
implied.flags().contains(NodeFlags::IMPLIED_CLASS),
"implied class node is flagged"
);
assert!(implied.origin().is_some(), "implied class records its origin");
Ok(())
}
#[test]
fn prim_index_is_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<PrimIndex>();
assert_send_sync::<Node>();
assert_send_sync::<NodeId>();
}
#[test]
fn variant_from_external_reference() -> Result<()> {
let root = parse_usda(
"#usda 1.0\ndef \"Model\" (\n references = @ref.usd@</Ref>\n variants = { string v = \"b\" }\n) {}\n",
);
let refl = parse_usda(
"#usda 1.0\ndef \"Ref\" (\n add variantSets = \"v\"\n) {\n variantSet \"v\" = {\n \"a\" { custom string x = \"a\" }\n \"b\" { custom string x = \"b\" }\n }\n}\n",
);
let mut stack = two_layer_stack(root, refl);
let index = build(&mut stack, "/Model");
assert!(
index
.nodes()
.any(|n| n.arc == ArcType::Variant && n.path.as_str().contains("{v=b}")),
"selected variant from the referenced layer must be composed"
);
Ok(())
}
#[test]
fn variant_from_internal_reference() -> Result<()> {
let root = parse_usda(
"#usda 1.0\ndef \"Base\" (\n add variantSets = \"v\"\n) {\n variantSet \"v\" = {\n \"a\" { custom string x = \"a\" }\n \"b\" { custom string x = \"b\" }\n }\n}\ndef \"Model\" (\n references = </Base>\n variants = { string v = \"b\" }\n) {}\n",
);
let mut stack = one_layer_stack(root);
let index = build(&mut stack, "/Model");
assert!(
index.nodes().any(|n| n.path.as_str().contains("{v=b}")),
"selected variant from the internal-reference target must be composed"
);
assert!(
!index.nodes().any(|n| n.path.as_str().contains("{v=a}")),
"fallback variant must not be composed when v=b is selected"
);
Ok(())
}
#[test]
fn variant_contains_reference() -> Result<()> {
let root = parse_usda(
"#usda 1.0\ndef \"Model\" (\n references = @ref.usd@</Ref>\n variants = { string v = \"b\" }\n) {}\n",
);
let refl = parse_usda(
"#usda 1.0\ndef \"Inner\" { custom string y = \"inner\" }\ndef \"Ref\" (\n add variantSets = \"v\"\n) {\n variantSet \"v\" = {\n \"a\" {}\n \"b\" ( references = </Inner> ) {}\n }\n}\n",
);
let mut stack = two_layer_stack(root, refl);
let index = build(&mut stack, "/Model");
assert!(
index
.nodes()
.any(|n| n.arc == ArcType::Variant && n.path.as_str().contains("{v=b}")),
"selected variant node present"
);
assert!(
index.nodes().any(|n| n.path.as_str() == "/Inner"),
"reference inside the selected variant must be followed"
);
Ok(())
}
#[test]
fn structural_links_consistent() -> Result<()> {
let mut stack = load_stack(&fixture_path("ref_external.usda"))?;
let index = build(&mut stack, "/World/MyPrim");
let root = index.root().expect("non-empty index has a root");
assert_eq!(index.node(root).arc, ArcType::Root);
let reference = index
.arena()
.iter()
.position(|n| n.arc == ArcType::Reference)
.map(|i| NodeId(i as u32))
.expect("reference fixture has a reference node");
let parent = index.parent(reference).expect("reference has a parent");
assert!(index.children(parent).contains(&reference));
for (i, _) in index.arena().iter().enumerate() {
let id = NodeId(i as u32);
if let Some(parent) = index.parent(id) {
assert!(
index.children(parent).contains(&id),
"node {i} parent {parent:?} missing it as a child"
);
}
}
Ok(())
}
#[test]
fn graft_preserves_subtree() -> Result<()> {
let root = parse_usda(
"#usda 1.0\ndef \"Model\" ( inherits = </Class> ) {}\ndef \"Class\" ( references = @base.usd@</Base> ) {}\n",
);
let base = parse_usda("#usda 1.0\ndef \"Base\" {}\n");
let layers = vec![sdf::Layer::new("root.usd", root), sdf::Layer::new("base.usd", base)];
let mut stack = LayerGraph::from_layers(layers, 0, sdf::LayerRegistry::default());
let index = build(&mut stack, "/Model");
let find = |p: &str| {
index
.arena()
.iter()
.position(|n| n.path.as_str() == p)
.map(|i| NodeId(i as u32))
};
let class = find("/Class").expect("inherited /Class node");
let base = find("/Base").expect("grafted /Base node from /Class's reference");
assert_eq!(
index.parent(base),
Some(class),
"reference subtree preserved under its inherit root"
);
assert!(index.node(base).origin().is_some(), "grafted node carries an origin");
Ok(())
}
#[test]
fn dump_renders_tree() -> Result<()> {
let root = parse_usda(
"#usda 1.0\ndef \"Model\" ( inherits = </Class> ) {}\ndef \"Class\" ( references = @base.usd@</Base> ) {}\n",
);
let base = parse_usda("#usda 1.0\ndef \"Base\" {}\n");
let layers = vec![sdf::Layer::new("root.usd", root), sdf::Layer::new("base.usd", base)];
let mut stack = LayerGraph::from_layers(layers, 0, sdf::LayerRegistry::default());
let index = build(&mut stack, "/Model");
let dump = index.dump_to_string();
let line = |needle: &str| {
dump.lines()
.find(|l| l.contains(needle))
.unwrap_or_else(|| panic!("dump missing {needle}: {dump}"))
};
let indent = |l: &str| l.len() - l.trim_start().len();
assert!(dump.lines().all(|l| l.contains('#')), "each line has a strength rank");
assert!(line("/Model").starts_with("Root"), "root prim is the tree root");
assert!(
indent(line("/Class")) > indent(line("/Model")),
"inherit nests under the root"
);
assert!(
indent(line("/Base")) > indent(line("/Class")),
"grafted reference nests under the inherit"
);
Ok(())
}
#[test]
fn inherit_arc_present() -> Result<()> {
let mut stack = load_stack(&composition_path("class_inherit.usda"))?;
let index = build(&mut stack, "/World/cubeWithoutSetColor");
assert!(index.nodes().any(|n| n.arc == ArcType::Inherit));
Ok(())
}
#[test]
fn inherit_root_is_strongest() -> Result<()> {
let mut stack = load_stack(&composition_path("class_inherit.usda"))?;
let index = build(&mut stack, "/World/cubeWithSetColor");
let arcs: Vec<_> = index.nodes().map(|n| n.arc).collect();
assert_eq!(arcs[0], ArcType::Root);
assert!(arcs.contains(&ArcType::Inherit));
Ok(())
}
#[test]
fn variant_arc_with_selection() -> Result<()> {
let path = format!(
"{}vendor/usd-wg-assets/docs/CompositionPuzzles/VariantSetAndLocal1/puzzle_1.usda",
env!("CARGO_WORKSPACE_DIR")
);
let mut stack = load_stack(&path)?;
let index = build(&mut stack, "/World/Sphere");
assert!(index.nodes().any(|n| n.arc == ArcType::Variant));
let variant_node = index.nodes().find(|n| n.arc == ArcType::Variant).unwrap();
assert_eq!(variant_node.path.as_str(), "/World/Sphere{size=small}");
Ok(())
}
#[test]
fn specialize_arc_present() -> Result<()> {
let mut stack = load_stack(&composition_path("inherit_and_specialize.usda"))?;
let index = build(&mut stack, "/World/cubeScene/specializes");
assert!(index.nodes().any(|n| n.arc == ArcType::Specialize));
Ok(())
}
#[test]
fn reference_diamond_recursive() -> Result<()> {
let path = format!(
"{}vendor/core-spec-supplemental-release_dec2025/composition/tests/assets/BasicReferenceDiamond_root/usda/root.usd",
env!("CARGO_WORKSPACE_DIR")
);
let mut stack = load_stack(&path)?;
let index = build(&mut stack, "/Root");
assert!(
index
.nodes()
.any(|n| n.arc == ArcType::Reference && n.path.as_str() == "/A"),
"should have node from A.usd"
);
assert!(
index
.nodes()
.any(|n| n.arc == ArcType::Reference && n.path.as_str() == "/B"),
"should have node from B.usd"
);
assert!(
index
.nodes()
.any(|n| n.arc == ArcType::Reference && n.path.as_str() == "/C"),
"should have node from C.usd via nested reference"
);
let a_idx = stack.find_by_leaf("A.usd").unwrap();
let a_attr_path = Path::new("/A.A_attr").unwrap();
assert!(
stack.layer(a_idx).data().has_spec(&a_attr_path),
"A.usd should have spec at /A.A_attr"
);
Ok(())
}
#[test]
fn specializes_from_variant() -> Result<()> {
let path = format!(
"{}vendor/core-spec-supplemental-release_dec2025/composition/tests/assets/SpecializesAndVariants_root/usda/root.usd",
env!("CARGO_WORKSPACE_DIR")
);
let mut stack = load_stack(&path)?;
let index = build(&mut stack, "/B");
assert!(
index.nodes().any(|n| n.arc == ArcType::Specialize),
"should have specialize node from variant"
);
assert!(
index
.nodes()
.any(|n| n.path.as_str().contains("{nestedVariantSet=nestedVariant}")),
"should have /A's variant node"
);
Ok(())
}
#[test]
fn variant_reference_and_inherit_propagation() -> Result<()> {
let path = format!(
"{}vendor/core-spec-supplemental-release_dec2025/composition/tests/assets/BasicVariantWithConnections_root/usda/root.usd",
env!("CARGO_WORKSPACE_DIR")
);
let mut stack = load_stack(&path)?;
assert!(
stack.find_by_leaf("camera_perspective.usd").is_some(),
"camera_perspective.usd should be collected from variant reference"
);
let index = build(&mut stack, "/main_cam/Lens");
assert!(
index.nodes().any(|n| n.path.as_str() == "/camera/_localclass_Lens"),
"should have inherit node for _localclass_Lens"
);
Ok(())
}
#[test]
fn inherited_variant_selection_propagation() -> Result<()> {
let path = format!(
"{}vendor/core-spec-supplemental-release_dec2025/composition/tests/assets/TrickyVariantWeakerSelection2_root/usda/root.usd",
env!("CARGO_WORKSPACE_DIR")
);
let mut stack = load_stack(&path)?;
let index = build(&mut stack, "/bob");
assert!(
index.nodes().any(|n| n.path.as_str().contains("{geotype=cube}")),
"should have geotype=cube variant node from inherited selection"
);
Ok(())
}
fn parse_usda(text: &str) -> Box<dyn sdf::AbstractData> {
let data = crate::usda::parser::Parser::new(text).parse().expect("parse usda");
Box::new(sdf::Data::from_specs(data))
}
#[test]
fn child_ctx_expr_selection() {
let stack = one_layer_stack(parse_usda(
r#"#usda 1.0
(
expressionVariables = {
string SEL = "hi"
}
)
def "World" (
variantSets = ["v", "w", "u"]
variants = {
string v = "`${SEL}`"
string w = "`${UNDEF}`"
string u = "`None`"
}
)
{
variantSet "v" = {
"hi" { custom double x = 1 }
}
}
"#,
));
let index =
PrimIndex::build_with_context(&Path::new("/World").unwrap(), &stack, &CompositionContext::default())
.expect("index build");
let ctx = index.context_for_children(&stack, &CompositionContext::default()).0;
assert_eq!(
ctx.selections.get("v").map(String::as_str),
Some("hi"),
"the evaluated selection seeds children"
);
assert!(
!ctx.selections.contains_key("w"),
"an unevaluable selection opinion is skipped, got {:?}",
ctx.selections
);
assert_eq!(
ctx.selections.get("u").map(String::as_str),
Some(""),
"a successful `None` records the empty, blocking selection"
);
}
#[test]
fn empty_selection_fallback() {
let stack = one_layer_stack(parse_usda(
r#"#usda 1.0
def "World" (
variantSets = "v"
variants = { string v = "`None`" }
)
{
variantSet "v" = {
"hi" { custom double x = 1 }
"lo" { custom double x = 2 }
}
}
"#,
));
let ctx = CompositionContext {
variant_fallbacks: VariantFallbackMap::new().add("v", ["hi"]),
..CompositionContext::default()
};
let index = PrimIndex::build_with_context(&Path::new("/World").unwrap(), &stack, &ctx).expect("index build");
assert!(
index.nodes().any(|n| n.path.as_str() == "/World{v=hi}"),
"the index composes the fallback, got {:?}",
index.nodes().map(|n| n.path.as_str()).collect::<Vec<_>>()
);
let child = index.context_for_children(&stack, &ctx).0;
assert_eq!(
child.selections.get("v").map(String::as_str),
Some("hi"),
"the child context matches the composed index"
);
}
#[test]
fn variant_child_expr() {
let mut stack = one_layer_stack(parse_usda(
r#"#usda 1.0
(
expressionVariables = {
string SEL = "hi"
}
)
def "World" (
variantSets = "v"
variants = { string v = "`${SEL}`" }
)
{
variantSet "v" = {
"hi" {
def "Child"
{
custom double x = 1
}
}
}
}
"#,
));
let index = build_with_fallbacks(&mut stack, "/World/Child", VariantFallbackMap::new());
assert!(
index
.nodes()
.any(|n| n.path.as_str() == "/World{v=hi}Child" && n.has_specs()),
"the child under the evaluated selection composes, got {:?}",
index.nodes().map(|n| n.path.as_str()).collect::<Vec<_>>()
);
}
#[test]
fn arc_cycle_recorded() -> Result<()> {
let a = parse_usda(
r#"#usda 1.0
(
defaultPrim = "Root"
)
def "Root" (
references = @b.usd@
)
{
}
"#,
);
let b = parse_usda(
r#"#usda 1.0
(
defaultPrim = "Root"
)
def "Root" (
references = @a.usd@
)
{
}
"#,
);
let layers = vec![sdf::Layer::new("a.usd", a), sdf::Layer::new("b.usd", b)];
let stack = LayerGraph::from_layers(layers, 0, sdf::LayerRegistry::default());
let (_index, errors, _pending, _deps) = PrimIndex::build_with_cache(
&Path::new("/Root").unwrap(),
&stack,
&CompositionContext::default(),
&sdf::PathTable::new(),
true,
)?;
assert!(
errors.iter().any(|e| matches!(e, CompositionDiagnostic::ArcCycle(_))),
"expected a recorded ArcCycle error, got {errors:?}"
);
Ok(())
}
#[test]
fn cyclic_subroot_unresolved() -> Result<()> {
let a = parse_usda(
r#"#usda 1.0
(
defaultPrim = "Root"
)
def "Root" (
references = @b.usd@</Outer/Inner>
)
{
}
"#,
);
let b = parse_usda(
r#"#usda 1.0
def "Outer" (
references = @a.usd@
)
{
}
"#,
);
let layers = vec![sdf::Layer::new("a.usd", a), sdf::Layer::new("b.usd", b)];
let stack = LayerGraph::from_layers(layers, 0, sdf::LayerRegistry::default());
let (_index, errors, _pending, _deps) = PrimIndex::build_with_cache(
&Path::new("/Root").unwrap(),
&stack,
&CompositionContext::default(),
&sdf::PathTable::new(),
true,
)?;
assert!(
errors.iter().any(|e| matches!(e, CompositionDiagnostic::ArcCycle(_))),
"expected the cycle itself, got {errors:?}"
);
assert!(
errors
.iter()
.any(|e| matches!(e, CompositionDiagnostic::UnresolvedPrimPath { .. })),
"the cyclic target composed nothing, so its prim path is unresolved, got {errors:?}"
);
Ok(())
}
#[test]
fn subroot_arc_cycle_recorded() -> Result<()> {
let a = parse_usda(
r#"#usda 1.0
(
defaultPrim = "Root"
)
def "Root" (
references = @b.usd@</Outer/Inner>
)
{
}
"#,
);
let b = parse_usda(
r#"#usda 1.0
def "Outer"
{
def "Inner" (
references = @a.usd@
)
{
}
}
"#,
);
let layers = vec![sdf::Layer::new("a.usd", a), sdf::Layer::new("b.usd", b)];
let stack = LayerGraph::from_layers(layers, 0, sdf::LayerRegistry::default());
let (_index, errors, _pending, _deps) = PrimIndex::build_with_cache(
&Path::new("/Root").unwrap(),
&stack,
&CompositionContext::default(),
&sdf::PathTable::new(),
true,
)?;
assert!(
errors.iter().any(|e| matches!(e, CompositionDiagnostic::ArcCycle(_))),
"expected a recorded ArcCycle error for a cross-frame cycle, got {errors:?}"
);
Ok(())
}
#[test]
fn unresolved_layer_recorded() -> Result<()> {
let layer = parse_usda(
r#"#usda 1.0
def "Prim" (
references = @nonexistent.usd@
)
{
custom string marker = "ok"
}
"#,
);
let layers = vec![sdf::Layer::new("test.usda", layer)];
let stack = LayerGraph::from_layers(layers, 0, sdf::LayerRegistry::default());
let (index, errors, _pending, _deps) = PrimIndex::build_with_cache(
&Path::new("/Prim").unwrap(),
&stack,
&CompositionContext::default(),
&sdf::PathTable::new(),
true,
)?;
assert!(
errors
.iter()
.any(|e| matches!(e, CompositionDiagnostic::UnresolvedLayer { .. })),
"expected a recorded UnresolvedLayer error, got {errors:?}"
);
assert!(
!index.is_empty(),
"the prim's local opinion must survive the skipped arc"
);
Ok(())
}
#[test]
fn missing_default_prim_recorded() -> Result<()> {
let root = parse_usda(
r#"#usda 1.0
def "Prim" (
references = @target.usda@
)
{
custom string marker = "ok"
}
"#,
);
let target = parse_usda("#usda 1.0\ndef \"Foo\" {}\n");
let layers = vec![
sdf::Layer::new("root.usda", root),
sdf::Layer::new("target.usda", target),
];
let stack = LayerGraph::from_layers(layers, 0, sdf::LayerRegistry::default());
let (index, errors, _pending, _deps) = PrimIndex::build_with_cache(
&Path::new("/Prim").unwrap(),
&stack,
&CompositionContext::default(),
&sdf::PathTable::new(),
true,
)?;
assert!(
errors
.iter()
.any(|e| matches!(e, CompositionDiagnostic::UnresolvedDefaultPrim { .. })),
"expected a recorded UnresolvedDefaultPrim error, got {errors:?}"
);
assert!(
!index.is_empty(),
"the prim's local opinion must survive the skipped arc"
);
Ok(())
}
fn variant_paths(index: &PrimIndex) -> Vec<String> {
index
.nodes()
.filter(|n| n.arc == ArcType::Variant)
.map(|n| n.path.as_str().to_string())
.collect()
}
#[test]
fn variant_no_selection_unselected() -> Result<()> {
let mut stack = load_stack(&fixture_path("variant_fallback.usda"))?;
let index = build(&mut stack, "/NoSelection");
let paths = variant_paths(&index);
assert!(
paths.is_empty(),
"no variant should be selected without an authored selection or fallback: got {paths:?}"
);
Ok(())
}
#[test]
fn variant_fallback_overrides_default() -> Result<()> {
let mut stack = load_stack(&fixture_path("variant_fallback.usda"))?;
let fb = VariantFallbackMap::new().add("shadingComplexity", ["simple"]);
let index = build_with_fallbacks(&mut stack, "/NoSelection", fb);
let paths = variant_paths(&index);
assert!(
paths.iter().any(|p| p.contains("{shadingComplexity=simple}")),
"fallback should select 'simple': got {paths:?}"
);
assert!(
!paths.iter().any(|p| p.contains("{shadingComplexity=full}")),
"'full' should NOT be selected when fallback says 'simple'"
);
Ok(())
}
#[test]
fn variant_authored_selection_beats_fallback() -> Result<()> {
let mut stack = load_stack(&fixture_path("variant_fallback.usda"))?;
let fb = VariantFallbackMap::new().add("shadingComplexity", ["none"]);
let index = build_with_fallbacks(&mut stack, "/Root", fb);
let paths = variant_paths(&index);
assert!(
paths.iter().any(|p| p.contains("{shadingComplexity=full}")),
"authored selection 'full' should win over fallback 'none': got {paths:?}"
);
Ok(())
}
#[test]
fn variant_fallback_skips_nonexistent() -> Result<()> {
let mut stack = load_stack(&fixture_path("variant_fallback.usda"))?;
let fb = VariantFallbackMap::new().add("shadingComplexity", ["ultra", "simple"]);
let index = build_with_fallbacks(&mut stack, "/NoSelection", fb);
let paths = variant_paths(&index);
assert!(
paths.iter().any(|p| p.contains("{shadingComplexity=simple}")),
"should skip 'ultra' and use 'simple': got {paths:?}"
);
Ok(())
}
#[test]
fn variant_fallback_all_invalid_unselected() -> Result<()> {
let mut stack = load_stack(&fixture_path("variant_fallback.usda"))?;
let fb = VariantFallbackMap::new().add("shadingComplexity", ["ultra", "mega"]);
let index = build_with_fallbacks(&mut stack, "/NoSelection", fb);
let paths = variant_paths(&index);
assert!(
paths.is_empty(),
"no variant should be selected when every fallback is invalid: got {paths:?}"
);
Ok(())
}
#[test]
fn node_strength_comparator() {
let p = |s: &str| Path::new(s).unwrap();
let id = MapFunction::identity();
let lid = LayerId::from_raw(0);
let lsid = LayerStackId::from_raw(1);
let mut g = PrimIndexGraph::default();
let root = g.add_child(NodeId::INVALID, lsid, lid, p("/A"), ArcType::Root, id.clone(), false);
let inh = g.add_child(root, lsid, lid, p("/Class"), ArcType::Inherit, id.clone(), false);
let r1 = g.add_child(root, lsid, lid, p("/R1"), ArcType::Reference, id.clone(), false);
let r2 = g.add_child(root, lsid, lid, p("/R2"), ArcType::Reference, id.clone(), false);
assert_eq!(g.compare_sibling_node_strength(inh, r1), Ordering::Less);
assert_eq!(g.compare_sibling_node_strength(r1, r2), Ordering::Less);
assert_eq!(g.compare_sibling_node_strength(r2, r1), Ordering::Greater);
assert_eq!(g.compare_node_strength(root, inh), Ordering::Less);
assert_eq!(g.compare_node_strength(inh, root), Ordering::Greater);
assert_eq!(g.compare_node_strength(inh, r2), Ordering::Less);
assert_eq!(g.compare_node_strength(r2, r2), Ordering::Equal);
let deep = g.add_child(root, lsid, lid, p("/D"), ArcType::Reference, id.clone(), false);
g.nodes[deep.idx()].namespace_depth = 5;
assert_eq!(g.compare_sibling_node_strength(deep, r1), Ordering::Less);
}
#[test]
fn arc_type_liverps_ordering() {
assert!(ArcType::Root < ArcType::Inherit);
assert!(ArcType::Inherit < ArcType::Variant);
assert!(ArcType::Variant < ArcType::Relocate);
assert!(ArcType::Relocate < ArcType::Reference);
assert!(ArcType::Reference < ArcType::Payload);
assert!(ArcType::Payload < ArcType::Specialize);
}
fn spec_composition_path(relative: &str) -> String {
format!(
"{}vendor/core-spec-supplemental-release_dec2025/composition/tests/assets/{relative}",
env!("CARGO_WORKSPACE_DIR")
)
}
fn layer_name(identifier: &str) -> &str {
std::path::Path::new(identifier)
.file_name()
.and_then(|s| s.to_str())
.unwrap_or(identifier)
}
fn prim_stack(index: &PrimIndex, stack: &LayerGraph) -> Vec<(String, String)> {
index
.nodes()
.map(|n| {
(
layer_name(stack.identifier(n.layer_id())).to_owned(),
n.path.to_string(),
)
})
.collect()
}
#[test]
fn specialize_global_weakness_basic() -> Result<()> {
let mut stack = load_stack(&spec_composition_path("BasicSpecializes_root/usda/root.usd"))?;
let index = build(&mut stack, "/Root");
let ps = prim_stack(&index, &stack);
assert_eq!(
ps,
vec![
("root.usd".into(), "/Root".into()),
("ref.usd".into(), "/Ref".into()),
("ref2.usd".into(), "/Ref".into()),
("root.usd".into(), "/Specializes".into()),
("ref.usd".into(), "/Specializes".into()),
("ref2.usd".into(), "/Specializes".into()),
("root.usd".into(), "/Base".into()),
("ref.usd".into(), "/Base".into()),
("ref2.usd".into(), "/Base".into()),
]
);
let ns: Vec<_> = index.nodes().collect();
for node in &ns[..3] {
assert!(
!node.introduced_by_specialize(),
"node {:?} should not be specialize",
node.path
);
}
for node in &ns[3..] {
assert!(
node.introduced_by_specialize(),
"node {:?} should be specialize",
node.path
);
}
Ok(())
}
#[test]
fn specialize_global_weakness_multiple() -> Result<()> {
let mut stack = load_stack(&spec_composition_path("BasicSpecializes_root/usda/root.usd"))?;
let index = build(&mut stack, "/MultipleSpecializes");
let first_spec = index
.nodes()
.position(|n| n.introduced_by_specialize())
.expect("should have specialize nodes");
assert!(first_spec >= 2, "at least Root + Reference before specializes");
for node in index.nodes().skip(first_spec) {
assert!(
node.introduced_by_specialize(),
"node {:?} should be globally weak",
node.path
);
}
Ok(())
}
#[test]
fn specialize_chain_ordering() -> Result<()> {
let mut stack = load_stack(&spec_composition_path("BasicSpecializes_root/usda/root.usd"))?;
let index = build(&mut stack, "/Basic");
let ps = prim_stack(&index, &stack);
assert_eq!(
ps,
vec![
("root.usd".into(), "/Basic".into()),
("root.usd".into(), "/BasicSpecializes1".into()),
("root.usd".into(), "/BasicSpecializes2".into()),
]
);
Ok(())
}
fn node_layer_offsets(node: &Node, graph: &LayerGraph) -> Vec<(LayerId, LayerOffset)> {
let arc_offset = node.map_to_root().time_offset();
graph
.layer_stack(node.layer_stack_id())
.iter()
.map(|&(li, sub)| (li, arc_offset.concatenate(&sub)))
.collect()
}
fn offset_stack(index: &PrimIndex, stack: &LayerGraph) -> Vec<(String, String, ArcType, f64, f64)> {
index
.nodes()
.flat_map(|n| {
let path = n.path.to_string();
let arc = n.arc;
node_layer_offsets(n, stack).into_iter().map(move |(li, off)| {
(
layer_name(stack.identifier(li)).to_owned(),
path.clone(),
arc,
off.offset,
off.scale,
)
})
})
.collect()
}
fn basic_time_offset_stack() -> Result<LayerGraph> {
load_stack(&spec_composition_path("BasicTimeOffset_root/usda/root.usd"))
}
#[test]
fn time_offset_reference_then_sublayer() -> Result<()> {
let mut stack = basic_time_offset_stack()?;
let index = build(&mut stack, "/Root");
assert_eq!(
offset_stack(&index, &stack),
vec![
("root.usd".into(), "/Root".into(), ArcType::Root, 0.0, 1.0),
("A.usd".into(), "/Model".into(), ArcType::Reference, 10.0, 1.0),
("B.usd".into(), "/Model".into(), ArcType::Reference, 30.0, 1.0),
]
);
Ok(())
}
#[test]
fn time_offset_payload_with_scale_and_sublayer() -> Result<()> {
let mut stack = basic_time_offset_stack()?;
let index = build(&mut stack, "/PayloadRefPayload");
let got = offset_stack(&index, &stack);
assert!(
got.contains(&("root.usd".into(), "/PayloadRefPayload".into(), ArcType::Root, 0.0, 1.0,)),
"missing root opinion: got {got:?}"
);
assert!(
got.contains(&("ref_sub.usd".into(), "/Ref".into(), ArcType::Payload, 50.0, 2.0)),
"missing ref_sub /Ref payload opinion at (50,2): got {got:?}"
);
assert!(
got.contains(&("B.usd".into(), "/Model".into(), ArcType::Payload, 50.0, 2.0)),
"missing B.usd /Model payload opinion at (50,2): got {got:?}"
);
Ok(())
}
#[test]
fn time_offset_payload_with_nested_reference() -> Result<()> {
let mut stack = basic_time_offset_stack()?;
let index = build(&mut stack, "/PayloadMultiRef");
let got = offset_stack(&index, &stack);
assert!(
got.contains(&("root.usd".into(), "/PayloadMultiRef".into(), ArcType::Root, 0.0, 1.0,)),
"missing root opinion: got {got:?}"
);
assert!(
got.contains(&("ref_sub.usd".into(), "/Ref2".into(), ArcType::Payload, 50.0, 2.0)),
"missing ref_sub /Ref2 payload opinion: got {got:?}"
);
assert!(
got.contains(&("B.usd".into(), "/Model".into(), ArcType::Reference, 50.0, 2.0)),
"missing B.usd /Model ref opinion at (50,2): got {got:?}"
);
Ok(())
}
#[test]
fn time_offset_descendant_inherits_parents_offset() -> Result<()> {
let mut stack = basic_time_offset_stack()?;
let index = build(&mut stack, "/Root/Anim");
let got = offset_stack(&index, &stack);
assert!(
got.contains(&("B.usd".into(), "/Model/Anim".into(), ArcType::Reference, 30.0, 1.0)),
"missing /Model/Anim at effective (30, 1): got {got:?}"
);
Ok(())
}
#[test]
fn time_samples_retimed_across_reference() -> Result<()> {
let root = parse_usda(
r#"#usda 1.0
def "Root" (
references = @model.usd@</Model> (offset = 10; scale = 2)
)
{
}
"#,
);
let model = parse_usda(
r#"#usda 1.0
def "Model"
{
double radius.timeSamples = {
1: 0.0,
5: 1.0,
}
}
"#,
);
let layers = vec![sdf::Layer::new("root.usda", root), sdf::Layer::new("model.usd", model)];
let stack = LayerGraph::from_layers(layers, 0, sdf::LayerRegistry::default());
let index =
PrimIndex::build_with_context(&Path::new("/Root").unwrap(), &stack, &CompositionContext::default())?;
let samples = index
.resolve_time_samples(&stack, Some(".radius"))?
.expect("retimed samples");
let times: Vec<f64> = samples.iter().map(|(t, _)| *t).collect();
assert_eq!(times, vec![12.0, 20.0]);
Ok(())
}
#[test]
fn spec_stack_offsets_survive_rebase() -> Result<()> {
let root = parse_usda(
r#"#usda 1.0
def "Root" (
references = @model.usd@</Model> (offset = 10; scale = 2)
)
{
}
"#,
);
let model = parse_usda("#usda 1.0\ndef \"Model\" { custom double radius = 1.0 }\n");
let layers = vec![sdf::Layer::new("root.usda", root), sdf::Layer::new("model.usd", model)];
let stack = LayerGraph::from_layers(layers, 0, sdf::LayerRegistry::default());
let index =
PrimIndex::build_with_context(&Path::new("/Root").unwrap(), &stack, &CompositionContext::default())?;
let sites = |index: &PrimIndex| -> Vec<(NodeId, f64, f64)> {
index
.live_spec_sites()
.map(|(s, _)| (s.node, s.offset.offset, s.offset.scale))
.collect()
};
let before = sites(&index);
assert!(
before.iter().any(|&(_, off, scale)| off == 10.0 && scale == 2.0),
"the referenced Model site carries the arc's (offset=10, scale=2): {before:?}"
);
let mut rebased = index.clone();
rebased.rebase_root(&Path::new("/Root").unwrap(), &Path::new("/__Prototype_1").unwrap());
assert_eq!(
before,
sites(&rebased),
"rebase_root must preserve spec-stack node ids and folded offsets"
);
Ok(())
}
#[test]
fn reference_offset_zero_scale_falls_back_to_identity() -> Result<()> {
let root = parse_usda(
r#"#usda 1.0
def "Root" (
references = @model.usd@</Model> (offset = 10; scale = 0)
)
{
}
"#,
);
let model = parse_usda(
r#"#usda 1.0
def "Model" {}
"#,
);
let layers = vec![sdf::Layer::new("root.usda", root), sdf::Layer::new("model.usd", model)];
let mut stack = LayerGraph::from_layers(layers, 0, sdf::LayerRegistry::default());
let index = build(&mut stack, "/Root");
let got = offset_stack(&index, &stack);
assert!(
got.contains(&("model.usd".into(), "/Model".into(), ArcType::Reference, 0.0, 1.0)),
"expected reference offset to fall back to identity for scale=0: got {got:?}"
);
Ok(())
}
#[test]
fn payload_offset_negative_scale_falls_back_to_identity() -> Result<()> {
let root = parse_usda(
r#"#usda 1.0
def "Root" (
payload = @model.usd@</Model> (offset = 5; scale = -2)
)
{
}
"#,
);
let model = parse_usda(
r#"#usda 1.0
def "Model" {}
"#,
);
let layers = vec![sdf::Layer::new("root.usda", root), sdf::Layer::new("model.usd", model)];
let mut stack = LayerGraph::from_layers(layers, 0, sdf::LayerRegistry::default());
let index = build(&mut stack, "/Root");
let got = offset_stack(&index, &stack);
assert!(
got.contains(&("model.usd".into(), "/Model".into(), ArcType::Payload, 0.0, 1.0)),
"expected payload offset to fall back to identity for scale=-2: got {got:?}"
);
Ok(())
}
#[test]
fn sublayer_offset_zero_scale_falls_back_to_identity() -> Result<()> {
let root = parse_usda(
r#"#usda 1.0
(
subLayers = [
@sub.usda@ (offset = 10; scale = 0)
]
)
def "Root" {}
"#,
);
let sub = parse_usda(
r#"#usda 1.0
def "Root" {}
"#,
);
let layers = vec![sdf::Layer::new("root.usda", root), sdf::Layer::new("sub.usda", sub)];
let mut stack = LayerGraph::from_layers(layers, 0, sdf::LayerRegistry::default());
let index = build(&mut stack, "/Root");
let got = offset_stack(&index, &stack);
assert!(
got.iter().any(|(name, path, _, off, scale)| name == "sub.usda"
&& path == "/Root"
&& *off == 0.0
&& *scale == 1.0),
"expected sublayer offset to fall back to identity for scale=0: got {got:?}"
);
Ok(())
}
#[test]
fn clip_sets_order_folds_layers() -> Result<()> {
let root = parse_usda(
r#"#usda 1.0
(
subLayers = [
@sub.usda@
]
)
def "P" (
prepend clipSets = ["a"]
)
{
}
"#,
);
let sub = parse_usda(
r#"#usda 1.0
over "P" (
append clipSets = ["b"]
)
{
}
"#,
);
let layers = vec![sdf::Layer::new("root.usda", root), sdf::Layer::new("sub.usda", sub)];
let mut stack = LayerGraph::from_layers(layers, 0, sdf::LayerRegistry::default());
let index = build(&mut stack, "/P");
assert_eq!(
index.clip_sets_order(&stack)?,
Some(vec!["a".to_string(), "b".to_string()])
);
Ok(())
}
#[test]
fn clip_sets_order_unauthored() -> Result<()> {
let root = parse_usda(
r#"#usda 1.0
def "P" {}
"#,
);
let layers = vec![sdf::Layer::new("root.usda", root)];
let mut stack = LayerGraph::from_layers(layers, 0, sdf::LayerRegistry::default());
let index = build(&mut stack, "/P");
assert_eq!(index.clip_sets_order(&stack)?, None);
Ok(())
}
#[test]
fn clip_sets_order_authored_empty() -> Result<()> {
let root = parse_usda(
r#"#usda 1.0
def "P" (
clipSets = []
)
{
}
"#,
);
let layers = vec![sdf::Layer::new("root.usda", root)];
let mut stack = LayerGraph::from_layers(layers, 0, sdf::LayerRegistry::default());
let index = build(&mut stack, "/P");
assert_eq!(index.clip_sets_order(&stack)?, Some(Vec::new()));
Ok(())
}
#[test]
fn clip_sets_order_overlapping_add_prepend() -> Result<()> {
let root = parse_usda(
r#"#usda 1.0
(
subLayers = [
@sub.usda@
]
)
def "P" (
add clipSets = ["a"]
)
{
}
"#,
);
let sub = parse_usda(
r#"#usda 1.0
over "P" (
prepend clipSets = ["a"]
)
{
}
"#,
);
let layers = vec![sdf::Layer::new("root.usda", root), sdf::Layer::new("sub.usda", sub)];
let mut stack = LayerGraph::from_layers(layers, 0, sdf::LayerRegistry::default());
let index = build(&mut stack, "/P");
assert_eq!(index.clip_sets_order(&stack)?, Some(vec!["a".to_string()]));
let list_op = index.clip_sets_list_op(&stack)?.expect("authored");
assert_eq!(list_op.flatten(), vec!["a".to_string()]);
Ok(())
}
}