Files

83 lines
2.1 KiB
Go

package infra
import "sync/atomic"
// WSHub gestiona el ciclo de vida de conexiones WebSocket.
// Mantiene un mapa de clientes activos y canales para registro,
// desregistro y broadcast. Se ejecuta como goroutine via su Run().
type WSHub struct {
Clients map[*WSClient]bool
Broadcast chan []byte
Register chan *WSClient
Unregister chan *WSClient
done chan struct{}
count atomic.Int64
}
// ClientCount retorna el numero de clientes registrados de forma thread-safe.
// Util para metricas, dashboards y tests.
func (h *WSHub) ClientCount() int {
return int(h.count.Load())
}
// NewWSHub crea un WSHub vacio con canales inicializados.
// Ejecutar Run() en una goroutine para activar el loop de eventos.
func NewWSHub() *WSHub {
return &WSHub{
Clients: make(map[*WSClient]bool),
Broadcast: make(chan []byte, 256),
Register: make(chan *WSClient),
Unregister: make(chan *WSClient),
done: make(chan struct{}),
}
}
// Run ejecuta el loop principal del hub. Atiende registros, desregistros
// y broadcasts en un select. Bloqueante: lanzar como goroutine.
// Para parar, llamar a Stop().
func (h *WSHub) Run() {
for {
select {
case <-h.done:
// Cerrar todos los Send y vaciar el mapa
for client := range h.Clients {
delete(h.Clients, client)
close(client.Send)
}
h.count.Store(0)
return
case client := <-h.Register:
h.Clients[client] = true
h.count.Store(int64(len(h.Clients)))
case client := <-h.Unregister:
if _, ok := h.Clients[client]; ok {
delete(h.Clients, client)
close(client.Send)
h.count.Store(int64(len(h.Clients)))
}
case msg := <-h.Broadcast:
for client := range h.Clients {
select {
case client.Send <- msg:
default:
// Cliente lento: desconectar
delete(h.Clients, client)
close(client.Send)
}
}
h.count.Store(int64(len(h.Clients)))
}
}
}
// Stop cierra el loop de Run() y limpia todos los clientes.
// Idempotente — llamar mas de una vez no panica.
func (h *WSHub) Stop() {
select {
case <-h.done:
// Ya cerrado
default:
close(h.done)
}
}