Files
oak-editor/crates/oaktimeline
Mike-Solar ab1a2e9c7b refactor: drop internal bridge/ffi layers; exporter family lands
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).
2026-08-16 00:33:45 +08:00
..

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) follows crates/oakplugin/README.md and crates/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)

  1. 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.md maps every C++ type to its Rust home; review that first.

  2. No C++ UndoCommand subclass hierarchy. Following the oaknode crate decision (#4), each undo command is a plain Rust struct that exposes prepare() / redo() / undo() (all todo!() here) and is surfaced to the world through the oakundo C ABI vtable (bridge::undo::oakundo_command_init, Rust callbacks as userdata). Every command struct carries a to_command() -> CHandle factory doc comment describing the wiring.

  3. Value types come from oakcore-rs. Markers and work areas are built on Rational/TimeRange, so the crate depends on oakcore-rs (crates/oakcore) exactly like oaknode does; no pixel/sample formats are involved here.

  4. 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 helpers oakundo_capi::make_command_handle / oaknode_c_api::to_native are not replicated in Rust — their role is subsumed by vtable commands and by value handles treated as opaque.

  5. Handles. handle.rs provides the shared RefBox/CHandle scaffolding (duplicated per crate, as in oaknode) with OAKTIMELINE_ABI_VERSION = 1. Borrowed handles into node-owned objects and owning handles created by *_create share 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

  1. Every extern "C" body goes through handle::guard*; no panic crosses FFI.
  2. Timeline objects never outlive their owning node; borrowed handles are created under a guard that owns the node reference.
  3. Behavior parity with C++ is proven by the C ABI test-suite (src/timeline/tests, unchanged) plus the golden tests in tests/ (XML save/load formats captured verbatim from src/timeline/src/timelinemarker.cpp / timelineworkarea.cpp).
  4. 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.