Command Queue
CommandQueue 是直接 File / Artboard / StateMachineInstance API(在其他文档中介绍)的异步、线程安全替代方案。你的应用线程会将命令入队(加载文件、推进状态机、转发指针事件),然后由 CommandServer 工作线程在渲染线程上取出并执行这些命令。
适用场景:
- 你的应用线程和渲染线程是分离的,并且你不想跨线程调用直接 API。
- 你想要一个与 GPU 线程解耦的 Rive 内容线程。
- 你需要为
File/Artboard/StateMachineInstance对象提供跨线程边界的、稳定的基于句柄的标识。
如果你的渲染循环和应用逻辑已经在同一个线程上,请优先使用直接 API —— CommandQueue 会引入你并不需要的间接层和监听器机制。
架构

CommandQueue是引用计数的(rcp<CommandQueue>),并且是线程安全的。应用线程持有一个引用;服务器持有另一端。CommandServer拥有真实的File、ArtboardInstance和StateMachineInstance对象。它们永远不会离开渲染线程。- 所有跨线程标识符都是带类型的句柄:
FileHandle、ArtboardHandle、StateMachineHandle、ViewModelInstanceHandle、RenderImageHandle、FontHandle、AudioSourceHandle。应用线程持有句柄;服务器将它们解析为真实对象。 - 异步结果(文件已加载、状态机已稳定、图像已解码)会通过针对句柄注册的监听器回调返回。
设置 Queue 和 Server
#include "rive/command_queue.hpp"
#include "rive/command_server.hpp"
// Shared between threads.
rcp<CommandQueue> queue = make_rcp<CommandQueue>();
在渲染线程上创建 CommandServer 并驱动它:
// `factory` is your Factory* — usually your RenderContext.
CommandServer server(queue, factory);
// Drain pending commands once per frame...
server.processCommands();
// ...or block on a dedicated thread until disconnect.
// server.serveUntilDisconnect();
processCommands() 是非阻塞的 —— 在每一帧之前调用它。serveUntilDisconnect() 是用于工作线程的运行循环变体:它会阻塞等待命令,并在应用线程调用 queue->disconnect() 后返回。
加载文件并创建状态机
以下所有调用都发生在应用线程上。它们会将命令入队并立即返回句柄 —— 实际工作会在服务器线程上执行。
std::vector<uint8_t> rivBytes = readFile("hero.riv");
FileHandle file = queue->loadFile(std::move(rivBytes));
ArtboardHandle artboard = queue->instantiateDefaultArtboard(file);
StateMachineHandle sm = queue->instantiateDefaultStateMachine(artboard);
这些句柄可以立即使用,即使加载尚未实际发生 —— 后续针对这些句柄入队的命令会按顺序在服务器上执行。
推进和绘制
// App thread:
queue->advanceStateMachine(sm, /* dt = */ 1.f / 60.f);
绘制的处理方式不同 —— 你需要注册一个绘制回调,该回调会在队列要求服务器执行时运行在服务器线程上。模式是:创建一个绘制键,附加一个回调,然后告诉服务器运行该键。
关于完整的绘制回调 API,请参阅 CommandQueue::drawCallback 和 runDraw 方法(位于 command_queue.hpp);规范示例是 rive monorepo 中的 command-queue D3D11 示例(packages/sample_win32_d3d11_cq/)。
异步结果:监听器
由于命令是异步运行的,结果会通过监听器回调返回。每种句柄类型都有一个对应的监听器,其中包含 on… 方法:
class MyFileListener : public CommandQueue::FileListener
{
public:
void onFileLoaded(const FileHandle, uint64_t requestId) override
{
// Safe to instantiate artboards now, etc.
}
};
MyFileListener listener;
listener.attach(queue, file); // register against this handle
可用的监听器包括:
FileListener—onFileLoaded、onFileDeleted、onArtboardsListed、onViewModelsListed,等等。ArtboardListener—onArtboardInstanced、onArtboardError、onArtboardDeleted。StateMachineListener—onStateMachineInstanced、onStateMachineSettled(会在 导致状态机稳定的请求上调用),等等。ViewModelInstanceListener、RenderImageListener、FontListener、AudioSourceListener— 用于其他资源类型。
requestId 可让你将回调与特定命令关联起来。向接受 requestId 的队列方法传入非零值(例如 deleteFile(handle, requestId)),监听器就会收到相同的 ID。
指针事件
指针事件也通过队列传递。CommandQueue::PointerEvent 携带屏幕空间位置;服务器会使用状态机最近一次的变换将其转换为 artboard 空间:
CommandQueue::PointerEvent ev;
ev.position = { mouseX, mouseY };
ev.kind = CommandQueue::PointerEvent::Kind::move;
queue->queuePointerEvent(sm, ev);
清理
应用线程通知服务器停止:
queue->disconnect();
如果服务器正在运行 serveUntilDisconnect(),当 disconnect 命令到达时该调用会返回。之后,你需要自行负责 join 服务器线程。
资源句柄可以通过显式入队删除命令来清理(或让服务器析构函数回收):
queue->deleteStateMachine(sm);
queue->deleteArtboard(artboard);
queue->deleteFile(file);
何时改用直接 API
当满足以下情况时,其他页面中介绍的直接 File / Artboard / StateMachineInstance API 更简单:
- 你的应用已经从渲染线程驱动 Rive。
- 你不需要跨线程的基于句柄的标识。
- 你想要同步返回值,而不是监听器回调。
CommandQueue 是为多线程场景而存在的 —— 对于引擎、渲染服务器以及拥有专用 GPU 线程的应用来说,它是合适的工具。