fixed event/live transition

This commit is contained in:
Martino Ferrari committed 2026-10-13 23:37:49 +02:00
1 parent bb99be5889
commit 881b52748e
9 files changed
+314 -2

No files matched your search

@@ -182,6 +182,53 @@ deliberately **not** one of the conditions: a stored event being examined is a f
trigger, and tying the pane to the trigger would close the list under the click that opened an event.
The recording switch lives in the trigger panel, not in the pane it empties.
## Opening a file is a detour, so *Go live* is a restore
A file view overwrites the live view's settings and every overwrite has a reason: the window becomes
the event's own span because that is what *framing* an event means, and the trigger goes off because
a file has no edge to wait for (leaving it on kept a frozen capture on screen *instead* of the file,
so the click looked like it did nothing at all). None of those reasons survive the way back. What
the user had set is theirs, and "going back to live loses the trigger I spent a minute setting up"
is the complaint this answers.
- **`snapshot_live_state()` on the way in, `restore_live_state()` on the way out.** The snapshot is
taken by both ways into a file view -- `replay_event()` and `build_event_group()` -- *before* the
acquisition is stopped, and is a **no-op while `replaying`**: a second event opened from the first,
or a group opened from a replay, would otherwise snapshot a file's settings as "live", which are
the very values the restore exists to undo. The group takes it after its argument checks, because a
group that is refused leaves the live view alone.
- **The trigger channel is stored by *name*.** Same reason `TriggerEngine::configure()` takes one: the
index is a position in a channel list that a file view rebuilds, and a group **renames** its
channels outright (`square (A)`). Putting an index back would select a different signal.
- **`enabled` and `armed` are deliberately not restored from the snapshot.** They belong to the
toolbar toggle, which owns an atomic mirror of `enabled` (`set_trigger_enabled()`) that a plain
field write under the lock would leave stale. The restore writes the rest of the `TriggerConfig`
and then moves the *toggle*, whose handler states both.
- **The restore runs from the tick, not from `go_live_again()`, and *after* the deferred layout
block.** Two writers would otherwise beat it:
- `apply_layout()` ends in `apply_time_window(st.window_s)` -- the window the **session file**
holds, not the one the live view had. `go_live_again()` clears `layout_loaded`, so that block
fires on the next tick; a restore done inside `go_live_again()` is overwritten by the saved value
a frame later. Being the next statement after it in `tick()` is the fix.
- `rebuild_trigger_signals()`, finding a `channel` of `-1` after the rebuild renumbered the
channels, silently settles on the first signal *and* re-defaults the level -- which is why
`trigger_level_touched` is restored with the configuration.
- **It waits for the stream's own channel list, gated on `generation()` having moved.** The restored
name has to be resolved against the live channels, and those do not exist until the first CONFIG of
the restarted client lands on the producer thread. `App::live_restore_generation` is the generation
read when *Go live* was pressed; `live_restore_pending` stays set until it differs **and**
`channels()` is non-empty. A `pending=1` that never clears is therefore a reading about the
*stream*, not about the restore -- see `invariants-session-and-export.md` for the save that made it
stick at `channels=0`.
- **Only the two controls a file view actually moved are restated** -- the signal dropdown and the
level spin. The rest of the trigger panel was never touched, and writing to a control fires its
handler, so the ones with nothing to put back are left alone. Both writes are inside
`updating_panels`.
- **`pinned` is not a pass/fail reading on the way back.** `apply_time_window()` drops the file's pin
and re-arms the live follow, but if the restored trigger was *on* it can latch again within the
frame or two before the probe reads -- and a held capture is pinned, correctly. That is what
`held=` and `mode=` are beside it for.
## A comparison is two plots of one model
*Compare* and *accumulate* are still one model -- the buffers cannot hold two independent loads -- but a