feat: the buffer can be searched for the trigger's own condition, and the hits stepped through
This commit is contained in:
1 parent
2cee85c52b
commit
415fceca87
4 files changed
+517
No files matched your search
@@ -0,0 +1,98 @@
|
||||
// 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);
|
||||
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_; }
|
||||
int channel() const noexcept { return channel_; }
|
||||
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_{};
|
||||
EventCondition condition_{};
|
||||
int channel_ = -1;
|
||||
State state_ = State::Idle;
|
||||
bool started_ = false; ///< the index range has been established.
|
||||
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
|
||||
Reference in new issue
Block a user