- ClockEngine with full state machine (stopped/running/paused transitions) - Level management: load, advance, rewind, jump, hand-for-hand mode - Drift-free ticker at 100ms with 1/sec broadcast (10/sec in final 10s) - ClockRegistry for multi-tournament support (thread-safe) - ClockSnapshot for reconnecting clients (CLOCK-09) - Configurable overtime mode (repeat/stop) - Crash recovery via RestoreState (resumes as paused for safety) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
67 lines
1.6 KiB
Go
67 lines
1.6 KiB
Go
package clock
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"time"
|
|
)
|
|
|
|
// StartTicker runs the clock ticker for the given engine.
|
|
// It ticks every 100ms and broadcasts clock snapshots:
|
|
// - 1/sec during normal play
|
|
// - 10/sec (every 100ms) during the final 10 seconds
|
|
//
|
|
// The ticker stops when the context is cancelled.
|
|
func StartTicker(ctx context.Context, engine *ClockEngine) {
|
|
ticker := time.NewTicker(100 * time.Millisecond)
|
|
defer ticker.Stop()
|
|
|
|
var lastBroadcastSecond int64 = -1
|
|
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-ticker.C:
|
|
shouldBroadcast, snapshot := engine.Tick()
|
|
if !shouldBroadcast {
|
|
continue
|
|
}
|
|
|
|
// Determine broadcast rate:
|
|
// - Final 10 seconds: broadcast every tick (10/sec)
|
|
// - Normal: broadcast once per second (1/sec)
|
|
currentSecond := snapshot.RemainingMs / 1000
|
|
isFinalSeconds := snapshot.RemainingMs <= 10000
|
|
|
|
if isFinalSeconds {
|
|
// Broadcast every 100ms tick in final 10 seconds
|
|
broadcastSnapshot(engine, snapshot)
|
|
lastBroadcastSecond = currentSecond
|
|
} else if currentSecond != lastBroadcastSecond {
|
|
// Broadcast once per second change
|
|
broadcastSnapshot(engine, snapshot)
|
|
lastBroadcastSecond = currentSecond
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// broadcastSnapshot sends a clock snapshot to all WebSocket clients.
|
|
func broadcastSnapshot(engine *ClockEngine, snapshot ClockSnapshot) {
|
|
engine.mu.RLock()
|
|
hub := engine.hub
|
|
tournamentID := engine.tournamentID
|
|
engine.mu.RUnlock()
|
|
|
|
if hub == nil {
|
|
return
|
|
}
|
|
|
|
data, err := json.Marshal(snapshot)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
hub.Broadcast(tournamentID, "clock.tick", data)
|
|
}
|