# 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 ```sh cargo build --release # binary: target/release/oak-worker cargo test # unit + integration tests ``` The worker is **self-contained** (M14 R2): the whole runtime is compiled into this binary and links the module crates directly — no `liboakengine` dylib is needed at build or run time. - `src/worker.rs` is the port of `engine/src/capi/worker.cpp` `oakengine_worker_main()` and owns the whole runtime: render backend selection through the oakrender crate's direct Rust API (dynamic → OpenGL fallback), the startup handshake and the NDJSON control loop. `src/main.rs` only scans argv for `--backend` and forwards. - `src/ipc.rs` owns the shared-memory frame-slot transport (the real `SpscRingBuffer` + `FrameSlotPool` over POSIX `shm_open`/`mmap`) and the NDJSON control-plane message structs. The oakrender module crate (`../oakrender`) is a plain Rust dependency; it 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.: ```sh CARGO_TARGET_DIR=/path/to/oak/crates/oakrender/target cargo build --release ``` ## What the worker does Same flow as the C++ main, in the same order: 1. **parse `--backend `** (default `opengl`; `none` skips renderer creation and the process exits 1, like the C++ main). 2. **initialize the render backend** (inside `src/worker.rs`): the oakrender `DisplayRenderer` direct Rust API, 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 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** (`src/ipc.rs` — 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/worker.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 exposes no GL context version (the C++ worker reads them off its `QOpenGLContext`). ## Layout ``` src/ main.rs argv --backend scanning (default opengl; last flag wins); forwards to worker::worker_main worker.rs the real worker runtime: backend selection (oakrender DisplayRenderer, dynamic -> OpenGL fallback), WorkerSession, handshake + NDJSON loop (M14 R2: the facade's port, owned by this binary since the facade C ABI was cut) ipc.rs control-plane message structs + NDJSON framing (serde), AND the real shared-memory frame-slot transport (SpscRingBuffer + FrameSlotPool over POSIX shm) tests/worker.rs binary-level tests (--backend none exit 1) ``` The NDJSON control-loop behavior is exercised in-process in `src/worker.rs` against the local 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: ```sh target/release/oak-worker --backend opengl <<< '{"type":"shutdown"}' ```