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

+62 -7
View File
@@ -163,6 +163,16 @@ BufferStore::BufferStore(const Partition& part, f64 span_s)
}
}
void BufferStore::reset() noexcept {
time_->clear();
if (abs_time_) abs_time_->clear();
for (auto& v : values_) v->clear();
for (auto& c : channels_) {
c->invalidate();
c->set_epoch(0.0);
}
}
void BufferStore::acquire_all() {
for (auto& c : channels_) c->acquire();
}
@@ -536,9 +546,32 @@ void ScopeModel::rebuild_locked() {
return;
}
part_ = plan_partition(dts, cfg_.buffer_mb * 1024.0 * 1024.0,
target_span_.load(std::memory_order_relaxed), cfg_.relative_time);
store_ = std::make_unique<BufferStore>(part_, part_.span_s);
const Partition want = plan_partition(dts, cfg_.buffer_mb * 1024.0 * 1024.0,
target_span_.load(std::memory_order_relaxed),
cfg_.relative_time);
// A plan that describes the storage already allocated is cleared, not rebuilt. Allocating
// a store is what costs -- its rings are value-initialised, so it touches the whole
// `--buffer-mb` -- and most structural changes do not move the plan at all: a calibration
// keeps the channel set, and so does leaving an event for the live view. Startup asked for
// sixteen of these and eight of them planned the same buffer twice. Clearing has the same
// meaning (`BufferStore::reset()`): the history is gone either way, which is why
// `plan_generation_` bumps above regardless.
if (store_ && part_.same_storage_as(want)) {
part_ = want;
store_->reset();
} else {
part_ = want;
const std::uint64_t t0 = now_ns();
store_ = std::make_unique<BufferStore>(part_, part_.span_s);
// Named, with what it cost: this is the one operation in the application whose duration a
// user can see with no instrument, because it blocks the main thread for the time it takes
// to touch the whole buffer -- about 300 ms per GB. A startup or a mode change that reads
// more than one of these lines is a re-plan that should have been coalesced, and that is
// the defect the line exists to name (it was fifteen of them at startup).
std::fprintf(stderr, "[udpscope] buffer allocated: %zu channels, %.3g s span, %.0f ms\n",
channels_.size(), part_.span_s, double(now_ns() - t0) / 1.0e6);
std::fflush(stderr);
}
if (trigger_.channel >= static_cast<int>(channels_.size())) trigger_.channel = -1;
prepare_math_locked();
@@ -590,9 +623,8 @@ bool ScopeModel::signal_enabled(std::size_t signal) const {
return signal < enabled_.size() && enabled_[signal];
}
void ScopeModel::set_signal_enabled(std::size_t signal, bool on) {
std::lock_guard<std::mutex> lock(mutex_);
if (signal >= enabled_.size() || enabled_[signal] == on) return;
bool ScopeModel::enable_locked(std::size_t signal, bool on) {
if (signal >= enabled_.size() || enabled_[signal] == on) return false;
enabled_[signal] = on;
// A math channel's synthetic signal is enabled *here* -- it is the panel's switch -- while
// `append_math_signals_locked()` re-states `enabled_` from `math_` on the next edit. The two have
@@ -603,9 +635,14 @@ void ScopeModel::set_signal_enabled(std::size_t signal, bool on) {
if (m.name == signals_[signal].name) m.enabled = on;
}
}
return true;
}
void ScopeModel::rebuild_enabled_locked() {
// The trigger names a channel by its display name, which is stable across
// renumbering: when its own signal is disabled the channel vanishes (the trigger
// then waits, and comes back when the signal is re-enabled).
// then waits, and comes back when the signal is re-enabled). Read before
// `rebuild_channels()`, which is what renumbers.
const int old_trigger = trigger_.channel;
const std::string trigger_name =
(old_trigger >= 0 && old_trigger < static_cast<int>(channels_.size()))
@@ -622,6 +659,24 @@ void ScopeModel::set_signal_enabled(std::size_t signal, bool on) {
generation_.fetch_add(1, std::memory_order_release);
}
void ScopeModel::set_signal_enabled(std::size_t signal, bool on) {
std::lock_guard<std::mutex> lock(mutex_);
if (!enable_locked(signal, on)) return;
rebuild_enabled_locked();
}
void ScopeModel::set_signals_enabled(const std::vector<std::size_t>& signals, bool on) {
std::lock_guard<std::mutex> lock(mutex_);
bool any = false;
for (const std::size_t s : signals) {
if (enable_locked(s, on)) any = true;
}
// Nothing moved: no re-plan, and in particular no `generation_` bump -- a restored session
// re-enables what is already on, and a bump there would make the UI rebind for nothing.
if (!any) return;
rebuild_enabled_locked();
}
std::pair<double, double> ScopeModel::calibration(std::size_t signal) {
std::lock_guard<std::mutex> lock(mutex_);
if (signal >= signals_.size()) return {1.0, 0.0};