scope: full-screen figures, second windows, gated measurements, per-configuration events
A batch of view and instrument work, with the probes and docs for each. - **Full screen per figure row**: a toggle on each row's top-right corner, revealed by pointer proximity (`kMaximizeRevealPx` around its placed rect, per row, read from an overlay-level motion controller so a revealed button under the pointer cannot flicker); the plot menu offers the same as *Full screen this figure* / *Restore the layout*. The arrangement is saved and restored through `RowState`, so nothing plotted moves. A layout of one figure carries no toggle. - **Second window**: `open_scope_window()` copies a figure (cells, width shares, signals in their colours) or opens an empty one, with its own `RowSet` and `PlotArea`; the main window's tick binds, acquires and repaints it, mirroring the time window and pin. Entries: the plot menu's *Open this plot in a new window* and the overflow menu's *New scope window*. - **Layout editor from the plot**: the plot menu gains *Edit the layout...* / *Done editing the layout*, flipping the app's one toggle. - **Measurements gate**: `MeasureOptions::gate` restricts a measurement to the held/pause snapshot (live falls back to the live ring), persisted with the session and shown in the measure dialog. - **Crosshair axis badges**: a preference (`crosshair_axis_labels`, on by default) and the plotter's badges where the lines meet the axes, in the axes' own formats; the plot menu and the probe read them. - **Event cache per configuration**: `EventStore` is keyed `<source>/<configuration>/`, `retarget_event_store()` follows every configuration switch, and a pre-configuration cache (loose files) is adopted into the configuration's folder on first start. - **GTK4 lifecycle**: the main window's handlers move from the non-existent `"destroy"` signal to `close-request`; scope windows are closed with it and the application quits explicitly (the exit run's hang). The exit probe closes with `gtk_window_close()`. - Probes: `[maximize-probe]` (single-figure rule and the corner reveal), `[scope-probe]`, `[split-probe]` cell crosshair leg, `[prefs-probe]` store keying, `[configs]` store key, `[rowmenu]` layout-editor entry; docs, help pages, README and AGENT.md updated. Also fixes a probe-only use-after-free: `toggle_maximize_figure_row` replaces the figure object, so the area must be re-pointed (the button's path does; the probe's raw call did not).
This commit is contained in:
1 parent
bfe13630f9
commit
bdf0ff7ddc
39 files changed
+2267
-136
No files matched your search
@@ -60,7 +60,8 @@ see [probe-readings.md](probe-readings.md).
|
||||
|
||||
That second baseline used to be "exactly two, `gtk_widget_get_height` and `gtk_widget_get_width`,
|
||||
from the dump asking a destroyed widget for its size", and tolerating them was a mistake: the dump
|
||||
runs from the destroy handler, so the widget pointer is **freed memory**, and a critical is only
|
||||
runs **after `g_application_run` returns**, so the window and every widget in it are gone -- the pointer
|
||||
is **freed memory**, and a critical is only
|
||||
the lucky reading of it. A run that had built and destroyed other windows first —
|
||||
`UDPSCOPE_PROBE_HELP_MS`, which opens the About and shortcut dialogs and then closes them — has had
|
||||
that memory reused, and the same two calls
|
||||
@@ -74,7 +75,26 @@ precedent, not an exception.
|
||||
then dump the whole pipeline state to stdout (frame/sample counts, per-plot painted
|
||||
frames, trace bindings, panel sizes, status line in both states).
|
||||
- `UDPSCOPE_ENABLE_ALL_MS`, `UDPSCOPE_ARM_TRIGGER_MS`, `UDPSCOPE_PROBE_DISARM_MS`,
|
||||
`UDPSCOPE_TRIG_TRACE_MS` — schedule individual probes. `UDPSCOPE_PROBE_DISARM_MS` only
|
||||
`UDPSCOPE_TRIG_TRACE_MS` — schedule individual probes. `UDPSCOPE_ARM_TRIGGER_MS` clicks the
|
||||
trigger toggle and then samples the **trigger status circle** once a second for four
|
||||
seconds: an armed, waiting trigger reads `circle=flash-lit` / `circle=flash-dim` (the two
|
||||
halves of the 1 Hz flash, in the phase the sample lands in), a latched event `circle=green`,
|
||||
a disarm `circle=red`; the exit dump carries the last tick's state as `trigger circle:`
|
||||
(`off` under `UDPSCOPE_EXIT_AFTER_MS`, whose own probe goes live before it dumps).
|
||||
`UDPSCOPE_PROBE_CROSSHAIR_MS` hovers the pointer over row 0 through the adapter's own
|
||||
controller and asserts the crosshair's two **axis badges** landed in their label strips
|
||||
(`[crosshair-probe] active=1 ... y_ok=1 x_ok=1`), then flips the flag off and reads the
|
||||
plot's back; the pixel-level contract is the plotter suite's. The preference behind the
|
||||
flag is `[prefs-probe]`'s `xhair=` field. `UDPSCOPE_PROBE_MAXIMIZE_MS` arranges two
|
||||
single-cell rows (sine, square) and exercises the one-click **full-screen** toggle through
|
||||
the same `RowSet` call the button's handler makes — `[maximize-probe] before: rows=2 frows=2
|
||||
row0=sine row1=square buttons=2; max: rows=1 frows=1 flag=1 row0=sine; back: rows=2
|
||||
row0=sine row1=square flag=0` — where `buttons=` is the overlay's one toggle per figure row
|
||||
and the `back` leg is the proof that a maximize moves no signal. `UDPSCOPE_PROBE_SCOPE_MS`
|
||||
opens the **second window** twice, once copying figure 0 and once empty, and reads it back
|
||||
a few frames later: `[scope-probe] count=2 copied='sine' bound=1 empty_ok=1 windows=1` —
|
||||
`bound=1` is the main tick having bound the copy's rows to the same model, and `empty_ok=1`
|
||||
the empty scope staying empty. `UDPSCOPE_PROBE_DISARM_MS` only
|
||||
*starts* its sequence: it switches the trigger on and then polls for a capture to open,
|
||||
so the Disarm lands ~300 ms into one rather than wherever a fixed delay happened to fall.
|
||||
- `UDPSCOPE_PROBE_FORCE_MS` — the trigger panel's **Force** button: one acquisition at the present
|
||||
@@ -234,13 +254,16 @@ precedent, not an exception.
|
||||
window prices a 15 s one at nothing (four measurements over four channels read `35.69 ms` a
|
||||
recompute and a 4.58 ms tick mean at a 15 s window against `6.58 ms`/1.34 ms for one window at
|
||||
the same window).
|
||||
- `[measure-probe] N measurements on 'X'` (stderr, `UDPSCOPE_PROBE_MEASURE_MS`) — adds one
|
||||
- `[measure-probe] N measurements on 'X' (channel C)` (stderr, `UDPSCOPE_PROBE_MEASURE_MS`) — adds one
|
||||
measurement of each statistical type (average, top, peak to peak, amplitude) on the first
|
||||
*channel* and forces a recompute. It must name a **channel**, not `signals().front()`: the
|
||||
first signal of a stream is its timestamp, which is not acquired, and naming it adds four
|
||||
measurements that all read `--` *and* makes a performance run look free (each bails before
|
||||
collecting anything -- that is how the first baseline of the stall below read 0.4 ms).
|
||||
The values are in the exit dump as `measurements: <type>=<value>`; on the fast mock
|
||||
The values are in the exit dump as `measurements: N.signal <type>=<value>`, each numbered
|
||||
with the signal's active-channel number (the same one the pane's rows carry, and unnumbered
|
||||
when the signal is not acquired -- `enable_all` switches one off before the dump, so its
|
||||
measurements read unnumbered there); on the fast mock
|
||||
(`tools/mock_udpstreamer.py --rate 1300000 --batch 2000`) a unit sine must read
|
||||
`peak to peak=2` **exactly**, `average=0`, and the two flat-level types as their definition
|
||||
says: **`top=0.957988`** and `amplitude=1.915983`. `top` is no longer 2/pi -- it is the *flat*
|
||||
@@ -346,6 +369,42 @@ precedent, not an exception.
|
||||
[probe-readings.md](probe-readings.md); the window rule itself is in
|
||||
[invariants-measurements.md](invariants-measurements.md).
|
||||
|
||||
- `[gate-probe]` (stderr, `UDPSCOPE_PROBE_GATE_MS`) — the **per-measurement measurement gate**:
|
||||
a gated measurement must read the shared band and an ungated one the whole window, and the
|
||||
band must stay put while the data moves under it. It enables the sine, puts it on a plot
|
||||
(the way `enable_all_probe` does — an unbound trace leaves the display epoch at 0, and a
|
||||
still window is the state the `still=` leg asserts moves), adds **two** averages — index 0
|
||||
ungated, index 1 gated, which is what makes the pane rebuild *derive* the band from the
|
||||
measure set — and re-states the band's bounds to a fixed relative interval, 300 ms ago to
|
||||
100 ms ago, where samples exist whatever the run's age. It first waits for the ring to be
|
||||
older than the window, because the `plain=` assertion below is exact zero only while a
|
||||
whole-seconds window of the mock's 1 Hz sine holds whole periods; a young ring clips the
|
||||
window to a fraction of a cycle and the reading is merely small. Two lines:
|
||||
|
||||
```sh
|
||||
python3 tools/mock_udpstreamer.py --port 8140 --rate 100000 --batch 100 --freq 1 &
|
||||
sleep 1.5
|
||||
xvfb-run -a env UDPSCOPE_EXIT_AFTER_MS=14000 UDPSCOPE_PROBE_GATE_MS=3000 \
|
||||
./build/udpscope --server 127.0.0.1 --port 8140 2>&1 | grep -E 'gate-probe'
|
||||
```
|
||||
|
||||
The first line must read **`band=1 placed=1 plain=1 split=1 gwin=1`**: one gated
|
||||
measurement showed the band, the plot's region holds exactly the bounds the `RowSet` does,
|
||||
the ungated average is zero (it read the whole window — `-1.36e-17 V` on the reference run),
|
||||
the gated average differs (it read the band — `355.31 mV`, wherever in its period the
|
||||
stream happened to be), and the pass booked `measure_gate_window` = the band moved onto the
|
||||
stream's clock (`s0=`), which is the phase-free statement of *which* slice the gated walk
|
||||
read. The second line, a second later, must read **`still=1 s0_moved=1`**: the band exactly
|
||||
where it was put while the stream's clock moved under it — the *relative* behaviour the
|
||||
gate exists for. The probe saves the bounds the way a drag end does; the exit dump's
|
||||
**`gate:` line** is the round trip — a second run on the same config, with the probe unset,
|
||||
must restore both measurements (`measurements: 2 average≈0 average≠0`) and read
|
||||
`gate: on=1 bounds=[-0.300000..-0.100000]`. **Not in one run with the other measurement
|
||||
probes** (`UDPSCOPE_PROBE_MEASURE_MS`, `_MEASURES_MS`, `_BANDAVG_MS`, `_LEVELS_MS`,
|
||||
`_MEASUREPIN_MS`): they add measurements to one pane and these lines quote indexes 0 and 1.
|
||||
The lines are in [probe-readings.md](probe-readings.md); the rule is in
|
||||
[invariants-measurements.md](invariants-measurements.md).
|
||||
|
||||
- `[pan-probe] asked=[..] was=[..]`, `[pan-probe] after=[..] held=D asked=[..]`, then
|
||||
`[pan-probe] zoom asked=[..] after=[..] span=S window=W limited=L`
|
||||
(stderr, `UDPSCOPE_PROBE_PAN_MS`) — **`held` must be 1**: a gesture must not move a window
|
||||
|
||||
Reference in new issue
Block a user