Implemented new C++ logic

This commit is contained in:
Martino Ferrari
2026-06-12 15:25:13 +02:00
parent 617b5bd712
commit f25bd7f08e
220 changed files with 39185 additions and 850 deletions
+44 -17
View File
@@ -139,6 +139,11 @@ type sourceHubState struct {
timeSigCalib map[string]float64
configSeq uint64
configSeqAtCalib uint64
// lastPktNs tracks the wall-clock time (UnixNano) of the last received packet
// per signal name. Used by the default (TimeModePacket, n>1) path to estimate
// per-element dt when only one packet arrives in a 30 Hz tick.
lastPktNs map[string]int64
}
// taggedSample is a DataSample annotated with its source ID.
@@ -450,6 +455,7 @@ func (h *Hub) Run() {
addr: cmd.addr,
connState: "connecting",
timeSigCalib: make(map[string]float64),
lastPktNs: make(map[string]int64),
}
h.statsMu.Lock()
h.statsMap[cmd.sourceID] = &SourceStat{}
@@ -509,10 +515,12 @@ func (h *Hub) Run() {
isTemporal := ne > 1 && sig.TimeMode != udpsprotocol.TimeModePacket
if isTemporal {
h.rings[pfxUpd+sig.Name] = newSigRing(ringCapTemporal)
} else {
// Both scalar (ne==1) and snapshot-waveform (ne>1, TimeModePacket)
// are stored under the base signal name.
} else if ne == 1 {
h.rings[pfxUpd+sig.Name] = newSigRing(ringCapScalar)
} else {
// n>1, TimeModePacket snapshot-waveform: each packet contributes n
// elements, so use the temporal capacity to hold enough history.
h.rings[pfxUpd+sig.Name] = newSigRing(ringCapTemporal)
}
}
h.ringsMu.Unlock()
@@ -807,9 +815,17 @@ func (h *Hub) buildBinaryDataMessageForSource(src *sourceHubState, batch []udpsp
pairs[sig.Name] = pairBuf{t: ts, v: vs}
default:
// n > 1, TimeModePacket: no samplingRate from C++, so we interpolate
// timestamps from wall-clock differences between consecutive packets.
// Each packet covers n elements; dt per element ≈ (T_{i+1}-T_i)/n.
// n > 1, TimeModePacket: C++ sends samplingRate=0 so we interpolate
// per-element timestamps from wall-clock differences between packets.
//
// Three fixes vs the naïve approach:
// 1. Use src.lastPktNs[name] for the single-packet case so dt is
// estimated from the actual inter-packet gap, not 1/n.
// 2. Send all n elements to the browser without LTTB so sinusoidal
// waveforms are not degraded (packets arrive at ≤30 Hz, bandwidth
// is trivially acceptable).
// 3. Always write the ring buffer regardless of shouldWriteRing() so
// the first zoom request immediately returns full-resolution data.
allT := make([]float64, 0, len(batch)*n)
allV := make([]float64, 0, len(batch)*n)
for bi, s := range batch {
@@ -817,29 +833,40 @@ func (h *Hub) buildBinaryDataMessageForSource(src *sourceHubState, batch []udpsp
if !ok || len(vals) < n {
continue
}
wallSec := float64(s.WallTime.UnixNano()) / 1e9
wallNs := s.WallTime.UnixNano()
wallSec := float64(wallNs) / 1e9
var dtSec float64
if bi+1 < len(batch) {
dtSec = (float64(batch[bi+1].WallTime.UnixNano())-float64(s.WallTime.UnixNano()))/1e9/float64(n)
// Two consecutive packets in this tick → exact dt.
dtSec = (float64(batch[bi+1].WallTime.UnixNano())-float64(wallNs))/1e9/float64(n)
} else if bi > 0 {
dtSec = (float64(s.WallTime.UnixNano())-float64(batch[bi-1].WallTime.UnixNano()))/1e9/float64(n)
// Last of multiple packets → use diff from previous.
dtSec = (float64(wallNs)-float64(batch[bi-1].WallTime.UnixNano()))/1e9/float64(n)
} else if prevNs, ok2 := src.lastPktNs[sig.Name]; ok2 && prevNs > 0 && wallNs > prevNs {
// Single packet this tick → gap from the previous tick's packet.
dtSec = (float64(wallNs)-float64(prevNs))/1e9/float64(n)
} else {
dtSec = 1.0 / float64(n) // single-sample fallback
// Truly first packet ever — inter-packet timing unknown.
// Skip to avoid poisoning the ring with wrongly-spaced timestamps;
// lastPktNs will be recorded below so the next packet uses correct dt.
continue
}
for j := 0; j < n; j++ {
allT = append(allT, wallSec+float64(j)*dtSec)
allV = append(allV, vals[j])
}
}
if len(batch) > 0 {
src.lastPktNs[sig.Name] = batch[len(batch)-1].WallTime.UnixNano()
}
if len(allT) > 0 {
if writeRing {
ringT, ringV := lttbDecimate(allT, allV, maxRingPoints)
if rb := h.getRing(pfx + sig.Name); rb != nil {
rb.write(ringT, ringV)
}
// Ring: always populate (fix 3), LTTB only if it actually reduces size.
ringT, ringV := lttbDecimate(allT, allV, maxRingPoints)
if rb := h.getRing(pfx + sig.Name); rb != nil {
rb.write(ringT, ringV)
}
decimT, decimV := lttbDecimate(allT, allV, maxPushPoints)
pairs[sig.Name] = pairBuf{t: decimT, v: decimV}
// Live push: send all points without LTTB (fix 2).
pairs[sig.Name] = pairBuf{t: allT, v: allV}
}
}
}