Files
udpscope/src/search.cpp
T

156 lines
4.7 KiB
C++

// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: 2026 Martino Ferrari
#include "search.hpp"
#include <chrono>
#include <cmath>
#include <mutex>
namespace udpscope {
namespace {
double ms_since(const std::chrono::steady_clock::time_point& t0) {
const std::chrono::duration<double, std::milli> d = std::chrono::steady_clock::now() - t0;
return d.count();
}
} // namespace
void BufferSearch::start(const EventCondition& c, int channel, double dt) {
clear();
channel_ = channel;
detector_.reset();
detector_.configure(c, dt);
// `configure()` is a no-op for an unchanged condition, so the explicit reset above is what
// makes a re-run of the *same* search start from a clean machine.
detector_.reset();
state_ = State::Running;
t_scratch_.resize(static_cast<std::size_t>(kChunk));
v_scratch_.resize(static_cast<std::size_t>(kChunk));
}
void BufferSearch::cancel() noexcept {
if (state_ == State::Running) state_ = State::Cancelled;
}
void BufferSearch::clear() noexcept {
state_ = State::Idle;
started_ = false;
truncated_ = false;
at_ = 0;
end_ = 0;
first_s_ = 0.0;
scanned_s_ = 0.0;
total_s_ = 0.0;
hits_.clear();
cursor_ = -1;
chunks_ = 0;
worst_chunk_ms_ = 0.0;
total_ms_ = 0.0;
}
void BufferSearch::select(int i) noexcept {
if (hits_.empty()) {
cursor_ = -1;
return;
}
if (i < 0) i = 0;
if (static_cast<std::size_t>(i) >= hits_.size()) i = static_cast<int>(hits_.size()) - 1;
cursor_ = i;
}
bool BufferSearch::next() noexcept {
if (hits_.empty() || cursor_ + 1 >= static_cast<int>(hits_.size())) return false;
++cursor_;
return true;
}
bool BufferSearch::prev() noexcept {
if (hits_.empty() || cursor_ <= 0) return false;
--cursor_;
return true;
}
bool BufferSearch::pump(ScopeModel& model) {
if (state_ != State::Running) return false;
const auto chunk_t0 = std::chrono::steady_clock::now();
u64 n = 0;
{
std::lock_guard<std::mutex> lock(model.mutex());
if (channel_ < 0 || static_cast<std::size_t>(channel_) >= model.channel_count()) {
state_ = State::Cancelled;
return false;
}
BufferStore& store = model.live();
if (!started_) {
// The whole retained buffer: the bounds are deliberately absurd, because
// `live_window_locked()` clamps them to the time ring and to *every* value ring, which
// is the one cut that keeps the stamps and the values index-aligned by construction.
u64 first = 0;
u64 last = 0;
model.live_window_locked(-1.0e300, 1.0e300, first, last);
at_ = first;
end_ = last;
started_ = true;
if (end_ > at_) {
f64 stamp = 0.0;
store.time().copy_from(at_, 1, &stamp);
first_s_ = static_cast<double>(stamp);
store.time().copy_from(end_ - 1, 1, &stamp);
total_s_ = static_cast<double>(stamp) - first_s_;
}
}
// The producer keeps pushing while we scan, so the oldest samples go. `copy_from()` fixes
// *which* indices are read, not that the slots still hold them -- a read behind the base
// would silently return whatever overwrote it, which would be a hit at a time that never
// existed. Skipping forward and reporting `Truncated` is the honest answer.
const u64 base = store.time().base();
if (base > at_) {
at_ = base;
truncated_ = true;
detector_.reset(); // the sequence has a gap in it; no pulse spans it
}
if (at_ >= end_) {
state_ = truncated_ ? State::Truncated : State::Done;
total_ms_ += ms_since(chunk_t0);
return false;
}
n = end_ - at_ < kChunk ? end_ - at_ : kChunk;
store.time().copy_from(at_, n, t_scratch_.data());
store.values(static_cast<std::size_t>(channel_)).copy_from(at_, n, v_scratch_.data());
}
// The evaluation is outside the lock: it is the expensive half, and it touches only the
// copies above.
for (u64 i = 0; i < n; ++i) {
const double t = static_cast<double>(t_scratch_[static_cast<std::size_t>(i)]);
const double e =
detector_.feed(static_cast<double>(v_scratch_[static_cast<std::size_t>(i)]), t);
if (std::isfinite(e)) {
hits_.push_back(e);
if (hits_.size() >= kHitsMax) {
at_ += i + 1;
scanned_s_ = t - first_s_;
state_ = State::Capped;
++chunks_;
const double cost = ms_since(chunk_t0);
if (cost > worst_chunk_ms_) worst_chunk_ms_ = cost;
total_ms_ += cost;
return false;
}
}
scanned_s_ = t - first_s_;
}
at_ += n;
++chunks_;
const double cost = ms_since(chunk_t0);
if (cost > worst_chunk_ms_) worst_chunk_ms_ = cost;
total_ms_ += cost;
if (at_ >= end_) {
state_ = truncated_ ? State::Truncated : State::Done;
return false;
}
return true;
}
} // namespace udpscope