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
+29
-5
@@ -484,6 +484,11 @@ struct App {
|
||||
/// `[-window, 0]` whatever arrives, so "the window moved" is invisible in display coordinates
|
||||
/// and the pane would compute once and then never again -- the readout frozen on the samples
|
||||
/// that happened to be on screen when the pane opened.
|
||||
///
|
||||
/// **Which** window that is depends on the view: the viewport while the view is live, and the
|
||||
/// **pin** (`RowSet::pinned_range()`, captured in `measure_pin_window` below) while it is
|
||||
/// pinned. A still frame is measured as a frame, so a gesture inside the pin moves neither this
|
||||
/// nor any number.
|
||||
plot::Range measure_window{};
|
||||
bool measure_window_valid = false;
|
||||
bool measure_was_held = false;
|
||||
@@ -493,7 +498,16 @@ struct App {
|
||||
/// destroy handler, after the exit probe has switched the trigger on, and `stream_at_x0()` by
|
||||
/// then answers for a mode no frame was ever drawn in -- which read as a 45000 % drift on a run
|
||||
/// whose every frame was exact.
|
||||
///
|
||||
/// While the view is pinned this and `measure_window` are *allowed* to differ: the pane
|
||||
/// describes the pin, and a gesture may have narrowed what is on screen to a slice of it.
|
||||
plot::Range measure_view_window{};
|
||||
/// The pin's own extent in the same stream seconds, and whether the view was pinned when the
|
||||
/// tick read it. This is the window a pinned view's results describe, so the dump measures its
|
||||
/// drift against it -- otherwise a zoomed held event would read as a pane describing a window
|
||||
/// the plot has left, which is precisely what it must do there.
|
||||
plot::Range measure_pin_window{};
|
||||
bool measure_pin_valid = false;
|
||||
/// What the two walks over one channel's visible window produced, shared by every measurement
|
||||
/// on that signal: the window's statistics, and one accumulator per *distinct level* any of
|
||||
/// them asked for. Several measurements on one signal must cost two walks, not two each -- and
|
||||
@@ -1076,16 +1090,22 @@ struct ColorPick {
|
||||
};
|
||||
class MeasureWindowReader {
|
||||
public:
|
||||
MeasureWindowReader(App& app, std::size_t channel) {
|
||||
/// `window` is the x range to read, **in the coordinates the axes read** -- the viewport for a
|
||||
/// live view, the pin for a pinned one -- and it is handed in rather than read off the plot
|
||||
/// here, because *which* window a measurement describes is a decision (`update_measures()`)
|
||||
/// that also has to be booked: the pane compares the window it last measured against this one
|
||||
/// to decide whether a recompute is owed, and a reader that chose its own would leave the two
|
||||
/// disagreeing -- a pinned view recomputing on every gesture frame for a number that cannot
|
||||
/// change.
|
||||
MeasureWindowReader(App& app, std::size_t channel, plot::Range window) {
|
||||
const RowSet::Row& r0 = app.rows->row(0);
|
||||
if (r0.plot == nullptr) return;
|
||||
const plot::Range view = r0.plot->x_range(r0.axis_x);
|
||||
if ((snap_ = app.model->display_snapshot()) != nullptr) {
|
||||
if (channel >= snap_->values.size()) return;
|
||||
const std::vector<f64>& st = snap_->stamps;
|
||||
if (snap_->values[channel].size() != st.size()) return;
|
||||
const auto lo = std::lower_bound(st.begin(), st.end(), view.min);
|
||||
const auto hi = std::upper_bound(st.begin(), st.end(), view.max);
|
||||
const auto lo = std::lower_bound(st.begin(), st.end(), window.min);
|
||||
const auto hi = std::upper_bound(st.begin(), st.end(), window.max);
|
||||
if (hi <= lo) return;
|
||||
off_ = static_cast<std::size_t>(lo - st.begin());
|
||||
count_ = static_cast<std::size_t>(hi - lo);
|
||||
@@ -1101,7 +1121,7 @@ class MeasureWindowReader {
|
||||
epoch_ = app.model->display_epoch();
|
||||
u64 first = 0;
|
||||
u64 last = 0;
|
||||
app.model->live_window_locked(view.min - epoch_, view.max - epoch_, first, last);
|
||||
app.model->live_window_locked(window.min - epoch_, window.max - epoch_, first, last);
|
||||
if (last <= first) return;
|
||||
store_ = &app.model->live();
|
||||
channel_ = channel;
|
||||
@@ -1710,6 +1730,10 @@ gboolean mathwrap_probe(gpointer user);
|
||||
gboolean mathwrap_probe_wait(gpointer user);
|
||||
gboolean mathwrap_probe_report(gpointer user);
|
||||
gboolean measure_probe(gpointer user);
|
||||
gboolean measurepin_probe(gpointer user);
|
||||
gboolean measurepin_held_wait(gpointer user);
|
||||
gboolean measurepin_pause_wait(gpointer user);
|
||||
gboolean measurepin_live_wait(gpointer user);
|
||||
gboolean measure_trace(gpointer user);
|
||||
gboolean stats_probe_report(gpointer user);
|
||||
gboolean stats_probe(gpointer user);
|
||||
|
||||
Reference in new issue
Block a user