Module-internal object references are Rust types now (values, Arc, Mutex); CHandle remains only at the oakengine C-ABI boundary: - oakundo: the global stack holds UndoStack/UndoCommand values directly (stack token is the static's address) - oaktimeline: marker/workarea boxes carry Arc<Mutex<T>>; commands share the same allocation through Arc clones (readers in oakengine stubs and the app's graphops updated to lock) - oaktask/oakstorage: sessions, write-through bindings and the database backend pass ProjectArc; the Session drops its manual release bookkeeping; nodeutil keeps the CHandle<->Arc boundary conversion (release_project restored for the app) - oakcodec: handle.rs deleted outright (no facade entry needed it); texture/block placeholders are unit structs - oakrender: copier's project handle is an identity u64; alive-count machinery removed; handle.rs is make_owned/get/get_mut only - oakplugin: the instance registry is gone (its unregister key never matched, leaking weak entries); handle.rs is the RefBox boundary type - oaknode/oakcommon: only dead guard/borrow helpers removed; external payload handles (texture/processor) documented as the boundary Flake hunts landed along the way: the audio recording test serializes on the shared manager lock with a normalized state; the autocacher cancel test uses a slow producer so cancellation is deterministic.
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.