Skip to content

feat(stage): add references_in() and payloads_in() arc enumeration#44

Open
yohawing wants to merge 1 commit intomxpv:mainfrom
yohawing:upstream-review/stage-arc-enumeration
Open

feat(stage): add references_in() and payloads_in() arc enumeration#44
yohawing wants to merge 1 commit intomxpv:mainfrom
yohawing:upstream-review/stage-arc-enumeration

Conversation

@yohawing
Copy link
Copy Markdown
Contributor

@yohawing yohawing commented Apr 7, 2026

Summary

Adds two layer-level arc enumeration methods to Stage:

pub fn references_in(&self, path: impl Into<Path>) -> Vec<Reference>
pub fn payloads_in(&self, path: impl Into<Path>)   -> Vec<Payload>

Both methods iterate over every layer in the stack and collect the as-authored references / payload fields for the given prim path, without following composition arcs across files. This makes them suitable for asset-health inspection tasks such as detecting broken or missing references before attempting full composition, or building a dependency graph without paying the cost of full composition.

Design notes (open for discussion)

  • Returns Vec, not a composed/deduplicated result. Callers doing asset inspection typically want to know exactly what each layer declares, duplicates and all. A composed/dedup'd helper can be layered on top if needed.
  • No new public types. Reuses the existing sdf::Reference and sdf::Payload types.
  • payloads_in handles both forms. Upstream stores payloads as either a single Value::Payload or Value::PayloadListOp depending on how they were authored; both are matched.
  • references_in only matches ReferenceListOp since the parser never emits a single-value form for references.

If upstream prefers a different name (authored_references / layer_references / local_references) or prefers to return an iterator instead of a Vec, happy to adjust.

Changes

  • src/stage.rs: two new methods with doc-examples (~90 lines).
  • fixtures/arc_enumeration.usda: fixture with one WithReference, one WithPayload, and one NoArcs prim, all under /Root.
  • Tests added in stage::tests:
    • references_in_returns_declared_reference
    • references_in_empty_for_prim_without_references
    • payloads_in_returns_declared_payload
    • payloads_in_empty_for_prim_without_payloads

Test plan

  • cargo test passes (229 tests, 4 new)
  • cargo clippy --all-targets -- -D warnings clean
  • cargo fmt --check clean

Adds two layer-level arc enumeration methods to Stage:

    pub fn references_in(&self, path: impl Into<Path>) -> Vec<Reference>
    pub fn payloads_in(&self, path: impl Into<Path>)   -> Vec<Payload>

Both methods iterate over every layer in the stage and collect the
as-authored `references` / `payload` fields for the given prim
path, **without** following composition arcs across files.  This
makes them suitable for asset-health inspection tasks such as
detecting broken or missing references before attempting full
composition.

Design notes:
- Returns `Vec` (not a composed/deduplicated result) so callers see
  exactly what each layer declares.
- Both `Payload` (single value) and `PayloadListOp` (list-edit)
  storage forms are handled in `payloads_in`.
- The existing `sdf::Reference` and `sdf::Payload` types are
  reused; no new public types introduced.

Tests added:
- references_in_returns_declared_reference
- references_in_empty_for_prim_without_references
- payloads_in_returns_declared_payload
- payloads_in_empty_for_prim_without_payloads

Fixture: fixtures/arc_enumeration.usda
Copilot AI review requested due to automatic review settings April 7, 2026 07:43
@yohawing
Copy link
Copy Markdown
Contributor Author

yohawing commented Apr 7, 2026

Hi @mxpv,

Apologies — this PR was opened by an AI agent on my behalf without
enough human review of upstream etiquette. I should have filed an
issue first to discuss before dropping code on you. Sorry for the noise.

For context: I'm building yw-look (https://github.com/yohawing/yw-look),
a Windows quick-look style asset inspector for CG workflows, and I need
a few small accessors on top of openusd to surface stage metadata
(upAxis, metersPerUnit) and detect broken references in user assets.
This change is one of those minimal accessors.

If the API and approach look reasonable to you, I'd be grateful if you
could merge it. If not — totally fine to close, I'd just maintain it on
my fork instead. Happy to revise based on any feedback.

Thanks for the great crate!

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds Stage-level helpers to enumerate authored (layer-by-layer) reference and payload arcs at a given prim path, intended for asset-health / dependency inspection without performing full cross-file composition.

Changes:

  • Added Stage::references_in() to collect per-layer references entries for a prim path.
  • Added Stage::payloads_in() to collect per-layer payload entries for a prim path (single or list-op forms).
  • Added a new USDA fixture and 4 unit tests validating empty/non-empty behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
src/stage.rs Adds the two new enumeration APIs plus targeted tests.
fixtures/arc_enumeration.usda Adds a fixture stage with prims that author a reference, a payload, and no arcs.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +135 to +144
for layer in &self.layers {
if !layer.has_field(&path, FieldKey::References.as_str()) {
continue;
}
let Ok(value) = layer.get(&path, FieldKey::References.as_str()) else {
continue;
};
if let Value::ReferenceListOp(list_op) = value.into_owned() {
result.extend(list_op.iter().cloned());
}
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

references_in silently skips layer.get(...) errors (even after has_field), which can hide real decode/IO issues (e.g. USDC value decoding failures) and return incomplete results without any signal. Consider changing the API to Result<Vec<Reference>> and propagating get errors (or at least distinguishing missing-field from other errors and surfacing the latter).

Copilot uses AI. Check for mistakes.
Comment on lines +111 to +117
/// references across files), this method reads only the **as-authored**
/// `references` field of each layer spec. It is intended for asset-health
/// inspection tasks such as detecting broken or missing references before
/// attempting full composition.
///
/// Duplicate entries from multiple layers are included as-is; callers can
/// de-duplicate if needed. The ordering is strongest-layer-first.
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docs say this returns the as-authored references field, but the implementation uses ListOp::iter(), which explicitly excludes deleted_items and ordered_items (see sdf::ListOp::iter). If the intent is to enumerate all authored list-edit entries, the docs should be tightened (e.g., “opinion-contributing entries”), or the implementation should include deleted/ordered items as well.

Suggested change
/// references across files), this method reads only the **as-authored**
/// `references` field of each layer spec. It is intended for asset-health
/// inspection tasks such as detecting broken or missing references before
/// attempting full composition.
///
/// Duplicate entries from multiple layers are included as-is; callers can
/// de-duplicate if needed. The ordering is strongest-layer-first.
/// references across files), this method reads only the authored
/// `references` entries surfaced by the underlying list-op iteration for
/// each layer spec. It is intended for asset-health inspection tasks such
/// as detecting broken or missing references before attempting full
/// composition.
///
/// This does not expose every authored list-edit bucket: deleted and
/// ordered entries are not included. Duplicate entries from multiple
/// layers are included as-is; callers can de-duplicate if needed. The
/// ordering is strongest-layer-first.

Copilot uses AI. Check for mistakes.
Comment on lines +174 to +185
for layer in &self.layers {
if !layer.has_field(&path, FieldKey::Payload.as_str()) {
continue;
}
let Ok(value) = layer.get(&path, FieldKey::Payload.as_str()) else {
continue;
};
match value.into_owned() {
Value::Payload(p) => result.push(p),
Value::PayloadListOp(list_op) => result.extend(list_op.iter().cloned()),
_ => {}
}
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

payloads_in also swallows layer.get(...) errors and will silently return partial results if decoding fails. For consistency with other Stage queries (and to avoid hiding layer corruption), consider returning Result<Vec<Payload>> and propagating non-missing errors after the has_field check.

Copilot uses AI. Check for mistakes.
Comment on lines +9 to +15
references = @./ref_target.usda@</World>
)
{
}

def "WithPayload" (
payload = @./ref_target.usda@</World>
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fixture references/payloads </World> in ref_target.usda, but fixtures/ref_target.usda defines defaultPrim = "Source" and the root prim is /Source (no /World). If this mismatch is intentional (to model a broken arc), it would help to add a brief comment in the fixture; otherwise consider changing the prim path to </Source> so the fixture is self-consistent.

Suggested change
references = @./ref_target.usda@</World>
)
{
}
def "WithPayload" (
payload = @./ref_target.usda@</World>
references = @./ref_target.usda@</Source>
)
{
}
def "WithPayload" (
payload = @./ref_target.usda@</Source>

Copilot uses AI. Check for mistakes.
@yohawing
Copy link
Copy Markdown
Contributor Author

yohawing commented Apr 7, 2026

Hi @mxpv,

Apologies — this PR (and the other four in this batch: #41 #42 #43 #44 #45) was opened by an AI agent on my behalf without enough human review of upstream etiquette. I should have filed an issue first to discuss before dropping a stack of code on you. Sorry for the noise.

For context: I'm building yw-look, a Windows quick-look style asset inspector for CG workflows, and I need a few small accessors on top of openusd to surface stage metadata (upAxis, metersPerUnit) and detect broken references in user assets. These five PRs are the minimal set of additions I needed.

If the API and approach look reasonable to you, I'd be grateful if you could merge them. If not — totally fine to close, I'd just maintain them on my fork instead. Happy to revise based on any feedback.

Thanks for the great crate!

@mxpv
Copy link
Copy Markdown
Owner

mxpv commented Apr 7, 2026

Thanks for the contribution! Could you clarify the use case here?

Stage::open already does full composition in collect_layers — by the time you have a Stage, all layers are loaded and resolved. There's no lazy loading to skip. And iterating raw layers manually to collect references reimplements what build_prim_index already does, but with weaker semantics (flattens ListOps via iter(), ignores deleted_items).

You can already enumerate authored references and payloads on any prim via the generic field API:

  let refs: Option<ReferenceListOp> = stage.field("/World/Prop", FieldKey::References)?;                                                                                                       
  let payloads: Option<PayloadListOp> = stage.field("/World/Prop", FieldKey::Payload)?;                                                                                                      

This returns the properly composed ListOp, preserving the full edit semantics. Combined with stage.traverse() you can walk every prim and collect all arcs.

If there's a specific workflow that this doesn't cover?

@mxpv
Copy link
Copy Markdown
Owner

mxpv commented Apr 7, 2026

I think #46 is what was missing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants