Scene Graph introduced multi-threaded operations to Roku application programming. There are three basic threads available to a Scene Graph application programmer.
- Main BrightScript Thread
This is the thread that is launched for all Roku applications from the
main.brsfile. For Scene Graph applications, the thread is used primarily to create the scene component object, which starts the Scene Graph render thread. For other applications, this is the only thread for the entire application.
- Scene Graph Render Thread
The render thread is the main Scene Graph thread that performs all rendering of the application visual elements. Certain BrightScript operations and components that might block or modify the scene graph in the render thread cannot be used in this thread. Operations and components that might block the render thread can be used in a Task node thread. The thread usage of these operations and components is listed in Scene Graph BrightScript Support.
- Task Node Threads
By creating and running a Task node, you can launch asynchronous BrightScript threads. These threads can perform most typical BrightScript operations.
All Scene Graph node objects have a thread owner, which by default, is the render thread. Only components that extend the Node node class and the ContentNode node class can be owned by a Task node or main BrightScript thread when created by those threads. When a node object is stored in a field, the ownership of the node is changed to the node object that contains the field. When a node object is added as a child of another node object, the ownership is changed to the parent node object.
Operations on node objects are executed on their owning thread. If invoked by another thread, the invoking thread must rendezvous with the owning thread to execute the operation.
In a thread rendezvous, the invoking thread makes a request of the owning thread, and then waits for a response. The owning thread receives the request, processes it, and then responds. The invoking thread then continues with the response as if it had made the call itself. The response appears synchronous to the invoking thread.
Only the render thread may serve a rendezvous. Since Task node threads do not have an implicit event loop (though they may have an explicit event loop), they cannot serve a rendezvous. No node object owned by a Task node thread is accessible outside that thread. But the Task node itself is owned by the render thread, so the Task node and its fields can only be accessed by rendezvous from other threads, even from the thread launched by the Task node itself.
The entire interface to a node object, including field creation, setting, and getting, uses this rendezvous mechanism to ensure thread safety, without having to use explicit locks in the application, and without the possibility of deadlock. The rendezvous mechanism does add more overhead than simple field getting and setting, so Scene Graph application programmers should use it carefully, taking into account the following concerning Task node threads.
BrightScript Operations Without Scene Graph Node Objects
If a BrightScript operation does not involve a Scene Graph node object, such as reading a URL, or parsing XML, the rendezvous mechanism is not used. These types of operations can be used in a Task node thread without the additional overhead of the rendezvous mechanism.
Task Node Thread Fully-Owned Node Objects
Task node threads can create node and ContentNode node objects that are fully owned by the Task node thread. These node objects cannot be parented to unowned nodes, or be set as fields of unowned nodes. Entire trees of the nodes fully owned by the Task node thread may be created.
Renderable Node Objects
You should not create renderable node objects, or groups of node objects, in a Task node. The rendezvous mechanism will be required to create and operate on those node objects, with every field set or get operation requiring a full rendezvous, which will dramatically slow down the performance of your application.
Excessive Rendezvous Operations
You should avoid as many rendezvous operations as possible to ensure maximum performance of your application. It is better to build an entire tree of nodes or ContentNode nodes, then pass the tree to the render thread using one rendezvous, than to repeatedly pass each node in the tree as it is created. For field setting and getting, methods such as
setFields(), which set and get multiple fields at once, should be used rather than several get and set operations.
Task Node Thread Rendezvous Timeout
A Task node thread rendezvous can time out after a few seconds. This can happen if the render thread is very busy, maybe with other rendezvous operations. If a timeout occurs while getting a field, the response will be invalid, which may crash the application if the script is not prepared for the invalid response. During application development, you should check for these timeouts to increase the performance of your published application.
Also note that the render thread can time out because of long linear searches, long iterative calculations, synchronous file accesses, and the like. These types of operations not only slow down the rendering of the UI, but can lead to a rendezvous timeout generating an invalid response, possibly crashing the application.
Task Node Objects Ownership
Since Task node objects are owned by the render thread, setting Task node object fields is done on the render thread, and all observer callbacks on the fields are executed in the render thread. The only case where observer callbacks are executed in a Task node object is if the observed field is in a node object owned by the Task node thread.
Component Global Associative Array
All components have a global associative array with an
m object reference designator, including Task node objects. This associative array is local to the component but global within it. For Task node objects, this associative array is not shared between threads. The Task node global object reference designator is initially owned by the render thread. The Task node
init() function then populates the Task node object with references in the associative array. On every setting of the Task node control field to
RUN, a new thread is launched, and the Task node object associative array is cloned, with the launched thread receiving the original object associative array, and the render thread receiving the clone. Only basic object types are cloned: integers, Booleans, strings, floats, nodes, and recursively, arrays and associative arrays. These are the same object types that are copied through the fields. Because of this cloning mechanism, some object references, such as a message port created in
init(), are only passed to the first thread launched from a Task node, and not to subsequent threads launched by the same Task node.