Thread Map
Venturi has one owner of the document at a time: the UI thread holds the Session (project + undo history), and only lends it to the MCP host thread between frames when MCP is enabled. Edits are applied only by whoever holds it. Every other thread works on a copy, a snapshot or its own inputs, and talks back through channels, atomics or a small shared slot.
Long-lived threads

The serial workers (proxy, waveform, thumbnail, the three mix_buffers queues) share one helper, vv_session::worker::Worker: an mpsc job queue drained by one thread, closed and joined on drop. RenderAhead has its own loop because it must react to playhead jumps mid-decode.
Short-lived threads
| Thread | Spawned by | Why off the UI thread |
|---|---|---|
| Import probes (one per core, up to one per file) | vv_session::import_worker | Probing a video costs ~100 ms |
| OTIO read | Session::import_otio | Reads the file and probes its media |
| Relink | relink_job::spawn | Indexes folders by file name |
forced-relink | forced_relink::spawn_search | Searches a folder tree for matching media |
| Export + 3 stage threads | Session::export | Decode, compose, encode and audio mix run in parallel (see chapter 6) |
| Silence analysis | silence_dialog | Runs VAD over each media's audio |
| Fast-forward stretch | timeline_audio | Stretches 8 s windows of the mix, one request at a time |
| File dialogs | project_io, export_dialog | A native dialog on the event-loop thread marks the app unresponsive |
| MCP media tools | vv_mcp::media_tools | render_frame and get_audio_levels decode on a project snapshot |
| HW decode probe | hw_decode::start | GPU enumeration and CUDA init take up to a few hundred ms |
| Encoder check | vv_media::encode | Each HW encoder is tested with a real encode |
| Font warm-up | vv_render::text::warm_up | The system font scan would stall the first title |
| Drop read | wayland_dnd | Reading a drop waits on the source app |
| stdin pump | vv_mcp::bridge_stdio | --attach copies both directions at once |
Rules for the audio callback
| Rule | How |
|---|---|
| Never block | Only try_lock: a busy lock means "keep the current state" or one block of silence. |
| Never allocate or free | MixerState is built on the UI thread, which also keeps every published state alive until the callback has let go of it, so the last drop happens there. |
| Own the clock | The callback advances position with a compare-exchange, so a seek that lands mid-block wins. |
No comments to display
No comments to display