Gated measurements #2

Merged
martino merged 12 commits from gated-measurements into main 2026-11-06 12:30:43 +01:00
6 changed files with 75 additions and 9 deletions
Showing only changes of commit ecdbf4f0e1 - Show all commits

No files matched your search

@@ -629,3 +629,11 @@ samples. Three rules keep that honest:
its ink is not in the widget's paint tree, so `gtk_snapshot_free_to_node()` on a widget paintable of
it returns NULL and the "screenshot" would be an empty file. Drawing it in the store also keeps that
class GTK-free, which is what makes the cache testable without a window.
- **A pin is mirrored as a *range*, never as a pair of numbers that might be spans.** `Range`
(`pinned_range()`: `{-pre, +post}`) and the pin's *spans* (`pin_event_window(pre, post)`) are two
different things that both look like a pair, and mixing them inverts the window: a second view
that handed `pinned_range().min` to `pin_event_window()` pinned to `[+pre, +post]`, and the plot
drew nothing — no trace, no axis labels — because the x range was degenerate. Views that mirror a
pin use `pin_display_range(x0, x1)`, the range's own form. `[scope-probe] pin_mirror=1` reads the
two windows' pins and the scope's x range, and an inverted pin reads `0`.
+7
View File
@@ -1474,6 +1474,7 @@ a defect in the copy. Measured output:
[scope-probe] gl copy=ok(67f) empty=ok(39f) empty_bound=1
[scope-probe] gl detached ok=1 frames=42 rows=1 slot0='sine'
[scope-probe] rclick=1
[scope-probe] pin_mirror=1 main=[-1.000..1.000] scope=[-1.000..1.000] x=[-1.000..1.000]
```
- `count=2` is both windows having been created and retained; `windows=1` each being a visible
@@ -1493,6 +1494,12 @@ a defect in the copy. Measured output:
signal into it" — landing in its row set. `drop=0` names a refused drop (a full plot or an
unacquirable signal); `page_groups` less than the window count is a page rebuilt before the
windows were read or a group that lost its window.
- `pin_mirror=1` is a scope window **mirroring a pin as a range**: the probe pins the main window
and reads the scope's pin and its plot's x range a frame later. Both must agree and the x range
must be ordered (`x=[min..max]`, `min < max`). A `0` with an inverted scope pin (`[+pre, +post]`)
is the defect this exists for: `pin_event_window()` takes **spans**, and passing it a range's
signed `min` turned the window inside out, so a scope in Held or Paused mode drew no trace and no
x labels at all.
- `rclick=1` is the scope window's **own right-click menu** being wired (one click gesture with
button 3 on its GL area, beside the drop target): the menu's actions act on that window's row
set.
+13
View File
@@ -1509,6 +1509,19 @@ void RowSet::pin_event_window(double pre_s, double post_s) {
figure_->set_x_link_range(x_group_, {-pre_s, post_s});
}
void RowSet::pin_display_range(double x0, double x1) {
if (!(x1 > x0)) return;
window_s_ = std::max(1.0e-3, x1 - x0);
pinned_ = true;
pinned_pre_ = -x0;
pinned_post_ = x1;
if (!figure_) return;
// The same two statements `pin_event_window()` makes, from the range instead of the spans: the
// group stops following the live edge and is placed on the event's own window.
figure_->set_x_link_follow(x_group_, plot::XFollow::Off);
figure_->set_x_link_range(x_group_, {x0, x1});
}
void RowSet::fit_all() {
for (int i = 0; i < rows_; ++i) {
Row& r = row(i);
+7
View File
@@ -570,6 +570,13 @@ class RowSet {
/// 0, so nothing outside the pre+post window is visible.
void pin_event_window(double pre_s, double post_s);
/// The same pin, stated as the **range** a second view mirrors: the caller has the display
/// range (`x0 < x1`) and not the spans -- a scope window takes `pinned_range()` from the main
/// window and hands it on. `pin_event_window()` takes *spans*, and passing it a range's signed
/// `min` turned the pin inside out (a scope in Held mode pinned to `[+pre, +post]`: no trace
/// and no x labels). A degenerate or inverted range pins nothing.
void pin_display_range(double x0, double x1);
/// Fits every row's y axis to the data it holds, and puts x back on the whole time window --
/// `fit_x()`'s x half exactly, so the two cannot drift apart. Like it, **this does not change
/// the time window**: the y axes are fitted to their data, x is merely un-zoomed.
+12 -6
View File
@@ -4633,17 +4633,23 @@ gboolean tick(gpointer user) {
const double window_s = app->rows->time_window();
for (const std::unique_ptr<App::ScopeWindow>& scope : app->scopes) {
if (pinned) {
scope->rows->pin_event_window(pin.min, pin.max);
// The pin as a *range*: `pin_event_window()` takes spans (pre, post), and handing it
// `pin.min` -- a signed time -- inverted the window, so a scope in Held or Paused mode
// drew no trace and no x labels at all.
scope->rows->pin_display_range(pin.min, pin.max);
} else if (std::fabs(scope->rows->time_window() - window_s) > 1.0e-12) {
scope->rows->set_time_window(window_s);
}
// An arrangement change (a drop, a *Plot on* button) rebuilt the row set: point the area at
// the new figure here, in the tick -- the change arrives from inside this very widget (a drop
// signal), and a synchronous rebuild would reset the drag controller under it.
// REPRO-TEMP: rebuild disabled
// if (scope->rows->rebuild_if_dirty() && scope->area != nullptr) {
// scope->area->set_figure(&scope->rows->figure());
// }
// signal), and a synchronous rebuild would reset the drag controller under it. The traces
// themselves are `SlotSeries` rebinds, so a slot that gains or loses a source draws either
// way; what *does* need this is the figure's own state: a row's legend, its cell structure,
// and the trace objects `install()` creates only for the slots that hold a signal at rebuild
// time -- a window opened empty and filled by a drop has none until this runs.
if (scope->rows->rebuild_if_dirty() && scope->area != nullptr) {
scope->area->set_figure(&scope->rows->figure());
}
if (scope->detached) sync_scope_attach(*app, *scope);
// **A window that cannot paint says so.** A blank plot is indistinguishable from a
// context that failed or an area that never got a frame, and the report ("nothing is
+28 -3
View File
@@ -1915,11 +1915,13 @@ gboolean scope_probe_step(gpointer user) {
det.rows != nullptr && det.rows->row_count() > 0
? det.rows->row(0).signal_name[0].c_str()
: "");
}
// The scope window carries its own right-click menu (its gesture is on the GL area, wired
// beside the drop target): one click gesture whose button is 3, per window.
int right_clicks = 0;
if (det.widget != nullptr) {
GListModel* controllers = gtk_widget_observe_controllers(det.widget);
GtkWidget* det_widget = a->scopes.empty() ? nullptr : a->scopes.back()->widget;
if (det_widget != nullptr) {
GListModel* controllers = gtk_widget_observe_controllers(det_widget);
const guint n = g_list_model_get_n_items(G_LIST_MODEL(controllers));
for (guint i = 0; i < n; ++i) {
GObject* c = G_OBJECT(g_list_model_get_item(controllers, i));
@@ -1933,8 +1935,31 @@ gboolean scope_probe_step(gpointer user) {
}
std::fprintf(stderr, "[scope-probe] rclick=%d\n", right_clicks);
std::fflush(stderr);
}
// **The pin mirror**: a held event pins the main window, and every scope must be pinned to
// the *same range*. `pin_event_window()` takes spans (pre, post) and passing it a range's
// signed min turned the pin inside out (a scope in Held drew no trace and no x labels).
a->rows->pin_event_window(1.6, 0.4);
a->dbg_scope_step = 4;
g_timeout_add(350, scope_probe_step, a);
return G_SOURCE_REMOVE;
}
case 4: {
const plot::Range main_pin = a->rows->pinned_range();
const plot::Range scope_pin = a->scopes[0]->rows->pinned_range();
const plot::Range scope_x = a->scopes[0]->rows->row(0).plot != nullptr
? a->scopes[0]->rows->row(0).plot->x_range(0)
: plot::Range{};
const bool pinned = a->scopes[0]->rows->window_pinned() == a->rows->window_pinned();
const bool same = std::fabs(scope_pin.min - main_pin.min) < 1e-9 &&
std::fabs(scope_pin.max - main_pin.max) < 1e-9 &&
scope_x.max > scope_x.min;
std::fprintf(stderr,
"[scope-probe] pin_mirror=%d main=[%.3f..%.3f] scope=[%.3f..%.3f] "
"x=[%.3f..%.3f]\n",
pinned && same ? 1 : 0, main_pin.min, main_pin.max, scope_pin.min,
scope_pin.max, scope_x.min, scope_x.max);
std::fflush(stderr);
a->rows->set_time_window(2.0); // unpin: leave the window as the run found it
return G_SOURCE_REMOVE;
}
default: