improved perfs and event management

This commit is contained in:
Martino Ferrari committed 2026-10-13 23:00:42 +02:00
1 parent 667ed0c100
commit bb99be5889
17 files changed
+626 -59

No files matched your search

+27
View File
@@ -12,6 +12,33 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
`plan_partition()` rather than recomputing the factor, because two expressions for that
decision is how the buffer and the view end up disagreeing about the stored rate. It
takes `mutex()` itself — do not call it holding the lock.
- **A rebuild that would plan the *same* storage clears the store instead of allocating one.**
`rebuild_locked()` compares the new `Partition` against the current one with
`Partition::same_storage_as()` — every field that decides an allocation: `time_capacity`,
`time_stride`, `abs_time`, `base_dt` and the per-channel `stride`/`value_capacity` vectors — and
on a match calls `BufferStore::reset()` rather than constructing a new one. `base_dt` is in the
comparison on purpose: it is not a size, but it is what the slots *mean*, and keeping samples
under a changed `base_dt` would re-date the whole ring.
**Construction is the cost.** A `RingBuffer<T>` holds a `std::vector<std::atomic<T>>`, which is
value-initialised, so making a store touches every byte of `--buffer-mb` — about a second per
gigabyte, mostly first-touch page faults — while `reset()` is one relaxed store per ring plus an
`invalidate()` and an epoch back to zero. It must leave nothing of the old history behind, which
is why it clears the time ring, the absolute-time ring, every value ring, and both the published
window and the pyramid of every channel: a reused store that kept a stale published window would
hand the renderer samples the new plan does not describe.
`reset()` runs on the consumer thread under `ScopeModel::mutex()`, which is what makes a
`RingBuffer::clear()` safe here at all — the producer holds that same mutex for the whole of
`on_data()`.
Every allocation that *is* made says so: `[udpscope] buffer allocated: N channels, S s span,
T ms` on stderr. A start used to emit fifteen of those; two is a real start, and a count that
climbs again is this rule having stopped applying.
- **Enables are applied in one batch, because each one re-plans.** `set_signals_enabled()` takes
the mutex once, moves every signal in the list, and re-plans and bumps the generation **once** —
and not at all when nothing actually moved, which is the common case for a restored session
re-enabling what is already on. Calling `set_signal_enabled()` in a loop is a buffer rebuild per
signal; `apply_layout()` and the waveform loader collect their wanted signals into one vector for
that reason, and `apply_layout()` records the session's span **first**, with no channels bound, so
the `apply_time_window()` that follows re-plans nothing.
- **The shared clock is decimated to fit the requested window; channels never are.** A
window too long to store at the wire rate used to be refused (the control was capped to
`Partition::span_s`), which made a 600 s view of a 1 MS/s stream unreachable.