From 2b9e617c10754b92028ca11e9d091de7942a3056 Mon Sep 17 00:00:00 2001 From: Branden J Brown Date: Mon, 22 Jan 2024 20:16:10 -0600 Subject: [PATCH] start labor on server part of server --- go.mod | 5 +++- go.sum | 2 ++ server.go | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 server.go diff --git a/go.mod b/go.mod index af45d49..cbd9e8e 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,7 @@ module git.sunturtle.xyz/studio/shotgun go 1.21.6 -require github.com/google/uuid v1.5.0 +require ( + github.com/google/uuid v1.5.0 + nhooyr.io/websocket v1.8.10 +) diff --git a/go.sum b/go.sum index 040e221..b0aca0c 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,4 @@ github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +nhooyr.io/websocket v1.8.10 h1:mv4p+MnGrLDcPlBoWsvPP7XCzTYMXP9F9eIGoKbgx7Q= +nhooyr.io/websocket v1.8.10/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c= diff --git a/server.go b/server.go new file mode 100644 index 0000000..6ce62a4 --- /dev/null +++ b/server.go @@ -0,0 +1,88 @@ +package main + +import ( + "context" + "errors" + "net/http" + "sync" + "time" + + "nhooyr.io/websocket" + + "git.sunturtle.xyz/studio/shotgun/lobby" + "git.sunturtle.xyz/studio/shotgun/player" + "git.sunturtle.xyz/studio/shotgun/serve" +) + +type Server struct { + l *lobby.Lobby + mu sync.Mutex + rooms map[lobby.GameID]*room +} + +type room struct { + mu sync.Mutex + pp []person + // NOTE(zeph): since only the players can ever see revealed shells, we + // could factor out the players into separate fields to save work on + // generating dtos for observers. hold that idea until it's needed. +} + +type person struct { + conn *websocket.Conn + id player.ID +} + +func (s *Server) room(id serve.GameID) *room { + s.mu.Lock() + defer s.mu.Unlock() + return s.rooms[id] +} + +func (s *Server) join(id serve.GameID, p person) { + s.mu.Lock() + defer s.mu.Unlock() + r := s.rooms[id] + if r == nil { + r = &room{} + s.rooms[id] = r + } + r.pp = append(r.pp, p) +} + +func (s *Server) close(id serve.GameID) { + s.mu.Lock() + defer s.mu.Unlock() + delete(s.rooms, id) +} + +// Queue connects players to games. The connection immediately upgrades. +// This handler MUST be wrapped in [serve.WithPlayerID]. +func (s *Server) Queue(w http.ResponseWriter, r *http.Request) { + p := serve.PlayerID(r.Context()) + if p == (player.ID{}) { + panic("missing player ID") + } + conn, err := websocket.Accept(w, r, nil) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + person := person{conn: conn, id: p} + go s.joinAndServe(person) +} + +func (s *Server) joinAndServe(p person) { + ctx, stop := context.WithTimeoutCause(context.Background(), 10*time.Minute, errQueueEmpty) + game := s.l.Queue(ctx, p.id) + stop() + if game == (lobby.GameID{}) { + // Context canceled. + p.conn.Close(websocket.StatusNormalClosure, "sorry, queue is empty...") + return + } + s.join(game, p) + // TODO(zeph): need to broadcast to observers, need to listen for updates in a single goroutine, ... +} + +var errQueueEmpty = errors.New("sorry, queue is empty")