Commit Graph
22 Commits
Author SHA1 Message Date
Mike-Solar b36cbd6b6f refactor: purge CHandle from module internals (M14 R5)
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.
2026-08-17 16:40:15 +08:00
Mike-Solar c5f1d0d76c refactor(engine): pure cdylib + undo-stack test race fix (M14 R4)
- oakengine is now cdylib-only (no rlib/staticlib consumers anywhere;
  cargo tree verified) — the plugin/external C ABI layer; README and
  docs updated
- cd.yml drops the dylib embedding/re-sign steps (the app no longer
  links it)
- test race root-caused and fixed for good: the global undo stack lock
  is now a re-entrant mutex (parking_lot) shared by every test that
  drives the stack, including the previously unlocked node/render
  families; the render-manager serial-ordering bug (an earlier repro
  test initialized the global manager before the not-initialized test)
  is fixed with a shared SERIAL guard and a manager shutdown
- 5 consecutive parallel runs clean; serial 209/209
2026-08-17 00:51:36 +08:00
Mike-Solar 022c0a7a5a refactor(app): cut liboakengine, link module rlibs directly (M14 R3)
- real.rs rewritten over module Rust APIs (Arc<Mutex<Project>> +
  NodeId; the addref handle dance and renderer boxes are gone);
  AppEngine trait and all panels untouched
- new app assembly layers: graphops (project/timeline/edit
  primitives), effectchain (chain composition with undo groups),
  renderops (montage build + ticket render + ExportTask export),
  library via oakstorage directly
- module-side safe API additions: oakundo global value-semantic
  push/undo/redo + from_closures, oakstorage project_arc_of
- deleted: src/oakui/ffi.rs, src/oakui/host_syms.rs, the dylib link
  config in build.rs (only the gpui IOSurface framework link remains)
- the binary carries zero liboakengine references (otool/nm verified);
  101 app tests green incl. the real-render and full-res e2e tests
- behavior improvements for free: sequences land in the project graph
  (the facade scratch-project deviation is gone), footage drops take
  one undo record, effect remove/reorder undo restores edges
2026-08-16 23:36:43 +08:00
Mike-Solar 8a2e45225f refactor: sink facade glue into modules (M14 R1)
- oakundo::global: the process-global undo stack, grouping and a
  command observer API; facade undo.rs becomes a thin forwarder
- oakstorage::writethrough: the binding table, snapshot thread, flush
  and config resolution; it subscribes to oakundo's observer itself
- the remaining non-forwarding facade logic (TaskMeta, effect chains,
  timeline composites, RendererBox, exporter path) is documented as
  facade-owned with reasons
- facade exports unchanged; full suite stays green (the one
  render_manager_not_initialized failure is pre-existing on the base
  commit)
2026-08-16 20:37:45 +08:00
Mike-Solar 061aab9180 fix(app): full-res worker crash + source playback frozen + cli/worker DS batch
- full-res source jobs carried only the footage node box: dropping the
  project mid-flight left the node dangling (crash in the worker's free
  path) and the node was also freed twice (at renderer creation AND at
  release). The request now carries an addref'd project copy and the
  node is freed exactly once; regression test drops the project before
  the worker runs
- the source clock ticked at the SEQUENCE length, so playing footage
  with an empty sequence froze the source playhead at 0; the source
  clock now loops at the selected footage's probed duration
- oak-cli/oak-worker: DeepSeek's refactor batch (clap migration, engine
  FFI consumers); the stale exporter-family test flipped to the real
  contract (mp4 is written)
- engine: render_audio smoke test on an empty sequence
2026-08-16 19:07:07 +08:00
Mike-Solar 4e5d8747b5 refactor(oakengine): absorb oakcore host symbols into the dylib
The 'host-provided' oakcore_audioparams_* runtime imports dated from
the deleted C++ host; the facade is their only caller. The dylib now
defines and exports the six symbols itself (repr(C) AudioParams mirror,
liboakcore-compatible semantics), -Wl,-undefined,dynamic_lookup is gone,
and the Windows DLL undefined-symbol blocker is removed by construction
(Windows CI/packaging stays off until a real toolchain verifies links).
2026-08-16 18:05:03 +08:00
Mike-Solar 92daff1a83 feat(engine,app): timeline markers, work area, cross-track move (M12 P4)
- facade: oakengine_sequence_set_workarea_undoable (enable+range as one
  undo record); marker add/remove/list already existed and are now
  covered by it_timeline (10 new tests: markers, workarea, cross-track)
- fixes: stubs workarea_get honored NULL out-params (is_enabled always
  failed); export tasks honor custom ranges (export_params_pod dropped
  them; oaktask EncodingParams carries has_custom_range)
- app: markers on the ruler (gpui diamond markers), menu
  sequence-add/remove marker, set/clear work area (selected-clip bounds
  or playhead+1), ruler drag previews live and commits one undoable
  record (C++ ruler semantics); export uses the work area when enabled
- cross-track clip moves were already wired; now covered end to end
2026-08-16 16:06:19 +08:00
Mike-Solar 693e2df5eb feat(app): project browser on real project data + drag-drop import (M12 P3)
- ProjectDataSource<RealEngine> reads the real bin tree (roots/
  children via the facade folder/footage enumeration)
- AppEngine::import_footage implemented (facade project_import_footage,
  last_error surfaced); double-click opens in the source viewer
- dragging files onto the browser imports them (also fixes a real gpui
  bug: ExternalPaths arrives as the bare type, not Arc-wrapped, so
  drops never fired)
- tests: real-engine import->browser listing, widget drop routing,
  facade folder enumeration failure matrix
2026-08-16 15:05:01 +08:00
Mike-Solar 0f82092e95 refactor(app): retire manual-save semantics (M13 D5)
- AppEngine: save_project -> export_project_path(explicit path);
  project_modified removed (write-through has no dirty flag)
- File menu's save/save-as are the export path (extension-dispatched
  ove/otio/fcpxml); the library row export path is unchanged
- facade project_save/is_modified stay frozen, documented as the
  legacy export surface
- docs: M13 D5 checked off; M12 inventory updated
2026-08-16 14:17:45 +08:00
Mike-Solar 025dc88c25 feat(storage,app): PostgreSQL backend (D3) + project manager window (D4)
D3: oakdb+pg:// fully wired (shared sea-orm entities, BIGSERIAL DDL,
connect-probe instead of pool retry on dead servers); Storage/Backend=pg
+ Storage/PgUrl config; 13 OAK_TEST_PG_URL-gated PG tests (verified
against a Docker postgres:16), always-on clean-error tests otherwise.

D4: DaVinci-style project manager — list with derived stats, create/
rename/duplicate/delete (confirm)/import/export (native dialogs,
ove/otio/fcpxml), shown at startup and from the file menu; facade
oakengine_library_* exports (list/create/delete/rename/duplicate/
import/export + project_load_library that binds write-through);
save/save-as menu becomes 'export project file', open splits into
from-library/from-file; status bar shows library write state; storage
activates on app start and flushes on exit; spawn_modal reentrancy
fixed (window-callback path) with a doc note.

Also: the P1 audio test's environment probe was lost in the ffi purge;
restored on cpal (the output device is cpal now).
2026-08-16 10:39:31 +08:00
Mike-Solar 5fabad8efd feat(engine): write-through persistence (M13 D2)
- facade storage session manager: project handles bind to the default
  SQLite library on project_new/load; every undo push/group_end/jump
  write-throughs via DatabaseBackend::save (diff journal); project_free
  flushes and unbinds
- background snapshot thread (Storage/SnapshotIntervalSec, latest-wins,
  newest 3 kept) with exit flush (oakengine_storage_flush)
- config-gated: storage only activates with an explicit
  Storage/Backend=sqlite, so headless consumers and tests never touch
  the real library; new exports: storage_flush/is_bound/last_error
- it_storage: kill -9 recovery, cross-session undo, snapshot pruning,
  multi-project isolation, graceful degradation
- also fixes a real config test polluting the user config.ini and the
  undo-stack test races
2026-08-16 08:52:23 +08:00
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
Mike-Solar 48d3027d50 feat(engine,app): node editor bound to the facade graph (M12 P2)
facade (API only extended): oakengine_sequence_as_node /
_sequence_node_count / _sequence_node_at / _sequence_remove_node;
remove validates ownership by project UUID (arena slot/generation
collide across projects). it_node covers enumeration/edits and the
NULL/illegal matrix.

app: NodeGraphDataSource enumerates the current sequence's graph —
footage / effect / clip / output cards at their context positions
(deterministic role-grid fallback), real edges plus synthesized
clip->sequence tex_in edges; connect/disconnect/remove/drag-release
all go through undoable facade commands, drag previews stay local.
2026-08-14 13:33:49 +08:00
Mike-Solar 4240df1d71 refactor(crates): implement std::error::Error for all module error enums
thiserror derive across oakundo/oakcommon/oaknode/oaktimeline/oaktask/
oakotio/oakcodec/oakaudio/oakrender/oakplugin/oakengine/oakstorage;
Display carries the module prefix and the Failed context, source()
stays default except oakotio's #[from] forwarding. code() mappings and
variants unchanged; each error.rs gains Display/object-safety/code
regression tests.
2026-08-14 05:45:05 +08:00
Mike-Solar 5afa95c80e feat(render,app): footage decode lands (M12 P0) + UI density pass
engine:
- oakrender eval footage hook decodes via oakcodec (JobSpec::Footage
  carries filename/stream); ticket/ffi/manager wiring, real-media
  decode test with programmatically generated MPEG-2
- oakrender bridge/codec.rs + node.rs: direct oakcodec/oaknode calls;
  the crate's dlsym module is gone (project_deep_copy/sync_copy remain
  documented always-fail stubs — never implemented in oaknode)
- oakaudio waveform/decoder path adjustments for the decode hook

app (gpui + gpui_widgets):
- menu bar scrubbing: hovering another top-level title while a menu is
  open switches to it; popup width is content-aware (CJK-aware) instead
  of fixed 160px
- density pass: window rem 16 -> 14px, menu rows 26 -> 22px, dock tabs
  32 -> 26px, viewer transport tightened
- open/import/save-as use the native platform file dialogs
  (prompt_for_paths / prompt_for_new_path; multi-select import);
  MockEngine records imported footage for tests
- project explorer Tree/Icons toggle is localized (explorer.tree /
  explorer.icons widget keys)
2026-08-13 18:16:10 +08:00
Mike-Solar 06f47235b2 feat(engine,app): source-monitor frames + real audio meter
source monitor:
- new facade exports oakengine_renderer_create_for_node (render any
  node, not just sequences) and oakengine_project_footage_at (fetch a
  footage node by index); it_render covers the e2e render plus the
  NULL/illegal matrix
- RealEngine renders the selected footage's real frames to the source
  viewer (per-node renderer slot, frame cache invalidated on selection
  change, synthetic fallback kept)
- known gap (documented): pixels stay transparent black until
  oakrender's footage decode hook lands — the render surface itself is
  real end to end

audio meter:
- oakaudio manager can now report per-channel linear peaks of the
  buffered output (PreviewAudioDevice::peek_tail + levelmeter analysis
  of the newest 8192 frames, packed/planar F32)
- new facade export oakengine_audio_output_levels (negative codes pass
  through); RealEngine's AudioMeterDataSource reads it instead of
  returning hardcoded silence
- tests: module peak readback + facade validation/readback matrix
2026-08-12 14:19:18 +08:00
Mike-Solar 18ff60f147 feat(engine): clip move, clip effect_input, mandatory static FFmpeg
- 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)
2026-08-11 23:04:48 +08:00
Mike-Solar cdda643d64 feat(engine,app): effect chain facade + app effect stack wiring
facade (oakengine/oaknode, API only extended):
- oaknode_node_get_effect_input / oaknode_node_get_flags module exports
- oakengine_clip_as_node, oakengine_node_effect_count/at/insert/remove/
  move/set_enabled, oakengine_node_identity/is_enabled/get_type_id,
  oakengine_node_factory_id_at — chain edits wrap disconnect/reconnect
  into single undo groups; insert/move/remove validated against the
  factory and the host's effect input
- it_node covers enumeration/insert/remove/move/toggle/undo-redo and
  the NULL/illegal-input matrix

app:
- RealEngine binds the selected clip's effect chain to the inspector's
  effect stack (cards, enable, reorder, remove, expansion state), all
  edits undoable through the facade
- AppEngine gains set_selected_clips / addable_effects / add_effect
  (default no-ops keep the mock untouched); the timeline selection
  drives the stack target; the inspector gets a small add-effect menu
- known module gap (documented): ClipBlockBehavior sets no effect_input
  yet, so timeline clips cannot host effects until oaknode grows tex_in
2026-08-11 20:04:44 +08:00
Mike-Solar 26009a5ea5 refactor(oakaudio): drop ffmpeg_bridge fb_* imports, call ffmpeg-next in-process
- processor.rs: resample/channel-convert/time-stretch now runs an
  in-process FFmpeg filter graph (abuffer -> atempo -> aformat ->
  abuffersink) via ffmpeg-next
- waveform.rs: extraction decodes through oakcodec's FFmpegDecoder
  (interleaved f32) instead of the fb_decoder/fb_audio_graph pair
- bridge/ffmpeg.rs and the null fb_* test stubs deleted;
  oakcommon::ffmpegutils bridge constants become plain ints
- liboakengine.dylib no longer imports any fb_* symbol (nm -u clean);
  the remaining runtime imports are the host-provided oakcore_* symbols
- real decode/resample tests added (generated PCM input, no network)
2026-08-11 16:10:14 +08:00
Mike-Solar aa5fcef66e fix(oakengine): facade bugs found by integration tests
undo:
- NULL/empty label no longer crosses to oakundo as a dangling 0x1
  pointer (push, group_begin/end) — fixed SIGSEGV
- group_abort now undoes each executed child in reverse order

task:
- create_project_import addrefs the borrowed project handle instead of
  freeing it under the async task — fixed UAF/SIGSEGV

timeline:
- toggle_enabled/delete_clips guard NULL+0 slices — fixed SIGABRT
- BlockSplitCommand halves placed correctly (oaktimeline undosplit)
- PreservingLinks / ripple remove / ripple delete-gaps commands
  self-prepare on first redo — fixes silent no-op split/ripple
- trim_clips_to targets the block containing the point, not the track
- delete_empty_tracks applies the live track removal
- ripple facades no longer free borrowed track handles still referenced
  by commands — fixed UAF

node:
- project_add_node releases the factory handle — fixes per-call leak
- inputs_from(recursive=0) matches direct feeders (BFS off-by-one)
- group passthrough id/resolve treat two-stage string length as success
- node_connect(_command) reject duplicate connects with E_STATE
- folder_add_child enforces one-folder-per-node
- value_split_to_tracks splits vector/color per component
- set_context_position/expanded establish the first entry
- node_get_flags on an empty box returns 0, not u64::MAX
- footage_borrow addrefs its wrapper — fixes double-free

render:
- renderer_create rejects invalid pixel formats (real range check)
- render_frame forwards renderer width/height to the ticket

tests: repro #[ignore]s removed, bug-behavior assertions corrected,
it_undo global-stack tests serialized with a shared lock
2026-08-11 01:30:18 +08:00
Mike-Solar 3110e5cc3a test(oakengine): rename integration test families to English names
it_*族.rs -> it_audio/codec/common/node/plugin/render/task/timeline/undo.rs;
also fixes a pre-existing racy assertion in it_task.
2026-08-11 00:24:57 +08:00
Mike-Solar 013a175707 refactor: workspace layout — crates/, app at root, legacy C++ removed
Single mechanical restructure commit:
- root Cargo.toml = oakapp bin + workspace; one cargo build produces
  oakapp, oak-cli, oak-worker, liboakengine.dylib
- app/rust/src -> src/ (app at repo root, no rust/ nesting)
- src/<mod>/rust -> crates/oak<mod>; src/oakcore-rs -> crates/oakcore;
  src/bindings/oakotio -> crates/oakotio; src/engine/rust ->
  crates/oakengine (keeps cdylib+staticlib+rlib)
- public C headers include/<mod>/ -> crates/oakengine/include/<mod>/
- OFX SDK headers vendored into crates/oakplugin/ofx/ (HostSupport gone)
- legacy deleted: old src/ C++ modules, engine/, core/, ffmpeg_bridge/,
  app/ (Qt), cli/worker C++, root CMakeLists, third_party/KDDockWidgets
  submodule, otio-install, all build-* output (~40GB)
- oakstorage kept but excluded from the workspace (skeleton w/ todos);
  gpui excluded (own workspace)
- verified: cargo build green, cargo test --workspace 1845/0
  (with the documented OCIO_RS_* env override for the homebrew OCIO)
2026-08-10 20:24:25 +08:00