Files
udpscope/src/status_text.cpp
T
Martino FerrariandClaude Opus 4.6 b313cac46e feat: the x axis is relative to now, and an export carries both clocks
Three asked-for changes, one commit because they are one coordinate system.

The live axis is a fixed [-window, 0] with the newest sample at 0, so x reads
as an age and the labels stand still while the trace rolls under them. That
makes the display clock distinct from the stream clock for the first time, so
`ScopeModel::stream_at_x0()` becomes the single conversion between them
(`display_x = stream_t - stream_at_x0()` in all four modes) and a snapshot
carries its own origin in `EventSnapshot::x_offset`. The pause view is now
rebased like an event's, reversing an earlier deliberate choice: a frozen
absolute-stamped picture would sit a whole uptime right of a newest-relative
axis. That also fixed a latent bug, the export adding `trigger_time()` to a
*paused* view's stamps.

The status bar says how long you have been paused -- wall time, since the
producer never stops, through `format_elapsed()` so the field cannot widen.

The export writes two time columns: `time`, the axis as displayed, and
`abs_seconds`, the stream's own clock for the same sample. f64 seconds and
not the wire's integer, because `udps_frame_t::values` is a `const double*`
and the integer never exists in this process.

Cursors and measurement marks move in opposite directions under the rolling
axis, deliberately: a cursor is a position on screen and stays put, a mark
names a sample and travels with it. `App::measure_window` is stream-clocked
for a sharper reason -- a display-coordinate live window never moves, so the
"the window moved" recompute would never fire again.

Moves the deps/plotter pin to 58473db, which stops a time label's mantissa
reaching its own radix: a one-second tick differenced off a live axis read
`1e+3ms`.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-10-05 21:49:12 +02:00

96 lines
3.4 KiB
C++

// SPDX-License-Identifier: MIT
#include "status_text.hpp"
#include <cmath>
#include <cstdio>
namespace udpscope {
/// `12 s`, `3m 07s`, `2h 14m` -- one unit coarser as soon as the finer one would need
/// three digits, so the field never grows past six characters and the status line's shape
/// does not depend on how long the wait has been.
std::string format_elapsed(double seconds) {
if (!(seconds >= 0.0)) return "--";
char buf[32];
if (seconds < 60.0) {
std::snprintf(buf, sizeof(buf), "%.0f s", std::floor(seconds));
} else if (seconds < 3600.0) {
const int m = static_cast<int>(seconds) / 60;
std::snprintf(buf, sizeof(buf), "%dm %02ds", m, static_cast<int>(seconds) % 60);
} else {
const int h = static_cast<int>(seconds) / 3600;
std::snprintf(buf, sizeof(buf), "%dh %02dm", h, (static_cast<int>(seconds) / 60) % 60);
}
return buf;
}
/// A sample rate in the unit it reads shortest in. Rates here span six decades (a 1 Hz
/// housekeeping signal next to a 1 MS/s acquisition), so a fixed unit makes one of the
/// two unreadable.
std::string format_rate(double per_s) {
char buf[32];
if (!(per_s > 0.0)) {
return "0 S/s";
} else if (per_s >= 1.0e6) {
std::snprintf(buf, sizeof(buf), "%.3g MS/s", per_s / 1.0e6);
} else if (per_s >= 1.0e3) {
std::snprintf(buf, sizeof(buf), "%.3g kS/s", per_s / 1.0e3);
} else {
std::snprintf(buf, sizeof(buf), "%.3g S/s", per_s);
}
return buf;
}
std::string format_glance(const StatusSnapshot& s) {
std::string out = s.state_word;
out += " · ";
out += s.rate_text.empty() ? "no data" : s.rate_text;
out += " · ";
out += s.window_text;
out += " window";
if (s.paused) {
// Directly after the state word's own field rather than at the end, because it qualifies
// *that* word: "paused · 10 kS/s · 2 s window · for 12 s" reads as one statement.
out += " · for " + format_elapsed(s.paused_for_s);
}
if (s.trigger_on) {
out += " · ";
if (s.have_event) {
out += "event " + format_elapsed(s.since_event_s) + " ago";
} else {
out += "no event yet";
}
}
return out;
}
std::string format_detail(const StatusSnapshot& s) {
char buf[1024];
std::snprintf(buf, sizeof(buf),
"[%s] %s %s | rate %s | packets %llu frames %llu gaps %llu | %zu signals, "
"%zu channels | plot 1: %d trace(s), %zu samples | buffer %.1f MB, %.2f s "
"span, time x%llu",
s.state_detail.c_str(), s.connected ? "connected" : "waiting",
s.have_config ? "configured" : "-",
s.rate_text.empty() ? "--" : s.rate_text.c_str(),
static_cast<unsigned long long>(s.packets),
static_cast<unsigned long long>(s.frames),
static_cast<unsigned long long>(s.gaps), s.signals, s.channels,
s.plot1_traces, s.plot1_samples, s.buffer_mb, s.span_s,
static_cast<unsigned long long>(s.time_stride));
std::string out = buf;
if (s.paused) out += " | paused for " + format_elapsed(s.paused_for_s);
if (s.trigger_on) {
out += s.have_event ? " | last event " + format_elapsed(s.since_event_s) + " ago"
: " | no event yet";
}
if (s.reconnects > 0) {
char note[48];
std::snprintf(note, sizeof(note), " | %d reconnects", s.reconnects);
out += note;
}
return out;
}
} // namespace udpscope