- name
- runtime-loop
- description
- Headless load -> populate -> read -> update -> read loop with no renderer attached: open a USD scene into the ovstage runtime table, read prims back to confirm, then update the live stage two ways — write straight into the ovstage table (e.g. animate a transform), or edit the USD source and propagate it through. Use when the user wants to drive an ovstage scene headlessly, populate USD into the runtime stage, or see runtime-table vs USD-source edits reflected in reads.
- license
- LicenseRef-NvidiaProprietary
- version
- 0.1.0
- author
- NVIDIA ovstage
- tags
- ["ovstage","population","usd","runtime"]
- tools
- ["Read","Grep"]
# Runtime Loop (Populate, Read, Update)
## When to Use
Use this skill when the user wants to work an ovstage scene **headlessly** (no renderer): load a
USD scene and populate it into the runtime table, read prims back, and update the live stage. It
covers the **two update paths** a client has once a scene is live:
1. **Directly in the ovstage runtime table** — `write_attribute` at a new ordinal (e.g. animate a
prim's `omni:xform` transform over frames). The fast GPU-side edit; no USD round-trip.
2. **Through the USD source** — `add_usd_reference` / `apply_usd_changes` (or `update_from_usd_time`
for time samples), so USD-side edits propagate into the runtime table.
Do **not** use it for rendering a scene (that stays in ovrtx), or for the mechanics of a single
call — route those to the focused skills (`application-flow`, `dlpack-tensor-exchange`, etc.).
## Inputs
Resolve inputs in this order: existing repository files and referenced snippets, explicit user request, then broader agent context.
- The scene: a USD file path (`open_usd`) or inline USDA string (`open_usd_from_string`).
- The prims to query and the attribute(s) to read/write (as interned tokens or strings).
- The caller-owned **ordinals**: under strict admission, use newer
ordinals for repeated changes to a cell; disjoint producers may share an ordinal
above their applicable floors.
- Which update path(s) the request needs: direct runtime-table write, USD-source edit, or both.
- Target API surface: C, Python, or both.
- The shipped headers and the referenced example snippets are the authoritative contract.
## Prerequisites
- Population entry points: `ovstage_population_*` in C, `ovstage.population` in Python.
- Read the relevant `> **Source:**` snippet before writing or explaining API usage.
- Understand the async **submit/observe** model (`cpu-ahead-gpu-async`): populate and write are
enqueues that return an `op_index`; nothing is visible until you await and **advance the write
floor**. Population has its own wait (`ovstage_population_wait_op`), parallel to the data-plane
`ovstage_wait_op`.
- The **application owns the ordinal lifecycle** — population never opens or seals an ordinal; you
pass the current one to each call and advance the write floor per tick.
## Instructions
1. **Populate.** `open_usd` (file) or `open_usd_from_string` (inline) at ordinal 1 with a
`PopulationDomain`, then `advance_write_floor` to seal it so reads can see it.
2. **Read to confirm.** Read the reserved `usd-prim-type` metadata (auto-maintained for every
populated prim) over a path-list query — the guaranteed proof the populate landed.
3. **Update path 1 — runtime table.** `write_attribute` at a new ordinal (e.g. a prim's
`omni:xform` transform, `is_array=false`, `semantic=MATRIX`), advancing the write floor each
step; read the column back to confirm.
4. **Update path 2 — USD source.** `add_usd_reference` (or `_from_string`) edits only USD;
`apply_usd_changes` at a fresh ordinal propagates it into the runtime table. Advance the floor,
then re-read to see the change.
5. **Own the ordinals.** Clear applicable floors and satisfy the configured
admission policy for affected cells and prim lifecycle. Under the
`STRICTLY_INCREASING` policy, disjoint cells may share an ordinal; rewriting a
cell needs a newer ordinal even before sealing. See
[Ordinal Admission Policies](../../docs/scene/writing_attributes.rst) for options.
## Output Format
- For explanations, cite the API names, the source snippet(s), and the key caveats (population
support, ordinal ownership, which update path, transform recipe).
- For code changes, summarize the files touched, snippets affected, and validation run.
## Scripts
This skill has no scripts.
## Limitations
- The referenced snippets remain the source of truth; update or add tested snippets before
documenting new API usage.
- **Transform layout: `omni:xform` is one 16-lane element per prim.** The canonical 4×4 double
tensor uses `dtype.lanes = 16`, `shape = [1]` (`semantic = MATRIX`, row-vector convention,
translation in row `[3][0..2]`). A compact `shape = [1, 4, 4]`, `lanes = 1` copy-in is also
accepted. The input dimensions are folded into storage; raw reads and maps reconstruct
`shape = [1, 4, 4]`, `lanes = 1`. Their strides count scalar elements.
- **Transform scope.** This loop reads back only the local `omni:xform` value **it wrote itself**.
It does not validate hierarchy-derived world-transform reads. Derived world rows may be absent or
stale until their owning workflow materializes them and runs hierarchy computation; follow
`docs/scene/transforms.rst` for the authoring and freshness contract.
- **Inline USDA for `add_usd_reference` must be multi-line.** A single-line layer-metadata + prim
body does not parse through the anonymous-layer import path.
- **`apply_usd_time` / structural edits.** For the RENDERING domain, `apply_usd_changes` /
`apply_usd_time` also reflect structural USD edits; other domains apply structural edits only via
`apply_usd_changes`.
- **Latest-snapshot build** — data becomes visible at/below the write floor once you advance it;
don't design around reading historical ordinals.
## Overview
ovstage is a headless, GPU-native runtime stage for USD data. The **population** bridge
(`ovstage_population.h`) reads USD (a file or inline USDA) and mirrors it into an ovstage instance;
the **data plane** (`ovstage.h`) then reads and writes columns against the populated prims. This
skill ties them into one loop: populate → read → update (table and/or USD) → read, with the
application owning the ordinal lifecycle and sealing each tick with `advance_write_floor`.
## C
Populate a USD file and seal the ordinal (population has its own enqueue/wait; the `waitPop`
helper mirrors the data-plane `waitOp`):
> **Source:** `examples/c/runtime-loop/main.cpp` snippet `populate`
Confirm the populate by reading the reserved `usd-prim-type` metadata and resolving the tokens:
> **Source:** `examples/c/runtime-loop/main.cpp` snippet `read-populated`
Update path 1 — write the transform straight into the runtime table over frames:
> **Source:** `examples/c/runtime-loop/main.cpp` snippet `update-table`
Update path 2 — edit the USD source and propagate it through:
> **Source:** `examples/c/runtime-loop/main.cpp` snippet `update-usd`
## Python
The Python population surface is `ovstage.population.*` (blocking `foo(...)` plus async
`foo_async(...)`). Imports:
> **Source:** `examples/python/runtime-loop/main.py` snippet `setup`
Populate and seal:
> **Source:** `examples/python/runtime-loop/main.py` snippet `populate`
Read the reserved `usd-prim-type` metadata to confirm the populate:
> **Source:** `examples/python/runtime-loop/main.py` snippet `read-populated`
Update path 1 — animate the transform straight into the runtime table:
> **Source:** `examples/python/runtime-loop/main.py` snippet `update-table`
Update path 2 — edit the USD source and propagate it through:
> **Source:** `examples/python/runtime-loop/main.py` snippet `update-usd`
## Key Types / Functions
| Python | C |
|--------|---|
| `population.open_usd(stage, path, ordinal, ...)` / `open_usd_from_string` | `ovstage_population_open_usd_from_file` / `_from_string` |
| `population.apply_usd_changes(stage, ordinal)` | `ovstage_population_apply_usd_changes` |
| `population.add_usd_reference_from_string(stage, usda, target)` | `ovstage_population_add_usd_reference_from_string` |
| `Stage.write_attribute(query, attr, ordinal, tensors, is_array=, semantic=)` | `ovstage_write_attribute` (+ `ovstage_write_data_t`) |
| `Stage.read_attributes(query, attrs, OrdinalRange)` → `Read` | `ovstage_read_attributes` (+ `ovstage_fetch_read_next`) |
| `Stage.advance_write_floor(ordinal)` | `ovstage_advance_write_floor` |
## Troubleshooting
- **Read fails with `OVSTAGE_ERROR_WRITE_FLOOR_VIOLATION` after populate** — advance the write
floor to the populate ordinal; reads only see sealed data at/below the floor.
- **`OVSTAGE_ERROR_WRITE_FLOOR_VIOLATION`** — the ordinal conflicts with a floor or
the configured admission policy for affected cells or prim lifecycle. Use a
newer ordinal for overlapping updates in this default-policy loop
(for example, populate 1 → write 2 → USD edit 3).
- **USD edit didn't show up in reads** — `add_usd_reference` only edits USD; you must call
`apply_usd_changes` at a fresh ordinal and advance the floor before re-reading.
- **Transform read looks wrong / empty** — confirm the `omni:xform` recipe and that the query
reads a column written or populated for the selected prims. For derived world rows, see
**Transform scope** under Limitations and `docs/scene/transforms.rst`.
## References
- Use the `> **Source:**` directives in this skill to locate tested snippets before reusing API patterns.
- `application-flow` — the create → write → seal → read lifecycle this loop instantiates.
- `cpu-ahead-gpu-async` — the async submit/observe model and ordinal/write-floor semantics.
- `dlpack-tensor-exchange` — tensor interchange for the read/write payloads (matrices, columns).
- `error-handling` — status checks and per-op error reporting for enqueues and waits.
- `path-dictionary` / `string-handling` — interning prim paths and attribute names (`ovx_string_t`).
- Keep related skills, docs, and snippets synchronized when changing the workflow.
View on GitHub