98 lines
4.2 KiB
C++
98 lines
4.2 KiB
C++
// SPDX-License-Identifier: MIT
|
|
// SPDX-FileCopyrightText: 2026 Martino Ferrari
|
|
|
|
#pragma once
|
|
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <vector>
|
|
|
|
#include "condition.hpp"
|
|
#include "model.hpp"
|
|
|
|
namespace udpscope {
|
|
|
|
/// A scan of the whole retained buffer for every sample satisfying an `EventCondition`.
|
|
///
|
|
/// It runs an `EventDetector` -- the *same* evaluator the live trigger runs -- over the ring
|
|
/// in stream order, so a hit is by construction an instant the trigger would have fired on.
|
|
/// That is the claim the probe's armed-vs-scanned cross-check checks, and it is the reason
|
|
/// the detector was separated from the engine at all.
|
|
///
|
|
/// Everything here is in **ring-stamp space**, which is stream seconds relative to the
|
|
/// model's epoch: the clock `TriggerEngine::process()` is fed, the clock
|
|
/// `ScopeModel::live_window_locked()` searches, and the clock `publish_pause_view()` cuts in.
|
|
/// No conversion to display x happens in this class.
|
|
///
|
|
/// It runs on the GTK thread in chunks rather than on a thread of its own: the samples live
|
|
/// behind `ScopeModel::mutex()`, which the producer holds for each packet, and a scan that
|
|
/// took the lock once would stall acquisition for the whole buffer. One chunk per call holds
|
|
/// it for `kChunk` samples and no longer.
|
|
class BufferSearch {
|
|
public:
|
|
/// The hit list's ceiling. A condition matching a large share of the samples -- an edge
|
|
/// trigger on a noisy channel -- would otherwise fill memory with a list nobody can
|
|
/// navigate; the scan stops and says `Capped`, which is information rather than a hang.
|
|
static constexpr std::size_t kHitsMax = 10000;
|
|
/// Samples per `pump()`, and therefore the only latency knob: the producer waits on the
|
|
/// lock for exactly one chunk's scan.
|
|
static constexpr u64 kChunk = 8192;
|
|
|
|
enum class State {
|
|
Idle, ///< nothing has been searched.
|
|
Running, ///< `pump()` has more to do.
|
|
Done, ///< the whole retained buffer was scanned.
|
|
Truncated, ///< the producer overwrote samples before they were reached.
|
|
Capped, ///< `kHitsMax` was reached.
|
|
Cancelled, ///< the user stopped it, or the channel went away.
|
|
};
|
|
|
|
/// Begins a scan of the channel for the condition. Discards any previous result.
|
|
void start(const EventCondition& c, int channel, double dt);
|
|
void cancel() noexcept;
|
|
void clear() noexcept;
|
|
|
|
/// Scans one chunk. Returns true while there is more to do. Takes `model.mutex()` itself,
|
|
/// so the caller must *not* hold it.
|
|
bool pump(ScopeModel& model);
|
|
|
|
State state() const noexcept { return state_; }
|
|
const std::vector<double>& hits() const noexcept { return hits_; }
|
|
/// Which hit the view is on, or -1 for none.
|
|
int cursor() const noexcept { return cursor_; }
|
|
void select(int i) noexcept;
|
|
/// Moves the cursor and returns whether it moved; the ends do not wrap, because a wrap
|
|
/// from the last hit to the first looks exactly like the view not having moved at all.
|
|
bool next() noexcept;
|
|
bool prev() noexcept;
|
|
|
|
/// How much of the buffer has been scanned, and how much there was, in stream seconds.
|
|
double scanned_s() const noexcept { return scanned_s_; }
|
|
double total_s() const noexcept { return total_s_; }
|
|
/// Cost, for the probe: chunks scanned, the worst single chunk, and the whole scan.
|
|
std::uint64_t chunks() const noexcept { return chunks_; }
|
|
double worst_chunk_ms() const noexcept { return worst_chunk_ms_; }
|
|
double total_ms() const noexcept { return total_ms_; }
|
|
|
|
private:
|
|
EventDetector detector_{};
|
|
int channel_ = -1;
|
|
State state_ = State::Idle;
|
|
bool started_ = false; ///< the index range has been established.
|
|
bool truncated_ = false; ///< the producer overtook the scan at least once; sticky for the whole scan.
|
|
u64 at_ = 0; ///< next absolute index to scan.
|
|
u64 end_ = 0; ///< one past the last absolute index to scan.
|
|
double first_s_ = 0.0; ///< stamp of `at_` when the scan began.
|
|
double scanned_s_ = 0.0;
|
|
double total_s_ = 0.0;
|
|
std::vector<double> hits_;
|
|
int cursor_ = -1;
|
|
std::uint64_t chunks_ = 0;
|
|
double worst_chunk_ms_ = 0.0;
|
|
double total_ms_ = 0.0;
|
|
std::vector<f64> t_scratch_;
|
|
std::vector<f32> v_scratch_;
|
|
};
|
|
|
|
} // namespace udpscope
|