Implemented and fixed many issues
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
package wshub
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// capturedWindow is one delivered trigger capture, held at the resolution the
|
||||
// rings had when it was taken. Nothing mutates it after publication, so readers
|
||||
// may sub-slice it without copying.
|
||||
type capturedWindow struct {
|
||||
t0, t1 float64
|
||||
sigs map[string]sigData
|
||||
}
|
||||
|
||||
// captureHold is the read half of the trigger double buffer; the rings are the
|
||||
// write half.
|
||||
//
|
||||
// The rings keep rolling while the trigger re-arms and collects the next shot,
|
||||
// so within seconds of a capture they no longer hold the window the user is
|
||||
// looking at — a zoom into it came back with only the newest sliver, or with
|
||||
// nothing. Publishing the window here at capture time gives the viewer a
|
||||
// snapshot that the re-arming acquisition cannot overwrite: the swap happens
|
||||
// only when the *next* capture is complete, which is also the moment the client
|
||||
// stops displaying this one.
|
||||
type captureHold struct {
|
||||
mu sync.RWMutex
|
||||
cur *capturedWindow
|
||||
}
|
||||
|
||||
// publish swaps in a new capture, retiring the previous one. Readers that
|
||||
// already hold a pointer to the retired window keep reading it safely.
|
||||
func (ch *captureHold) publish(t0, t1 float64, sigs map[string]sigData) {
|
||||
if len(sigs) == 0 {
|
||||
return
|
||||
}
|
||||
w := &capturedWindow{t0: t0, t1: t1, sigs: sigs}
|
||||
ch.mu.Lock()
|
||||
ch.cur = w
|
||||
ch.mu.Unlock()
|
||||
}
|
||||
|
||||
// clear drops the held capture, releasing its memory.
|
||||
func (ch *captureHold) clear() {
|
||||
ch.mu.Lock()
|
||||
ch.cur = nil
|
||||
ch.mu.Unlock()
|
||||
}
|
||||
|
||||
// slice answers [a, b] for one signal out of the held capture, reporting
|
||||
// whether it could.
|
||||
//
|
||||
// It declines any range reaching outside the captured window: that is a live
|
||||
// zoom or a pan off the capture, and only the rings still track the stream.
|
||||
// Inside the window the hold is never worse than the rings — retuning does not
|
||||
// rewrite stored samples, so a ring that still covers the range holds the very
|
||||
// same points — which is why no trigger-state gating is needed here.
|
||||
func (ch *captureHold) slice(key string, a, b float64) ([]float64, []float64, bool) {
|
||||
ch.mu.RLock()
|
||||
w := ch.cur
|
||||
ch.mu.RUnlock()
|
||||
if w == nil || a < w.t0 || b > w.t1 {
|
||||
return nil, nil, false
|
||||
}
|
||||
sd, ok := w.sigs[key]
|
||||
if !ok || len(sd.T) == 0 {
|
||||
return nil, nil, false
|
||||
}
|
||||
// The window is what was asked for; this signal's samples are what could be
|
||||
// found. A capture whose front was never recoverable must not answer for the
|
||||
// stretch it is missing — the client would redraw the same hole on every
|
||||
// zoom and every "fit" instead of falling back to the archive.
|
||||
tol := shortCaptureTol * (w.t1 - w.t0)
|
||||
if sd.T[0] > a+tol || sd.T[len(sd.T)-1] < b-tol {
|
||||
return nil, nil, false
|
||||
}
|
||||
lo := sort.SearchFloat64s(sd.T, a)
|
||||
hi := lo + sort.Search(len(sd.T)-lo, func(i int) bool { return sd.T[lo+i] > b })
|
||||
if hi <= lo {
|
||||
return nil, nil, false
|
||||
}
|
||||
return sd.T[lo:hi], sd.V[lo:hi], true
|
||||
}
|
||||
Reference in New Issue
Block a user