refactor: deduplicate persistence, add upsert queries, throttle snake saves
Some checks failed
CI / Deploy / test (pull_request) Failing after 15s
CI / Deploy / lint (pull_request) Failing after 23s
CI / Deploy / deploy (pull_request) Has been skipped

- Replace Create+Get+Update with UpsertGame/UpsertSnakeGame queries
- Extract free functions (saveGame, loadGame, etc.) from duplicated
  receiver methods on Store and Instance types
- Remove duplicate generateID from snake package, reuse game.GenerateID
- Throttle snake game DB writes to every 2s instead of every tick
- Fix double-lock in c4game chat handler
- Update all code for sqlc pointer types (*string instead of sql.NullString)
This commit is contained in:
Ryan Hamamura
2026-03-02 16:56:29 -10:00
parent 9c3f659e96
commit bc6488f063
14 changed files with 318 additions and 494 deletions

View File

@@ -62,16 +62,14 @@ func (si *SnakeGameInstance) countdownPhase() {
si.game.Status = StatusInProgress
if si.queries != nil {
si.saveSnakeGame(si.game) //nolint:errcheck
saveSnakeGame(si.queries, si.game) //nolint:errcheck
}
si.gameMu.Unlock()
si.notify()
return
}
if si.queries != nil {
si.saveSnakeGame(si.game) //nolint:errcheck
}
// No DB save during countdown ticks — state is transient
si.gameMu.Unlock()
si.notify()
}
@@ -98,6 +96,7 @@ func (si *SnakeGameInstance) gamePhase() {
defer ticker.Stop()
lastInput := time.Now()
lastSave := time.Now()
var moveAccum time.Duration
for {
@@ -124,7 +123,7 @@ func (si *SnakeGameInstance) gamePhase() {
if time.Since(lastInput) > inactivityLimit {
si.game.Status = StatusFinished
if si.queries != nil {
si.saveSnakeGame(si.game) //nolint:errcheck
saveSnakeGame(si.queries, si.game) //nolint:errcheck
}
si.gameMu.Unlock()
si.notify()
@@ -175,13 +174,10 @@ func (si *SnakeGameInstance) gamePhase() {
alive := AliveCount(state)
gameOver := false
if si.game.Mode == ModeSinglePlayer {
// Single player ends when the player dies (alive == 0)
if alive == 0 {
gameOver = true
// No winner in single player - just final score
}
} else {
// Multiplayer ends when 1 or fewer alive
if alive <= 1 {
gameOver = true
winnerIdx := LastAlive(state)
@@ -195,8 +191,10 @@ func (si *SnakeGameInstance) gamePhase() {
si.game.Status = StatusFinished
}
if si.queries != nil {
si.saveSnakeGame(si.game) //nolint:errcheck
// Throttle DB saves: persist on game over or every 2 seconds
if si.queries != nil && (gameOver || time.Since(lastSave) >= 2*time.Second) {
saveSnakeGame(si.queries, si.game) //nolint:errcheck
lastSave = time.Now()
}
si.gameMu.Unlock()