package lobby import "context" // matchMade is a message sent from the challenger to the dealer to indicate // to the latter that they have matched. type matchMade[ID, T any] struct { match chan ID carry T } // match is a match request. Each match receives one message from the matching // opponent. It MUST be buffered with a capacity of at least 1. type match[ID, T any] chan matchMade[ID, T] // Lobby is a matchmaking service. ID is the type of a game ID. T is the type of an exchange from one player // to the other when a match is found. type Lobby[ID, T any] struct { // matches is dealers waiting for a match. It MUST be unbuffered. matches chan match[ID, T] } // New creates a matchmaking lobby. func New[ID, T any]() *Lobby[ID, T] { return &Lobby[ID, T]{ matches: make(chan match[ID, T]), } } // Queue waits for a match. Both recipients of the match receive the same // result of new and the challenger's value of p. func (l *Lobby[ID, T]) Queue(ctx context.Context, new func() ID, p T) (id ID, chall T, deal bool) { var zid ID var zero T select { case m := <-l.matches: // We found a dealer waiting for a match. r := matchMade[ID, T]{ match: make(chan ID, 1), carry: p, } // We don't need to check the context here because the challenger // channel is buffered and we have exclusive send access on it. m <- r // We do need to check the context here in case they disappeared. select { case <-ctx.Done(): return zid, zero, false case id := <-r.match: return id, p, false } default: // do nothing } // We're a new dealer. m := make(match[ID, T], 1) select { case <-ctx.Done(): return zid, zero, false case l.matches <- m: // Our match is submitted. Move on. case m := <-l.matches: // We might have become a dealer at the same time someone else did. // We created our match, but we can try to get theirs as well and // never send ours, since l.matches is unbuffered. r := matchMade[ID, T]{ match: make(chan ID, 1), carry: p, } m <- r select { case <-ctx.Done(): return zid, zero, false case id := <-r.match: return id, p, false } } select { case <-ctx.Done(): return zid, zero, false case r := <-m: // Got our challenger. Create the game and send the ID back. id := new() // Don't need to check context because the match channel is buffered. r.match <- id return id, r.carry, true } }