| |
| package main |
|
|
| import ( |
| "encoding/json" |
| "log" |
| "sync" |
| "time" |
| "os" |
| ) |
|
|
| type WaitingGame struct { |
| Pin string `json:"pin"` |
| HostID string `json:"host_id"` |
| Required int `json:"required"` |
| CreatedAt int64 `json:"created_at"` |
| } |
|
|
| type SlotManager struct { |
| mu sync.Mutex |
| capacity int |
| available int |
| waiting []WaitingGame |
| active map[string]int |
| savePath string |
| } |
|
|
| func NewSlotManager(cap int, savePath string) *SlotManager { |
| return &SlotManager{capacity: cap, available: cap, waiting: make([]WaitingGame, 0), active: make(map[string]int), savePath: savePath} |
| } |
|
|
| func (sm *SlotManager) Load() { |
| data, err := os.ReadFile(sm.savePath) |
| if err != nil { return } |
| var state struct { |
| Available int `json:"available"` |
| Waiting []WaitingGame `json:"waiting"` |
| Active map[string]int `json:"active"` |
| } |
| if json.Unmarshal(data, &state) == nil { |
| sm.mu.Lock() |
| sm.available = state.Available |
| sm.waiting = state.Waiting |
| sm.active = state.Active |
| sm.mu.Unlock() |
| Info("Queue State Restored: Avail=%d, Waiting=%d, Active=%d", sm.available, len(sm.waiting), len(sm.active)) |
| } |
| } |
|
|
| func (sm *SlotManager) Save() { |
| sm.mu.Lock() |
| defer sm.mu.Unlock() |
| state := struct { |
| Available int `json:"available"` |
| Waiting []WaitingGame `json:"waiting"` |
| Active map[string]int `json:"active"` |
| }{sm.available, sm.waiting, sm.active} |
| data, _ := json.Marshal(state) |
| os.WriteFile(sm.savePath, data, 0644) |
| } |
|
|
| func (sm *SlotManager) PersistLoop(interval time.Duration) { |
| ticker := time.NewTicker(interval) |
| for range ticker.C { sm.Save() } |
| } |
|
|
| |
| func (sm *SlotManager) RequestSlots(pin, hostID string, required int) (bool, chan struct{}) { |
| sm.mu.Lock() |
| defer sm.mu.Unlock() |
|
|
| if sm.available >= required { |
| sm.available -= required |
| sm.active[pin] = required |
| sm.Save() |
| return true, nil |
| } |
|
|
| ch := make(chan struct{}, 1) |
| wg := WaitingGame{Pin: pin, HostID: hostID, Required: required, CreatedAt: time.Now().Unix()} |
| sm.waiting = append(sm.waiting, wg) |
| sm.Save() |
| return false, ch |
| } |
|
|
| |
| func (sm *SlotManager) ReleaseSlots(pin string, count int) { |
| sm.mu.Lock() |
| defer sm.mu.Unlock() |
|
|
| reserved := sm.active[pin] |
| if reserved == 0 { return } |
| releaseCount := min(count, reserved) |
| sm.available += releaseCount |
|
|
| if releaseCount >= reserved { |
| delete(sm.active, pin) |
| } else { |
| sm.active[pin] = reserved - releaseCount |
| } |
|
|
| |
| newWaiting := make([]WaitingGame, 0, len(sm.waiting)) |
| for _, wg := range sm.waiting { |
| if sm.available >= wg.Required { |
| sm.available -= wg.Required |
| sm.active[wg.Pin] = wg.Required |
| |
| go notifyQueueReady(wg.Pin) |
| } else { |
| newWaiting = append(newWaiting, wg) |
| } |
| } |
| sm.waiting = newWaiting |
| sm.Save() |
| } |
|
|
| func (sm *SlotManager) GetQueuePosition(pin string) int { |
| sm.mu.Lock() |
| defer sm.mu.Unlock() |
| for i, wg := range sm.waiting { |
| if wg.Pin == pin { return i + 1 } |
| } |
| return -1 |
| } |
|
|
| |
| |
| func notifyQueueReady(pin string) { |
| Info("Queue Slot Granted -> Starting Game: %s", pin) |
| |
| StartGameLoop(hub, store, pin) |
| } |