Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions fixtures/instancing_nested_subroot.usda
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#usda 1.0
(
doc = """A nested instance referencing a sub-root prim (spec 11.3.3).

/A is instanceable and references /Outer, whose child /Outer/Nested is
itself instanceable and references /Library/Inner — a prim below the
namespace root. The nested instance's shared composition lives at
/__Prototype_N/Nested, so its prototype must be seeded from there for a
prim beneath the outer proxy (/A/Nested/Leaf) to resolve."""
)

def Scope "Library"
{
def Scope "Inner"
{
def Scope "Leaf"
{
double v = 3.0
}
}
}

def Scope "Outer"
{
def Scope "Nested" (
instanceable = true
references = </Library/Inner>
)
{
}
}

def Scope "A" (
instanceable = true
references = </Outer>
)
{
}
34 changes: 34 additions & 0 deletions src/pcp/index_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3315,6 +3315,40 @@ def "Scope"
Ok(())
}

/// A nested instance whose reference targets a sub-root prim composes from
/// its prim inside the enclosing prototype (spec 11.3.3): seeding the nested
/// prototype from the outer proxy's own namespace instead leaves its
/// descendants with no contributing specs.
#[test]
fn nested_subroot_proxy() -> Result<()> {
let root = format!("{}/fixtures/instancing_nested_subroot.usda", manifest_dir());
let (graph, mut cache) = single_layer_stack(&root);
let interp = |_: &sdf::TimeSampleMap, _: f64| None;

// /A mints /__Prototype_0 for /Outer; the nested instance mints
// /__Prototype_1 for /Library/Inner, and the outer proxy's descendant
// stands in for a prim there.
assert_eq!(
cache.value_at(&graph, &sdf::path("/A/Nested/Leaf.v")?, 0.0, &interp)?,
Some(Value::Double(3.0))
);
assert_eq!(
cache.prim_in_prototype(&graph, &sdf::path("/A/Nested/Leaf")?)?,
Some(sdf::path("/__Prototype_1/Leaf")?)
);

// The nested instance is never composed at the proxy path, and reaching
// it through the instance namespace or the prototype namespace yields
// one prototype — two in total, not three.
assert!(!cache.is_indexed(&sdf::path("/A/Nested")?));
assert_eq!(
cache.prototype_of(&graph, &sdf::path("/A/Nested")?)?,
cache.prototype_of(&graph, &sdf::path("/__Prototype_0/Nested")?)?,
);
assert_eq!(cache.prototypes().len(), 2);
Ok(())
}

/// A reference nested inside the prototype (below the instanceable arc) is
/// shared (spec 11.3.3): its opinions reach the instance through the direct
/// instanceable arc, so they survive in the instance's child names and
Expand Down
58 changes: 36 additions & 22 deletions src/pcp/instancing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -371,22 +371,33 @@ impl IndexCache {
))
}

/// Composes `instance`, registers it against its prototype, and materializes
/// the prototype's index on first use, returning the `(canonical instance,
/// prototype path)` pair. The first instance registered for a key becomes
/// canonical and seeds the prototype; later instances with the same key reuse
/// the already-materialized prototype, so its subtree is composed only once
/// (spec 11.3.3). Composing the index here (and computing its [`InstanceKey`])
/// is the cache's job; the dedup is the [`PrototypeRegistry`]'s.
/// Composes `instance`'s shared subtree, registers it against its prototype,
/// and materializes the prototype's index on first use, returning the
/// `(canonical instance, prototype path)` pair. The first instance registered
/// for a key becomes canonical and seeds the prototype; later instances with
/// the same key reuse the already-materialized prototype, so its subtree is
/// composed only once (spec 11.3.3). Composing the index here (and computing
/// its [`InstanceKey`]) is the cache's job; the dedup is the
/// [`PrototypeRegistry`]'s.
fn register_prototype(&mut self, graph: &LayerGraph, instance: &Path) -> Result<(Path, Path)> {
self.ensure_index(graph, instance)?;
// A nested instance can itself be an instance proxy. Its shared
// composition lives at the corresponding prim inside the enclosing
// prototype, so that is the index that defines the nested prototype's
// key and materialized root.
let composed = self.effective_path(graph, instance)?;
self.ensure_index(graph, &composed)?;
// Load rules are authored against the stage namespace, so they scope at
// the instance's own path. `scoped_load_rules` translates a path inside a
// prototype onto that prototype's stored relative rules — the ones its
// canonical instance produced — so the two routes to one nested instance,
// the proxy and the prototype-namespace path, scope to the same table.
let relative_load_rules = {
let (rules, relative_instance) = self.scoped_load_rules(instance);
rules.make_relative_to(&relative_instance)
};
let key = instance_key(
self.cached(instance),
instance.prim_element_count() as u16,
self.cached(&composed),
composed.prim_element_count() as u16,
relative_load_rules,
);
let (canonical, prototype, minted) = self.prototypes.register(key, instance);
Expand All @@ -402,18 +413,21 @@ impl IndexCache {
if minted {
self.drop_index_subtree(&prototype);
self.redirected_prims.retain(|path, _| !path.has_prefix(&prototype));
self.materialize_prototype(graph, &canonical, &prototype);
self.materialize_prototype(graph, &composed, &prototype);
}
Ok((canonical, prototype))
}

/// Builds and caches the composed index for a freshly minted prototype root
/// (`/__Prototype_N`) from the canonical instance's shared subtree (spec
/// 11.3.3). The clone of the canonical index has its instance-local nodes
/// inerted at the instance root's own depth, so only the instanceable arc,
/// its descendants, and the implied classes contribute — the local root
/// override and the ancestral references above the instanceable arc drop out
/// — and its namespace is re-anchored onto the prototype root.
/// (`/__Prototype_N`) from the seeding instance's shared subtree (spec
/// 11.3.3). `composed` is the path whose cached index composes that instance:
/// its own path, or — when the instance is itself an instance proxy — its
/// prim inside the enclosing prototype. The clone of that index has its
/// instance-local nodes inerted at the instance root's own depth, so only the
/// instanceable arc, its descendants, and the implied classes contribute —
/// the local root override and the ancestral references above the
/// instanceable arc drop out — and its namespace is re-anchored onto the
/// prototype root.
///
/// The prototype root's child context is seeded as a namespace root with
/// `instance_depth` cleared — a prototype root is not an instance (see
Expand All @@ -428,14 +442,14 @@ impl IndexCache {
// `Indexer` already takes only `&` references; this needs the cache to build
// off the `&mut self` path first (compose into per-prototype results, then
// insert) and the shared `LayerGraph` handed to workers as `&`/`Arc`.
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;
fn materialize_prototype(&mut self, graph: &LayerGraph, composed: &Path, prototype: &Path) {
let mut index = self.cached(composed).clone();
let depth = composed.prim_element_count() as u16;
index.mark_instance_local_inert(depth, depth);
// Re-anchor the seeding instance's composed namespace onto the prototype
// root so the root's own target translation lands in the prototype
// namespace, not the canonical instance's.
index.rebase_root(canonical, prototype);
// namespace, not the instance's.
index.rebase_root(composed, prototype);

let (mut context, _) = index.context_for_children(graph, &self.root_parent_context());
context.instance_depth = None;
Expand Down