package main import ( "encoding/json" "log" "time" "sdr-visual-suite/internal/detector" ) func (s *signalSnapshot) set(sig []detector.Signal) { s.mu.Lock() defer s.mu.Unlock() s.signals = append([]detector.Signal(nil), sig...) } func (s *signalSnapshot) get() []detector.Signal { s.mu.RLock() defer s.mu.RUnlock() return append([]detector.Signal(nil), s.signals...) } func (g *gpuStatus) set(active bool, err error) { g.mu.Lock() defer g.mu.Unlock() g.Active = active if err != nil { g.Error = err.Error() } else { g.Error = "" } } func (g *gpuStatus) snapshot() gpuStatus { g.mu.RLock() defer g.mu.RUnlock() return gpuStatus{Available: g.Available, Active: g.Active, Error: g.Error} } func newHub() *hub { return &hub{clients: map[*client]struct{}{}, lastLogTs: time.Now()} } func (h *hub) add(c *client) { h.mu.Lock() defer h.mu.Unlock() h.clients[c] = struct{}{} log.Printf("ws connected (%d clients)", len(h.clients)) } func (h *hub) remove(c *client) { c.closeOnce.Do(func() { close(c.done) }) h.mu.Lock() defer h.mu.Unlock() delete(h.clients, c) log.Printf("ws disconnected (%d clients)", len(h.clients)) } func (h *hub) broadcast(frame SpectrumFrame) { b, err := json.Marshal(frame) if err != nil { log.Printf("marshal frame: %v", err) return } h.mu.Lock() clients := make([]*client, 0, len(h.clients)) for c := range h.clients { clients = append(clients, c) } h.mu.Unlock() for _, c := range clients { select { case c.send <- b: default: h.remove(c) } } h.frameCnt++ if time.Since(h.lastLogTs) > 2*time.Second { h.lastLogTs = time.Now() log.Printf("broadcast frames=%d clients=%d", h.frameCnt, len(clients)) } }