Files
oak-editor/worker/rust/README.md
T
Mike-Solar e563b340ac feat: oakengine facade, oaknode/oakrender impls, worker+CLI, app skeleton
- oaknode Rust crate: full implementation (core engine, sequence/
  track/block/footage, traverser, serializer, 43 node behaviors;
  493 tests green)
- oakrender Rust crate: full implementation incl. wgpu backend
  skeleton, ticket arena, worker pool (136 tests green; fixed
  lost-wakeup and ticket ordering races)
- src/facade/rust (oakfacade): 222 oakengine_* exports over the
  module C ABIs (61 tests green); worker_main + real POSIX shm
  frame-slot transport (SpscRingBuffer/FrameSlotPool, wire-compatible
  with engine/render/ipc)
- cli/rust + worker/rust binaries (29 + 29 tests green)
- oakotio: FCPXML import/export (49 tests green)
- oaktask: OTIO/FCPXML format dispatch (90 tests green)
- app/rust: gpui app skeleton — dock panels (viewers/timeline/
  explorer/inspector/node editor), transport, olive themes,
  i18n (en/zh), 37 tests green
- gpui submodule: menu checkmarks, dock ratios, vertical meter,
  CPU-frame viewer surface, drop-frame timecode
2026-08-10 08:12:08 +08:00

4.8 KiB

oak-worker (Rust)

Headless render worker process — the Rust rewrite of worker/workermain.cpp (contract: engine/include/oakengine/worker.h and engine/include/oakengine/ipc.h).

Build and test

cargo build --release      # binary: target/release/oak-worker
cargo test                 # unit + integration tests (29 tests)

The worker is a thin shell over the facade, exactly like the C++ worker/workermain.cpp is a thin shell over liboakengine:

  • oakfacade::worker::worker_main (the port of engine/src/capi/worker.cpp oakengine_worker_main()) owns the whole runtime: render backend selection through the oakrender module C ABI (dynamic → OpenGL fallback), the startup handshake and the NDJSON control loop. src/main.rs only parses --backend (clap) and forwards.
  • oakfacade::ipc owns the shared-memory frame-slot transport (the real SpscRingBuffer + FrameSlotPool over POSIX shm_open/mmap); src/transport.rs attaches through it.

The oakrender module crate (../../src/render/rust) is linked so the facade's renderer imports resolve; oakrender depends on ocio-rs with the bundled feature, whose first-time build fetches a vendored OpenColorIO dependency (sse2neon) from github.com. On networks without github access, build with a shared target directory that already contains a completed oakrender build tree, e.g.:

CARGO_TARGET_DIR=/path/to/oak/src/render/rust/target cargo build --release

What the worker does

Same flow as the C++ main, in the same order:

  1. parse --backend <name> (clap; default opengl; none skips renderer creation and the process exits 1, like the C++ main).
  2. initialize the render backend (inside oakfacade::worker): the oakrender module C ABI oakrender_display_renderer_create_dynamic + _init, falling back to the direct OpenGL renderer exactly like the C++ create_renderer() fallback chain.
  3. write the startup handshake (protocol version 1, empty shared-memory geometry — same as the C++ worker's startup handshake; the parent creates the segments and announces their geometry in its reply).
  4. serve the NDJSON control loop on stdin/stdout until a shutdown message or EOF: handshake attaches the announced shared-memory frame-slot pools through the real transport; load_graph / render_frame / cancel / shutdown are dispatched by the facade session. Responses are one compact JSON line per message.

Implemented vs stubbed (nothing is faked)

Real: argument parsing, render backend initialization (real wgpu renderer, dynamic → OpenGL fallback), startup handshake, NDJSON framing, message validation (protocol version, handshake geometry, load_graph file existence/size — the same messages the C++ worker emits), the shared-memory frame-slot transport (oakfacade::ipc — POSIX shm_open/mmap/munmap/shm_unlink, the SPSC ring buffer and the frame-slot pool with the exact version-1 shared layout; a handshake genuinely attaches the output and input pools), unknown-type/ malformed-message errors, shutdown/EOF termination.

Stubbed (documented in src/transport.rs):

area reason
node-graph deserialization (load_graph beyond the file checks) the oaknode crate is a todo!() skeleton
frame rendering (render_frame) no graph/render-pipeline backing (the shm frame-slot transport is attached, but there is no graph to render)

Stubbed requests answer with a clear {"type":"error","message":…} that names the missing piece (a render_frame error also carries the ticket, mirroring the C++ error_message() shape). A real load_graph on a non-existent/empty file produces the C++-identical error before reaching the stub.

Deviation from the C++: the startup handshake omits gl_major/ gl_minor — the oakrender module C ABI exposes no GL context version (the C++ worker reads them off its QOpenGLContext).

Layout

src/
  main.rs       clap entry; thin shell forwarding to oakfacade::worker
                (renderer init, handshake, NDJSON loop all live there)
  ipc.rs        control-plane message structs + NDJSON framing (serde)
  session.rs    in-process session mirror (message dispatch + real shm
                handshake attach), exercised by the unit tests
  transport.rs  real shared-memory frame-slot transport over oakfacade::ipc
tests/worker.rs binary-level tests (help, clap errors, --backend none exit 1)

The NDJSON control-loop behavior is exercised in-process in src/session.rs against the facade's real shared memory (no GPU needed via --backend none sessions); a binary-level loop test would require a working GPU backend and is deliberately not part of the unit suite. Run the binary against a created segment to see the real attach path:

target/release/oak-worker --backend opengl <<< '{"type":"shutdown"}'