exchange syncing persons in matchmaking

This commit is contained in:
Branden J Brown 2024-02-04 09:42:42 -06:00
parent 82aba814e6
commit 658634c0db
3 changed files with 14 additions and 57 deletions

View File

@ -98,7 +98,6 @@ func gameActor(ctx context.Context, g *game.Match, dealer, chall person, join <-
broadcast(ctx, g, dealer, chall, obs, dl) broadcast(ctx, g, dealer, chall, obs, dl)
if g.MatchWinner() != nil { if g.MatchWinner() != nil {
gameOver(ctx, dealer, chall, obs) gameOver(ctx, dealer, chall, obs)
// TODO(zeph): server needs to know the players are gone
return return
} }
g.NextRound() g.NextRound()
@ -161,14 +160,12 @@ func playerActor(ctx context.Context, p person, actions chan<- action) {
} }
func broadcast(ctx context.Context, g *game.Match, dealer, chall person, obs []observer, deadline time.Time) { func broadcast(ctx context.Context, g *game.Match, dealer, chall person, obs []observer, deadline time.Time) {
// TODO(zeph): this probably should return an error or some other signal // NOTE(zeph): If our sends to players fail, then the socket will close,
// if a player drops so that the actor knows to quit // causing the readers to fail and send concede messages. No leak.
if err := wsjson.Write(ctx, dealer.conn, g.DTO(dealer.id, deadline)); err != nil { if err := wsjson.Write(ctx, dealer.conn, g.DTO(dealer.id, deadline)); err != nil {
// TODO(zeph): concede, but we need to be careful not to recurse
slog.WarnContext(ctx, "lost dealer", "player", dealer.id, "err", err.Error()) slog.WarnContext(ctx, "lost dealer", "player", dealer.id, "err", err.Error())
} }
if err := wsjson.Write(ctx, chall.conn, g.DTO(chall.id, deadline)); err != nil { if err := wsjson.Write(ctx, chall.conn, g.DTO(chall.id, deadline)); err != nil {
// TODO(zeph): concede, but we need to be careful not to recurse
slog.WarnContext(ctx, "lost challenger", "player", chall.id, "err", err.Error()) slog.WarnContext(ctx, "lost challenger", "player", chall.id, "err", err.Error())
} }
if len(obs) == 0 { if len(obs) == 0 {
@ -183,7 +180,6 @@ func broadcast(ctx context.Context, g *game.Match, dealer, chall person, obs []o
} }
func gameOver(ctx context.Context, dealer, chall person, obs []observer) { func gameOver(ctx context.Context, dealer, chall person, obs []observer) {
// TODO(zeph): need to communicate to the server that these have gone away
go dealer.conn.Close(websocket.StatusNormalClosure, "match ended") go dealer.conn.Close(websocket.StatusNormalClosure, "match ended")
go chall.conn.Close(websocket.StatusNormalClosure, "match ended") go chall.conn.Close(websocket.StatusNormalClosure, "match ended")
for _, p := range obs { for _, p := range obs {

View File

@ -10,7 +10,6 @@ import (
"github.com/go-chi/chi/v5/middleware" "github.com/go-chi/chi/v5/middleware"
"github.com/google/uuid" "github.com/google/uuid"
"gitlab.com/zephyrtronium/sq" "gitlab.com/zephyrtronium/sq"
"nhooyr.io/websocket"
"git.sunturtle.xyz/studio/shotgun/lobby" "git.sunturtle.xyz/studio/shotgun/lobby"
"git.sunturtle.xyz/studio/shotgun/player" "git.sunturtle.xyz/studio/shotgun/player"
@ -43,11 +42,9 @@ func main() {
return return
} }
s := Server{ s := Server{
l: lobby.New[uuid.UUID, player.ID](), l: lobby.New[uuid.UUID, matchingPerson](),
creds: db, creds: db,
sessions: db, sessions: db,
pp: map[player.ID]*websocket.Conn{},
j: map[uuid.UUID]chan person{},
} }
r := chi.NewRouter() r := chi.NewRouter()

View File

@ -6,7 +6,6 @@ import (
"errors" "errors"
"log/slog" "log/slog"
"net/http" "net/http"
"sync"
"time" "time"
"github.com/google/uuid" "github.com/google/uuid"
@ -20,14 +19,10 @@ import (
) )
type Server struct { type Server struct {
l *lobby.Lobby[uuid.UUID, player.ID] l *lobby.Lobby[uuid.UUID, matchingPerson]
creds db creds db
sessions db sessions db
mu sync.Mutex
pp map[player.ID]*websocket.Conn
j map[uuid.UUID]chan person
} }
type db interface { type db interface {
@ -40,10 +35,9 @@ type person struct {
id player.ID id player.ID
} }
func (s *Server) person(p player.ID) person { type matchingPerson struct {
s.mu.Lock() sync chan struct{}
defer s.mu.Unlock() person
return person{conn: s.pp[p], id: p}
} }
func (s *Server) accept(w http.ResponseWriter, r *http.Request, p player.ID) (person, error) { func (s *Server) accept(w http.ResponseWriter, r *http.Request, p player.ID) (person, error) {
@ -52,27 +46,9 @@ func (s *Server) accept(w http.ResponseWriter, r *http.Request, p player.ID) (pe
return person{}, err return person{}, err
} }
slog.Debug("upgraded", "player", p) slog.Debug("upgraded", "player", p)
s.mu.Lock()
defer s.mu.Unlock()
s.pp[p] = conn
return person{conn: conn, id: p}, nil return person{conn: conn, id: p}, nil
} }
func (s *Server) left(p player.ID) {
s.mu.Lock()
// NOTE(zeph): neither map index nor map delete can panic, so this critical
// section does not need a defer
c := s.pp[p]
delete(s.pp, p)
s.mu.Unlock()
if c != nil {
// We don't care about the error here since the connection is leaving.
// It's probably already closed anyway.
slog.Debug("leaving", "player", p)
c.Close(websocket.StatusNormalClosure, "bye")
}
}
func (s *Server) Register(w http.ResponseWriter, r *http.Request) { func (s *Server) Register(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
slog := slog.With( slog := slog.With(
@ -208,8 +184,10 @@ func (s *Server) joinAndServe(p person) {
stop() stop()
} }
}() }()
id, chall, deal := s.l.Queue(ctx, uuid.New, p.id) mp := matchingPerson{sync: make(chan struct{}), person: p}
id, chall, deal := s.l.Queue(ctx, uuid.New, mp)
<-ch <-ch
close(mp.sync)
stop() stop()
if id == uuid.Nil { if id == uuid.Nil {
// Context canceled. // Context canceled.
@ -218,24 +196,10 @@ func (s *Server) joinAndServe(p person) {
} }
if deal { if deal {
ch := make(chan person, 2) ch := make(chan person, 2)
s.mu.Lock() g := game.New(p.id, chall.id)
s.j[id] = ch <-chall.sync
s.mu.Unlock() go gameActor(context.TODO(), g, p, chall.person, ch)
g := game.New(p.id, chall)
other := s.person(chall)
go gameActor(context.TODO(), g, p, other, ch)
ch <- p ch <- p
} else {
// very gross, but i just want this to exist
for {
s.mu.Lock()
ch := s.j[id]
s.mu.Unlock()
if ch != nil {
ch <- p
break
}
}
} }
// Reply with the game ID so they can share. // Reply with the game ID so they can share.
r := serve.GameStart{ r := serve.GameStart{
@ -244,7 +208,7 @@ func (s *Server) joinAndServe(p person) {
} }
if err := wsjson.Write(context.TODO(), p.conn, r); err != nil { if err := wsjson.Write(context.TODO(), p.conn, r); err != nil {
slog.WarnContext(ctx, "got a game but player dropped", "game", id, "player", p.id) slog.WarnContext(ctx, "got a game but player dropped", "game", id, "player", p.id)
s.left(p.id) p.conn.Close(websocket.StatusNormalClosure, "looks like you dropped")
return return
} }
} }