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.

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 = renderer.open_usd_async(TEST_BASE_PATH)
# TODO: Restore the assertion once the packaged open_usd_async wrapper returns _VOID_RESULT.
op.wait()
assert "/World/Plane" in renderer.query_prims(attribute_filter_mode=ovrtx.AttributeFilterMode.NONE)
# 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,
)
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.

// Load a USD layer into 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";

std::cerr << "Adding " << usd_url << " at root..." << std::endl;
ovrtx_enqueue_result_t enqueue_result =
    ovrtx_open_usd_from_file(renderer, {usd_url, strlen(usd_url)});

// This operation is asynchronous as loading the USD may take a long time.
// We'll just poll every 100ms till it's done.
ovrtx_op_wait_result_t wait_result;
result = ovrtx_wait_op(
    renderer, enqueue_result.op_index, ovrtx_timeout_t{0}, &wait_result);
if (check_and_print_error(result, "wait_op")) {
    ovrtx_destroy_renderer(renderer);
    return 1;
}
while (ovrtx_wait_op(renderer, enqueue_result.op_index, ovrtx_timeout_t{0},
                    &wait_result).status == OVRTX_API_TIMEOUT) {
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
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(OVRTX_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(OVRTX_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(OVRTX_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.