package clock import ( "context" "fmt" "sync" "github.com/felt-app/felt/internal/server/ws" ) // Registry manages multiple clock engines, one per tournament. // Thread-safe for concurrent access. type Registry struct { mu sync.RWMutex engines map[string]*ClockEngine cancels map[string]context.CancelFunc hub *ws.Hub } // NewRegistry creates a new clock registry. func NewRegistry(hub *ws.Hub) *Registry { return &Registry{ engines: make(map[string]*ClockEngine), cancels: make(map[string]context.CancelFunc), hub: hub, } } // GetOrCreate returns the clock engine for the given tournament, // creating one if it doesn't exist. func (r *Registry) GetOrCreate(tournamentID string) *ClockEngine { r.mu.Lock() defer r.mu.Unlock() if engine, ok := r.engines[tournamentID]; ok { return engine } engine := NewClockEngine(tournamentID, r.hub) r.engines[tournamentID] = engine return engine } // Get returns the clock engine for the given tournament, or nil if not found. func (r *Registry) Get(tournamentID string) *ClockEngine { r.mu.RLock() defer r.mu.RUnlock() return r.engines[tournamentID] } // StartTicker starts the ticker goroutine for the given tournament's engine. // If a ticker is already running, it is stopped first. func (r *Registry) StartTicker(ctx context.Context, tournamentID string) error { r.mu.Lock() defer r.mu.Unlock() engine, ok := r.engines[tournamentID] if !ok { return fmt.Errorf("clock: no engine for tournament %s", tournamentID) } // Stop existing ticker if any if cancel, ok := r.cancels[tournamentID]; ok { cancel() } tickerCtx, cancel := context.WithCancel(ctx) r.cancels[tournamentID] = cancel go StartTicker(tickerCtx, engine) return nil } // StopTicker stops the ticker for the given tournament. func (r *Registry) StopTicker(tournamentID string) { r.mu.Lock() defer r.mu.Unlock() if cancel, ok := r.cancels[tournamentID]; ok { cancel() delete(r.cancels, tournamentID) } } // Remove removes a clock engine and stops its ticker. func (r *Registry) Remove(tournamentID string) { r.mu.Lock() defer r.mu.Unlock() if cancel, ok := r.cancels[tournamentID]; ok { cancel() delete(r.cancels, tournamentID) } delete(r.engines, tournamentID) } // Count returns the number of active clock engines. func (r *Registry) Count() int { r.mu.RLock() defer r.mu.RUnlock() return len(r.engines) } // Shutdown stops all tickers and clears all engines. func (r *Registry) Shutdown() { r.mu.Lock() defer r.mu.Unlock() for id, cancel := range r.cancels { cancel() delete(r.cancels, id) } for id := range r.engines { delete(r.engines, id) } }