- oakengine_sequence_move_clip implemented for real (oaktimeline TrackMoveBlockCommand; fixes the graph-ownership/gap-anchor/ripple trim bugs the stub was hiding); same-track via the frozen C ABI, cross-track supported by the module command - oaknode clip blocks now declare a tex_in texture input and set effect_input to it, so timeline clips can host effect chains; facade test covers effect insert/remove on a real clip - oakffmpeg-link: FFMPEG_DIR is now mandatory with a clear panic (a Homebrew upgrade left the system ffmpeg .pc pointing at a deleted dav1d Cellar path, breaking links); reads a git-ignored workspace .env for IDEs that cannot inject env vars (RustRover); links the C++ stdlib for C++ codec libs (svt-av1) - oakengine re-exports oaknode so tests share one crate instance; it_node uses the direct instance's value type where it calls the module FFI (the --workspace dev-dependency feature split builds oaknode twice)
oaktimeline Rust crate (declaration draft, for review)
Status: declaration draft. Signatures + doc comments are the spec; every body is
todo!(). Not wired into any build. The crate template (FFI discipline, testing layers) followscrates/oakplugin/README.mdandcrates/oaknode/README.md.
Scope
Replaces the C++ oaktimeline module (src/timeline/src): timeline
markers and work areas, the timeline undo-command family (add/remove
tracks, place/trim/split blocks, ripple edits, slide, gap insertion),
and the shared Timeline namespace / utility helpers.
Public contract: include/timeline/*.h (error.h, displaymode.h,
marker.h, workarea.h, edit.h) — frozen, implemented verbatim by
src/ffi.rs. The timeline value handles (OakTimelineMarkerList,
OakTimelineWorkArea) and every edit command are exported through this
ABI only; consumers (the facade/app, the oaknode crate) never see the
internal Rust types.
Key architectural decisions (C++ → Rust mapping)
-
Domain modules mirror the C++ header files. The C++ module is a flat set of headers, not a deep class hierarchy, so the Rust crate keeps one module per C++ header family (
common,marker,workarea,undocommon,undotrack,undogeneral,undopointer,undoripple,undosplit,util).COVERAGE.mdmaps every C++ type to its Rust home; review that first. -
No C++
UndoCommandsubclass hierarchy. Following the oaknode crate decision (#4), each undo command is a plain Rust struct that exposesprepare()/redo()/undo()(alltodo!()here) and is surfaced to the world through the oakundo C ABI vtable (bridge::undo::oakundo_command_init, Rust callbacks asuserdata). Every command struct carries ato_command() -> CHandlefactory doc comment describing the wiring. -
Value types come from
oakcore-rs. Markers and work areas are built onRational/TimeRange, so the crate depends onoakcore-rs(crates/oakcore) exactly like oaknode does; no pixel/sample formats are involved here. -
All cross-module access goes through the C ABI. Per the project rule (no cross-module C++ member calls), timeline commands touch the node graph exclusively through the oaknode C ABI (
bridge::node), undo through the oakundo C ABI (bridge::undo), and XML/config through the oakcommon C ABI (bridge::common). The C++ internal helpersoakundo_capi::make_command_handle/oaknode_c_api::to_nativeare not replicated in Rust — their role is subsumed by vtable commands and by value handles treated as opaque. -
Handles.
handle.rsprovides the sharedRefBox/CHandlescaffolding (duplicated per crate, as in oaknode) withOAKTIMELINE_ABI_VERSION = 1. Borrowed handles into node-owned objects and owning handles created by*_createshare one box layout{ctx, addref, release, abi_version}.
Layout
src/
lib.rs crate doc + module map
error.rs error codes (mirrors include/timeline/error.h)
handle.rs refcounted-handle scaffolding (same pattern as node)
common.rs Timeline namespace (MovementMode/ThumbnailMode/
WaveformMode, EditToInfo) — timelinecommon.h
marker.rs TimelineMarker/MarkerList + 5 marker commands
workarea.rs TimelineWorkArea + 2 workarea commands
undocommon.rs node/block remove helpers + CHandleCommandWrapper
undotrack.rs track ripple/prepend/insert-after/replace commands
undogeneral.rs resize/media-in/add/remove-track/transition/gap/
enable-disable/insert-gaps/default-transition commands
undopointer.rs BlockTrimCommand/TrackSlideCommand/TrackPlaceBlockCommand
undoripple.rs ripple remove-area / ripple-tool / delete-gaps commands
undosplit.rs BlockSplitCommand/BlockSplitPreservingLinksCommand/
TrackSplitAtTimeCommand
util.rs timelineutil.h inline helpers (rat_nd, same_*,
free_detached_handle, block/track queries)
bridge/ C ABI imports: node.rs, undo.rs, common.rs
ffi.rs include/timeline/*.h export layer
tests/ contract + golden tests (see README test section)
Hard rules for the implementer
- Every
extern "C"body goes throughhandle::guard*; no panic crosses FFI. - Timeline objects never outlive their owning node; borrowed handles are created under a guard that owns the node reference.
- Behavior parity with C++ is proven by the C ABI test-suite
(
src/timeline/tests, unchanged) plus the golden tests intests/(XML save/load formats captured verbatim fromsrc/timeline/src/timelinemarker.cpp/timelineworkarea.cpp). - Where C++ behavior is genuinely load-bearing but ugly (e.g. marker
list kept sorted by time, ripple's compensation gap rules), port the
behavior, not the aesthetics; leave a
// CPP-PARITY:comment with the C++ file:line.
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.