Client Library Live Functions

The Omniverse USD Resolver uses the Omniverse Client library to perform all communication with Nucleus.

The client library functions which deal with live layers are described below.

Understanding Object IDs

An ObjectId is a unique identifier for a live layer.

A URL does not uniquely identify a live layer. A live layer could be deleted, then re-created at the same URL. Attempting to perform any delta operations on the new layer would be a disaster. We use the object id to detect this case.

Additionally, a live layer could map to more than one URL. When moving/renaming a live layer, the object id remains the same. We could use this for better caching (though at the moment we do not).

Understanding Sequence Numbers

As described in Receiving Updates Out of Order, the server may send updates out-of-order. Therefore, the server includes a sequence number with each result it provides to the client. We use this to ensure we apply updates in the correct order.

We also use sequence numbers to tell the server which ‘version’ of a live layer we already have. The server will build a custom delta update which brings us from that version to the latest version.

Creating a live layer

OmniClientRequestId omniClientLiveCreate(char const *url, struct OmniClientContent *content, void *userData, OmniClientLiveCreateCallback callback);
  • url: The full URL of the live layer to create.

  • content: A buffer in the format described by Live Layer Wire Format.

  • userData: Arbitrary data which is passed to the callback.

  • callback: Called with the results.

typedef void (*OmniClientLiveCreateCallback)(void *userData, OmniClientResult result, uint64_t objectId, uint64_t sequenceNum);
  • result: eOmniClientResult_Ok if sucessful, or any other error code if not.

  • objectId: A unique id for this object on this server.

  • sequenceNum: The starting sequence number assigned to your create command. This is normally 1 beacuse it is the very first update.

Reading a live layer

OmniClientRequestId omniClientLiveRead(char const* url, uint64_t haveObjectId, uint64_t haveSequenceNum, void* userData, OmniClientLiveReadCallback callback);
  • url: The full URL of the live layer to read.

  • haveObjectId & haveSequenceNum: If you already have some version of this live layer, you can provide the object id and sequence number that you already have. The server will attempt to build a delta that brings you up to date. If you do not have any version of this live layer, you set both to 0. See also Understanding Sequence Numbers

  • userData: Arbitrary data which is passed to the callback.

  • callback: Called with the results.

typedef void (*OmniClientLiveReadCallback)(void* userData, OmniClientResult result, uint64_t objectId, uint64_t sequenceNum, struct OmniClientContent* content);
  • result:

    • eOmniClientResult_Ok if this is an update with content.

    • eOmniClientResult_OkLatest to indicate the server has sent everything it currently has. You will only ever receive a single OkLatest but you may receive zero or more Ok messages before and after OkLatest.

    • Any other error code indicates an error has occured and your callback will not be called any more.

  • objectId: A unique id for this object on this server. This will never change during a single read request. If the object is deleted and re-created, you will receive an eOmniClientResult_ErrorAccessLost result (and you must issue a new read request).

  • sequenceNum: The sequence number for this update.

Updating a live layer

OmniClientRequestId omniClientLiveUpdate(char const* url, uint64_t objectId, struct OmniClientContent* content, void* userData, OmniClientLiveUpdateCallback callback);
  • url: The full URL of the live layer to update.

  • objectId: Object Id of the layer you are attempting to update.

  • content: A buffer in the format described by Live Layer Wire Format.

  • userData: Arbitrary data which is passed to the callback.

  • callback: Called with the results.

typedef void (*OmniClientLiveUpdateCallback)(void* userData, OmniClientResult result, uint64_t sequenceNum, uint64_t requestId);
  • result: eOmniClientResult_Ok if sucessful, or any other error code if not.

  • sequenceNum: The sequence number for this update.

Processing Live Updates

void omniClientLiveProcess();
void omniClientLiveWaitForPendingUpdates();

These functions will send any pending delta updatess and apply any received delta updates.

These must be called when no other threads are accessing USD. Processing an update could result in modifying the structure of the layer, and will lead to a crash if another thread is simultaneously traversing the layer. Similarly, when building a delta update, we may be trying to read a value that another thread is deleting.

omniClientLiveWaitForPendingUpdates calls omniClientLiveProcess in a loop until there are no more pending updates. This is intended to be called at shutdown to ensure that all pending delta updates have been sent.

void (*OmniClientLiveProcessUpdatesCallback)(void* userData);
uint32_t omniClientLiveRegisterProcessUpdatesCallback(void* userData, OmniClientLiveProcessUpdatesCallback callback);

The application itself is responsible for calling the process functions. The client library notifies the USD resolver that the application has called one of the process functions through a callback registered with this function. The USD resolver will then call SendDeltas on each OmniUsdObjectData.

void (* OmniClientLiveQueuedCallback)();
void omniClientLiveSetQueuedCallback(OmniClientLiveQueuedCallback callback);

This allows the application to register a callback to be notified any time a live update is received. This can be used by event-driven applications to know when they should call omniClientLiveProcess. Frame-based applications should just call omniClientLiveProcess every frame.

uint64_t omniClientLiveGetLatestServerTime(const char* url);
void omniClientLiveProcessUpTo(const char* url, uint64_t serverTime);

These can be used to syncronize machines in multi-node rendering. One machine calls omniClientLiveGetLatestServerTime then all machines call omniClientLiveProcessUpTo to process updates received up to that time. This ensures that all machines have the same set of updates, and are therefore rendering the same version scene.

Jitter Reduction

void omniClientLiveConfigureJitterReduction(uint32_t delayConstantMilliseconds, uint32_t delayMultiple, uint32_t delayMaximumMilliseconds);

Jitter is a variance in latency. For example if your ping times are variable from 100ms up to 250ms, then your received updates will not be smooth.

Jitter reduction attempts to reduce jitter by holding incoming updates in a queue and releasing them at a consistent rate. The amount of delay is a multiple of your average ping time, plus some constant, up to a maximum amount.

The formula is: delay = sentTime + min(delayMaximum, delayConstant + delayMultiple * averageLatency) - receivedTime

  • sentTime is the (actual) server time when the update was sent

  • receivedTime is the (estimated) server time when the update was received

  • averageLatency is the average amount of latency measured from periodic pings

  • delay is the amount of time to wait before processing the update

The default values are 10ms of constant delay, 2x average ping time, and 1 second maximum. Pass (0,0,0) to completely disable jitter reduction.