fixed event/live transition
This commit is contained in:
1 parent
bb99be5889
commit
881b52748e
9 files changed
+314
-2
No files matched your search
@@ -59,6 +59,17 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
|
||||
out — a source whose streamer was merely down during one run would silently vanish from the
|
||||
only list it can be picked from, for ever. Keeping the old file loses nothing: a run that
|
||||
configured nothing had nothing worth persisting.
|
||||
- **It refuses again while `replaying` is set: a file view is not the source's session.** Same
|
||||
argument, different cause — this view configured nothing about the stream. A plain replay leaves
|
||||
the rows where the user put them, so its save would merely be pointless; a **group** rebinds every
|
||||
slot to its own renamed channels and the save wrote `signals = ["square (A)", …]` into the file.
|
||||
Nothing noticed until the way back: *Go live* re-applies the session, those names match no live
|
||||
signal, so nothing was enabled and **the live view came back with no channels at all** — on the
|
||||
window the poisoned file carried, which is also how it swallowed the window restore below it. The
|
||||
reading is `[events] live restored (after a group)`: the symptom was a stuck `pending=1` beside
|
||||
`channels=0`, because the restore waits for a channel list that was never going to arrive. The
|
||||
explicit *Save as* is deliberately **not** guarded: that one is a user's own answer to "save this
|
||||
view", and its dialog reports the outcome.
|
||||
|
||||
## A configuration is a named session, and a source may hold several
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -992,6 +992,7 @@ configuration that carries settings, is the defect this exists for — a saved h
|
||||
[events] activated row0: replaying=1 pane=1 pane_w=240 pane_box=228 tick=0 window=2.000 axis=[-1.600,0.400] marker=0.000 thumbnail=1 thumb_size=138x46 thumb_visible=1 rows_kept=1 selected=2/2
|
||||
[events] replayed: channels=3 samples=20001 replaying=1 waveform=1
|
||||
[events] live again: replaying=0 waveform=0
|
||||
[events] live restored (after replay): window=0.500000 (want 0.500000) trigger=1 (want 1) channel='square' (want 'square') level=0 (want 0) enabled=1 armed=1 pinned=0 held=0 mode=2 pending=0 gen=10/5
|
||||
[events] recording off, trigger off: button_visible=1 pane=1 pane_w=240 tick=1 recording=0
|
||||
[events] recording off, trigger on: button_visible=1 pane=1 pane_w=240 tick=1 recording=0
|
||||
[events] compare buttons: cached=2 selected=2 compare=1 accumulate=1
|
||||
@@ -1000,6 +1001,7 @@ configuration that carries settings, is the defect this exists for — a saved h
|
||||
[events] compare sync: y_a=-3.500 y_b=-3.500 mirrored=1
|
||||
[events] accumulate: kind=2 rows=1 channels=8 samples0=32964 selected=2 traces0=8 window=2.000
|
||||
[events] group left: replaying=0 kind=0 waveform=0
|
||||
[events] live restored (after a group): window=0.500000 (want 0.500000) trigger=1 (want 1) channel='square' (want 'square') level=0 (want 0) enabled=1 armed=1 pinned=0 held=0 mode=2 pending=0 gen=22/16
|
||||
```
|
||||
|
||||
`thumb_size` is the built size of the row's picture: `kEventThumbW` wide, and as tall as the *stored*
|
||||
@@ -1016,6 +1018,21 @@ header — `480` wide and `44 * signals` tall — and `row_drawn=1` says the row
|
||||
The probe therefore needs a cache a **previous** run populated: it fires ~4.5 s in, before this run's
|
||||
first latch.
|
||||
|
||||
`[events] live restored (…)` is the way *back*, read once per way into a file view — after a plain
|
||||
replay and after a group — and four of its fields are a **pair**: the value now, and the value the
|
||||
live view held before the file view opened over it. The probe captures that expectation itself
|
||||
(`capture_live_expectation()`, `App::dbg_live_*`) rather than asking the restore path for it: a
|
||||
reading taken from the mechanism under test asserts nothing. The window is set to 0.5 s just before
|
||||
the event is opened precisely so the pair can disagree — with the event's own span there would be
|
||||
nothing to tell "restored" from "untouched".
|
||||
|
||||
Both lines are read a *later* step, not in the handler that pressed *Live*: the restore waits for the
|
||||
restarted client's first CONFIG, so it is performed by a tick ~900 ms on. `pending=0` is what says it
|
||||
happened; `gen=a/b` is the generation now against the one *Go live* read. A **stuck `pending=1`** is
|
||||
not a reading about the restore but about the stream behind it — the generation never moved, or the
|
||||
channel list came back empty, which is how a session file poisoned by a save taken during a group
|
||||
produced `pending=1 channels=0` and a live view with nothing on it.
|
||||
|
||||
| field | value it must read | the defect a wrong reading names |
|
||||
| --- | --- | --- |
|
||||
| `n`, `stem`, `seq` | one entry per latch, named by its time and the run's counter | two events in one second overwriting each other, or a cache that is never written |
|
||||
@@ -1032,6 +1049,9 @@ first latch.
|
||||
| `axis`, `marker`, `pane_box` (in `activated row0`) | `[-pre, +post]` of the event just opened, `marker=0.000`, and a `pane_box` close to `pane_w` | an opened event framed on something other than its own pin; a trigger line left **parked** (a large negative) rather than on the edge, which is a file opened without its marker; or a pane whose *rendered* width is 0 while the divider says 240 -- the position is what was asked for, `pane_box` is what the pane actually got, and a divider at 240 with a zero-width pane is still no pane |
|
||||
| `rows_kept`, `selected` (in `activated row0`) | `rows_kept=1` and `selected=N/N` -- the counts either side of the activation equal | the list **rebuilt** by the activation. It is the row *object* that is compared, not its index: a rebuilt list has a row 0 too, and only identity tells a list that was left alone from one that was torn down under the user's scroll position and their comparison selection. `mark_events_on_screen()` must rewrite the titles in place |
|
||||
| `replaying` (last line) | 0 | a replay with no way back: *Live* must start a fresh client and thread |
|
||||
| `window`, `trigger`, `channel`, `level` (in `live restored`) | each equal to its `(want …)` | the way back out of a file view discarding what the live view was set to. One wrong half names which writer won: a `window` that reads the **session file's** value is the deferred `apply_layout()` beating the restore; `trigger=0` is the toggle the file view switched off and nobody switched back; an empty or shifted `channel` is an index restored instead of a name, after a rebuild renumbered (or a group renamed) the channels; and a `level` that moved on its own is `rebuild_trigger_signals()` re-defaulting it because `trigger_level_touched` was not restored with the configuration |
|
||||
| `pending`, `gen` (in `live restored`) | `pending=0`, and the two generations different | the restore still waiting. It is gated on the stream's first CONFIG, so `pending=1` says the channel list the stored trigger name needs never arrived -- with `channels=0` behind it, a session file that enabled nothing |
|
||||
| `pinned`, `held`, `mode` (in `live restored`) | `pinned=0`, **or** `pinned=1` with `held=1` | a file's pin surviving the way back, which is a live view that cannot be navigated. `apply_time_window()` drops it; the second reading is the legitimate case -- a restored trigger that was *on* can latch again before the probe reads, and a held capture is pinned, correctly |
|
||||
| `pane_w` | the divider's position: 0 while the pane is closed, its remembered width (240 by default) while it is open | the pane reserving a column while closed (a paned hands its start child its minimum unless the position says otherwise), or a width that is not remembered |
|
||||
| `tick` | 1 in live mode, **0** once a file is on screen | the signal list still offering "acquire this signal" for samples that are already stored |
|
||||
| `window` (in `activated row0`) | the **event's** span -- the probe sets 0.5 s just before the click, so reading the event's span says opening it framed it | a replay that leaves the previous window alone, or one that restates it every frame instead of once |
|
||||
|
||||
Reference in new issue
Block a user