CommandQueue is an asynchronous, thread-safe alternative to the direct
File / Artboard / StateMachineInstance API documented elsewhere. Your
application thread enqueues commands (load a file, advance a state machine,
forward a pointer event) and a CommandServer worker drains them on the
render thread.
Use it when:
- Your app and render threads are separate, and you don’t want to cross-thread-call into the direct API.
- You want a Rive-content thread that’s decoupled from the GPU thread.
- You need a stable handle-based identity for
File/Artboard/StateMachineInstanceobjects across thread boundaries.
CommandQueue adds indirection and listener
plumbing you don’t need.
Architecture

CommandQueueis reference-counted (rcp<CommandQueue>) and thread-safe. The app thread keeps one; the server holds the other end.CommandServerowns the realFile,ArtboardInstance, andStateMachineInstanceobjects. They never escape the render thread.- All cross-thread identifiers are typed handles:
FileHandle,ArtboardHandle,StateMachineHandle,ViewModelInstanceHandle,RenderImageHandle,FontHandle,AudioSourceHandle. The app thread holds handles; the server resolves them to real objects. - Async results (file loaded, state machine settled, image decoded) come back via listener callbacks registered against handles.
Setting Up the Queue and Server
CommandServer and pump it:
processCommands() is non-blocking — call it before each frame.
serveUntilDisconnect() is the run-loop variant for a worker thread:
blocks waiting for commands, and returns when the app thread calls
queue->disconnect().
Loading a File and Creating a State Machine
All these calls happen on the app thread. They enqueue commands and return handles immediately — the actual work happens on the server thread.Advancing and Drawing
CommandQueue::drawCallback and runDraw methods in
command_queue.hpp
for the full draw-callback API; the canonical example is the
command-queue D3D11 sample in the rive monorepo
(packages/sample_win32_d3d11_cq/).
Async Results: Listeners
Because commands run asynchronously, results come back via listener callbacks. Each handle type has a matching listener withon… methods:
FileListener—onFileLoaded,onFileDeleted,onArtboardsListed,onViewModelsListed, …ArtboardListener—onArtboardInstanced,onArtboardError,onArtboardDeleted.StateMachineListener—onStateMachineInstanced,onStateMachineSettled(called on the request that caused the state machine to settle), …ViewModelInstanceListener,RenderImageListener,FontListener,AudioSourceListener— for the other resource types.
requestId lets you correlate callbacks with specific commands.
Pass a non-zero requestId to the queue methods that accept one (e.g.
deleteFile(handle, requestId)) and your listener will receive the same
ID back.
Pointer Events
Pointer events go through the queue too.CommandQueue::PointerEvent
carries the screen-space position; the server converts it to artboard
space using the state machine’s most recent transform:
Teardown
The app thread tells the server to stop:serveUntilDisconnect(), that call returns
when the disconnect command arrives. After that, joining the server
thread is your responsibility.
Resource handles are cleaned up by enqueuing explicit delete commands
(or letting the server destructor reap them):
When to Use the Direct API Instead
The directFile / Artboard / StateMachineInstance API documented in
the other pages on this site is simpler when:
- Your app already drives Rive from the render thread.
- You don’t need handle-based identity across threads.
- You want synchronous return values rather than listener callbacks.
CommandQueue exists for the multithreaded case — it’s the right tool
for engines, render servers, and apps with a dedicated GPU thread.