package wshub import "sync" // sigRing is a fixed-capacity circular buffer storing (time, value) pairs. // Writes come from the Hub.Run() goroutine; reads come from HTTP handler goroutines. // The embedded RWMutex protects concurrent access. type sigRing struct { mu sync.RWMutex t, v []float64 cap int head, size int // next write position; current fill } func newSigRing(capacity int) *sigRing { return &sigRing{ t: make([]float64, capacity), v: make([]float64, capacity), cap: capacity, } } // write appends (tArr[i], vArr[i]) pairs, overwriting oldest entries when full. func (rb *sigRing) write(tArr, vArr []float64) { rb.mu.Lock() defer rb.mu.Unlock() for i := 0; i < len(tArr); i++ { rb.t[rb.head] = tArr[i] rb.v[rb.head] = vArr[i] rb.head = (rb.head + 1) % rb.cap if rb.size < rb.cap { rb.size++ } } } // slice returns copies of all (t, v) pairs whose timestamp falls in [t0, t1]. // The returned slices are safe to use after the call without holding any lock. func (rb *sigRing) slice(t0, t1 float64) ([]float64, []float64) { rb.mu.RLock() defer rb.mu.RUnlock() if rb.size == 0 { return nil, nil } start := 0 if rb.size == rb.cap { start = rb.head } physAt := func(k int) int { return (start + k) % rb.cap } // Binary search for t0 lo, hi := 0, rb.size for lo < hi { m := (lo + hi) >> 1 if rb.t[physAt(m)] < t0 { lo = m + 1 } else { hi = m } } kStart := lo // Binary search for t1 lo, hi = kStart, rb.size for lo < hi { m := (lo + hi) >> 1 if rb.t[physAt(m)] <= t1 { lo = m + 1 } else { hi = m } } kEnd := lo n := kEnd - kStart if n <= 0 { return nil, nil } outT := make([]float64, n) outV := make([]float64, n) for i := 0; i < n; i++ { p := physAt(kStart + i) outT[i] = rb.t[p] outV[i] = rb.v[p] } return outT, outV }