2024-01-21 04:35:21 -06:00
|
|
|
package lobby
|
2024-01-21 01:33:44 -06:00
|
|
|
|
2024-02-04 09:25:09 -06:00
|
|
|
import "context"
|
2024-01-27 09:43:55 -06:00
|
|
|
|
2024-02-04 09:25:09 -06:00
|
|
|
// 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
|
2024-01-27 09:43:55 -06:00
|
|
|
}
|
|
|
|
|
2024-02-04 09:25:09 -06:00
|
|
|
// 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]
|
2024-01-27 09:43:55 -06:00
|
|
|
|
2024-02-04 09:25:09 -06:00
|
|
|
// 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 {
|
2024-01-21 19:53:08 -06:00
|
|
|
// matches is dealers waiting for a match. It MUST be unbuffered.
|
2024-02-04 09:25:09 -06:00
|
|
|
matches chan match[ID, T]
|
2024-01-21 01:33:44 -06:00
|
|
|
}
|
|
|
|
|
2024-02-04 09:25:09 -06:00
|
|
|
// New creates a matchmaking lobby.
|
|
|
|
func New[ID, T any]() *Lobby[ID, T] {
|
|
|
|
return &Lobby[ID, T]{
|
|
|
|
matches: make(chan match[ID, T]),
|
2024-01-21 01:33:44 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-04 09:25:09 -06:00
|
|
|
// 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
|
2024-01-27 09:43:55 -06:00
|
|
|
select {
|
|
|
|
case m := <-l.matches:
|
|
|
|
// We found a dealer waiting for a match.
|
2024-02-04 09:25:09 -06:00
|
|
|
r := matchMade[ID, T]{
|
|
|
|
match: make(chan ID, 1),
|
|
|
|
carry: p,
|
2024-01-27 09:43:55 -06:00
|
|
|
}
|
|
|
|
// 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():
|
2024-02-04 09:25:09 -06:00
|
|
|
return zid, zero, false
|
2024-01-27 09:43:55 -06:00
|
|
|
case id := <-r.match:
|
|
|
|
return id, p, false
|
|
|
|
}
|
|
|
|
default: // do nothing
|
|
|
|
}
|
|
|
|
// We're a new dealer.
|
2024-02-04 09:25:09 -06:00
|
|
|
m := make(match[ID, T], 1)
|
2024-01-27 09:43:55 -06:00
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
2024-02-04 09:25:09 -06:00
|
|
|
return zid, zero, false
|
2024-01-27 09:43:55 -06:00
|
|
|
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.
|
2024-02-04 09:25:09 -06:00
|
|
|
r := matchMade[ID, T]{
|
|
|
|
match: make(chan ID, 1),
|
|
|
|
carry: p,
|
2024-01-27 09:43:55 -06:00
|
|
|
}
|
|
|
|
m <- r
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
2024-02-04 09:25:09 -06:00
|
|
|
return zid, zero, false
|
2024-01-27 09:43:55 -06:00
|
|
|
case id := <-r.match:
|
|
|
|
return id, p, false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
2024-02-04 09:25:09 -06:00
|
|
|
return zid, zero, false
|
2024-01-27 09:43:55 -06:00
|
|
|
case r := <-m:
|
|
|
|
// Got our challenger. Create the game and send the ID back.
|
2024-02-04 09:25:09 -06:00
|
|
|
id := new()
|
2024-01-27 09:43:55 -06:00
|
|
|
// Don't need to check context because the match channel is buffered.
|
|
|
|
r.match <- id
|
2024-02-04 09:25:09 -06:00
|
|
|
return id, r.carry, true
|
2024-01-27 09:43:55 -06:00
|
|
|
}
|
2024-01-21 01:33:44 -06:00
|
|
|
}
|