Live Layer Data
OmniUsdLiveData implements SdfAbstractData which is how USD reads/writes layer data.
The object data contains 2 copies of the tree:
Authoring a structural change
When USD calls either CreateSpec, MoveSpec, or EraseSpec we consider this to be a structural modification.
First we apply the modification as directed to the “current tree”
Next we append structural command to the currently pending “delta buffer”
The delta buffer is sent during the next SendDeltas (which is called when the application calls one of the Processing Live Updates functions).
Once the server acknowledges the update, we apply the same delta to the “base tree”
Authoring a field change
When USD calls one of the functions to set a field or a timesample, that is not considered a structural modification.
First we store the modified field in a map of modified fields.
Next we store the modified field value in the “current tree”.
We set the “save count” on the field to indicate when we have modified this field. This save count is conceptually similar to the sequence number. All fields modified during the same “frame” will have the same “save count”, which is incremented every time we send a delta. We will not apply remote updates to fields which have a “save count” higher than our “most recently acknowledged update”.
During the next SendDeltas we collect all the modified fields and add them to the pending “delta buffer”
Once the server acknowledges our change, we set our “most recently acknowledged update” value to this update, so the fields may be updated by remote clients again.
Field values are only stored in memory once. The map of modified fields, as well as “current tree” all just point to the same underlying data.
We don’t build the delta buffer while setting fields because a field may be changed mulitple times prior to calling SendDeltas and we only want to store the most recent value in the delta buffer.
Authoring child list changes
When USD modifies one of the special “children list” fields, the process is different from other field updates.
We set a flag on the spec indicating that it has a changed children list.
We set a flag on the entire object indicating that there is a changed children list.
During the next SendDeltas we iterate through all the specs with changed children lists and create “ReorderChildren” commands for them.
We don’t generate the reorder structural commands at the time the field is set because a children list may be modified many times per frame. We only want to generate a single “ReorderChildren” command, regardless of how many times it was modified. For example, adding 4 children in one frame will modify the field 4 times.
Receiving updates
Apply the update to the “base tree”
Save the “current tree” as “old tree”
Reset the “current tree” to “base tree”
Re-apply any unacknowledged structural changes to “current tree”
Diff the “current tree” against the “old tree” to generate notices.
Apply reorder commands to child list fields and generate notices.
Apply field & timesample updates and generate notices. Remember, we ignore field & timesample changes if we have an unacknowledged change to the same field.
Steps 1-6 are skipped if the update doesn’t contain any structural changes.
We can receive updates out of order, so we store received updates in a queue and only process them in sequence. There should be no gaps, so if our current sequence number is 12, we will not process anything until we receive sequence number 13.
Receiving updates prior to “OkLatest” also skips a lot of steps, because we know that there cannot be any local changes, and we also don’t generate notices because USD doesn’t consider the layer to have been loaded yet.
Flood Control
If there are more than 16 unacknowledged updates, we will stop sending any more updates until we have less than 8 unacknowledged updates. This is to prevent flooding the server with updates faster than it can process them.