Files
MARTe-Integrated-Components/Client/udpscope/FrameDecoder.h
T
Martino FerrariandClaude Opus 4.6 892e3eae28 fix(udpscope): bound accumulated-burst chaining so packet loss cannot displace the trace
Forward-chaining each accumulated burst onto the previous one suppresses
arrival jitter, but an unchecked chain never recovers: one lost datagram, or a
declared sampling rate that differs from the producer's real one, dates every
later sample early for the rest of the run. The chain is now a prediction,
compared each packet against the arrival anchor and abandoned beyond
kBurstResyncThresholdS, which bounds the error instead of accumulating it.

Plan amended so the hrt-fit fallback (unusable here: the fit needs 32 packets
and is itself corrupted by bursty arrivals) cannot come back.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-08-27 20:08:05 +02:00

72 lines
2.7 KiB
C++

/**
* @file FrameDecoder.h
* @brief Per-element timestamp reconstruction for UDPS frames.
*
* The C client's udps_frame_element_time() is explicitly an arrival-anchored
* estimate. It is not sufficient: the kernel frequently delivers several queued
* datagrams in one burst, so two packets are processed microseconds apart even
* though each represents ~10 ms of signal, and arrival-time interpolation then
* crams a packet's samples into that tiny gap — the trace renders as a sawtooth.
* Source/Applications/StreamHub/UDPSourceSession.cpp documents this failure and
* solves it; these are the same rules, computed from udps_frame_t's own fields
* so the scope and StreamHub agree on the same stream.
*/
#pragma once
#include "TimeBase.h"
#include "Types.h"
#include <vector>
namespace udpscope {
class FrameDecoder {
public:
/** Installs the signal table. Clears all per-signal timing history. */
void setSignals(const std::vector<SignalMeta>& signals);
const std::vector<SignalMeta>& signals() const { return signals_; }
/** Call once per frame, before any timestamps() call for that frame. */
void beginFrame(const FrameView& f);
/**
* @brief Timestamps for every value of signal @p idx in this frame.
* @return false when the signal produced nothing usable — an empty slot, or
* the first PACKET burst after connect, which has no previous
* arrival to span from and would otherwise poison the ring with
* wrongly spaced timestamps.
*/
bool timestamps(const FrameView& f, uint32_t idx, std::vector<double>& tsOut);
/** Forgets all timing history; call on reconnect. */
void reset();
private:
bool packetBurst(uint32_t idx, uint32_t nElems, double wallNow,
std::vector<double>& tsOut);
struct SigState {
ClockOffset offset;
double lastPacketWall = 0.0;
bool lastPacketValid = false;
double lastAccHrtSec = 0.0;
bool lastAccValid = false;
uint32_t prevAccCount = 0;
/** For accumulated scalars with a declared sampling rate: end timestamp
* of the most recently emitted burst. The next burst is PREDICTED to
* start one sample period after it — immune to arrival-time jitter —
* but the prediction is discarded when arrival time disagrees with it
* by more than a delivery backlog can explain, so packet loss cannot
* displace the trace permanently. */
double lastEmittedEnd = 0.0;
bool lastEmittedValid = false;
};
std::vector<SignalMeta> signals_;
std::vector<SigState> state_;
HrtRateFit hrtFit_;
};
} /* namespace udpscope */