Async Operations, Status, and Errors#
ovrtx enqueue operations are internally asynchronous and stream-ordered. Python
offers blocking convenience methods plus *_async variants. C enqueue
functions return operation ids that must be waited before fetching results or
assuming side effects are visible.
In attached mode, wait for scene mutations through ovstage’s operation APIs. Accepted enqueues can still fail ordinal admission during execution. Check mutation completion, then advance and wait for the ovstage write floor before rendering that ordinal. Ordering async operations does not bypass the configured admission policy; see Ordinals and Write-Floor Gates.
Python Operations#
Operation.wait(timeout_ns=0) polls. Operation.wait() blocks indefinitely.
Operations that produce data use a two-phase lifecycle: wait returns a pending
fetch object, then fetch() retrieves the result.
op = ovstage.population.open_usd_async(stage, TEST_BASE_PATH, ordinal=1)
op.wait()
stage.advance_write_floor(1, ovstage.Scope.ALL).wait()
# step_async() returns an Operation. wait() resolves to a PendingFetch,
# whose fetch() produces the RenderProductSetOutputs that step() would
# have returned synchronously.
op = renderer.step_async(
render_products={"/Render/Camera"},
delta_time=1.0 / 60,
ordinal=ordinal,
)
pending = op.wait()
products = pending.fetch()
C Waits#
In C, check the immediate enqueue status first, then wait on op_index. Some
errors, such as a missing USD file during load, are reported only when the
operation is waited.
// Populate an ovstage instance from USD, then attach it to the renderer.
//
// As well as just passing a URI to an existing layer, we could pass a USDA
// string in order to compose a Stage at runtime. This can be very useful
// for dynamically creating the RenderProducts etc. that define the render
// output rather than editing the original layer to add them.
//
// A real application might want to load the USD layer and traverse it to
// find either existing RenderProducts, and/or Cameras and allow the user to
// select which one to render, and which RenderVars to output.
char const* usd_url = "https://omniverse-content-production.s3.us-west-2.amazonaws.com/Samples/Robot-OVRTX/robot-ovrtx.usda";
ovstage_config_t stage_config {};
#ifdef OVRTX_EXAMPLE_STATIC_LOADERS
// The static loader needs the package root linked beside the executable.
ovx_string_t ovstage_package_root = {
OVX_CONFIG_EXECUTABLE_DIR_TOKEN "/ovstage",
sizeof(OVX_CONFIG_EXECUTABLE_DIR_TOKEN "/ovstage") - 1};
ovstage_config_entry_t stage_config_entries[] = {
ovstage_config_entry_binary_package_root_path(ovstage_package_root),
};
stage_config.entries = stage_config_entries;
stage_config.entry_count = sizeof(stage_config_entries) / sizeof(stage_config_entries[0]);
#endif
ovstage_api_status_t stage_init_status = ovstage_initialize(&stage_config);
if (stage_init_status != OVSTAGE_OK) {
print_ovstage_error(nullptr, stage_init_status, "initialize");
return cleanup(1);
}
ovstage_instance_desc_t stage_desc {};
stage_desc.name = "minimal";
ovstage_api_status_t stage_result = ovstage_create_instance(&stage_desc, &stage);
if (stage_result != OVSTAGE_OK) {
print_ovstage_error(stage, stage_result, "create_instance");
return cleanup(1);
}
result = ovrtx_attach_ovstage(renderer, stage);
if (check_and_print_error(result, "attach_ovstage")) {
return cleanup(1);
}
stage_attached = true;
std::cerr << "Adding " << usd_url << " at root..." << std::endl;
ovstage_population_enqueue_result_t populate_result =
ovstage_population_open_usd_from_file(stage,
{usd_url, strlen(usd_url)},
stage_ordinal,
/* time = */ 0.0,
OVSTAGE_POPULATION_DOMAIN_RENDERING);
if (wait_population_op(stage, populate_result, "population_open_usd_from_file") ||
commit_ovstage_ordinal(stage, stage_ordinal)) {
return cleanup(1);
}
std::cerr << "USD loaded." << std::endl;
Status Queries#
Status queries take a point-in-time snapshot of a pending operation. They are useful for progress bars and logs while waiting for long-running work such as USD loading, shader compilation, stage queries, or render steps.
def wait_with_status(op: ovrtx.Operation, label: str):
result = op.wait(timeout_ns=1_000_000_000)
while result is None:
print_operation_status(label, op.query_status())
result = op.wait(timeout_ns=1_000_000_000)
return result
static bool wait_with_status(ovrtx_renderer_t* renderer,
ovrtx_op_id_t op_id,
std::string_view label) {
ovrtx_op_wait_result_t wait_result {};
ovrtx_result_t result =
ovrtx_wait_op(renderer, op_id, ovrtx_timeout_t {1000000000}, &wait_result);
while (result.status == OVRTX_API_TIMEOUT) {
if (print_operation_status(renderer, op_id, label)) {
return true;
}
result = ovrtx_wait_op(renderer, op_id, ovrtx_timeout_t {1000000000}, &wait_result);
}
if (check_and_print_error(result, "wait_op")) {
return true;
}
return print_wait_errors(wait_result);
}
Python Operation.query_status() is available only before wait() consumes
the operation context. In C, each successful ovrtx_query_op_status()
call must be paired with ovrtx_release_op_status().
Error Handling#
Python methods raise RuntimeError on API failures or async operation
failures. In C, every API result must be checked. Error strings returned by
ovrtx_get_last_error() are valid only until the next API call on the
same thread.
The minimal C example uses a helper that works with both synchronous and enqueue results:
template <typename ResultT>
static bool check_and_print_error(ResultT const& result,
std::string_view operation) {
if (result.status == OVRTX_API_ERROR) {
ovx_string_t error = ovrtx_get_last_error();
if (error.ptr && error.length > 0) {
std::cerr << "ovrtx " << operation << " failed: "
<< std::string_view(error.ptr, error.length) << std::endl;
} else {
std::cerr << "ovrtx " << operation << " failed" << std::endl;
}
return true;
}
return false;
}
C Log Callback#
The C API can install a process-global log callback. Use it for application logging, CI diagnostics, or routing ovrtx messages into another logging system.
// First pass: receive all messages (NULL channel filter).
g_message_count.store(0);
ovrtx_result_t r = ovrtx_set_log_callback(OVX_LOG_INFO,
nullptr, // NULL = all channels
&count_messages,
&g_message_count);
ASSERT_API_SUCCESS(r.status);
// Reset first so the renderer is in a clean state.
ovrtx_enqueue_result_t eq = ovrtx_reset_stage(renderer_);
ASSERT_API_SUCCESS(eq.status);
ovrtx_op_wait_result_t wait_result;
ASSERT_API_SUCCESS(ovrtx_wait_op(renderer_, eq.op_index, ovrtx_timeout_infinite, &wait_result).status);
do_open_usd();
// Ensure pending log messages have been delivered through the callback.
ovrtx_timeout_t flush_timeout{5'000'000'000ull};
ovrtx_flush_log(flush_timeout);
int observed_any_channel = g_message_count.load();
// Second pass: install a high default threshold and an explicit low
// threshold for a channel prefix that cannot match any real channel.
g_message_count.store(0);
std::string bogus = "this.channel.does.not.exist.42=info";
ovx_string_t filter{bogus.c_str(), bogus.size()};
r = ovrtx_set_log_callback(OVX_LOG_FATAL, &filter, &count_messages, &g_message_count);
ASSERT_API_SUCCESS(r.status);
// Re-run the same work and flush — the callback should not fire.
eq = ovrtx_reset_stage(renderer_);
ASSERT_API_SUCCESS(eq.status);
ASSERT_API_SUCCESS(ovrtx_wait_op(renderer_, eq.op_index, ovrtx_timeout_infinite, &wait_result).status);
do_open_usd();
ovrtx_flush_log(flush_timeout);
int observed_bogus_filter = g_message_count.load();
// Disable the callback before the renderer is torn down.
ovrtx_set_log_callback(OVX_LOG_INFO, nullptr, nullptr, nullptr);
The channel filter is a comma-separated list of channel_prefix=level rules.
Accepted levels are verbose/debug, info, warn/warning,
error, and fatal. The longest matching channel prefix wins.