Skip to main content

Data Flow Between Threads

Two patterns cover almost all cross-thread traffic: the playback loop, where the audio clock drives video decode and compositing, and session jobs, where background work reports back through a waker and Session::tick.

Playback

playback-flow.png
  • Video. After an edit the UI sends RenderAhead a clone of the project. The thread walks forward from the target frame (and a little behind it), decodes into the SharedFrameCache under a global RAM budget, and the UI composites whatever is cached for the current frame.
  • Audio. The UI builds an immutable MixSnapshot (clips, channel strips, effects) whose audio buffers come from the mix_buffers workers, and publishes it as an Arc<MixerState>. The callback picks it up at the start of a block and runs mix_range, the same pure function the export uses.
  • Clock. The callback's position is the playhead. Without an audio device the clock falls back to wall time.

Session jobs

job-flow.png

Import, OTIO import, relink and export all follow this shape. The thread that owns the Session (the UI thread, or the MCP host) starts the job and gets a JobId. The job never touches the session: it writes progress into shared atomics and wakes the owner when done. The owner's next tick() joins the thread and returns SessionEvents, and applying them (for example adding the imported media) goes through the history like any other edit.

Synchronisation primitives

PrimitiveUsed for
std::sync::mpscJob queues and results of every worker; RenderAhead commands; recorder chunks; MCP requests
AtomicsPlayhead, play state, peaks, progress counters, cancel flags
MutexFrame cache, export progress, pending mixer state (callback side: try_lock only)
Mutex + CondvarProxy worker pause; the MCP session slot
sync_channel (bounded)Export stages, so decode cannot run far ahead of encode
tokio::sync::oneshotThe reply to one MCP tool call