refactor: what counts as an event moves into its own evaluator, so a search can ask the same question the trigger asks

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Martino FerrariandClaude Sonnet 4.6 committed 2026-10-07 17:15:37 +02:00
1 parent 178cf54ef6
commit b3fd9ae00f
6 files changed
+189 -50

No files matched your search

+28 -33
View File
@@ -4,6 +4,7 @@
#include "trigger.hpp"
#include <algorithm>
#include <cmath>
namespace udpscope {
@@ -20,8 +21,10 @@ void TriggerEngine::configure(const TriggerConfig& cfg, const std::string& chann
post_seconds_ = post_seconds > 0.0 ? post_seconds : 0.0;
pre_seconds_ = pre_seconds > 0.0 ? pre_seconds : 0.0;
oldest_s_ = oldest_s;
// A no-op unless the condition actually changed: this runs on every packet.
detector_.configure(cfg_.condition);
if (channel_changed) {
have_prev_ = false;
detector_.reset();
pending_ = false;
post_accum_count_ = 0;
}
@@ -32,23 +35,21 @@ void TriggerEngine::arm(double now_s) noexcept {
pending_ = false;
post_accum_count_ = 0.0;
holdoff_until_s_ = now_s;
have_prev_ = false;
detector_.reset();
}
bool TriggerEngine::process(double value, double now_s) noexcept {
if (!cfg_.enabled || !armed_ || cfg_.channel < 0) return false;
// Hold-off: settled, waiting for the next edge.
if (!pending_ && now_s < holdoff_until_s_) {
prev_ = value;
have_prev_ = true;
last_t_s_ = now_s;
return false;
}
// The detector is fed **first and unconditionally**, on every sample the engine sees --
// including during the hold-off and during an open capture, where its answer is thrown
// away. It is a sequential machine: a pulse whose opening edge fell in a skipped stretch
// can never be closed, and the symptom would be a width condition that silently stops
// firing after the first hold-off.
const double event_t = detector_.feed(value, now_s);
if (!have_prev_) {
prev_ = value;
have_prev_ = true;
// Hold-off: settled, waiting for the next event.
if (!pending_ && now_s < holdoff_until_s_) {
last_t_s_ = now_s;
return false;
}
@@ -60,7 +61,6 @@ bool TriggerEngine::process(double value, double now_s) noexcept {
// past it. Time, not a sample count: `now_s` is the same clock the x axis reads,
// so the capture covers exactly the requested span whatever the cadence is.
++post_accum_count_;
prev_ = value;
if (now_s - pending_start_s_ >= post_seconds_) {
pending_ = false;
post_accum_count_ = 0;
@@ -74,33 +74,28 @@ bool TriggerEngine::process(double value, double now_s) noexcept {
return false;
}
// Edge detection against the level.
const bool crossed_up = prev_ < cfg_.level && value >= cfg_.level;
const bool crossed_down = prev_ > cfg_.level && value <= cfg_.level;
const bool fired = (cfg_.edge == TriggerEdge::Rising && crossed_up) ||
(cfg_.edge == TriggerEdge::Falling && crossed_down) ||
(cfg_.edge == TriggerEdge::Both && (crossed_up || crossed_down));
prev_ = value;
if (!fired) return false;
if (!std::isfinite(event_t)) return false;
// The pre-trigger window has to already be behind this edge, or the capture opens
// on a half-filled pre window and the event is short on its left side -- the plot
// then shows a window that is correctly sized but only partly drawn. The buffer
// fills whether or not the trigger is armed, so this normally passes on the first
// edge; it only bites just after a rebuild (a CONFIG, or enabling the signal),
// when the history genuinely is not there yet.
if (now_s - oldest_s_ < pre_seconds_) return false;
// The pre-trigger window has to already be behind the event, or the capture opens on a
// half-filled pre window and the event is short on its left side -- the plot then shows a
// window that is correctly sized but only partly drawn. The buffer fills whether or not the
// trigger is armed, so this normally passes on the first event; it only bites just after a
// rebuild (a CONFIG, or enabling the signal), when the history genuinely is not there yet.
//
// Measured from the *event's own* time, not from `now_s`: for a qualified condition the two
// differ (a pulse is reported at its closing edge), and the pre window belongs to the event.
if (event_t - oldest_s_ < pre_seconds_) return false;
// The edge's own time, which is what a trigger marker marks: the latch itself
// may happen a post-window later.
last_trigger_s_ = now_s;
// The event's own time, which is what a trigger marker marks: the latch itself may happen
// a post-window later.
last_trigger_s_ = event_t;
// The edge always opens a capture; there is no "latch on the edge itself" case.
// The event always opens a capture; there is no "latch on the event itself" case.
// A zero-length post window latches on the very next sample, which is the least a
// capture can hold, and every longer window is just more samples -- no conversion
// from seconds to a sample count, so no conversion to get wrong.
pending_ = true;
pending_start_s_ = now_s;
pending_start_s_ = event_t;
post_accum_count_ = 0;
return false;
}