- oakengine is now cdylib-only (no rlib/staticlib consumers anywhere; cargo tree verified) — the plugin/external C ABI layer; README and docs updated - cd.yml drops the dylib embedding/re-sign steps (the app no longer links it) - test race root-caused and fixed for good: the global undo stack lock is now a re-entrant mutex (parking_lot) shared by every test that drives the stack, including the previously unlocked node/render families; the render-manager serial-ordering bug (an earlier repro test initialized the global manager before the not-initialized test) is fixed with a shared SERIAL guard and a manager shutdown - 5 consecutive parallel runs clean; serial 209/209
141 lines
8.3 KiB
Markdown
141 lines
8.3 KiB
Markdown
# 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`](src/linkage.rs) 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`](src/deferred.rs) 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.
|