diff --git a/game.go b/game.go index 0ba5439..1e58e24 100644 --- a/game.go +++ b/game.go @@ -98,7 +98,6 @@ func gameActor(ctx context.Context, g *game.Match, dealer, chall person, join <- broadcast(ctx, g, dealer, chall, obs, dl) if g.MatchWinner() != nil { gameOver(ctx, dealer, chall, obs) - // TODO(zeph): server needs to know the players are gone return } 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) { - // TODO(zeph): this probably should return an error or some other signal - // if a player drops so that the actor knows to quit + // NOTE(zeph): If our sends to players fail, then the socket will close, + // 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 { - // TODO(zeph): concede, but we need to be careful not to recurse 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 { - // TODO(zeph): concede, but we need to be careful not to recurse slog.WarnContext(ctx, "lost challenger", "player", chall.id, "err", err.Error()) } 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) { - // TODO(zeph): need to communicate to the server that these have gone away go dealer.conn.Close(websocket.StatusNormalClosure, "match ended") go chall.conn.Close(websocket.StatusNormalClosure, "match ended") for _, p := range obs { diff --git a/main.go b/main.go index 58aa96c..0e982a6 100644 --- a/main.go +++ b/main.go @@ -10,7 +10,6 @@ import ( "github.com/go-chi/chi/v5/middleware" "github.com/google/uuid" "gitlab.com/zephyrtronium/sq" - "nhooyr.io/websocket" "git.sunturtle.xyz/studio/shotgun/lobby" "git.sunturtle.xyz/studio/shotgun/player" @@ -43,11 +42,9 @@ func main() { return } s := Server{ - l: lobby.New[uuid.UUID, player.ID](), + l: lobby.New[uuid.UUID, matchingPerson](), creds: db, sessions: db, - pp: map[player.ID]*websocket.Conn{}, - j: map[uuid.UUID]chan person{}, } r := chi.NewRouter() diff --git a/server.go b/server.go index 8045ad9..37037e0 100644 --- a/server.go +++ b/server.go @@ -6,7 +6,6 @@ import ( "errors" "log/slog" "net/http" - "sync" "time" "github.com/google/uuid" @@ -20,14 +19,10 @@ import ( ) type Server struct { - l *lobby.Lobby[uuid.UUID, player.ID] + l *lobby.Lobby[uuid.UUID, matchingPerson] creds db sessions db - - mu sync.Mutex - pp map[player.ID]*websocket.Conn - j map[uuid.UUID]chan person } type db interface { @@ -40,10 +35,9 @@ type person struct { id player.ID } -func (s *Server) person(p player.ID) person { - s.mu.Lock() - defer s.mu.Unlock() - return person{conn: s.pp[p], id: p} +type matchingPerson struct { + sync chan struct{} + person } 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 } slog.Debug("upgraded", "player", p) - s.mu.Lock() - defer s.mu.Unlock() - s.pp[p] = conn 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) { ctx := r.Context() slog := slog.With( @@ -208,8 +184,10 @@ func (s *Server) joinAndServe(p person) { 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 + close(mp.sync) stop() if id == uuid.Nil { // Context canceled. @@ -218,24 +196,10 @@ func (s *Server) joinAndServe(p person) { } if deal { ch := make(chan person, 2) - s.mu.Lock() - s.j[id] = ch - s.mu.Unlock() - g := game.New(p.id, chall) - other := s.person(chall) - go gameActor(context.TODO(), g, p, other, ch) + g := game.New(p.id, chall.id) + <-chall.sync + go gameActor(context.TODO(), g, p, chall.person, ch) 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. r := serve.GameStart{ @@ -244,7 +208,7 @@ func (s *Server) joinAndServe(p person) { } 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) - s.left(p.id) + p.conn.Close(websocket.StatusNormalClosure, "looks like you dropped") return } }