Skip to main content

path-dictionary

Using the OVX path dictionary — the shared interning layer for tokens, prim paths, and prim-path lists across OV libraries. Use when user asks about interning strings/paths, ovx_token_t / ovx_primpath_t / ovx_primpath_list_t, or sharing paths with ovstage.

Source facts

Repository
NVIDIA-Omniverse/ovstage
Last source activity
October 15, 2026 at 05:21
Detected SKILL.md language
English
Stars
22
Forks
5

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
path-dictionary
description
Using the OVX path dictionary — the shared interning layer for tokens, prim paths, and prim-path lists across OV libraries. Use when user asks about interning strings/paths, ovx_token_t / ovx_primpath_t / ovx_primpath_list_t, or sharing paths with ovstage.
license
LicenseRef-NvidiaProprietary
version
0.1.0
author
NVIDIA ovstage
tags
["ovstage","ovx","path-dictionary"]
tools
["Read","Grep"]
# Path Dictionary ## When to Use Use this skill when the user asks about interning strings or prim paths, working with `ovx_token_t` / `ovx_primpath_t` / `ovx_primpath_list_t`, building a path list to query ovstage, or sharing interned paths/tokens across OV libraries. ## Inputs Resolve inputs in this order: existing repository files and referenced snippets, explicit user request, then broader agent context. - Whether the caller needs a token (attribute/name interning), a prim path, or a prim-path list (to query/read a set of prims). - The dictionary is owned by a producing subsystem; for ovstage, obtain it via `ovstage_get_path_dictionary` for zero-conversion sharing and never free it (there is no app-side create/destroy in the public API). - Whether a path list was created by the caller (`path_dictionary_create_path_list_from_*` returns it with refcount=1 — pair it with one `path_dictionary_release_path_list_reference`) or borrowed from an ovstage read result (release only references you added, never ovstage's). - Repository source snippets referenced below. Treat these snippets as the API source of truth. ## Prerequisites - Use an ovstage checkout with the path-dictionary headers under `include/ovx/path_dictionary/` — `path_dictionary.h` (which pulls in `path_dictionary_types.h` and the `path_dictionary_utils.h` inline wrappers). - Read the relevant `> **Source:**` snippet before writing or explaining API usage. - Know the refcount rule for any `ovx_primpath_list_t` before releasing a reference to it. ## Instructions 1. Identify whether the task needs a token, a path, or a path list. 2. Obtain a dictionary from its owner: for ovstage, `ovstage_get_path_dictionary(instance)` returns a `path_dictionary_instance_t*` (do not free its `vtable`/`context`). All calls below are the inline wrappers from `path_dictionary_utils.h` and take that instance. 3. Intern strings with `path_dictionary_create_tokens_from_strings` (tokens) and `path_dictionary_create_paths_from_strings` / `..._create_paths_from_tokens` (prim paths). Component tokens name an absolute path: omit separators from the tokens, and expect the same handle as the equivalent absolute string. Zero components identify `/`; still supply a non-null token array even when all per-path counts are zero. Resolve tokens back with `path_dictionary_get_strings_from_tokens`; decompose a prim path into its tokens with `path_dictionary_get_tokens_from_paths`, then resolve those tokens. 4. Build path lists with `path_dictionary_create_path_list_from_paths` (from interned paths) or `..._create_path_list_from_strings`; read them back with `path_dictionary_get_paths_from_path_list` / `..._get_num_paths_from_path_list`. 5. Path lists are refcounted: `path_dictionary_create_path_list_from_*` returns refcount=1 owned by you, so pair it with exactly one `path_dictionary_release_path_list_reference` (which frees the list when the count reaches zero). To keep a list borrowed from an ovstage read result, call `path_dictionary_add_path_list_reference` first and release that added reference when done; never release ovstage's own reference. 6. Check every `ovx_api_result_t::status` against `OVX_API_SUCCESS`; on `OVX_API_ERROR` the `result.error` (an `ovx_string_t`) describes the failure and must be freed with `path_dictionary_release_error`. 7. When changing code, run the path-dictionary unit test whenever practical. ## Output Format - For explanations, cite the relevant API names, source snippets, and caveats. - For code changes, summarize the files changed, 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. - **Python:** covered in the Python section (`ovstage.PathDictionary`; `int` handles, `str` names, `OvxError`). Inline Python pending a shipping Python example to snippet-source. - **Snippets** are sourced from the shipping example `examples/c/minimal/main.cpp`. ## Overview The OVX path dictionary (`<ovx/path_dictionary/path_dictionary.h>`) is the **shared, zero-cost interning layer** for OV libraries (ovstage, ovrtx, ovphysx, …). The public surface is a vtable (`path_dictionary_vtable_t`) plus inline wrappers in `path_dictionary_utils.h`; an instance (`path_dictionary_instance_t`) is produced by an owner subsystem (e.g. ovstage) and maps strings to stable, trivially comparable handles: - `ovx_token_t` — interned string (e.g. an attribute name). `OVX_INVALID_TOKEN` is `0`. - `ovx_primpath_t` — interned prim path. `OVX_INVALID_PRIMPATH` is `0`. - `ovx_primpath_list_t` — an immutable set/ordering of prim paths. `OVX_INVALID_PRIMPATH_LIST` is `0`. Design guarantees: - **Handle identity:** same string → same handle, so consumers use handle equality for O(1) identity checks (no string compare). - **Immutable lists:** once created, an `ovx_primpath_list_t` never changes; equal handles imply identical prim set + ordering (usable as a cache key). - **Thread-safe interning:** concurrent interning is supported; returned handles and string pointers are stable for the dictionary's lifetime. This is the type backbone for `ovx_string_or_token_t` dual-mode arguments throughout ovstage — see the `string-handling` skill. ## Ownership & lifetime - The dictionary is **owned by its producing subsystem** (for ovstage, obtained via `ovstage_get_path_dictionary`). Callers MUST NOT free its `vtable`/`context`; when the owner reports it gone, every handle minted through it is invalidated simultaneously. - Tokens and prim paths are **dict-lifetime** (interned, never freed individually); they stay valid for as long as the dictionary lives. - String pointers from `path_dictionary_get_strings_from_tokens` point into dictionary-owned storage and are valid for the dictionary's lifetime — do not free them; copy if you need them longer. (Error strings inside `ovx_api_result_t` are different — free those with `path_dictionary_release_error`.) - `ovx_primpath_list_t` handles are **explicitly refcounted**. `path_dictionary_create_path_list_from_*` returns a list with **refcount=1** owned by the caller; `path_dictionary_add_path_list_reference` increments and `path_dictionary_release_path_list_reference` decrements, freeing the list at zero. Every `create`/`add` must be paired with exactly one release. - A list **returned by an ovstage read result** is a **borrow** held by the producing op. Do not release ovstage's reference; to keep the list beyond the producing handle, call `path_dictionary_add_path_list_reference` first and release that added reference when finished. - In Python, `create_path_list*` returns an `ovstage.PathList` that owns its reference and releases it on `with`-exit or via a finalizer; see "Path-list ownership in Python" below. The C contract is unchanged — C callers pair every create/add with exactly one release. ## C — intern and resolve Obtain a dictionary, intern an attribute token and prim paths, build a path list, and resolve back to strings: > **Source:** `examples/c/minimal/main.cpp` snippet `intern-and-resolve` ## C — build a path list and query ovstage The basic stage test interns paths into a list and passes it to `ovstage_query_from_path_list`, then uses the interned attribute token as the `ovx_string_or_token_t` argument: > **Source:** `examples/c/minimal/main.cpp` snippet `path-list-query` ## Sharing with ovstage For zero-conversion sharing, obtain ovstage's own dictionary instead of creating a separate one, so tokens/paths interned by the application and by ovstage are directly comparable: ```c /* path_dictionary_instance_t* dict = ovstage_get_path_dictionary(instance); returns ovstage's dictionary (NULL if instance is NULL); valid for the instance lifetime. Do not free dict->vtable / dict->context. */ ``` ## Python The path dictionary is `ovstage.PathDictionary` — a context manager wrapping the same interning service. Tokens and prim paths are Python `int` handles; strings are `str`. Errors raise `ovstage.OvxError`. > **Source:** `examples/python/minimal/main.py` snippet `intern-and-resolve` > > Followed by: `examples/python/minimal/main.py` snippet `path-list-query` Resolve a path list back to strings with `paths.get_path_strings(list)`. Use `PathDictionary.intern_path` to construct an individual path from a string; there is no public Python `create_paths_from_tokens` method. Absolute strings share handle identity with equivalent C component-token paths in the same dictionary. The root string `/` corresponds to zero component tokens in C. Construct `PathDictionary(stage)` with the Stage that will use the lists. Each Stage owns its dictionary; recreate lists in a destination Stage's dictionary when transferring a selection. List references do not extend Stage lifetime. Same ownership rule as C: release **caller-created** lists; do not release lists handed back by an ovstage read result. ### Path-list ownership in Python `create_path_list` / `create_path_list_from_strings` return an `ovstage.PathList`. It subclasses `int` and **is** the handle — it passes into `query_from_path_list` and every other slot unchanged, and `isinstance(handle, int)` still holds — but it also owns the reference the create call minted, and releases it on `with`-exit: > **Source:** `tests/python/test_path_lists.py` snippet `path-list-context-manager` `plist.release()` and `paths.destroy_path_list(plist)` are equivalent explicit forms. A `PathList` whose reference is still outstanding when it is garbage-collected releases it and emits a `ResourceWarning` — a bug report, not a strategy. Only `create_path_list*` mints a `PathList`. Borrowed lists from read results stay plain `int`, so the finalizer can never release a reference the caller does not own. A `PathList` owns exactly one reference: the one `create_path_list*` minted. A reference you add with `add_path_list_reference` is yours — pair it with one extra `destroy_path_list`, and note that dropping the `PathList` never reclaims it, so a bare handle you hand to a longer-lived consumer keeps working. Passing the `PathList` to `destroy_path_list` releases its own reference; passing the plain `int` releases one of your added references while any are outstanding. `query_from_path_list` wraps a **caller-owned** list, so keep your list alive for as long as the query and release it yourself. The returned `Query` holds a reference so the list cannot be finalized under a live query, but that is a safety net, not a transfer of ownership — releasing the query does not release your list. Bind the list rather than passing a freshly created one inline: an inline list leaves no handle to release, so it is reclaimed by the finalizer with a `ResourceWarning`. ### Per-frame loops: create once, reuse Recreating the path list (and the query built from it) each iteration mints a reference per iteration. Python's GC does **not** reclaim these on its own — dropping a plain `int` handle does not decrement the C refcount — so under the pre-`PathList` bare-`int` contract the references accumulate for the life of the dictionary. Create both once outside the loop: > **Source:** `tests/python/test_path_lists.py` snippet `path-list-loop-reuse` > > Full runtime pattern: `examples/python/runtime-loop/main.py` Reuse is the point — the `PathList` finalizer is a safety net that bounds the damage of the anti-pattern, not a licence to keep writing it. ## Key Types / Functions All operations are inline wrappers from `path_dictionary_utils.h` taking a `path_dictionary_instance_t*` (except `ovstage_get_path_dictionary`, which produces one). | Symbol | Role | |--------|------| | `ovstage_get_path_dictionary` | obtain ovstage's `path_dictionary_instance_t*` (owner-owned; do not free) | | `path_dictionary_create_tokens_from_strings` / `..._get_strings_from_tokens` | string ↔ `ovx_token_t` | | `path_dictionary_create_paths_from_strings` / `..._create_paths_from_tokens` | build `ovx_primpath_t` (dict-lifetime) | | `path_dictionary_get_tokens_from_paths` | decompose a `ovx_primpath_t` into its tokens | | `path_dictionary_create_path_list_from_paths` / `..._create_path_list_from_strings` | build an `ovx_primpath_list_t`, returned with **refcount=1** owned by the caller | | `path_dictionary_get_paths_from_path_list` / `..._get_num_paths_from_path_list` | read a list back | | `path_dictionary_add_path_list_reference` / `..._release_path_list_reference` | increment / decrement a list's refcount; release frees the list at zero — pair every `create`/`add` with exactly one release (replaces the legacy `destroy_path_list` slot) | | `path_dictionary_release_error` | free the `ovx_string_t` error attached to an `ovx_api_result_t` | Every slot returns `ovx_api_result_t { ovx_api_status_t status; ovx_string_t error; }`, where `ovx_api_status_t` is `OVX_API_SUCCESS` (0) or `OVX_API_ERROR` (1). Handle sentinels `OVX_INVALID_TOKEN` / `OVX_INVALID_PRIMPATH` / `OVX_INVALID_PRIMPATH_LIST` are all `0` and are never returned on success. ## Troubleshooting - A successful mint never returns `0` — treat `OVX_INVALID_TOKEN` / `OVX_INVALID_PRIMPATH` / `OVX_INVALID_PRIMPATH_LIST` (all `0`) as "unset/invalid". - `create_*` slots intern on miss (create-on-miss), so there is no separate lookup call; empty token/path strings are rejected. Both path-list constructors accept zero paths and mint a valid empty list; their input array may be null when the count is zero. `get_*` slots require a live handle — calling one on a released/unknown path list returns `OVX_API_ERROR`. - On the dictionary ovstage hands out, `path_dictionary_get_tokens_from_paths` rejects `OVX_INVALID_PRIMPATH` and unknown or expired prim-path handles with an actionable `OVX_API_ERROR` and leaves the caller's output buffers untouched; Python `PathDictionary.path_to_string` propagates that failure as `OvxError` instead of silently returning an empty string. A valid root path still succeeds, with a zero-token decomposition, which Python joins to `/` (matching `SdfPath`) — so `path_to_string` never returns an empty string on success, and every valid handle round-trips back through `intern_path`. Scope note: this is ovstage's implementation of the shared `ovx` vtable slot. Other implementations behind the same slot may still report a successfully processed zero-token path for a bad handle, so do not carry this guarantee into non-ovstage code. - Pair every `path_dictionary_create_path_list_from_*` / `..._add_path_list_reference` with exactly one `..._release_path_list_reference`; the list is freed when its refcount reaches zero. `OVX_INVALID_PRIMPATH_LIST` is a no-op on add/release. Do not release ovstage's reference on a list that came from a read result (it is a borrow), and do not release an unknown or already-freed handle — those calls return `OVX_API_ERROR`. - String pointers from the dictionary are borrowed; copy before the dictionary is destroyed. - For passing a name as either a token or a raw string, see `ovx_string_or_token_t` in the `string-handling` skill (set `token` for hot paths, else `string`). ## References - Use the `> **Source:**` directives in this skill to locate tested snippets before reusing API patterns. - `string-handling` skill — `ovx_string_t` / `ovx_string_or_token_t` dual-mode usage. - `error-handling` skill — ovstage-level error handling (distinct `ovstage_api_status_t` codes). - Keep related skills, docs, and snippets synchronized when changing the workflow.
View on GitHub