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:
- 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.
- 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: one per populate/write/apply step, advanced monotonically.
- 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
- 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.
- 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.
- 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.
- 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.
- Own the ordinals. Keep ordinals monotonic across all steps; a write/apply at or below the
floor is a write-floor violation.
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, but the trailing dimensions are folded and not preserved: raw reads and maps return
the canonical shape = [1], lanes = 16 layout.
- 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 — a write/apply_usd_changes used an ordinal at/below
the floor. Keep ordinals monotonic (populate 1 → writes 2..N → USD edit N+1).
- 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.
1---2name: runtime-loop3description: 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.4license: LicenseRef-NvidiaProprietary5---67# Runtime Loop (Populate, Read, Update)89## When to Use1011Use this skill when the user wants to work an ovstage scene **headlessly** (no renderer): load a12USD scene and populate it into the runtime table, read prims back, and update the live stage. It13covers the **two update paths** a client has once a scene is live:14151. **Directly in the ovstage runtime table** — `write_attribute` at a new ordinal (e.g. animate a16 prim's `omni:xform` transform over frames). The fast GPU-side edit; no USD round-trip.172. **Through the USD source** — `add_usd_reference` / `apply_usd_changes` (or `update_from_usd_time`18 for time samples), so USD-side edits propagate into the runtime table.1920Do **not** use it for rendering a scene (that stays in ovrtx), or for the mechanics of a single21call — route those to the focused skills (`application-flow`, `dlpack-tensor-exchange`, etc.).2223## Inputs2425Resolve inputs in this order: existing repository files and referenced snippets, explicit user request, then broader agent context.2627- The scene: a USD file path (`open_usd`) or inline USDA string (`open_usd_from_string`).28- The prims to query and the attribute(s) to read/write (as interned tokens or strings).29- The caller-owned **ordinals**: one per populate/write/apply step, advanced monotonically.30- Which update path(s) the request needs: direct runtime-table write, USD-source edit, or both.31- Target API surface: C, Python, or both.32- The shipped headers and the referenced example snippets are the authoritative contract.3334## Prerequisites3536- Population entry points: `ovstage_population_*` in C, `ovstage.population` in Python.37- Read the relevant `> **Source:**` snippet before writing or explaining API usage.38- Understand the async **submit/observe** model (`cpu-ahead-gpu-async`): populate and write are39 enqueues that return an `op_index`; nothing is visible until you await and **advance the write40 floor**. Population has its own wait (`ovstage_population_wait_op`), parallel to the data-plane41 `ovstage_wait_op`.42- The **application owns the ordinal lifecycle** — population never opens or seals an ordinal; you43 pass the current one to each call and advance the write floor per tick.4445## Instructions46471. **Populate.** `open_usd` (file) or `open_usd_from_string` (inline) at ordinal 1 with a48 `PopulationDomain`, then `advance_write_floor` to seal it so reads can see it.492. **Read to confirm.** Read the reserved `usd-prim-type` metadata (auto-maintained for every50 populated prim) over a path-list query — the guaranteed proof the populate landed.513. **Update path 1 — runtime table.** `write_attribute` at a new ordinal (e.g. a prim's52 `omni:xform` transform, `is_array=false`, `semantic=MATRIX`), advancing the write floor each53 step; read the column back to confirm.544. **Update path 2 — USD source.** `add_usd_reference` (or `_from_string`) edits only USD;55 `apply_usd_changes` at a fresh ordinal propagates it into the runtime table. Advance the floor,56 then re-read to see the change.575. **Own the ordinals.** Keep ordinals monotonic across all steps; a write/apply at or below the58 floor is a write-floor violation.5960## Output Format6162- For explanations, cite the API names, the source snippet(s), and the key caveats (population63 support, ordinal ownership, which update path, transform recipe).64- For code changes, summarize the files touched, snippets affected, and validation run.6566## Scripts6768This skill has no scripts.6970## Limitations7172- The referenced snippets remain the source of truth; update or add tested snippets before73 documenting new API usage.74- **Transform layout: `omni:xform` is one 16-lane element per prim.** The canonical 4×4 double75 tensor uses `dtype.lanes = 16`, `shape = [1]` (`semantic = MATRIX`, row-vector convention,76 translation in row `[3][0..2]`). A compact `shape = [1, 4, 4]`, `lanes = 1` copy-in is also77 accepted, but the trailing dimensions are folded and not preserved: raw reads and maps return78 the canonical `shape = [1]`, `lanes = 16` layout.79- **Transform scope.** This loop reads back only the local `omni:xform` value **it wrote itself**.80 It does not validate hierarchy-derived world-transform reads. Derived world rows may be absent or81 stale until their owning workflow materializes them and runs hierarchy computation; follow82 `docs/scene/transforms.rst` for the authoring and freshness contract.83- **Inline USDA for `add_usd_reference` must be multi-line.** A single-line layer-metadata + prim84 body does not parse through the anonymous-layer import path.85- **`apply_usd_time` / structural edits.** For the RENDERING domain, `apply_usd_changes` /86 `apply_usd_time` also reflect structural USD edits; other domains apply structural edits only via87 `apply_usd_changes`.88- **Latest-snapshot build** — data becomes visible at/below the write floor once you advance it;89 don't design around reading historical ordinals.9091## Overview9293ovstage is a headless, GPU-native runtime stage for USD data. The **population** bridge94(`ovstage_population.h`) reads USD (a file or inline USDA) and mirrors it into an ovstage instance;95the **data plane** (`ovstage.h`) then reads and writes columns against the populated prims. This96skill ties them into one loop: populate → read → update (table and/or USD) → read, with the97application owning the ordinal lifecycle and sealing each tick with `advance_write_floor`.9899## C100101Populate a USD file and seal the ordinal (population has its own enqueue/wait; the `waitPop`102helper mirrors the data-plane `waitOp`):103104> **Source:** `examples/c/runtime-loop/main.cpp` snippet `populate`105106Confirm the populate by reading the reserved `usd-prim-type` metadata and resolving the tokens:107108> **Source:** `examples/c/runtime-loop/main.cpp` snippet `read-populated`109110Update path 1 — write the transform straight into the runtime table over frames:111112> **Source:** `examples/c/runtime-loop/main.cpp` snippet `update-table`113114Update path 2 — edit the USD source and propagate it through:115116> **Source:** `examples/c/runtime-loop/main.cpp` snippet `update-usd`117118## Python119120The Python population surface is `ovstage.population.*` (blocking `foo(...)` plus async121`foo_async(...)`). Imports:122123> **Source:** `examples/python/runtime-loop/main.py` snippet `setup`124125Populate and seal:126127> **Source:** `examples/python/runtime-loop/main.py` snippet `populate`128129Read the reserved `usd-prim-type` metadata to confirm the populate:130131> **Source:** `examples/python/runtime-loop/main.py` snippet `read-populated`132133Update path 1 — animate the transform straight into the runtime table:134135> **Source:** `examples/python/runtime-loop/main.py` snippet `update-table`136137Update path 2 — edit the USD source and propagate it through:138139> **Source:** `examples/python/runtime-loop/main.py` snippet `update-usd`140141## Key Types / Functions142143| Python | C |144|--------|---|145| `population.open_usd(stage, path, ordinal, ...)` / `open_usd_from_string` | `ovstage_population_open_usd_from_file` / `_from_string` |146| `population.apply_usd_changes(stage, ordinal)` | `ovstage_population_apply_usd_changes` |147| `population.add_usd_reference_from_string(stage, usda, target)` | `ovstage_population_add_usd_reference_from_string` |148| `Stage.write_attribute(query, attr, ordinal, tensors, is_array=, semantic=)` | `ovstage_write_attribute` (+ `ovstage_write_data_t`) |149| `Stage.read_attributes(query, attrs, OrdinalRange)` → `Read` | `ovstage_read_attributes` (+ `ovstage_fetch_read_next`) |150| `Stage.advance_write_floor(ordinal)` | `ovstage_advance_write_floor` |151152## Troubleshooting153154- **Read fails with `OVSTAGE_ERROR_WRITE_FLOOR_VIOLATION` after populate** — advance the write155 floor to the populate ordinal; reads only see sealed data at/below the floor.156- **`OVSTAGE_ERROR_WRITE_FLOOR_VIOLATION`** — a write/`apply_usd_changes` used an ordinal at/below157 the floor. Keep ordinals monotonic (populate 1 → writes 2..N → USD edit N+1).158- **USD edit didn't show up in reads** — `add_usd_reference` only edits USD; you must call159 `apply_usd_changes` at a fresh ordinal and advance the floor before re-reading.160- **Transform read looks wrong / empty** — confirm the `omni:xform` recipe and that the query161 reads a column written or populated for the selected prims. For derived world rows, see162 **Transform scope** under Limitations and `docs/scene/transforms.rst`.163164## References165166- Use the `> **Source:**` directives in this skill to locate tested snippets before reusing API patterns.167- `application-flow` — the create → write → seal → read lifecycle this loop instantiates.168- `cpu-ahead-gpu-async` — the async submit/observe model and ordinal/write-floor semantics.169- `dlpack-tensor-exchange` — tensor interchange for the read/write payloads (matrices, columns).170- `error-handling` — status checks and per-op error reporting for enqueues and waits.171- `path-dictionary` / `string-handling` — interning prim paths and attribute names (`ovx_string_t`).172- Keep related skills, docs, and snippets synchronized when changing the workflow.