Files
oak-editor/crates/oakengine.bk/README.md
T
Mike-Solar cf459d7e4c feat(app): multicam panel with live angle grid, switching, timeline enable
- New MulticamPanel: rows/cols angle grid with the current angle
  highlighted, click-to-switch, 1-9 switch-and-split and cmd-1-9
  switch-only shortcuts (focused-panel routed), deferred switch queue
  during playback.
- src/oakui/multicam.rs: clip->connected-sequence resolution, multicam
  state detection (selection then playhead fallbacks), per-angle frame
  requests rendered through the process backend into an LRU cache.
- Timeline clip context menu Multi-Cam checkable item wired to
  oaktimeline::multicam enable/disable with undo.
- Engine trait extended (real + mock); mock drives the real command
  path with synthesized angle frames.
2026-08-18 21:40:00 +08:00

8.3 KiB

oakengine — the liboakengine cdylib (plugin / external C ABI)

The frozen oakengine_* C ABI (engine/include/oakengine/*.h) as a pure cdylib. This is the plugin / external-consumer layer: OFX plugins and third-party embedders link liboakengine and call the C ABI. The app, oak-cli and oak-worker do not use it anymore (M14 R4) — they link the module crates directly as Rust rlibs. The C ABI itself is frozen: only additive changes, plus major version bumps.

Downward, every export is a direct Rust call into the module crates (oakundo/oaknode/oaktimeline/oakcodec/oakaudio/oakrender/oaktask/ oakcommon/oakplugin/oakstorage/oakcore-rs) through src/stubs.rs (the rewired replacement for the deleted bridge/). The facade itself owns only the engine's box/unbox, buf/size and error-code conventions; cross-cutting state that used to live here (the process-wide undo stack, the open undo group) has sunk into the modules (M14 R1: oakundo::global).

Architecture

src/
  lib.rs          crate docs, module list, test-only test_link
  error.rs        OAKENGINE error codes (module 00 → -1..-6)
  handle.rs       CHandle mirror, OakEngine* opaque wrappers, box/unbox,
                  catch_unwind guards, buf/size string helpers
  stubs.rs        direct-Rust shims replacing the deleted bridge/ (the
                  engine's only downward path to the modules)
  linkage.rs      #[used] anchors pulling every module rlib into the cdylib
  undo.rs         engine/include/oakengine/undo.h (thin forward to
                  oakundo::global)
  common.rs       config.h + videoparams.h (facade-static tables + POD↔handle)
  audio.rs        audio.h (manager + sync + processor)
  codec.rs        encoding.h + exporter.h (metadata, params POD, exporter)
  render.rs       renderer.h + color.h + lut.h (renderer/frame/color processor)
  plugin.rs       plugin.h
  node.rs         node.h + project.h + footage.h (node graph / project / footage)
  timeline.rs     timeline.h (sequences, clips, tracks, markers, workarea)
  task.rs         task.h (background tasks over oaktask)
  storage.rs      write-through session (project ↔ oakstorage backend)
  deferred.rs     documented deferrals (stub detail lives here and in the
                  family modules' stub bodies)
  test_support/   the former tests/*.rs, now in-crate unit tests

Dependencies

The facade's regular dependency is thiserror (the Display + std::error::Error impl for the facade error enum, src/error.rs). Every module call is a compile-time Rust call into the module crate's direct API (src/stubs.rs); the module crates are real dependencies, and linkage anchors them so their rlibs are embedded in the liboakengine cdylib next to the facade's oakengine_* exports. The oakcore_audioparams_* accessors the audio paths read through are implemented in the dylib too (src/stubs.rs, module audio, M12 P5) — they used to be C++ host symbols left as runtime lookups (macOS -undefined dynamic_lookup); the cdylib now carries no undefined imports.

Handle mapping

The engine headers' opaque pointers (OakEngineNode*, OakEngineTrack*, ...) are thin newtype wrappers around the module layer's CHandle ({ctx, addref, release, abi_version}) values. A box is created by handle::box_handle and freed by handle::free_box (release + dealloc); consuming exports (oakengine_*_free, oakengine_undo_push, ...) free their box, borrowed results never are. The process-wide undo stack and open undo group live in the oakundo module (oakundo::global); the undo.h exports are thin forwards over it (module 00 analogues of EngineCore::undo_stack() and the C++ capi's g_undo_group).

Error codes

Facade-local codes are -1..-6 (OAKENGINE_E_*); module codes pass through untranslated (the -MMCCCC prefix preserves provenance, e.g. -20004 is oakundo's NOT_FOUND). String getters follow the engine buf/size convention: the return value is the length excluding the NUL (handle::string_result converts the modules' size-including-NUL).

Scope

Family Header Wrapped Notes
undo undo.h 37 stack/group/command lifecycle over oakundo (oakundo::global + undocommand) + Qt leftovers (update_actions/actions → no-op/NULL)
common config.h, videoparams.h 34 config over oakcommon; videoparams static tables ported from engine/render/videoparams.cpp
audio audio.h 26 manager + sync; processor convert/output_params stubbed (interface mismatch)
plugin plugin.h 4 callbacks are facade state; push_button stubbed (no module API)
codec encoding.h, exporter.h 81/85 metadata family over oakcodec (include/codec/format.h); params handle is a facade box over the oakcodec_encoding_params POD; the exporter family drives the export task synchronously (integration-tested, real mp4 via FFmpeg); presets/load-save deferred
render renderer.h, color.h, lut.h 60/60 renderer over oakrender tickets; frame accessors over OakCodecFrame; color processor over oakrender_color_processor_*; color-manager list queries + LUT library stubs
node node.h, project.h, footage.h 226/327 the node graph, project and footage families over the oaknode crate; documented stubs where the module lacks the surface (gizmos, plugin messages, input properties, brush, thumbnail/waveform caches, shape/subtitle, keyframe enumeration, ...) — see the stub bodies
timeline timeline.h 126/139 sequences/clips/tracks/markers/workarea over oaknode + oaktimeline; documented stubs (ripple-tracks command, default transitions, move-track/clip, marker-create, auto-cache, cache invalidation, multicam find/switch — module-surface gaps, see the stub bodies)
task task.h 27 the background-task system over oaktask (manager + load/save/import/export creators + result accessors); create_proxy stubbed (the module has no proxy-task C creator); start-time/is-cancelled are facade-approximated

Deferred/stub detail lives in deferred and in the stub bodies' doc comments. The worker/IPC families (worker.h/ipc.h) are not part of the facade anymore: the frozen C++ ABI does not include them, so the render worker's runtime and the shared-memory frame-slot transport moved into the oak-worker crate (crates/oak-worker/src/{worker,ipc}.rs, self-contained, direct Rust calls into oakrender); the ipc.h control-plane message serializers remain unwrapped.

Testing

The crate is cdylib-only (no rlib artifact), so integration tests cannot link it as a crate; the former tests/*.rs moved into src/test_support/ and run as in-crate unit tests (pulled in from src/lib.rs under #[cfg(test)]), addressing the modules through crate::*. The module crates are real dependencies, so the test binary statically links the same rlibs the cdylib embeds:

  • src/linkage.rs (always-on) anchors the module rlibs into the cdylib for cargo build; the test-only test_link module in src/lib.rs forces the oakrender/oaknode/oaktimeline/oaktask rlibs into the lib unit-test binary, and src/test_support/common/mod.rs::force_link covers the test-support module (same symbol list, so the anchor paths stay proven against the current module layouts).
  • Tests that touch process-wide state (the audio manager, the undo stack, the task manager) take a shared serialization lock instead of relying on the process isolation the old integration tests had (test_support/common::with_manager, the per-family SERIAL mutexes).

The smoke tests exercise the module crates' real implementations. Where a wrapped family needs module behavior the crates do not implement yet, the engine function is a documented stub with its reason (see deferred.rs).

cargo test          # in-crate unit tests (undo, common, audio, codec,
                    # exporter, plugin, render, node, timeline, task,
                    # library/storage families)
cargo build         # liboakengine cdylib embeds the module rlibs + the
                    # folded-in oakcore_audioparams_* accessors (no
                    # undefined imports)

FFI discipline

Every export goes through a catch_unwind guard (handle::guard*); *_free is a NULL no-op; strings use the two-stage buf/size convention; module error codes pass through untranslated; handles are refcounted module values wrapped in opaque boxes.