With the C ABI facade (oakengine) retired, the frozen-ABI rationale is gone. UndoCommand now boxes a Send Command trait (new/from_closures/ multi), dropping OakUndoCommandVtable, the userdata trampolines, the refcount shell, the handle module, and all undostack_* handle exports. The global facade loses its raw-pointer out-params (can_undo/can_redo return bool, command_name returns String). oaktimeline/oaknode/ oakplugin/oaktask construct commands directly via UndoCommand::new. oakundo src is now free of unsafe; behavior (ordering, idempotence, done flags, groups, observers, 200-row cap) is unchanged and pinned by the rewritten tests.
3.1 KiB
oakundo Rust crate
Status: implemented. Ports the C++ oakundo module (
src/undo/src) to Rust. Template followscrates/oakplugin.
Scope
Replaces the C++ oakundo module (src/undo/src): undoable commands
and the undo/redo history stack. The frozen C ABI (include/undo/*.h)
and the engine facade that consumed it are gone (see the root
Cargo.toml note on crates/oakengine.bk): every consumer links the
crate as a plain rlib and uses the value-typed API below.
Architectural decisions
- Trait-object commands replace the vtable pattern. In C++ other
modules subclass
olive::UndoCommand(redo()/undo()overrides) and plug themselves in polymorphically. Rust models the same polymorphism with a boxed [undocommand::Command] trait object: one-off edits arrive as closure commands ([undocommand::UndoCommand::from_closures]) and whole-struct commands implement the trait and are boxed with [undocommand::UndoCommand::new]; composites are [undocommand::MultiUndoCommand]. The formerOakUndoCommandVtablecallback table, itsextern "C"trampolines and the refcountedCHandlelayer were deleted with the C ABI — domain logic dispatches through the trait the same way C++ virtual dispatch does. - Modified-state callbacks are intentionally omitted. The C++
UndoCommand::redo_and_set_modifiedpair records/restores a project dirty flag viastd::functionaccessors. The public headers exposed 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.
src/undo/src/*defines 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 (mirrors include/undo/error.h values)
undocommand.rs UndoCommand / Command trait / MultiUndoCommand
undostack.rs UndoStack + empty bottom command
global.rs process-wide stack, groups, observers
tests/ contract tests per module
The module has no unsafe code and no extern "C" surface; panics in
command callbacks propagate as normal process-internal panics (the
process-wide stack recovers a poisoned mutex the same way the former
guard* FFI wrappers did).
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.