fix: a still frame's measurements are the frame's, not the viewport's
A measurement is a statistic of the window the pane is *about*, and on a pinned view -- a held event, a paused stream, an opened file -- that is the frame, not the part of it on screen. The pane followed the viewport instead, so a wheel on a latched event moved `average` and `top`: the reported "when measuring a still frame, moving the zoom makes measurements such as average or top change". Nothing about the data had changed; the user was only looking closer. The window is `RowSet::pinned_range()` while the pin frames the view and the viewport otherwise, and the choice is made in `update_measures()` -- the call site -- rather than inside `MeasureWindowReader`, because it is *booked* as well as read: that window is what the "it moved" test compares against `App::measure_window`, so a gesture inside the pin spends no recompute either, and a reader that chose its own would leave a pinned view recomputing every gesture frame for a number that cannot change. `pinned_range()` is now the one expression of the pin's extent, shared with the framing (`hold_pinned_window()`, `fit_x()`), and the reader is handed the window instead of reading the plot. Live is unchanged: there the window really moves and the statistic follows it, and the accumulator's rule about what resets the history is about neither. `UDPSCOPE_PROBE_MEASUREPIN_MS` is the guard, three legs of before/after a gesture on the row's own axis. On a latched event zoomed to a quarter (`view=[-1..1]` -> `[-0.25..0.25]`, `pin` unchanged): `same=4/4 for_same=1 pass_delta=0`, every value bit-identical -- `average=0.000000 top=0.957975 peak to peak=2.000000 amplitude=1.915950` both sides. The same on a pause taken from it. Since the key `for=` follows `pin=` now, `[measure-trace]` gained the field, so a held run reads `for==pin` with `swin` free to sit inside it. On a live view a zoom to an eighth reads `same=0/4 for_same=0 pass_delta=3 span_after=0.125000 follows=1` with the values moving -- the case the rule must not swallow. Before the fix the same probe reads `same=1/4 for_same=0 pass_delta=1 span_after=0.500000` on the held event (`top` `0.957975` -> `0.957966`) and `same=0/4 span_after=0.125000` on the pause (`top` `0.957944` -> `0.358614`). Live reads the same either way. Also fixed, found by the pause leg: pausing while an event is *held* froze an **empty** picture. `on_pause_toggled()` read `stream_at_x0()` after setting the pause state, and that accessor follows the display -- `paused` with no pause view published yet answers `-display_epoch()`, which on a held event is 0, so the cut searched the ring at edge-relative x, found nothing, and the frame bound an empty snapshot: every measurement `--`, every ruler gone (`ev=650001` on the probe's line now, `ev=0` before). The offset and the window are read first, which is the offset the axis is actually displaying. The live case is unaffected, which is why the pause probe, which pauses a live view, never saw it. Docs corrected where they asserted the old half-truth: the invariants now separate the accumulator's rule (unchanged) from the window a value is computed over (this change), the trigger/pinning file names the pin as load-bearing for both the framing and the numbers and records the pause ordering rule, `verification.md`/`probe-readings.md` describe the new probe and `pin=`, and the README and the manual say what a still frame measures. Checked: `cmake --build build --clean-first -j4` 0 errors and the documented three warnings; `make -C build test-ui` 13 ok; `make -C build test-help` green; an exit-dump run 0 Gtk-WARNING/Gtk-CRITICAL and `gtk warnings: 0`; `[measurepin-probe]`, `[pan-probe]` (`held=1`, `allowed=1`, `limited=1`), `[pause-probe]` (`event_size` non-zero exactly while paused, `axis` inside `frozen_x`, `skips` 0 live / 30 paused), `[stats-probe]` (`dn=1` across the pause), `[zoom-probe]` (`gap_px=0.0` at every step) and `[measure-probe]` + the dump's `measures window:` (all-types run `marks_in_window=4/4`, `pin=off` live) on the fast mock. `measure cost:` is unchanged in kind: `raw=37298 (0.0%)` of `covered=121704537`, 1.04 ms a pass -- the runs that read higher are the probe's own zoomed-to-a-slice windows, where the window is legitimately shorter than a bucket.
This commit is contained in:
1 parent
d91957a2fe
commit
0a209becb1
13 files changed
+719
-114
No files matched your search
@@ -377,23 +377,37 @@ range") only for `between`, which is the one type whose two edges are both param
|
||||
words on the split row name its side ("above % of the range" for `above`) — and the "show on plot"
|
||||
line says what would be drawn.
|
||||
|
||||
The values are **window statistics over exactly the visible range, at the stored rate**: the
|
||||
same cut the export makes, so a measurement describes the data on screen rather than the ring
|
||||
behind it — and the marks it draws belong to that window, so they stay in the viewport.
|
||||
The values are **window statistics over the window the pane is about, at the stored rate**: the
|
||||
same cut the export makes, so a measurement describes data rather than the ring behind it — and
|
||||
the marks it draws belong to that window.
|
||||
|
||||
They are recomputed when the **window moves**, not on a timer: a timer decouples the two, and
|
||||
**Which** window, when the view is pinned, is the frame and not the viewport. A held event or a
|
||||
paused stream is a *still frame*: the data behind it cannot change, so a pan or a zoom there is
|
||||
only looking closer, and the pane goes on describing the **pin's own extent**
|
||||
(`RowSet::pinned_range()` — the same statement the framing itself is clamped into, so "what this
|
||||
view is a view of" has one expression). Zoom into a latched event and the numbers do not move:
|
||||
that is what a still picture means, and it is the reported *"when measuring a still frame, moving
|
||||
the zoom makes measurements such as average or top change"*. A live view is the other case and is
|
||||
unchanged: there the window genuinely moves and the statistic follows it.
|
||||
|
||||
They are recomputed when that **window moves**, not on a timer: a timer decouples the two, and
|
||||
with a scrolling window the stored marks then describe a range the plot has already left (the
|
||||
min/max marker ends up outside the viewport, and a dragged marker survives until the next
|
||||
tick). The trigger is 2 % of the span, so the numbers track the window within a few pixels
|
||||
while the cost stays bounded by the window's motion rather than by the frame rate; one pass
|
||||
copies the visible samples of each *channel* once, however many measurements read it.
|
||||
|
||||
"The window moved" is asked in **every** mode, including a held trigger event — which is the
|
||||
one mode where a zoom or a pan is the only thing that can move it. Held views were once
|
||||
exempted, on the grounds that a pinned window must not recompute every frame, and the result
|
||||
was a pane that kept describing the whole captured event while you zoomed into a slice of it.
|
||||
A pinned window needs no exemption: it lands on exactly the range it already measured and
|
||||
fails the 2 % test by itself.
|
||||
"The window moved" is asked in **every** mode, but it is asked about the window the pane
|
||||
*describes*. Suppressing it while a view is held left the pane describing the whole captured
|
||||
event while you zoomed into a slice of it; dropping the exemption made the opposite mistake — the
|
||||
numbers followed the zoom, so a still frame's `average` moved whenever the wheel did. On a pinned
|
||||
view neither can happen: the pin is what the pane is about, so a gesture moves no number and
|
||||
spends no recompute, while a fresh latch, a re-stated pin or a new snapshot still recomputes it.
|
||||
The one path that is *not* the pin is the window control, which unpins as it re-frames
|
||||
(`apply_time_window()`), so for that frame the view really is live-framed and measuring the
|
||||
viewport is right. `UDPSCOPE_PROBE_MEASUREPIN_MS` is the guard: a gesture on a held event and on a
|
||||
pause must leave every value bit-identical (`same=4/4`, `pass_delta=0`), and on a live view the
|
||||
results must still narrow with the axis (`follows=1`).
|
||||
|
||||
**Show on plot** marks the result on the row that plots the signal: a ruler at the level
|
||||
(`top`/`bottom`), both rulers at the extremes (`min`/`max`), the pulse's or the cycle's two
|
||||
@@ -534,9 +548,11 @@ loses that spread entirely.
|
||||
**Reset** clears every accumulator. So does editing, adding or deleting a measurement, a CONFIG
|
||||
that renames the signals, and a change of the display window — each of those changes *what is
|
||||
being measured*, and a mean across the change describes nothing. A pause, a resume, a trigger
|
||||
latch, a pan and a zoom do **not** reset: they change which samples are in view, which is
|
||||
exactly what a statistic over many windows is meant to average. The toggle itself does not
|
||||
reset either, so turning the line off to read the plot and back on again keeps the history.
|
||||
latch, a pan and a zoom do **not** reset: on a live view they change which samples are in view,
|
||||
which is exactly what a statistic over many windows is meant to average — and on a pinned one they
|
||||
change nothing at all, values included (above), so the history has nothing to see either way. The
|
||||
toggle itself does not reset either, so turning the line off to read the plot and back on again
|
||||
keeps the history.
|
||||
|
||||
## Resequencing and relative timing
|
||||
|
||||
@@ -1556,6 +1572,17 @@ event pin already worked this way, and the pause agrees with it. `[pause-probe]`
|
||||
beside `frozen_x=`, and while paused the axis must lie *inside* `frozen_x` — equal at the pause, a
|
||||
subset after a zoom-in.
|
||||
|
||||
**The pause reads its display offset *before* it sets the pause state**, and pausing while an event
|
||||
is held is the case that depends on the order. `stream_at_x0()` follows the *display*: the moment
|
||||
`paused` is true with no pause view published yet it answers `-display_epoch()`, which on a held
|
||||
event is `0` — so the cut searched the ring at the event's edge-relative x, found no samples, and
|
||||
froze an **empty** picture: no trace, every measurement `--`, every ruler gone. Read first, the
|
||||
offset is the one the axis is actually displaying, which is what hands the snapshot stamps back on
|
||||
the coordinates the axis is already showing. The live case is unaffected (its epoch does not depend
|
||||
on `paused()`), which is why the pause probe, which pauses a live view, never caught it;
|
||||
`[measurepin-probe]`'s `paused/before` leg pauses a *held* event and prints the bound frame's own
|
||||
sample count (`ev=650001` for a quarter-of-the-event zoom), which is `ev=0` when this regresses.
|
||||
|
||||
**Zooming into a held event clipped it further and further from the left**, and the cause was the
|
||||
one the library's own margin fix is about, in the half of the code the fix could not reach.
|
||||
`EventSeries::neighbours()` — the call the renderer maps the visible range onto sample indices with
|
||||
|
||||
Reference in new issue
Block a user