Implemented new C++ logic
This commit is contained in:
@@ -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}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user