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>
This commit is contained in:
1 parent
2f4530c128
commit
b313cac46e
18 files changed
+501
-145
No files matched your search
+29
-10
@@ -290,16 +290,17 @@ class ScopeModel {
|
||||
void live_window_locked(double lo_s, double hi_s, u64& first, u64& last);
|
||||
/// The frozen view taken when the user paused, or null while running. A pause is a
|
||||
/// snapshot of the *visible window* in exactly the sense a trigger event is -- an immutable
|
||||
/// series the plot binds to -- and the difference is the anchoring: an event's stamps are
|
||||
/// edge-relative, a pause's are the stream's own, so the axis does not move when the pause
|
||||
/// begins and the reader keeps the times they were already reading.
|
||||
/// series the plot binds to -- and both are anchored the same way: the stamps are the
|
||||
/// coordinates the axis was already reading, with `x_offset` recording which instant that
|
||||
/// x = 0 was. So the axis does not move when the pause begins.
|
||||
std::shared_ptr<const EventSnapshot> pause_view() const noexcept {
|
||||
return pause_view_.load(std::memory_order_acquire);
|
||||
}
|
||||
/// Snapshots the live ring over `[lo_s, hi_s]` (stream seconds, the display epoch already
|
||||
/// removed) as the pause view. The acquisition thread keeps writing, so this is a copy: what
|
||||
/// is frozen is the picture, not the data.
|
||||
void publish_pause_view(double lo_s, double hi_s);
|
||||
/// Snapshots the live ring over `[lo_s, hi_s]` (stream seconds, i.e. the display window put
|
||||
/// back on the stream's clock) as the pause view, rebased onto `x_offset` so its stamps are
|
||||
/// the x the axis is already showing. The acquisition thread keeps writing, so this is a
|
||||
/// copy: what is frozen is the picture, not the data.
|
||||
void publish_pause_view(double lo_s, double hi_s, double x_offset);
|
||||
void clear_pause_view() noexcept { pause_view_.store(nullptr, std::memory_order_release); }
|
||||
|
||||
/// Whether the user paused acquisition. Lives here rather than on `Acquisition` because the
|
||||
@@ -378,12 +379,30 @@ class ScopeModel {
|
||||
return event() != nullptr ? DisplayMode::Held : DisplayMode::Waiting;
|
||||
}
|
||||
|
||||
/// The x origin the plot uses: 0 for "seconds since the stream started", or minus
|
||||
/// the trigger instant while in trigger mode, so the axis reads seconds from the
|
||||
/// event. Applied to the live store's channels.
|
||||
/// The offset the **live** channels report their x with: `display_x = stream_t + epoch`.
|
||||
/// Minus the newest stored sample's stamp while streaming, so the axis reads
|
||||
/// `[-window, 0]` and the newest data is at 0; minus the trigger instant while a capture
|
||||
/// is filling, so the axis reads seconds from the event. It is 0 while a *snapshot* is on
|
||||
/// screen (a held event, a paused view), because then the live channels are bound to
|
||||
/// nothing and the snapshot's own `x_offset` is what says which clock x = 0 is --
|
||||
/// `stream_at_x0()` is the accessor that does not have to know which of the two it is.
|
||||
f64 display_epoch() const noexcept { return display_epoch_.load(std::memory_order_relaxed); }
|
||||
void set_display_epoch(f64 e) noexcept;
|
||||
|
||||
/// The stream time of the display's x = 0, so `stream_t = display_x + stream_at_x0()` and
|
||||
/// `display_x = stream_t - stream_at_x0()` in **every** display mode.
|
||||
///
|
||||
/// One accessor for the two places the offset can live: a snapshot carries its own
|
||||
/// (`EventSnapshot::x_offset`, frozen with the picture), and a live view carries it as
|
||||
/// minus the display epoch. Everything that has to cross between the two clocks goes
|
||||
/// through here -- the export's absolute column, and the pause handler putting a display
|
||||
/// window back on the ring to cut it -- rather than picking the branch itself and getting
|
||||
/// one of the four modes wrong.
|
||||
f64 stream_at_x0() const noexcept {
|
||||
if (std::shared_ptr<const EventSnapshot> snap = display_snapshot()) return snap->x_offset;
|
||||
return -display_epoch();
|
||||
}
|
||||
|
||||
// --- stream state ---------------------------------------------------------
|
||||
StreamStats& stats() noexcept { return stats_; }
|
||||
const StreamStats& stats() const noexcept { return stats_; }
|
||||
|
||||
Reference in new issue
Block a user