fix(multiplayer): dispatch live abandonment lifecycle

This commit is contained in:
Josh Creek
2026-09-03 20:55:56 +01:00
parent bd93a2657e
commit 947fefc95c
5 changed files with 62 additions and 5 deletions
+35 -2
View File
@@ -53,9 +53,14 @@ SET revision = revision + 1
WHERE match_id = $1 AND state = 'LIVE'
RETURNING revision`
const liveAbandonmentTargetsSQL = `SELECT player_id
FROM match_participants
WHERE match_id = $1 AND participation_active
ORDER BY player_id`
const liveAbandonmentOutboxSQL = `INSERT INTO outbox
(event_id, aggregate_type, aggregate_id, revision, event_type, payload)
VALUES ($1, 'match', $2, $3, 'participant_abandoned', $4)`
VALUES ($1, 'match', $2, $3, 'state_changed', $4)`
// ReconcileLiveAbandonments applies a bounded, durable reconnect-grace sweep.
// It does not deactivate participants or alter LIVE tickets: an abandonment
@@ -148,7 +153,18 @@ func ApplyLiveAbandonments(ctx context.Context, db *sql.DB, matchID string, now
if err := tx.QueryRowContext(ctx, liveAbandonmentRevisionSQL, matchID).Scan(&revision); err != nil {
return err
}
payload, err := json.Marshal(map[string]any{"match_id": matchID, "abandoned_player_ids": abandonmentIDs(planned)})
targets, err := loadLiveAbandonmentTargets(ctx, tx, matchID)
if err != nil {
return err
}
if len(targets) == 0 {
return fmt.Errorf("%w: live match has no active event targets", domain.ErrConflict)
}
payload, err := json.Marshal(map[string]any{
"event": "state_changed", "revision": revision, "resource_id": matchID,
"occurred_at": now, "state": domain.Live, "match_id": matchID,
"player_ids": targets, "abandoned_player_ids": abandonmentIDs(planned),
})
if err != nil {
return err
}
@@ -204,6 +220,23 @@ func loadLiveAbandonmentHistory(ctx context.Context, tx *sql.Tx, participants []
return history, nil
}
func loadLiveAbandonmentTargets(ctx context.Context, tx *sql.Tx, matchID string) ([]string, error) {
rows, err := tx.QueryContext(ctx, liveAbandonmentTargetsSQL, matchID)
if err != nil {
return nil, err
}
defer rows.Close()
var players []string
for rows.Next() {
var playerID string
if err := rows.Scan(&playerID); err != nil {
return nil, err
}
players = append(players, playerID)
}
return players, rows.Err()
}
func abandonmentIDs(abandonments []domain.Abandonment) []string {
ids := make([]string, len(abandonments))
for i := range abandonments {