preview_window_capacity used the *configured* worker count, so a
window opened while workers were still handshaking (or after a crash)
could claim every slot of the smaller live pool — the synchronous
render ticket then never gets a free slot, and since the slot-releasing
cleanup runs on the same UI thread that is blocked in TicketArena::wait,
playback deadlocks permanently. Intermittent on Linux CI (the
playback_display_tracks_the_playhead hang, caught by the new test
watchdog): depends on how many workers had handshaken when playback
started. Count only Alive workers (fall back to the configured count
while none are alive, keeping the existing unit test semantics).
oakstorage: the sqlite URI parse tests used /tmp/lib.db, which is not
absolute on Windows, so parse_target's is_absolute check rejected it.
Pick the absolute path per platform (C:/tmp/lib.db on Windows).
ci (Linux): wrap the test step in a 1500 s watchdog — a deadlocked
test prints nothing and never fails; on timeout the watchdog dumps
every test/worker process's thread stacks with gdb and then kills the
suite. (One such hang already ate a run; the previous green run needed
~4 min.)
ci+cd: Swatinem/rust-cache gains cache-on-failure everywhere, so a
red run still saves its compile cache (the actions/cache FFmpeg cache
already saves in its post phase regardless of outcome).
examples/screenshot.rs: migrate to language_code()/set_language_code
(the Language enum is gone); only CI's example build caught it — local
--lib runs never compile examples.
oakcodec tests: build path expectations with Path::join instead of
'/'-joined literals — production uses platform-native separators, so
the derivation assertions failed on Windows ("dir\img007.jpg" vs
"dir/img007.jpg"). Behaviour unchanged; the tests were never reached
on Windows before (earlier failures aborted the run first).
oakui::ofx interact test: poll up to 5s for the destroy marker record
before asserting. The active-interact slot is process-global; a
concurrent viewer frame sync from another test's real engine can take
the interact out of the slot and be preempted between take and
destroy, so the plugin's destroy record occasionally lands a few
milliseconds after this test removed the marker env var (Linux CI:
"lifecycle actions missing: [...]" with everything but destroy
present).
i18n: test_lock() and lang_test_lock() used to be TWO different static
mutexes, so the i18n tests and the ~30 app/actions/dialogs tests that
mutate the language global never excluded each other. Windows thread
scheduling exposed the race: tr_falls_back_to_english_then_the_key got
the English value because another test flipped the language mid-assert.
Both entry points now lock the same mutex.
oak_test_plugin.c: on Windows the plugin DLL has its own CRT
environment block, so getenv() never sees what the host's
std::env::set_var set via SetEnvironmentVariableW — the interact
lifecycle test's marker file stayed empty ("lifecycle actions
missing: []"). Read the marker path through GetEnvironmentVariableA
on _WIN32.
The fixture plugin binary was copied into Contents/Linux-x86-64 under
the extension-less name "plugin" on every non-macOS platform. On
Windows the host never loads it: LoadLibrary appends .dll to
extension-less module names, so the scan found the bundle but produced
no plugin — and because the (passing) draw-overlay test scans first,
the path dedupe then hid the failure from the lifecycle test, which
died with "interact variant instance: NotFound". Use Contents/Win64
and plugin.dll on Windows in both bundle assembly sites.
oakrender/build.rs: -Wl,-export_dynamic is the macOS spelling. Since
Rust 1.90 x86_64-unknown-linux-gnu links with rust-lld by default, and
lld parses the single-dash form as '-e xport_dynamic', every oakrender
integration test binary was linked with NO entry point and died with
SIGSEGV inside ld.so's dl_main (jumping to the image base) before
printing anything — the copier_test CI failure. Emit the flag on macOS
only.
oaknode timeformat: value_localtime_flag_routes_to_localtime_r called
localtime_r/gmtime_r directly, which do not exist on Windows. Factor
the cfg-gated FFI (localtime_r/gmtime_r vs _localtime64_s/_gmtime64_s)
into break_down_time() and use it from both value() and the test.
oakcodec: gate find_ffmpeg_searches_path to unix (chmod 0755 + shebang
fixture) and make find_ffmpeg_missing_returns_empty assert absoluteness
instead of a '/' prefix so the tests compile and pass on Windows.
ci (Windows): export RUSTFLAGS=-C link-args=-lmsvcrt in the build and
test steps. mingw-w64 (Nov 2025) forwards _assert to __msvcrt_assert
inside libmingwex.a, and rustc's link order leaves -lmingwex last, so
binaries that pull _assert.o (oakcommon's real_ocio test) fail to link;
a trailing -lmsvcrt re-scans the CRT import lib afterwards.
ci (Linux): copier_test dies inside ld.so before printing anything.
Replace the LD_DEBUG probe with stronger forensics: exported dynsyms
(interposition suspects), strace tail, valgrind tail, and siginfo
(si_code/si_addr) from the gdb run.
oakaudio: drop cpal's `asio` feature. asio-sys needs the proprietary
Steinberg ASIO SDK at link time (undefined ASIOGetSamplePosition etc.
on the GNU toolchain); WASAPI remains the Windows backend.
ci: the failure-only gdb step passed test args without --args, so gdb
treated --nocapture as a core file. Also collect loader-stage evidence
for the copier_test dl_main SIGSEGV: IRELATIVE reloc count, LD_DEBUG
tail, full backtrace and registers.
- localtime_s/gmtime_s are MinGW header inlines, not symbols — link
_localtime64_s/_gmtime64_s
- copier_test also segfaults only on the Linux runner; add it to the
on-failure gdb backtrace
- timeformat node: localtime_s/gmtime_s (reversed args, 64-bit time_t)
on Windows — MinGW has no localtime_r/gmtime_r
- the multithread suite test passed non-NUL-terminated property names
(str::as_ptr) to the C property suite — UB that resolved to garbage
lookups on the CI runner
- libsnappy off on the MinGW FFmpeg build (only feeds the hap encoder;
its pkg-config entry does not reach the static link)
- FFmpeg for Windows no longer enables libopenh264 (redundant with the
native h264 decoder + x264 encoder; its MinGW packaging does not
satisfy the static link — Wels* undefined references)
- suites_test's question-type call passed a 3-placeholder format with
one variadic arg — UB that vsnprintf turns into a SIGSEGV on glibc
(masked on macOS); use a placeholder-free format
- oakffmpeg-link forwards pkg-config --static --libs verbatim; FFmpeg's
.pc files can list -ldl via external deps, and MinGW has no libdl
- suites_test segfaults on the Linux runner too; run both plugin test
binaries under gdb on test failure
Tests (and any host that shuts down then rescans) can hold instances of
a PREVIOUS plugin generation; their entry points dangle after dlclose,
so the next shutdown's destroyInstance notification jumped into
unmapped memory (SIGSEGV on Linux; masked on macOS). Plugin gains an
flag set by unload_all before dlclose; call_action/call_entry
fail fast instead of calling into freed code.
- the Windows runner image exports MSVC's INCLUDE/LIB; cc-rs was
appending the MSVC SDK headers to MinGW compiles (vcruntime.h not
found)
- oak-worker handshake test helper advertised the input pool's total
byte size as per-slot data bytes (macOS tolerated the oversized
attach; Linux correctly rejects it)
- hw/sw decode comparison tolerance 0.05 -> 0.08 (VideoToolbox's
YUV->RGB legitimately differs by ~1 LSB of intermediate depth)
- the vendored OCIO source needs MSVC-only constructs (wide-path
ifstream); MSYS2's mingw build of the exact 2.5.2 the bridge targets
is the sane Windows path — DLLs get packaged next to the binaries
- oak-worker handshake test prints the error response on failure
(CI-only attach failure needs the message)
Each dimension of a parametric param renders as a CurveEditor in the
inspector (bezier handles map to the Hermite slopes of the host curve
model; edits serialize back through the JSON mirror — undoable and
project-persisted). The engine re-sync skips in-progress drags and
identical curves so the per-render sync neither steals gestures nor
loops. Also: physical-memory probe for the worker-count policy on
Windows (GlobalMemoryStatusEx).
- ValueType::Parametric; the node input carries the whole curve set as
NodeValue::Text(JSON) so undo and project serialization come for free
- translation pass builds the input with the default-curve JSON and the
dimension/range/ui-colour properties
- edits flow both ways: node input (UI) -> curves_from_json ->
set_ofx(Parametric) on the instance; plugin-side Set/Add/Delete ->
notify_instance_changed -> JSON written back to the input (undoable)
- screenshot example: gate the macOS-only offscreen capture items so
the workspace tests build on Linux/Windows
- oakplugin host: Win32 LoadLibraryExW/GetProcAddress/FreeLibrary
backend (LOAD_WITH_ALTERED_SEARCH_PATH so bundle-sibling DLLs
resolve), same dl_open/dl_sym/dlclose surface — the POSIX path is
untouched; OFX hosts now compile on Windows
- proxymanager: PATH split via std::env::split_paths (Windows ';'),
ffmpeg.exe name, Windows candidate locations; split logic unit
tested
SharedMemoryRegion gains a Win32 backend behind the unchanged public
API: Local\OakShm<key> names, OpenFileMapping for attach, VirtualQuery
for the size check, UnmapViewOfFile/CloseHandle for teardown. Semantic
differences from POSIX are documented: unlink_key is a no-op (the
kernel destroys the object with the last handle, so crashed owners
self-heal) and Create on a live name fails instead of replacing.
Windows CI builds the workspace again.
Parametric (curve/LUT) parameters: ParamValue::Parametric holds one
ordered control-point curve per dimension (identity default over the
declared range), evaluated as piecewise cubic Hermite with auto
(centered-difference) slopes; the full suite — evaluate / count / get /
set / add / delete / delete-all — with the spec's error codes, descriptor
defaults copied to instances, and instanceChanged notifications on
edits. paramDefine accepts OfxParamTypeParametric; the dimension/range
and UI-colour properties round-trip. 148/148 real plugins discovered,
135 registered (one more than before: the parametric-suite consumer).
- cargo fetch does not extract sources; the yaml-cpp <cstdint> patch
now untars the .crate into the registry src dir first (the glob
found nothing and the step failed with exit 2)
- oakaudio: the watchdog-wrapped audio test called Self::... from a
free-function test module (compile error in lib test)
- Linux: libxkbcommon-x11-dev for the gpui X11 client link
- Windows: patch <cstdint> into the vendored yaml-cpp (a cached cmake
configure ignores CXXFLAGS; the patch is idempotent and runs after
cargo fetch)
- macOS: the hw-decode test skips its VideoToolbox engagement
assertions on hosts where VT cannot initialize (headless/virtualized
runners) instead of failing
- display color management: the display ICC (system or custom) is
applied to viewer frames at present time (F32 in place, or in place
on the BGRA staging copy with the R/B swizzle baked into the OCIO
chain); preferences get a Color section (mode + custom ICC file); on
macOS the Metal layer is tagged with the display colorspace when
self-managing so ColorSync passes pixels through (no double
correction); frame caches track the transform generation so a mode
or profile change drops stale pixels
The distro OCIO is too old for the bridge's API floor where it matters
(Ubuntu 24.04 ships 2.1; the bridge uses 2.4+ APIs), and version drift
across platforms is a support hazard — enable ocio-rs' bundled feature
and drop the OCIO_INSTALL_DIR/system-package wiring from CI and CD so
Linux, macOS and Windows all build the same vendored OCIO. cmake/make/
diffutils added where the runners lack them (Windows FFmpeg build needs
make + cmp).
- serializer resolves node types through the factory's dynamic
(runtime-registered OpenFX) entries, so a project carrying plugin
nodes loads again (was: "unknown node type"); covered by a new
CI-gated round-trip test driving the real fixture plugin
- built-in effect nodes expose their inputs as inspector parameters
like the C++ parameter editor: localized input names from the
behavior, combo option tables via the new
NodeBehavior::input_combo_strings (16 nodes, string-for-string from
the C++ set_combo_box_strings), connection/data inputs excluded
- effect library: live drag-and-drop — onto the inspector's effect
stack (lands at the indicator position) and onto the node editor
canvas (creates the node at the drop point); double-click still
appends to the selected clip
- inspector parameter controls are no longer recreated per render
(gpui stack view caches them per effect), so sliders drag and
checkboxes click; the view observes the engine and silently re-syncs
values (undo/redo land on the widgets)
- timeline: left-press selects clips (plain/keep-multi/Ctrl-Cmd
toggle); clip moves clamp the shared delta so no clip of a linked
group lands before frame 0 instead of failing with "invalid move
target"
- oakplugin: createInstance-rejected instances skip the destroyInstance
notification (the plugin never owned them); vendor-suite fetchSuite
misses moved behind OAK_OFX_TRACE; the worker logs the discovered/
registered plugin counts
- CI: the OFX probe step also runs the serialization round-trip test
- gpui submodule: params view caching, clip click-select, library
drag payload, graph_position_at
- new minimal C OFX plugin fixture (ci_test_plugin.c) compiled into a
real .ofx.bundle by build_fixture.sh; the CI step points
OFX_PLUGIN_PATH at it and asserts the scan_probe example discovers
AND registers it (Linux/macOS)
- host bundle binary search now also covers the OFX-standard Win64
platform directory
- cd.yml: restore the Windows NSIS packaging job (obsolete oakengine
cdylib prebuild dropped) and repair the job indentation that had
silently detached the macos/release jobs from the jobs: map;
releases now gate on all three platforms
- save() writes <config>/config.toml (atomic temp+rename), flat keys
at the top level and group/sub keys as [group] tables, values as
native TOML int/float/bool/string (non-finite doubles degrade to
strings and restore via the declared type)
- load() prefers config.toml; a legacy config.ini (C++ or pre-TOML
Rust builds) is read once and immediately re-persisted as TOML; the
INI file is left in place; a corrupt TOML is reported, never
silently discarded
- cd.yml: drop a stale oakengine comment (the crate is retired)
- NLE track growth is now a display concern: video/subtitle track
lists render reversed (a new track lands on top), audio lists render
in order (a new track lands at the bottom); the graph list always
appends. Track-add undo removes THIS track by id instead of blindly
removing the last one
- add_track returns the actual index of the new track (diffed against
the pre-command list) instead of assuming append-at-end
- status bar proxy segment reflects the real Use Proxy Media switch
instead of a static "Proxy: Off"
- proxy transcode PROGRESS events no longer invalidate the rendered
frame cache on every tick (only completion does) — progress updates
used to keep the playback cache permanently cold while generating
- effect library: live search box (name/type-id substring), Built-in
group header, and the addable-effects table is sorted alphabetically
(built-ins first, then OFX sub-category groups)
Real openfx-misc/CImg/Shadertoy bundles (148 plugins at
/Library/OFX/Plugins) all failed to load before; every failure was
silent. Root causes found one by one with a probe example + lldb:
- property suite rejected propSet on undefined properties and
propGetDimension on empty ones, and disallowed the index==size
append — OFX semantics are create-on-set and appendable dimensions
(this alone failed every plugin's describe)
- host property set missed the mandatory OfxPropType/OfxPropAPIVersion
and the capability props ofxs' fetchHostDescription reads with
throwOnFailure=true (IsBackground, TemporalClipAccess, MaxPages,
PageRowColumnCount, host SupportedContexts, ...) — one missing prop
aborted the read chain and left a half-initialised host description,
which made every temporal plugin refuse to load
- MultiThreadSuiteV1 lacked the five mutex functions (the plugin reads
past the short table — UB); implemented as a real counting-semaphore
registry
- the OfxHost struct was a stack local; ofxs keeps the POINTER past
setHost, so describe/render-time fetchSuite calls dereferenced a
dangling stack address (bus error once plugins actually loaded) —
the struct is now a leaked process global
- General is a standard OFX context and is no longer filtered out
(Roto/AppendClip/STMap declare only it)
- every scan/load/describe early-out now logs its reason; suite entry
points report non-OK statuses with caller location under
OAK_OFX_TRACE
- examples/scan_probe.rs: scans the real plugin dirs and prints
discovered/registered counts (also usable from CI)
Result: 148/148 plugins discovered, 134 registered as node types (the
remaining 14 need vendor suites — Vegas stereoscopic etc. — and are
logged, not silent)
- The Window menu lists every panel, checks the open ones, and toggles
visibility on click (closed panels reopen at their last dock target,
falling back to the default group) — a panel closed by accident
(e.g. the inspector) is one menu click away again. The menu refreshes
on dock structure events.
- OFX plugin scanning now covers the full standard location set:
per-user (~/.OFX/Plugins, ~/.local/share, ~/Library/OFX/Plugins on
macOS), system-level (/Library/OFX/Plugins, /usr/OFX/Plugins,
/usr/local, %ProgramFiles%\Common Files\OFX\Plugins), app-relative,
and the OFX_PLUGIN_PATH environment variable.
TrackPlaceBlockCommand::redo now homes the block's in point to the
placement target (capturing the original for undo): the Rust block
stores its position on the block, so a fresh clip that never had its
in point set always rendered at the timeline zero — the 'drops always
land at zero' bug. The original in point is captured on the first redo
and restored on undo, keeping the sync re-place round-trip exact. The
A/V drop test now asserts the clip lands at the drop frame.
- Main-process interact instances for the selected OFX effect card
(create on selection change, describe, destroy on deselect/close),
coexisting with the render-worker plugin instances per the OFX
multi-instance model.
- Program viewer composites the interact's overlay: draw into a GL
FBO via gl_bridge, read back, straight-alpha 'over' composite onto
the displayed frame; cached and only re-rendered on frame/time/
viewport/instance change or plugin redraw requests.
- Event forwarding: picture-area pointer maps through the contain-fit
letterbox inverse to OFX pen coordinates (pen_motion/down/up);
Keystroke to OFX key symbols (ASCII, navigation, F1-F35) for
key_down/up; a 50ms idle pump; global shortcut consumption keeps
precedence.
- e2e with the real test plugin: lifecycle marker assertions, pen/key
event records, and macOS GL overlay compositing verified (265 tests
green incl. gpui_widgets viewer suite).
- gl_bridge: macOS CGL offscreen context (process-wide singleton,
serialized GlGuard), real GL output textures/FBOs, glReadPixels
readback with vertical flip and format conversion; use_opengl now
really engages for OpenGLRenderSupported plugins (verified with real
GL rendering: C smoke 11/11, unit tests, GL e2e).
- OfxColor: color params get a swatch button plus a real picker popup
(RGBA sliders, live preview, hex input, undoable commit) replacing
the four spinboxes.
- Push buttons route kOfxActionInstanceChanged (UserEdited) per the
OFX contract; test plugin asserts the callback.
- Worker-side plugin progress flows to the main-process progress
dialog over the NDJSON control channel, with cancel propagation.
- OFX Interact host: NewInteract/Describe lifecycle, Draw/Pen/Key/Idle
action surface with proper in-args, DrawSuite v1 host implementation
sharing the gl_bridge context; interact test plugin verifies the
event stream and real GL drawing.
- Right-clicking an unselected clip selects it first (C++ parity) —
this is what made Cut/Delete appear to do nothing.
- Cut/Copy/Paste clipboard: clipboard_copy/cut/paste on the engine,
clipboard clips keep footage/range/speed/track kind and stay linked
in the pasted group; paste lands at the playhead as one undo entry.
- Dropping a video-with-audio footage places the video clip plus a
linked audio clip at the same range in ONE 'Add Clip' undo entry.
- Add Video/Audio Track buttons in the timeline toolbar and the track
header context menu; new sequences start with 2 video + 2 audio
tracks (not an undoable edit).
- oaknode Graph::add_entry now reclaims the slot from the free list —
before, a detached-then-reattached node left its slot in the free
list, so node_count undercounted and the next add_node silently
clobbered the restored node. This was the user's 'undo, redo, undo,
redo and the result changed' bug; regression covered by cycle tests
(move/trim/delete/split/add-track/linked-placement all converge).
FFmpeg 8 removed the standalone hardware decoders (h264_videotoolbox/
vaapi/nvdec/d3d11va no longer exist in its configure) — hardware decode
now only exists as a hwaccel attached to the software decoder. The new
oakcodec::hwdecode module therefore opens the regular decoder with the
platform's hardware device context attached (VideoToolbox on macOS,
VA-API then NVDEC on Linux, D3D11VA then NVDEC on Windows): FFmpeg
engages the matching hwaccel, decodes into hardware surfaces, and we
transfer them to system memory (NV12/P010) ahead of swscale.
- HardwareDecoding config switch, default ON by mandate; a checkbox in
Preferences > Rendering (EN/ZH); device creation failure skips to the
next candidate and finally to software; a decode-time failure on a
hardware session reopens it as software and retries once.
- hw_decoder_name() observability hook plus a HW_TRANSFERS counter so
tests can prove the hwaccel really engaged (not silently software).
- Verification: demo.mp4 H.264 decodes through VideoToolbox with a
transferred hardware surface, and the pixels match the software
decode within 0.05; switch off forces software.
- build-ffmpeg.sh also enables nvdec when ffnvcodec headers exist.
Two compounding causes behind 'playhead advances but the picture stays
frozen' and 'pause freezes the app':
- The wall-anchored clock teleported the playhead past the pre-render
window during any long stall (the first render after pressing play
costs seconds while the worker pool spins up: measured +104 frames in
one 4.1s block). The window then started behind and, with stale
in-flight frames occupying the workers, never converged.
RealClock::tick now clamps the advance to 2 frames/tick and
re-anchors the dropped time (NLE drop-frames semantics).
- Window frames the playhead had already passed stayed pending/in
flight, burning worker time on frames that could never be displayed.
update_preview_window now cancels them per tick via the new
JobDispatch::cancel_preview_frame, keeping the workers on frames
around the playhead.
Includes a production-shaped regression test (real 1080p media on the
timeline, actual cpu_frame display path) that failed with the exact
production signature (playhead 240 / displayed 0 / 36 stale slots)
before the fix and passes after.
The main-process sample showed the UI thread spending 100% of its time
in TicketArena::wait from the painted frame's synchronous render: every
cache-missed playhead frame sync-rendered inline, and the seek-priority
ticket then stole worker capacity from the pre-render window while the
blocked tick loop could not feed it — a self-reinforcing loop that made
playback unusably choppy.
On a playback miss the viewer now shows the last displayed frame while
the pre-render window warms up/catches up (paused monitors and the very
first frame keep the synchronous path). Adds a gpui test driving real
playback that requires the window to supply playhead frames, and the
real-footage bench_playback example used for the measurements
(152 fps aggregate on 1080p H.264 at 480p preview, decode-bound).
A claim mixing audio and video tickets is delivered as the video
message first and the audio message second, and the worker pops one
free-ring slot per ticket in that message order, checking each pop
against the assignment. The dispatcher however assigned slots in the
scheduler's interleaved frame order, so every audio ticket inside a
mixed batch mismatched, and each mismatch consumed a worker slot
without recycling it — cascading into the 'slot assignment mismatch'
flood and failed frames during playback.
Slot assignment now partitions the claim: video tickets first, then
audio. The mixed_audio_video integration test forces mixed claims
(queue depth > slot count with immediate releases) and fails with the
exact production signature when the fix is reverted.
Pressing play froze the app: the 120-frame pre-render window could
hold every shm slot in the pool (e.g. 8 workers x 3 F32 slots = 24 <
120). Once the wall-clock playhead outran the renders, the UI's
synchronous frame wait had no credit to dispatch, and the
slot-releasing cleanup runs on that same blocked UI thread — a hard
deadlock.
The window is now capped to (workers x slots - workers), reserving one
slot per worker so interactive (seek/sync display) and audio tickets
always dispatch. preview_window_capacity is exposed through
JobDispatch; a unit test pins the reserve math.
- Audio tickets join the process backend: render_audio_batch wire
message, workers mix straight into shm slots (SLOT_FORMAT_AUDIO_F32),
ShmAudio payload with release semantics, crash isolation covers audio
renders; playback audio uses an async 4-chunk prefetch drained on the
UI tick (also fixes the sub-60fps chunk truncation bug); oversized
ranges and dispatcher outages fall back to in-process inline.
- Per-ticket slot formats: force_format is honored (exports request
F32 slots, dropping the BGRA8 round-trip and its 8-bit quantization);
segments grow on demand via worker-idle rebuild with generation
handoff; the scheduler filters over-capacity tickets.
- Adaptive defaults: 128-256MB/worker segment budgets drive slots per
worker, batch size follows workers/slots; bench_process example
measures throughput and adjacent-frame completion deltas
(e.g. 4 workers: 841 fps, 4.6ms mean delta).
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.
- crates/oakengine moved to crates/oakengine.bk (excluded from the
workspace): the frozen C-ABI cdylib had no in-workspace consumers
left after the direct-rlib migration (M14); git history is the
authoritative backup.
- oakcommon: remove the CHandle module (no remaining users); config
store and shared value types are unaffected.
Part of the CHandle/unsafe cleanup: can_undo/can_redo/command_text/
command_is_done and command_init take raw pointers and are now unsafe
fn, with call sites wrapped in explicit unsafe blocks.
- New MulticamPanel: rows/cols angle grid with the current angle
highlighted, click-to-switch, 1-9 switch-and-split and cmd-1-9
switch-only shortcuts (focused-panel routed), deferred switch queue
during playback.
- src/oakui/multicam.rs: clip->connected-sequence resolution, multicam
state detection (selection then playhead fallbacks), per-angle frame
requests rendered through the process backend into an LRU cache.
- Timeline clip context menu Multi-Cam checkable item wired to
oaktimeline::multicam enable/disable with undo.
- Engine trait extended (real + mock); mock drives the real command
path with synthesized angle frames.
- WorkerPool thread pool deleted; RenderManager defaults to the
Processes backend (oak-worker children), Threads kept as a test-only
inline dispatcher; audio tickets stay in-process until S3.
- Onscreen path reads worker shm slots directly: BGRA8 slot format,
RenderedFrame::Shm wrapped into the display buffer (single disclosed
GPU-staging memcpy), scopes analyze BGRA8; the long-lived full-res /
thumbnail paths take the counted slot_to_vec copy and release.
- Playback pre-render window: forward 120 frames (configurable) fed to
the PreviewScheduler at Playback priority, interleaved across
workers, cached in shm slots until the playhead consumes them;
generation-based invalidation cancels and releases on edits.
- oaktask export and oak-cli run on private ProcessDispatchers (fixed
a pump-while-locked self-deadlock in the export loop); facade
get_frame handles ShmFrame payloads.
- Acceptance: preview path main_heap_frame_copies == 0 with spawned
workers, CLI transcode/render verified end to end.
- oaktimeline::multicam: clip_find_multicam (buffer/tex_in depth-1
lookup), multicam_enable/disable (rewire sequence<->clip through a
MultiCamNode), multicam_switch (split-preserving-links at the
playhead, each half owns an independent multicam copy, linked clips
switched together) as single undo commands with C++ labels.
- BlockSplitCommand now duplicates the clip's whole dependency graph
(copy_node_and_dependency_graph_minus_items) instead of just the
block core, matching the C++ BlockSplitCommand::prepare semantics;
undo detaches the copied subgraph, redo re-attaches identity-
preserving.
- oaknode: fix serializer dropping edges from the first-created node
(ptr=0 was not registered in id_map), restoring sequence_in edge
round-trips; multicam node and clip wiring serializer round-trip
tests.
Proxy: preview-path proxy substitution (global UseProxyMedia AND
per-footage enabled AND on-disk ready; export always uses originals),
proxy generate/delete/reveal/enable actions, ProxyDialog with global
and per-footage custom params, Tools menu + context-menu Proxy
submenus, progress in the status bar, OVE serialization of proxy
metadata and source_start_time.
Sync: timeline context-menu Synchronize by Source Time / by Waveform /
by Waveform (Adjust Speed) with ctrl-shift-w, cache-envelope
extraction with validity masks, reference/anchor selection and
single multi-undo application (replace-with-gap, speed adjust,
re-place) mirroring timelinewidget.cpp semantics.
FootageBehavior now keeps the probed stream list (video/audio, per-stream
duration in rationals) instead of dropping it, and the probe CLI walks
that inventory to report real durations, frame rates and stream counts
rather than the previous zero placeholders.