mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-13 16:22:04 +00:00
feat: persist authenticated queue probe RTT
This commit is contained in:
@@ -163,6 +163,34 @@ func (q *Queue) Expire(now time.Time) []QueueTicket {
|
||||
return q.expireLocked(now)
|
||||
}
|
||||
|
||||
// RecordProbe stores server-computed RTT metadata on the player's active
|
||||
// ticket. It never accepts client-provided latency and refuses expired or
|
||||
// non-queueable tickets.
|
||||
func (q *Queue) RecordProbe(playerID, region string, rtt time.Duration, now time.Time) error {
|
||||
if playerID == "" || (region != "EU" && region != "NA") || rtt < 0 || now.IsZero() {
|
||||
return fmt.Errorf("invalid probe recording")
|
||||
}
|
||||
q.mu.Lock()
|
||||
defer q.mu.Unlock()
|
||||
ticketID, ok := q.byPlayer[playerID]
|
||||
if !ok {
|
||||
return ErrTicketNotFound
|
||||
}
|
||||
ticket, ok := q.tickets[ticketID]
|
||||
if !ok || (ticket.State != Queued && ticket.State != Proposed) {
|
||||
return ErrTicketNotFound
|
||||
}
|
||||
if !now.Before(ticket.ExpiresAt) {
|
||||
return ErrTicketExpired
|
||||
}
|
||||
if ticket.Candidate.PredictedRTT == nil {
|
||||
ticket.Candidate.PredictedRTT = make(map[string]float64)
|
||||
}
|
||||
ticket.Candidate.PredictedRTT[region] = float64(rtt) / float64(time.Millisecond)
|
||||
q.tickets[ticketID] = ticket
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *Queue) expireLocked(now time.Time) []QueueTicket {
|
||||
var expired []QueueTicket
|
||||
for id, ticket := range q.tickets {
|
||||
|
||||
@@ -136,3 +136,24 @@ func TestQueueConcurrentCreateKeepsOneActiveTicketPerPlayer(t *testing.T) {
|
||||
t.Fatalf("concurrent creates succeeded %d times", succeeded)
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueueRecordProbeBindsServerRTTToActivePlayerTicket(t *testing.T) {
|
||||
now := time.Unix(1000, 0).UTC()
|
||||
queue := NewQueue()
|
||||
if _, err := queue.Create("player-a", "ticket-a", "create-key-123456", Candidate{PlayerID: "player-a", TicketID: "ticket-a", Playlist: Casual, EnqueuedAt: now}, now); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := queue.RecordProbe("player-a", "EU", 42*time.Millisecond, now); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ticket, err := queue.Get("player-a", "ticket-a", now)
|
||||
if err != nil || ticket.Candidate.PredictedRTT["EU"] != 42 {
|
||||
t.Fatalf("ticket=%+v err=%v", ticket, err)
|
||||
}
|
||||
if err := queue.RecordProbe("player-a", "NA", time.Millisecond, now.Add(QueueExpiryWindow)); err != ErrTicketExpired {
|
||||
t.Fatalf("expired record err=%v", err)
|
||||
}
|
||||
if err := queue.RecordProbe("player-other", "EU", time.Millisecond, now); err != ErrTicketNotFound {
|
||||
t.Fatalf("unknown player err=%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user