improved perfs and event management
This commit is contained in:
1 parent
667ed0c100
commit
bb99be5889
17 files changed
+626
-59
No files matched your search
@@ -109,6 +109,23 @@ struct Partition {
|
||||
/// Nyquist. The boxcar the ingest path averages with has its first null at the stored
|
||||
/// rate itself, so everything that would fold back is attenuated.
|
||||
double antialias_hz() const noexcept { return 0.5 * stored_rate(); }
|
||||
|
||||
/// Two plans that describe the **same storage**: the same rings, of the same lengths, at
|
||||
/// the same decimation, over the same clock. This is the test `rebuild_locked()` uses to
|
||||
/// decide whether it can clear the buffer it has instead of allocating another one.
|
||||
///
|
||||
/// `base_dt` is compared although it buys no byte, because a reused store keeps the
|
||||
/// `Partition` *it* was built with and hands `slot_dt()` to every `ChannelBuffer` as the
|
||||
/// nominal grid `dx()` reports -- and `dx()` is what picks the LOD level. Two channel sets
|
||||
/// can want the same capacities off a different base tick, and reusing across that would
|
||||
/// leave every channel describing a grid the samples are not on. It is an exact compare of
|
||||
/// a value that is copied, never recomputed, from the fastest channel's `dt`.
|
||||
/// `span_s` needs no compare of its own: it is the product of the three fields above.
|
||||
bool same_storage_as(const Partition& o) const noexcept {
|
||||
return time_capacity == o.time_capacity && time_stride == o.time_stride &&
|
||||
abs_time == o.abs_time && base_dt == o.base_dt && stride == o.stride &&
|
||||
value_capacity == o.value_capacity;
|
||||
}
|
||||
};
|
||||
|
||||
/// Plans a partition for `channel_dt` cadences within `buffer_bytes`, covering at least
|
||||
@@ -156,6 +173,27 @@ class BufferStore {
|
||||
}
|
||||
const Partition& partition() const noexcept { return part_; }
|
||||
|
||||
/// Drops every sample and leaves a store indistinguishable from a freshly built one with
|
||||
/// the same `Partition` -- the state `rebuild_locked()` wants when the plan did not change.
|
||||
///
|
||||
/// Why it exists: constructing a store *is* its cost. A ring is a
|
||||
/// `vector<std::atomic<T>>`, which is value-initialised, so building one touches every
|
||||
/// byte of the user's `--buffer-mb` -- about 300 ms per GB, nearly all of it first-touch
|
||||
/// page faults. Clearing is a count store per ring. Most structural changes (a
|
||||
/// calibration, a re-enable of what was just disabled, going back to live) re-plan to the
|
||||
/// *same* partition, and paying a second full allocation for them is what made a large
|
||||
/// buffer take ten seconds to start and an event take seconds to leave.
|
||||
///
|
||||
/// It must leave **nothing** of the old history behind, in either copy: the rings are
|
||||
/// emptied and every `ChannelBuffer` is `invalidate()`d, which drops the published window
|
||||
/// and the pyramid built over it. The epoch goes back to 0 for the same reason -- a fresh
|
||||
/// store has none, and a reused one keeping the old event's epoch would re-base the next
|
||||
/// axis against a trigger that no longer exists.
|
||||
///
|
||||
/// Caller's thread: the consumer, under `ScopeModel::mutex()` -- the same lock the
|
||||
/// producer's pushes hold. `RingBuffer::clear()` is not safe against a concurrent push.
|
||||
void reset() noexcept;
|
||||
|
||||
/// Consumer: publishes every channel's new samples.
|
||||
void acquire_all();
|
||||
|
||||
@@ -356,6 +394,17 @@ class ScopeModel {
|
||||
static bool is_time_signal(const std::vector<SignalInfo>& signals,
|
||||
std::size_t signal);
|
||||
void set_signal_enabled(std::size_t signal, bool on);
|
||||
/// The same switch thrown for several signals as **one** structural change.
|
||||
///
|
||||
/// Enabling a signal changes the channel count, so it re-plans the partition and the new
|
||||
/// plan really is a different buffer -- there is no reusing the old store for it (see
|
||||
/// `BufferStore::reset()`). A caller that enables n signals one at a time therefore pays n
|
||||
/// full allocations to reach a state one allocation describes, and every one of them drops
|
||||
/// the history the one before it started collecting. That is what a restored session, a
|
||||
/// loaded waveform and an event group all do, and at a 1 GB buffer it is seconds of a
|
||||
/// blank window: the plots are bound to a store that is replaced again before it holds a
|
||||
/// screenful. Indices the model has no signal for are ignored.
|
||||
void set_signals_enabled(const std::vector<std::size_t>& signals, bool on);
|
||||
/// Stores the calibration for a signal's *name* and applies it to that signal's
|
||||
/// live channels. Keyed by name, not index: a CONFIG renumbers and an
|
||||
/// enable/disable rebuilds the channel list, and `rebuild_channels()` re-applies
|
||||
@@ -587,6 +636,15 @@ class ScopeModel {
|
||||
|
||||
private:
|
||||
void rebuild_channels();
|
||||
/// One signal's enable bookkeeping -- `enabled_` and the math channel's own flag -- with no
|
||||
/// re-plan. Returns whether the state actually moved. The two halves of
|
||||
/// `set_signal_enabled()` are split so that a batch can do n of these and one of the
|
||||
/// rebuild below.
|
||||
bool enable_locked(std::size_t signal, bool on);
|
||||
/// The re-plan an enable needs: carry the trigger's channel across the renumbering,
|
||||
/// rebuild the channels and the buffers, and publish the new generation. Caller holds
|
||||
/// `mutex()` and has already moved `enabled_`.
|
||||
void rebuild_enabled_locked();
|
||||
/// Re-appends one synthetic signal per math channel, after the stream's own. Called under the
|
||||
/// lock by both `set_signals()` (a CONFIG) and `set_math_channels()` (an edit).
|
||||
void append_math_signals_locked();
|
||||
|
||||
Reference in new issue
Block a user