package main import ( "context" "encoding/json" "log" "net" "net/http" "time" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "github.com/gobwas/ws" "github.com/gobwas/ws/wsutil" ) type Point2d struct { X float64 `json:"x"` Y float64 `json:"y"` } type Player struct { Nickname string Connection net.Conn Position Point2d } type clientState Point2d type serverState struct { Entities []Point2d `json:"entities"` } func main() { r := chi.NewRouter() r.Use(middleware.Logger) r.Use(middleware.Recoverer) players := map[string]*Player{} r.Get("/api/ws", func(w http.ResponseWriter, r *http.Request) { username := r.URL.Query().Get("username") conn, _, _, err := ws.UpgradeHTTP(r, w) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } players[username] = &Player{ Nickname: username, Connection: conn, Position: Point2d{0, 0}, } go func() { log.Printf(`[%s] logged in`, username) ctx, cancelComms := context.WithCancel(context.Background()) go func() { defer cancelComms() for { serverMsg := &serverState{ Entities: []Point2d{}, } // generate custom message for this client for name, player := range players { if name != username { serverMsg.Entities = append(serverMsg.Entities, player.Position) } } rawServerMsg, err := json.Marshal(serverMsg) if err != nil { log.Print(err) return } if err := wsutil.WriteServerText(conn, rawServerMsg); err != nil { log.Print(err) return } select { case <-ctx.Done(): return case <-time.NewTimer(1000 / 30 * time.Millisecond).C: } } }() go func() { defer cancelComms() for { rawClientMsg, err := wsutil.ReadClientText(conn) if err != nil { log.Print(err) return } var clientMsg clientState if err := json.Unmarshal(rawClientMsg, &clientMsg); err != nil { log.Print(err) return } // log.Printf(`[%v] Server received: %s`, username, repr.String(clientMsg)) players[username].Position = Point2d(clientMsg) select { case <-ctx.Done(): return case <-time.NewTimer(1000 / 30 * time.Millisecond).C: } } }() <-ctx.Done() log.Printf(`[%s] left the session`, username) delete(players, username) conn.Close() }() }) log.Printf(`Starting server on :4000...`) log.Fatal(http.ListenAndServe(":4000", r)) }