fix(input,examples): a linked figure's wheel anchor uses the group's live edge, and every demo verdict reaches its exit status
Review fix pass 1 on the task 7 work; four findings, all closed. 1. The Qt demo's region sat across the middle of the window, which is exactly where its own scripted input check pans: after the check's box zoom the press opened DragMode::MoveRegion, the x range never moved, and the check printed 'input check pan FAIL' -- under an exit 0, because the verdict was printed and not returned. The region now rides the left sixth (as the GLFW demo's does), both checks pass, and the verdict is wired to the exit status. The same audit found two more printed-only verdicts -- the GTK demo's scripted-resize layout check and its screenshot write (and Qt's) -- now wired too. What stays printed-only is the screenshot line of the GLFW host and the ImGui widget, where the dump happens inside the adapter's frame loop. 2. A follow-ing link group was invisible to the input layer: a member's own axis must be XFollow::Off (link_x() refuses a Live member), so Controller's x_zoom_anchor() extrapolated a coordinate from the pointer instead of using the live edge and a wheel over a row's axis strip walked the window back by 19% of its width (behind 0.000000 -> 0.285809 s). The group now publishes its own edge to every member (Axis::live_owner + Plot::set_x_live_owner, written by Figure::push_link, cleared when the group stops following or the row unlinks), and x_zoom_anchor() reads it. Public-header addition, called out. Measured after: behind 0.012992 s and the window's right edge bit-identical to the edge the group published at the gesture. 3. The multiplot summary printed x_link_count() -- a number of groups -- under 'rows in it'. It now reports the members and the group count separately, and every row line carries its own group id. 4. Host::controller_at() promised to clamp a pixel in a row gap to the nearest row and only clamped below the last one, so a gap pixel went to row 0. It now selects by distance, and tests/test_glfw_host.cpp pins it with a 40 px gap -- the first coverage of the host's figure routing, which needs no window. New cases: tests/test_controller.cpp x2 (the anchor and the hint's lifecycle), tests/test_glfw_host.cpp x3 (routing, the gap, the single-plot fallback): 229 cases, 0 failed, ASan clean. All five demos exit 0, the three self-checking modes exit 0, and the amended assertions were confirmed failing against the pre-fix behaviour.
This commit is contained in:
1 parent
a70c381a1e
commit
daff60e3f0
14 files changed
+441
-55
No files matched your search
+57
-26
@@ -266,17 +266,15 @@ f64 newest_x(const Series& series) {
|
||||
/// is what "new data keeps arriving at the right edge" means for a window the user has
|
||||
/// moved away from the edge.
|
||||
///
|
||||
/// One measurement is deliberately *not* checked, and the reason is worth stating because
|
||||
/// the demo is where it shows: the zoom above rolls over the axis strip, outside `axes`,
|
||||
/// where a single `Live` axis anchors at its live edge and keeps the window ending at the
|
||||
/// newest sample. A row of a following link group holds `XFollow::Off` -- the group owns
|
||||
/// the follow state, and `link_x()` refuses a `Live` member -- so `Controller`'s
|
||||
/// `x_zoom_anchor()` cannot see that the window is live and falls back to the data
|
||||
/// coordinate the pointer's x extrapolates to (the middle of the axes for a strip-centre
|
||||
/// pointer). The zoom still multiplies the width and keeps following, but it also walks
|
||||
/// the window back from the edge. Measured here rather than assumed: `behind` after the
|
||||
/// zoom is what it is, and the checks below tolerate it while pinning everything the
|
||||
/// library does guarantee.
|
||||
/// The zoom above rolls over the axis strip, outside `axes`, where the anchor is the live
|
||||
/// edge rather than the coordinate under the pointer. That is the one thing a *link group*
|
||||
/// used to lose: a member's axis is `XFollow::Off` by construction (the group owns the
|
||||
/// follow state, and `link_x()` refuses a `Live` member), so `Controller::x_zoom_anchor()`
|
||||
/// could not see that the window was live and fell back to the extent the pointer's x
|
||||
/// extrapolates to -- the middle of the axes -- which walked the window back off the edge
|
||||
/// by ~19 % of its width. A following group now publishes its own edge to every member
|
||||
/// (`Plot::set_x_live_owner`), the controller reads it, and `behind` after the zoom must
|
||||
/// still be ~0 -- which the check below asserts, so the fix cannot silently regress.
|
||||
///
|
||||
/// The verdict is carried out of the run in `failed`, so `main()` exits non-zero on a
|
||||
/// failed check -- a check whose result only stdout knows is not a check -- and also when
|
||||
@@ -293,6 +291,12 @@ struct SyncScript {
|
||||
f64 span[4]{};
|
||||
bool shared[4]{};
|
||||
bool live[4]{};
|
||||
/// The live edge the group had published to its members when the zoom was made: the
|
||||
/// coordinate the window's right edge must still be at afterwards, to the bit. Reading
|
||||
/// it here rather than comparing against the newest sample is what makes the check
|
||||
/// exact -- the edge moves while the frame runs, so "the window ends at the newest
|
||||
/// sample" is true only to within the one frame the measurement takes.
|
||||
f64 anchor_edge = 0.0;
|
||||
|
||||
static SyncScript from_env() {
|
||||
SyncScript s;
|
||||
@@ -314,10 +318,22 @@ struct SyncScript {
|
||||
if (series[0]->size() < 65536u || figure.plot(0).text_backend() == nullptr) return;
|
||||
record(0, figure, series);
|
||||
break;
|
||||
case 1:
|
||||
case 1: {
|
||||
// The edge the zoom must anchor on. A following group publishes it to every member
|
||||
// (`Plot::set_x_live_owner`), which is what `Controller::x_zoom_anchor()` now reads
|
||||
// for a gesture outside `axes` -- without it the group's live window is invisible to
|
||||
// the input layer and the anchor is the extent the pointer's x extrapolates to.
|
||||
f64 edge = 0.0;
|
||||
if (!figure.plot(kRows - 1u).x_live_owner(0u, edge)) {
|
||||
std::fprintf(stderr, "[multiplot] sync: no live edge was published to the rows\n");
|
||||
failed = true;
|
||||
break;
|
||||
}
|
||||
anchor_edge = edge;
|
||||
notches(host, figure);
|
||||
record(1, figure, series);
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
pan(host, figure);
|
||||
record(2, figure, series);
|
||||
@@ -396,8 +412,15 @@ struct SyncScript {
|
||||
const bool following = live[0] && live[1] && live[2] && live[3];
|
||||
// (a) the steady state sits on the live edge.
|
||||
const bool at_edge = std::fabs(behind[0]) <= kBatchS;
|
||||
// (b) the zoom, made on the bottom row, narrowed the one shared window.
|
||||
// (b) the zoom, made on the bottom row, narrowed the one shared window -- and, because
|
||||
// the anchor outside `axes` is the live edge, left the window *ending on that edge*,
|
||||
// to the bit. `zoom_on_edge` is the same statement with the slack a frame of arriving
|
||||
// data costs: the edge moves on while the frame runs, so the window is within 5 % of
|
||||
// its own width of the newest sample -- where anchoring on the extrapolated coordinate
|
||||
// left it 30 % behind.
|
||||
const bool zoomed = span[1] < span[0] * 0.99;
|
||||
const bool anchor_kept = near(right[1], anchor_edge, 1e-9 * std::fabs(anchor_edge) + 1e-12);
|
||||
const bool zoom_on_edge = behind[1] <= 0.05 * span[1];
|
||||
// (c) the pan, made on the middle row, moved the window back and left the width alone.
|
||||
const bool panned_back = behind[2] > behind[1] + 1e-3;
|
||||
const bool width_kept = near(span[2], span[1], 1e-9 * span[1] + 1e-12);
|
||||
@@ -408,15 +431,18 @@ struct SyncScript {
|
||||
const bool tracked = near((right[3] - right[2]) - (newest[3] - newest[2]), 0.0, kBatchS);
|
||||
const bool behind_kept = near(behind[3], behind[2], kBatchS);
|
||||
const bool width_kept_after_slide = near(span[3], span[1], 1e-9 * span[1] + 1e-12);
|
||||
failed = !(one_range && following && at_edge && zoomed && panned_back && width_kept && slid &&
|
||||
tracked && behind_kept && width_kept_after_slide);
|
||||
failed = !(one_range && following && at_edge && zoomed && anchor_kept && zoom_on_edge &&
|
||||
panned_back && width_kept && slid && tracked && behind_kept &&
|
||||
width_kept_after_slide);
|
||||
completed = true;
|
||||
std::printf(
|
||||
"[multiplot] sync checks: one range across the rows %s; following armed %s; at the live"
|
||||
" edge %s; the zoom narrowed the shared window %s; the pan moved the window back %s;"
|
||||
" the right edge tracked the newest sample %s\n",
|
||||
" edge %s; the zoom narrowed the shared window %s; the zoom kept the window ending on the"
|
||||
" live edge %s (to the bit %s, behind %.6f s); the pan moved the window back %s; the right"
|
||||
" edge tracked the newest sample %s\n",
|
||||
one_range ? "yes" : "NO", following ? "yes" : "NO", at_edge ? "yes" : "NO",
|
||||
zoomed ? "yes" : "NO", panned_back && width_kept ? "yes" : "NO",
|
||||
zoomed ? "yes" : "NO", zoom_on_edge ? "yes" : "NO", anchor_kept ? "yes" : "NO", behind[1],
|
||||
panned_back && width_kept ? "yes" : "NO",
|
||||
tracked && slid && behind_kept && width_kept_after_slide ? "yes" : "NO");
|
||||
std::fflush(stdout);
|
||||
}
|
||||
@@ -635,21 +661,26 @@ int main() {
|
||||
static_cast<unsigned long long>(series[kSine]->size()),
|
||||
static_cast<f64>(series[kSine]->size()) * kDx, kSampleRate * 1.0e-3, yr0.min,
|
||||
yr0.max);
|
||||
// `x_link_count()` counts link *groups* (excluding the unlinked group 0), so it is
|
||||
// labelled as one and the members are listed separately: the readout a reader checks the
|
||||
// link with must not say "rows" about a number of groups.
|
||||
u32 members = 0;
|
||||
for (u32 row = 0; row < kRows; ++row) members += figure.x_link(row) == link ? 1u : 0u;
|
||||
std::printf("[multiplot] x link group %u: range [%.6f, %.6f] s (width %.6f), follow %s,"
|
||||
" rows in it %u\n",
|
||||
link, shared.min, shared.max, shared.span(), figure.x_link_follow(link) ==
|
||||
plot::XFollow::Live
|
||||
? "Live"
|
||||
: "Off",
|
||||
figure.x_link_count());
|
||||
" %u member row%s of %u link group%s\n",
|
||||
link, shared.min, shared.max, shared.span(),
|
||||
figure.x_link_follow(link) == plot::XFollow::Live ? "Live" : "Off", members,
|
||||
members == 1u ? "" : "s", figure.x_link_count(),
|
||||
figure.x_link_count() == 1u ? "" : "s");
|
||||
for (u32 row = 0; row < kRows; ++row) {
|
||||
const plot::Plot& p = figure.plot(row);
|
||||
const plot::Rect ra = p.layout().axes;
|
||||
std::printf("[multiplot] row %u x [%.6f, %.6f] s, axes %.0fx%.0f at (%.0f, %.0f),"
|
||||
" traces %u, legend rows %u\n",
|
||||
" traces %u, legend rows %u, in link group %u\n",
|
||||
row, p.x_range(0).min, p.x_range(0).max, static_cast<f64>(ra.w),
|
||||
static_cast<f64>(ra.h), static_cast<f64>(ra.x), static_cast<f64>(ra.y),
|
||||
p.trace_count(), static_cast<u32>(p.layout().legend.rows.size()));
|
||||
p.trace_count(), static_cast<u32>(p.layout().legend.rows.size()),
|
||||
figure.x_link(row));
|
||||
}
|
||||
std::printf("[multiplot] region %u: x [%g, %g] s, n %llu, mean %.4f, rms %.4f, min"
|
||||
" %.4f, max %.4f, integral %.4f\n",
|
||||
|
||||
Reference in new issue
Block a user