simplify matchmaking
This commit is contained in:
parent
cc2b54eac3
commit
f2ba9849f1
@ -1,51 +1,87 @@
|
|||||||
package lobby
|
package lobby
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
"context"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
|
||||||
"git.sunturtle.xyz/studio/shotgun/game"
|
|
||||||
"git.sunturtle.xyz/studio/shotgun/player"
|
"git.sunturtle.xyz/studio/shotgun/player"
|
||||||
"git.sunturtle.xyz/studio/shotgun/serve"
|
"git.sunturtle.xyz/studio/shotgun/serve"
|
||||||
)
|
)
|
||||||
|
|
||||||
type GameID = serve.GameID
|
type GameID = serve.GameID
|
||||||
|
|
||||||
// Lobby is a set of active games.
|
type matchMade struct {
|
||||||
|
match chan GameID
|
||||||
|
chall player.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
type match chan matchMade
|
||||||
|
|
||||||
|
// Lobby is a matchmaking service.
|
||||||
type Lobby struct {
|
type Lobby struct {
|
||||||
mu sync.Mutex
|
|
||||||
// games is the set of all active games in the lobby.
|
|
||||||
games map[GameID]*game.Game
|
|
||||||
// matches is dealers waiting for a match. It MUST be unbuffered.
|
// matches is dealers waiting for a match. It MUST be unbuffered.
|
||||||
matches chan match
|
matches chan match
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() *Lobby {
|
func New() *Lobby {
|
||||||
return &Lobby{
|
return &Lobby{
|
||||||
games: make(map[GameID]*game.Game),
|
|
||||||
matches: make(chan match),
|
matches: make(chan match),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Game returns the game with the given ID.
|
// Queue waits for a match and returns a unique ID for it.
|
||||||
func (l *Lobby) Game(id GameID) *game.Game {
|
func (l *Lobby) Queue(ctx context.Context, p player.ID) (id GameID, chall player.ID, deal bool) {
|
||||||
l.mu.Lock()
|
select {
|
||||||
defer l.mu.Unlock()
|
case m := <-l.matches:
|
||||||
return l.games[id]
|
// We found a dealer waiting for a match.
|
||||||
}
|
r := matchMade{
|
||||||
|
match: make(chan GameID, 1),
|
||||||
// Start begins a new game in the lobby.
|
chall: p,
|
||||||
// The caller must be able to distinguish the dealer's and challenger's conns
|
}
|
||||||
// in order to provide correct game start DTOs to each.
|
// We don't need to check the context here because the challenger
|
||||||
func (l *Lobby) Start(id GameID, dealer, challenger player.ID) {
|
// channel is buffered and we have exclusive send access on it.
|
||||||
g := game.New(dealer, challenger)
|
m <- r
|
||||||
l.mu.Lock()
|
// We do need to check the context here in case they disappeared.
|
||||||
defer l.mu.Unlock()
|
select {
|
||||||
l.games[id] = g
|
case <-ctx.Done():
|
||||||
}
|
return GameID{}, player.ID{}, false
|
||||||
|
case id := <-r.match:
|
||||||
// Finish removes a game from the lobby.
|
return id, p, false
|
||||||
func (l *Lobby) Finish(id GameID) {
|
}
|
||||||
l.mu.Lock()
|
default: // do nothing
|
||||||
defer l.mu.Unlock()
|
}
|
||||||
delete(l.games, id)
|
// We're a new dealer.
|
||||||
|
m := make(match, 1)
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return GameID{}, player.ID{}, 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{
|
||||||
|
match: make(chan GameID, 1),
|
||||||
|
chall: p,
|
||||||
|
}
|
||||||
|
m <- r
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return GameID{}, player.ID{}, false
|
||||||
|
case id := <-r.match:
|
||||||
|
return id, p, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return GameID{}, player.ID{}, false
|
||||||
|
case r := <-m:
|
||||||
|
// Got our challenger. Create the game and send the ID back.
|
||||||
|
id := GameID(uuid.New())
|
||||||
|
// Don't need to check context because the match channel is buffered.
|
||||||
|
r.match <- id
|
||||||
|
return id, r.chall, true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package lobby_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.sunturtle.xyz/studio/shotgun/lobby"
|
"git.sunturtle.xyz/studio/shotgun/lobby"
|
||||||
@ -15,10 +16,17 @@ func TestQueue(t *testing.T) {
|
|||||||
l := lobby.New()
|
l := lobby.New()
|
||||||
for i := 0; i < 100; i++ {
|
for i := 0; i < 100; i++ {
|
||||||
games = games[:0]
|
games = games[:0]
|
||||||
|
var dealers, challs atomic.Int32
|
||||||
for i := 0; i < N; i++ {
|
for i := 0; i < N; i++ {
|
||||||
i := i
|
i := i
|
||||||
go func() {
|
go func() {
|
||||||
ch <- l.Queue(context.Background(), player.ID{uint8(i), uint8(i >> 8)})
|
id, _, deal := l.Queue(context.Background(), player.ID{uint8(i), uint8(i >> 8)})
|
||||||
|
if deal {
|
||||||
|
dealers.Add(1)
|
||||||
|
} else {
|
||||||
|
challs.Add(1)
|
||||||
|
}
|
||||||
|
ch <- id
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
for i := 0; i < N; i++ {
|
for i := 0; i < N; i++ {
|
||||||
@ -34,16 +42,9 @@ func TestQueue(t *testing.T) {
|
|||||||
t.Errorf("game %v appears %d times", id, c)
|
t.Errorf("game %v appears %d times", id, c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Every game should have two different players.
|
// The number of dealers must match the number of challengers.
|
||||||
for _, id := range games {
|
if dealers.Load() != challs.Load() {
|
||||||
g := l.Game(id)
|
t.Errorf("%d dealers != %d challengers", dealers.Load(), challs.Load())
|
||||||
if g == nil {
|
|
||||||
t.Errorf("game %v was created but doesn't exist", g)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if g.CurrentPlayer().Is(g.Opponent()) {
|
|
||||||
t.Errorf("game %v matched with self", id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,70 +0,0 @@
|
|||||||
package lobby
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"git.sunturtle.xyz/studio/shotgun/player"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
type match struct {
|
|
||||||
dealer player.ID
|
|
||||||
chall chan player.ID
|
|
||||||
match chan GameID
|
|
||||||
}
|
|
||||||
|
|
||||||
// Queue waits for a match.
|
|
||||||
// This may cause a new match to be created.
|
|
||||||
func (l *Lobby) Queue(ctx context.Context, p player.ID) GameID {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return GameID{}
|
|
||||||
case m := <-l.matches:
|
|
||||||
// We found a dealer waiting for a match.
|
|
||||||
// We don't need to check the context here because the challenger
|
|
||||||
// channel is buffered and we have exclusive send access on it.
|
|
||||||
m.chall <- p
|
|
||||||
// We do need to check the context here in case they disappeared.
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return GameID{}
|
|
||||||
case id := <-m.match:
|
|
||||||
return id
|
|
||||||
}
|
|
||||||
default: // do nothing
|
|
||||||
}
|
|
||||||
// We're a new dealer.
|
|
||||||
m := match{
|
|
||||||
dealer: p,
|
|
||||||
chall: make(chan player.ID, 1),
|
|
||||||
match: make(chan GameID, 1),
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return GameID{}
|
|
||||||
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.
|
|
||||||
m.chall <- p
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return GameID{}
|
|
||||||
case id := <-m.match:
|
|
||||||
return id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return GameID{}
|
|
||||||
case chall := <-m.chall:
|
|
||||||
// Got our challenger. Create the game and send the ID back.
|
|
||||||
id := uuid.New()
|
|
||||||
l.Start(id, p, chall)
|
|
||||||
// Don't need to check context because the match channel is buffered.
|
|
||||||
m.match <- id
|
|
||||||
return id
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user