From 280d9801d666a5c460722be23db9e9cd123fae48 Mon Sep 17 00:00:00 2001 From: Smile Rex Date: Wed, 21 Jan 2026 18:49:12 +0300 Subject: [PATCH] new server --- controllers/connection.go | 69 --------------------------------------- controllers/entities.go | 31 ------------------ controllers/hub.go | 30 ----------------- controllers/msg.go | 14 -------- controllers/protocol.go | 8 ----- controllers/world.go | 47 -------------------------- controllers/ws.go | 22 ------------- go.mod | 5 ++- go.sum | 2 ++ main.go | 61 +++++++++++++++++++++++++++++++--- models/client.go | 8 ----- models/entity.go | 51 ----------------------------- models/message.go | 48 --------------------------- models/player.go | 20 ------------ models/reader.go | 50 ---------------------------- models/writer.go | 49 --------------------------- player.go | 13 ++++++++ protocol.go | 12 +++++++ room.go | 69 +++++++++++++++++++++++++++++++++++++++ 19 files changed, 157 insertions(+), 452 deletions(-) delete mode 100644 controllers/connection.go delete mode 100644 controllers/entities.go delete mode 100644 controllers/hub.go delete mode 100644 controllers/msg.go delete mode 100644 controllers/protocol.go delete mode 100644 controllers/world.go delete mode 100644 controllers/ws.go delete mode 100644 models/client.go delete mode 100644 models/entity.go delete mode 100644 models/message.go delete mode 100644 models/player.go delete mode 100644 models/reader.go delete mode 100644 models/writer.go create mode 100644 player.go create mode 100644 protocol.go create mode 100644 room.go diff --git a/controllers/connection.go b/controllers/connection.go deleted file mode 100644 index ad476b2..0000000 --- a/controllers/connection.go +++ /dev/null @@ -1,69 +0,0 @@ -package controllers - -import ( - "log" - "server/models" - - "github.com/gorilla/websocket" -) - -func (h *Hub) readLoop(conn *websocket.Conn) { - var id uint32 - log.Println("client ws connected") - - defer func() { - conn.Close() - if id != 0 { - h.removeEntity(id) - log.Println("entity [player] removed:", id) - } - - log.Println("client ws disconnected") - }() - - for { - _, bytes, err := conn.ReadMessage() - if err != nil { - return - } - - msg, err := models.Decode(bytes) - if err != nil { - log.Println(err) - continue - } - - switch msg.Type { - - case MSG_WELCOME: - reader := models.NewReader(msg.Payload) - log.Println("MSG_WELCOME received", &msg.Payload) - id = reader.ReadU32() - name := reader.ReadString() - - if h.Entities[id] != nil { - h.ErrorMsg("Entity already exists", conn) - } - - player := &models.Player{ - BaseEntity: models.BaseEntity{ID: id}, - Name: name, - } - - h.addEntity(id, player) - h.Clients[id] = conn - log.Println("entity [player] added:", id) - confimMessage := models.Message{Type: MSG_WELCOME, Payload: []byte{}} - conn.WriteMessage(websocket.BinaryMessage, confimMessage.Encode()) - - case MSG_INPUT: - reader := models.NewReader(msg.Payload) - id := reader.ReadU32() - x := reader.ReadF32() - y := reader.ReadF32() - z := reader.ReadF32() - h.updateEntityPosition(x, y, z, id) - log.Println(h.Entities) - } - } -} diff --git a/controllers/entities.go b/controllers/entities.go deleted file mode 100644 index d0c0919..0000000 --- a/controllers/entities.go +++ /dev/null @@ -1,31 +0,0 @@ -package controllers - -import "server/models" - -func (h *Hub) addEntity(id uint32, entity models.Entity) { - h.Mu.Lock() - defer h.Mu.Unlock() - - h.Entities[id] = entity -} - -func (h *Hub) updateEntityPosition(x, y, z float32, entity_id uint32) { - h.Mu.Lock() - defer h.Mu.Unlock() - - h.Entities[entity_id].SetPosition(x, y, z) -} - -func (h *Hub) updateEntityRotation(yaw float32, entity_id uint32) { - h.Mu.Lock() - defer h.Mu.Unlock() - - h.Entities[entity_id].SetYaw(yaw) -} - -func (h *Hub) removeEntity(id uint32) { - h.Mu.Lock() - defer h.Mu.Unlock() - - delete(h.Entities, id) -} diff --git a/controllers/hub.go b/controllers/hub.go deleted file mode 100644 index 884e5ad..0000000 --- a/controllers/hub.go +++ /dev/null @@ -1,30 +0,0 @@ -package controllers - -import ( - "log" - "net/http" - "server/models" - "sync" - - "github.com/gorilla/websocket" -) - -type Hub struct { - Entities map[uint32]models.Entity - Clients map[uint32]*websocket.Conn - Mu sync.RWMutex -} - -func NewHub() *Hub { - return &Hub{ - Entities: make(map[uint32]models.Entity), - Clients: make(map[uint32]*websocket.Conn), - } -} - -func (h *Hub) Start() { - go h.updateWorld() - http.HandleFunc("/ws", h.ws) - log.Println("Server listen port 8080") - log.Fatal(http.ListenAndServe(":8080", nil)) -} diff --git a/controllers/msg.go b/controllers/msg.go deleted file mode 100644 index 3fe855a..0000000 --- a/controllers/msg.go +++ /dev/null @@ -1,14 +0,0 @@ -package controllers - -import ( - "server/models" - - "github.com/gorilla/websocket" -) - -func (h *Hub) ErrorMsg(msg string, conn *websocket.Conn) { - wr := models.Writer{} - wr.WriteString(msg) - errMsg := &models.Message{Type: 9, Version: 1, Payload: wr.Bytes()} - conn.WriteMessage(websocket.BinaryMessage, errMsg.Encode()) -} diff --git a/controllers/protocol.go b/controllers/protocol.go deleted file mode 100644 index 3eede1e..0000000 --- a/controllers/protocol.go +++ /dev/null @@ -1,8 +0,0 @@ -package controllers - -const ( - MSG_WELCOME = 0 - MSG_INPUT = 1 - MSG_SNAPSHOT = 2 - MSG_EVENT = 3 -) diff --git a/controllers/world.go b/controllers/world.go deleted file mode 100644 index 6d73257..0000000 --- a/controllers/world.go +++ /dev/null @@ -1,47 +0,0 @@ -package controllers - -import ( - "server/models" - "time" - - "github.com/gorilla/websocket" -) - -func (h *Hub) broadcastSnapshot() { - h.Mu.RLock() - defer h.Mu.RUnlock() - - w := models.Writer{} - w.WriteU16(uint16(len(h.Entities))) - - for _, e := range h.Entities { - w.WriteU32(e.GetID()) - w.WriteU8(uint8(e.GetType())) - x, y, z := e.GetPosition() - w.WriteF32(x) - w.WriteF32(y) - w.WriteF32(z) - w.WriteF32(e.GetYaw()) - } - - msg := models.Message{ - Type: MSG_SNAPSHOT, - Version: 1, - Payload: w.Bytes(), - } - - for _, conn := range h.Clients { - if conn != nil { - _ = conn.WriteMessage(websocket.BinaryMessage, msg.Encode()) - } - } -} - -func (h *Hub) updateWorld() { - ticker := time.NewTicker(50 * time.Millisecond) - defer ticker.Stop() - - for range ticker.C { - h.broadcastSnapshot() - } -} diff --git a/controllers/ws.go b/controllers/ws.go deleted file mode 100644 index 06508de..0000000 --- a/controllers/ws.go +++ /dev/null @@ -1,22 +0,0 @@ -package controllers - -import ( - "log" - "net/http" - - "github.com/gorilla/websocket" -) - -func (h *Hub) ws(w http.ResponseWriter, r *http.Request) { - upgrader := websocket.Upgrader{ - CheckOrigin: func(r *http.Request) bool { return true }, - } - - conn, err := upgrader.Upgrade(w, r, nil) - if err != nil { - log.Println(err) - return - } - - go h.readLoop(conn) -} diff --git a/go.mod b/go.mod index 4e0ef13..c9a4a7e 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,7 @@ module server go 1.25.0 -require github.com/gorilla/websocket v1.5.3 +require ( + github.com/google/uuid v1.6.0 + github.com/gorilla/websocket v1.5.3 +) diff --git a/go.sum b/go.sum index 25a9fc4..73bbf57 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,4 @@ +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= diff --git a/main.go b/main.go index 8c9ed92..426311f 100644 --- a/main.go +++ b/main.go @@ -1,10 +1,63 @@ package main import ( - "server/controllers" + "log" + "net/http" + + "github.com/google/uuid" + "github.com/gorilla/websocket" ) -func main() { - hub := controllers.NewHub() - hub.Start() +var upgrader = websocket.Upgrader{ + CheckOrigin: func(r *http.Request) bool { return true }, +} + +func ServeWS(room *Room, w http.ResponseWriter, r *http.Request) { + conn, _ := upgrader.Upgrade(w, r, nil) + + id := uuid.New().String() + player := &Player{ + ID: id, + Conn: conn, + X: 180, + Y: 320, + } + + room.Players[id] = player + + player.Conn.WriteJSON(map[string]any{ + "type": "init", + "payload": map[string]string{ + "id": id, + }, + }) + + go readLoop(room, player) +} + +func readLoop(room *Room, player *Player) { + for { + var msg InputMessage + err := player.Conn.ReadJSON(&msg) + if err != nil { + delete(room.Players, player.ID) + return + } + + msg.PlayerID = player.ID + room.Input <- msg + } +} + +func main() { + room := NewRoom() + + go room.Run() + + http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) { + ServeWS(room, w, r) + }) + + log.Println("Server started on :8080") + log.Fatal(http.ListenAndServe(":8080", nil)) } diff --git a/models/client.go b/models/client.go deleted file mode 100644 index 12701ec..0000000 --- a/models/client.go +++ /dev/null @@ -1,8 +0,0 @@ -package models - -import "github.com/gorilla/websocket" - -type Client struct { - ID uint32 - Conn *websocket.Conn -} diff --git a/models/entity.go b/models/entity.go deleted file mode 100644 index 3403112..0000000 --- a/models/entity.go +++ /dev/null @@ -1,51 +0,0 @@ -package models - -type EntityType uint8 - -const ( - EntityPlayer EntityType = 1 - EntityNPC EntityType = 2 - EntityBullet EntityType = 3 -) - -type Entity interface { - GetID() uint32 - GetType() EntityType - GetPosition() (float32, float32, float32) - SetPosition(float32, float32, float32) - GetYaw() float32 - SetYaw(float32) -} - -type BaseEntity struct { - ID uint32 - Type EntityType - X, Y, Z float32 - Yaw float32 -} - -func (e *BaseEntity) GetID() uint32 { - return e.ID -} - -func (e *BaseEntity) GetType() EntityType { - return e.Type -} - -func (e *BaseEntity) GetPosition() (float32, float32, float32) { - return e.X, e.Y, e.Z -} - -func (e *BaseEntity) SetPosition(x, y, z float32) { - e.X = x - e.Y = y - e.Z = z -} - -func (e *BaseEntity) GetYaw() float32 { - return e.Yaw -} - -func (e *BaseEntity) SetYaw(yaw float32) { - e.Yaw = yaw -} diff --git a/models/message.go b/models/message.go deleted file mode 100644 index afb9a4e..0000000 --- a/models/message.go +++ /dev/null @@ -1,48 +0,0 @@ -package models - -import ( - "encoding/binary" - "errors" -) - -type Message struct { - Type uint16 - Version uint16 - Payload []byte -} - -func (m *Message) Encode() []byte { - payloadLen := len(m.Payload) - if payloadLen > 0xFFFF { - return nil - } - - buf := make([]byte, 8+payloadLen) - - binary.LittleEndian.PutUint16(buf[0:2], m.Type) - binary.LittleEndian.PutUint16(buf[2:4], m.Version) - binary.LittleEndian.PutUint16(buf[4:6], uint16(payloadLen)) - - copy(buf[8:], m.Payload) - return buf -} - -func Decode(data []byte) (*Message, error) { - if len(data) < 8 { - return nil, errors.New("message too short") - } - - payloadLen := binary.LittleEndian.Uint16(data[4:6]) - if len(data) != int(8+payloadLen) { - return nil, errors.New("invalid payload length") - } - - msg := &Message{ - Type: binary.LittleEndian.Uint16(data[0:2]), - Version: binary.LittleEndian.Uint16(data[2:4]), - Payload: make([]byte, payloadLen), - } - - copy(msg.Payload, data[8:]) - return msg, nil -} diff --git a/models/player.go b/models/player.go deleted file mode 100644 index ffe98e5..0000000 --- a/models/player.go +++ /dev/null @@ -1,20 +0,0 @@ -package models - -type Player struct { - BaseEntity - Name string -} - -func NewPlayer(id uint32, name string) *Player { - return &Player{ - BaseEntity: BaseEntity{ - ID: id, - Type: EntityPlayer, - X: 0, - Y: 0, - Z: 0, - Yaw: 0, - }, - Name: name, - } -} diff --git a/models/reader.go b/models/reader.go deleted file mode 100644 index a388a72..0000000 --- a/models/reader.go +++ /dev/null @@ -1,50 +0,0 @@ -package models - -import ( - "encoding/binary" - "math" -) - -type Reader struct { - buf []byte - off int -} - -func NewReader(b []byte) *Reader { - return &Reader{buf: b} -} - -func (r *Reader) ReadU8() uint8 { - v := r.buf[r.off] - r.off++ - return v -} - -func (r *Reader) ReadU16() uint16 { - v := binary.LittleEndian.Uint16(r.buf[r.off:]) - r.off += 2 - return v -} - -func (r *Reader) ReadU32() uint32 { - v := binary.LittleEndian.Uint32(r.buf[r.off:]) - r.off += 4 - return v -} - -func (r *Reader) ReadF32() float32 { - v := math.Float32frombits(binary.LittleEndian.Uint32(r.buf[r.off:])) - r.off += 4 - return v -} - -func (r *Reader) ReadBool() bool { - return r.ReadU8() == 1 -} - -func (r *Reader) ReadString() string { - l := r.ReadU16() - s := string(r.buf[r.off : r.off+int(l)]) - r.off += int(l) - return s -} diff --git a/models/writer.go b/models/writer.go deleted file mode 100644 index 4b2a2f1..0000000 --- a/models/writer.go +++ /dev/null @@ -1,49 +0,0 @@ -package models - -import ( - "encoding/binary" - "math" -) - -type Writer struct { - buf []byte -} - -func (w *Writer) Bytes() []byte { - return w.buf -} - -func (w *Writer) WriteU8(v uint8) { - w.buf = append(w.buf, v) -} - -func (w *Writer) WriteU16(v uint16) { - tmp := make([]byte, 2) - binary.LittleEndian.PutUint16(tmp, v) - w.buf = append(w.buf, tmp...) -} - -func (w *Writer) WriteU32(v uint32) { - tmp := make([]byte, 4) - binary.LittleEndian.PutUint32(tmp, v) - w.buf = append(w.buf, tmp...) -} - -func (w *Writer) WriteF32(v float32) { - tmp := make([]byte, 4) - binary.LittleEndian.PutUint32(tmp, math.Float32bits(v)) - w.buf = append(w.buf, tmp...) -} - -func (w *Writer) WriteBool(v bool) { - if v { - w.WriteU8(1) - } else { - w.WriteU8(0) - } -} - -func (w *Writer) WriteString(s string) { - w.WriteU16(uint16(len(s))) - w.buf = append(w.buf, []byte(s)...) -} diff --git a/player.go b/player.go new file mode 100644 index 0000000..c537a44 --- /dev/null +++ b/player.go @@ -0,0 +1,13 @@ +package main + +import ( + "github.com/gorilla/websocket" +) + +type Player struct { + ID string + Conn *websocket.Conn + + X, Y float64 + DX, DY float64 +} diff --git a/protocol.go b/protocol.go new file mode 100644 index 0000000..a7ed0ee --- /dev/null +++ b/protocol.go @@ -0,0 +1,12 @@ +package main + +type InputMessage struct { + PlayerID string + DX float64 `json:"dx"` + DY float64 `json:"dy"` +} + +type StateMessage struct { + Type string `json:"type"` + Payload any `json:"payload"` +} diff --git a/room.go b/room.go new file mode 100644 index 0000000..34f5968 --- /dev/null +++ b/room.go @@ -0,0 +1,69 @@ +package main + +import ( + "time" +) + +type Room struct { + Players map[string]*Player + Input chan InputMessage +} + +func NewRoom() *Room { + return &Room{ + Players: make(map[string]*Player), + Input: make(chan InputMessage, 128), + } +} + +func (r *Room) update() { + for { + select { + case input := <-r.Input: + p := r.Players[input.PlayerID] + if p != nil { + p.DX = input.DX + p.DY = input.DY + } + default: + goto DONE + } + } +DONE: + + for _, p := range r.Players { + p.X += p.DX * 4 + p.Y += p.DY * 4 + } +} + +func (r *Room) broadcast() { + state := map[string]map[string]float64{} + + for id, p := range r.Players { + state[id] = map[string]float64{ + "x": p.X, + "y": p.Y, + } + } + + msg := StateMessage{ + Type: "state", + Payload: map[string]any{ + "players": state, + }, + } + + for _, p := range r.Players { + p.Conn.WriteJSON(msg) + } +} + +func (r *Room) Run() { + ticker := time.NewTicker(time.Second / 30) + + for range ticker.C { + r.update() + r.broadcast() + } +} -- 2.49.1