Version

Scene and its various SceneSystem implement muti-threading using a lock-free model with a single synchronization point per frame to allow maximum scalability even with a large number of HW thread available.

While not mandatory, a minimal understanding of the model concepts is recommended as it has implications that might seem unclear otherwise.

Scene update

The scene update is split in two phases: Update and Commit. The Update phase is read-only while the Commit phase is read-write. Every write to the scene or any of its Component or Node during the Update phase is queued for execution in the Commit phase.

The following sequence performs a complete update of the scene and its systems.

scene.Update() # start the Update phase
while not scene.WaitUpdate():  # wait for the Update phase to end
    pass  # note that the caller thread (interpreter) is not blocked

scene.Commit() # start the Commit phase
scene.WaitCommit()  # wait for the Commit phase to end
  • Update starts the update of all scene systems which in turn spawn as many tasks as necessary to complete their update.
  • WaitUpdate waits for all system update to complete. This function defaults to a non-blocking wait and returns False as long as the scene update is not complete.
  • Commit execute all queued system writes then all scene writes.
  • WaitCommit waits for all system commit to complete. This function defaults to a blocking wait and returns False as long as the scene commit is not complete.

Note: The complete scene should not be accessed in any ways during the commit phase. Since this issue is entirely avoided with a blocking WaitCommit it is the default.

The update phase is by far the slowest of the two phases, often 10x to 100x slower than the commit phase. The commit phase is usually very fast even with a large number of writes to perform.

Warning about non-blocking wait commit/update

In some cases, it could be necessary to execute a non-blocking WaitCommit or WaitUpdate. You have to make sure the wait function returns true before proceeding.

Example with WaitCommit():

scene.Commit()
while not scene.WaitCommit(False):
    HeavyDutyComputations()
scene.Update()

In such a loop, HARFANG3D must be considered locked.

Failing to wait for WaitUpdate/WaitCommit to complete execution will lead to undefined behavior and will most likely result in a low-level crash.

See the WaitUpdate and WaitCommit documentation.

Threading model consequences

In the following example my_node_pos will contain the previous node position until the next commit completes.

-- from a Lua script component
my_node.transform:SetPosition(hg.Vector3(0, 10, 0))
my_node_pos = my_node.transform:GetPosition() -- my_node_pos is hg.Vector3(0, 0, 0)!

In the next example node will not appear in the scene until the next commit completes.

-- from a Lua script component
old_node_count = #scene:GetNodes()
scene.AddNode(hg.Node())
new_node_count = #scene:GetNodes() -- new_node_count equals old_node_count!

Basically any operation that requires writing to the scene state will have its effects observable only on the next Update.

While these examples might seem difficult to deal with at first sight they really are not in practice. The engine hides all the complexity from you and provides the tools needed to efficiently work asynchronously.

Last but not least, this model guarantees value coherency over a single update. This means that two reads from the same property will always return the same result over a single update.