Single-lib cleanup: the per-crate src/bridge/ and src/ffi.rs layers are gone (oakundo/oakcommon/oaknode/oaktimeline/oakcodec/oakaudio/ oakrender/oaktask/oakplugin/oakstorage); cross-crate calls are plain Rust, CHandle marshalling shrinks to the oakengine boundary, and tests call the Rust APIs directly (pure C-ABI wrapper tests removed where the domain layer already covers the behavior). exporter.h family implemented: oakengine_export_render (CLI contract), oakengine_export_render_with_params (was a stub), last_error and progress callback; synchronous path reuses task_create_export + start_sync. Fixes on the way: oaktask video ticket self-deadlock, audio params dropped on the export path, codec encoder AAC slicing and H.264 time base. Real-mp4 tests cover both entry points, progress and the illegal-argument matrix. Also: oakstorage session maps null project handles to None (version- info path), configstore test double literal 3.14 -> 3.15 (clippy PI lint), oakaudio output callback scratch buffer + env-aware P1 test, cli media round-trip test uses a generated 16-frame clip (no more minute-long debug runs).
oakundo Rust crate
Status: implemented. Ports the C++ oakundo module (
src/undo/src) to Rust behind its frozen C ABI (include/undo/*.h). Template followscrates/oakplugin.
Scope
Replaces the C++ oakundo module (src/undo/src): undoable commands
and the undo/redo history stack. Public contract: include/undo/*.h
(3 headers: error.h, undocommand.h, undostack.h) — frozen,
implemented verbatim by src/ffi.rs.
Architectural decisions
- Vtable-command pattern is the centerpiece. In C++ other modules
subclass
olive::UndoCommand(redo()/undo()overrides) and plug themselves in polymorphically. Rust has no inheritance, so the C ABI already models exactly this withOakUndoCommandVtable { redo, undo, free_fn }plus a caller-owneduserdatapointer. The safe layer's [undocommand::CommandKind] is the direct analog: either a caller-defined vtable command (function pointers + userdata) or a [undocommand::MultiUndoCommand] composite. Domain logic dispatches on the vtable the same way the C++ virtual dispatch does. - Modified-state callbacks are intentionally not part of the C ABI.
The C++
UndoCommand::redo_and_set_modifiedpair records/restores a project dirty flag viastd::functionaccessors. The public headers expose none of this; the stack drives state viadone_on the safe type instead, and the flag callbacks are left as a documented future extension. UndoStackstate machine is modeled directly on the C++: two deques —commands_(done, oldest at front) andundone_commands_(most-recently-undone at front);pushclears any redoable tail, executes redo, and drops the oldest when the cap (200) is exceeded;jumpclamps and walks viaundo/redo. The fresh stack holds a single "New/Open Project" empty command socan_undois false at the bottom (perundostack.cpp).- No merge semantics.
include/undo/*.handsrc/undo/src/*define nomerge_with/can_merge; commands are never coalesced. Tests reflect this (no merge tests).
Layout
src/
lib.rs crate doc + module map
error.rs error codes (include/undo/error.h)
handle.rs refcounted-handle scaffolding (OAKUNDO_ABI_VERSION=1)
undocommand.rs UndoCommand / vtable command / MultiUndoCommand
undostack.rs UndoStack + empty bottom command
ffi.rs export layer (one submodule per public header)
tests/ contract tests per module
error.h exports macros only and is folded into ffi.rs's preamble
(no own submodule), matching the codec crate convention.
Dependency policy
Prefer mature third-party crates (MIT/Apache-2.0/BSD, GPL-compatible) over hand-rolling; register each addition (name + reason) here. Large existing C++ libraries (OTIO, OCIO, OIIO, FFmpeg) are NEVER rewritten — they are consumed through their C ABI / bridge layers.