From bf08205c7d23f165f372eba0855967ff1ce8f449 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 29 Jan 2024 17:51:31 +0100 Subject: [PATCH] Refactored server flow --- client/Main.tscn | 5 +- client/camera/Camera.gd | 1 + server/game/Chat.go | 13 +-- server/game/Database.go | 8 +- server/game/Game.go | 87 +++++++++------------ server/game/Handler.go | 2 +- server/game/Jump.go | 12 ++- server/game/Login.go | 61 +++++++++++---- server/game/Move.go | 17 +++- server/game/{packet/packet.go => Packet.go} | 15 +++- server/game/Ping.go | 7 +- server/game/Player.go | 11 +-- server/game/PlayerManager.go | 79 +++++++++++-------- server/game/Router.go | 38 +++++++++ server/game/Server.go | 30 +++---- 15 files changed, 232 insertions(+), 154 deletions(-) rename server/game/{packet/packet.go => Packet.go} (51%) create mode 100644 server/game/Router.go diff --git a/client/Main.tscn b/client/Main.tscn index 1b33551..dd32a2d 100644 --- a/client/Main.tscn +++ b/client/Main.tscn @@ -149,10 +149,9 @@ render_target_update_mode = 4 [node name="CameraPivot" type="Node3D" parent="Viewport/SubViewport"] [node name="Camera" type="Camera3D" parent="Viewport/SubViewport/CameraPivot" node_paths=PackedStringArray("center")] -transform = Transform3D(0.707107, 0.353554, -0.612372, 0, 0.866026, 0.5, 0.707107, -0.353554, 0.612372, -10, 10, 10) -projection = 1 +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 20, 20) current = true -fov = 90.0 +fov = 30.0 size = 10.0 far = 100.0 script = ExtResource("18_wogcj") diff --git a/client/camera/Camera.gd b/client/camera/Camera.gd index 1789723..e91a085 100644 --- a/client/camera/Camera.gd +++ b/client/camera/Camera.gd @@ -5,6 +5,7 @@ extends Camera3D func _ready(): Global.camera = self + look_at(center.position) func _process(delta): if Global.player == null: diff --git a/server/game/Chat.go b/server/game/Chat.go index 8540bb7..0e82329 100644 --- a/server/game/Chat.go +++ b/server/game/Chat.go @@ -1,19 +1,20 @@ package game import ( - "fmt" "net" - "server/game/packet" ) // Chat is used for chat messages. -func (game *Game) Chat(data []byte, address *net.UDPAddr, server *Server) error { - player := game.players.Get(address) - fmt.Printf("[%s] %s\n", player.Name, string(data)) +func (game *Game) Chat(data []byte, address *net.UDPAddr) error { + player := game.players.ByAddress(address) + + if player == nil { + return ErrUnknownAddress + } newData := []byte{} newData = AppendString(newData, player.ID) newData = AppendStringBytes(newData, data) - game.Broadcast(packet.Chat, newData) + game.Broadcast(Chat, newData) return nil } diff --git a/server/game/Database.go b/server/game/Database.go index d13761a..d093691 100644 --- a/server/game/Database.go +++ b/server/game/Database.go @@ -5,25 +5,25 @@ var accounts = map[string]*Account{ ID: "4J6qpK1ve", Name: "user0", Password: "password", - Position: Vector3{5, 0, 0}, + Position: Vector3{3, 0, 0}, }, "user1": { ID: "I_vyeZamg", Name: "user1", Password: "password", - Position: Vector3{-5, 0, 0}, + Position: Vector3{-3, 0, 0}, }, "user2": { ID: "VJOK1ckvx", Name: "user2", Password: "password", - Position: Vector3{0, 0, 5}, + Position: Vector3{0, 0, 3}, }, "user3": { ID: "EkCcqbwFl", Name: "user3", Password: "password", - Position: Vector3{0, 0, -5}, + Position: Vector3{0, 0, -3}, }, } diff --git a/server/game/Game.go b/server/game/Game.go index 2111029..22cd79f 100644 --- a/server/game/Game.go +++ b/server/game/Game.go @@ -4,87 +4,78 @@ import ( "fmt" "os" "os/signal" - "server/game/packet" "time" ) // Game represents the entire state of the game server. type Game struct { server *Server + router Router players *PlayerManager } // New creates a new game. func New() *Game { - return &Game{ + game := &Game{ server: NewServer(), players: NewPlayerManager(), } + + game.router.Get(Ping, game.Ping) + game.router.Get(Login, game.Login) + game.router.Get(Move, game.Move) + game.router.Get(Jump, game.Jump) + game.router.Get(Chat, game.Chat) + return game } // Run starts all game systems. func (game *Game) Run() { - game.start() + physics := time.NewTicker(20 * time.Millisecond) + statistics := time.NewTicker(time.Second) + clean := time.NewTicker(time.Second) close := make(chan os.Signal, 1) signal.Notify(close, os.Interrupt) - <-close + + go game.server.Run(4242) + + for { + select { + case p := <-game.server.incoming: + game.router.handle(p) + + case <-physics.C: + game.players.Each(func(c *Player) { + c.Tick() + }) + + case <-statistics.C: + fmt.Printf("%d players | %d packets\n", game.players.Count(), game.server.PacketCount()) + game.server.ResetPacketCount() + + case <-clean.C: + game.players.Clean(5 * time.Second) + + case <-close: + return + } + } } // Broadcast sends the packet to all players. func (game *Game) Broadcast(code byte, data []byte) { - game.players.Each(func(other *Player) bool { + game.players.Each(func(other *Player) { game.server.Send(code, data, other.address) - return true }) } // BroadcastOthers sends the packet to all other players except the original sender. func (game *Game) BroadcastOthers(code byte, data []byte, exclude *Player) { - game.players.Each(func(other *Player) bool { + game.players.Each(func(other *Player) { if other == exclude { - return true + return } game.server.Send(code, data, other.address) - return true }) } - -// start starts all game systems. -func (game *Game) start() { - go game.network() - go game.physics() - go game.statistics() -} - -// network will listen for new packets and process them. -func (game *Game) network() { - game.server.SetHandler(packet.Ping, game.Ping) - game.server.SetHandler(packet.Login, game.Login) - game.server.SetHandler(packet.PlayerMove, game.Move) - game.server.SetHandler(packet.PlayerJump, game.Jump) - game.server.SetHandler(packet.Chat, game.Chat) - game.server.Run(4242) -} - -// physics periodically runs the Tick function for each player. -func (game *Game) physics() { - updater := time.NewTicker(20 * time.Millisecond) - - for range updater.C { - game.players.Each(func(c *Player) bool { - c.Tick() - return true - }) - } -} - -// statistics periodically shows server statistics on the command line. -func (game *Game) statistics() { - ticker := time.NewTicker(time.Second) - - for range ticker.C { - fmt.Printf("%d players | %d packets\n", game.players.Count(), game.server.PacketCount()) - game.server.ResetPacketCount() - } -} diff --git a/server/game/Handler.go b/server/game/Handler.go index 071e982..1d4f85d 100644 --- a/server/game/Handler.go +++ b/server/game/Handler.go @@ -3,4 +3,4 @@ package game import "net" // Handler is a byte code specific packet handler. -type Handler func([]byte, *net.UDPAddr, *Server) error +type Handler func([]byte, *net.UDPAddr) error diff --git a/server/game/Jump.go b/server/game/Jump.go index 3211700..c195bf0 100644 --- a/server/game/Jump.go +++ b/server/game/Jump.go @@ -2,12 +2,16 @@ package game import ( "net" - "server/game/packet" ) // Jump broadcasts the jump action. -func (game *Game) Jump(data []byte, address *net.UDPAddr, server *Server) error { - player := game.players.Get(address) - game.BroadcastOthers(packet.PlayerJump, []byte(player.ID), player) +func (game *Game) Jump(data []byte, address *net.UDPAddr) error { + player := game.players.ByAddress(address) + + if player == nil { + return ErrUnknownAddress + } + + game.BroadcastOthers(Jump, []byte(player.ID), player) return nil } diff --git a/server/game/Login.go b/server/game/Login.go index ea40612..2a140d2 100644 --- a/server/game/Login.go +++ b/server/game/Login.go @@ -6,7 +6,6 @@ import ( "encoding/json" "errors" "net" - "server/game/packet" ) const ( @@ -14,43 +13,71 @@ const ( Failure = 1 ) +var ( + ErrAlreadyLoggedIn = errors.New("already logged in") + ErrUnknownAccount = errors.New("unknown account") + ErrWrongPassword = errors.New("wrong password") +) + // Login checks the account credentials and gives a network peer access to an account. -func (game *Game) Login(data []byte, address *net.UDPAddr, server *Server) error { - if game.players.Get(address) != nil { - server.Send(packet.Login, []byte{Failure}, address) - return errors.New("already logged in") +func (game *Game) Login(data []byte, address *net.UDPAddr) error { + player := game.players.ByAddress(address) + + if player != nil { + game.server.Send(Login, []byte{Failure}, address) + return ErrAlreadyLoggedIn } username, password, err := getLoginData(data) if err != nil { - server.Send(packet.Login, []byte{Failure}, address) + game.server.Send(Login, []byte{Failure}, address) return err } account := GetAccountByName(username) if account == nil { - server.Send(packet.Login, []byte{Failure}, address) - return errors.New("unknown account name") + game.server.Send(Login, []byte{Failure}, address) + return ErrUnknownAccount } if password != "password" { - server.Send(packet.Login, []byte{Failure}, address) - return errors.New("login failure") + game.server.Send(Login, []byte{Failure}, address) + return ErrWrongPassword } + player = game.players.ByAccount(account.ID) + + if player != nil { + game.reconnect(player, address) + } else { + game.connect(account, address) + } + + return nil +} + +func (game *Game) reconnect(player *Player, address *net.UDPAddr) { + player.KeepAlive() + game.players.ChangeAddress(player, address) + game.sendLoginSuccess(player) + player.OnConnect() +} + +func (game *Game) connect(account *Account, address *net.UDPAddr) { player := NewPlayer(address, account, game) player.authToken = createAuthToken() player.KeepAlive() - - response := []byte{Success} - response = AppendString(response, account.ID) - response = AppendString(response, player.authToken) - server.Send(packet.Login, response, address) - + game.sendLoginSuccess(player) game.players.Add(player) - return nil +} + +func (game *Game) sendLoginSuccess(player *Player) { + response := []byte{Success} + response = AppendString(response, player.ID) + response = AppendString(response, player.authToken) + game.server.Send(Login, response, player.address) } func getLoginData(data []byte) (string, string, error) { diff --git a/server/game/Move.go b/server/game/Move.go index 9581ee5..55edfe0 100644 --- a/server/game/Move.go +++ b/server/game/Move.go @@ -2,14 +2,23 @@ package game import ( "encoding/binary" + "errors" "math" "net" - "server/game/packet" +) + +var ( + ErrUnknownAddress = errors.New("unknown address") ) // Move updates the location and direction the client is currently moving towards. -func (game *Game) Move(data []byte, address *net.UDPAddr, server *Server) error { - player := game.players.Get(address) +func (game *Game) Move(data []byte, address *net.UDPAddr) error { + player := game.players.ByAddress(address) + + if player == nil { + return ErrUnknownAddress + } + player.Position.X = math.Float32frombits(binary.LittleEndian.Uint32(data)) player.Position.Z = math.Float32frombits(binary.LittleEndian.Uint32(data[4:])) @@ -17,6 +26,6 @@ func (game *Game) Move(data []byte, address *net.UDPAddr, server *Server) error update = AppendFloat(update, player.Position.X) update = AppendFloat(update, player.Position.Z) - game.BroadcastOthers(packet.PlayerMove, update, player) + game.BroadcastOthers(Move, update, player) return nil } diff --git a/server/game/packet/packet.go b/server/game/Packet.go similarity index 51% rename from server/game/packet/packet.go rename to server/game/Packet.go index 2d7ec2d..69527b0 100644 --- a/server/game/packet/packet.go +++ b/server/game/Packet.go @@ -1,4 +1,8 @@ -package packet +package game + +import ( + "net" +) const ( Ping = 1 @@ -6,8 +10,13 @@ const ( Logout = 3 PlayerAdd = 10 PlayerRemove = 11 - PlayerMove = 12 - PlayerJump = 13 + Move = 12 + Jump = 13 PlayerAttack = 14 Chat = 20 ) + +type Packet struct { + Data []byte + Address *net.UDPAddr +} diff --git a/server/game/Ping.go b/server/game/Ping.go index 8aa4d57..80f67a5 100644 --- a/server/game/Ping.go +++ b/server/game/Ping.go @@ -2,15 +2,14 @@ package game import ( "net" - "server/game/packet" ) // Ping is used as a heartbeat and latency check. -func (game *Game) Ping(data []byte, address *net.UDPAddr, server *Server) error { - server.Send(packet.Ping, data, address) +func (game *Game) Ping(data []byte, address *net.UDPAddr) error { + game.server.Send(Ping, data, address) if game.players.Contains(address) { - game.players.Get(address).KeepAlive() + game.players.ByAddress(address).KeepAlive() } return nil diff --git a/server/game/Player.go b/server/game/Player.go index b4e98b9..15a92aa 100644 --- a/server/game/Player.go +++ b/server/game/Player.go @@ -3,7 +3,6 @@ package game import ( "fmt" "net" - "server/game/packet" "time" ) @@ -54,18 +53,16 @@ func (player *Player) OnConnect() { players := player.game.players server := player.game.server - players.Each(func(other *Player) bool { - server.Send(packet.PlayerAdd, other.State(), player.address) + players.Each(func(other *Player) { + server.Send(PlayerAdd, other.State(), player.address) if other != player { - server.Send(packet.PlayerAdd, player.State(), other.address) + server.Send(PlayerAdd, player.State(), other.address) } - - return true }) } func (player *Player) OnDisconnect() { fmt.Printf("%s disconnected.\n", player.Name) - player.game.BroadcastOthers(packet.PlayerRemove, []byte(player.ID), player) + player.game.BroadcastOthers(PlayerRemove, []byte(player.ID), player) } diff --git a/server/game/PlayerManager.go b/server/game/PlayerManager.go index 1e34d13..7ea785f 100644 --- a/server/game/PlayerManager.go +++ b/server/game/PlayerManager.go @@ -9,47 +9,48 @@ import ( // PlayerManager keeps tracks of all players. type PlayerManager struct { - players sync.Map - count atomic.Int64 + players sync.Map + accounts sync.Map + count atomic.Int64 } // NewPlayerManager creates a new player manager. func NewPlayerManager() *PlayerManager { - m := &PlayerManager{} - timeout := 5 * time.Second - interval := time.Second - - go func() { - ticker := time.NewTicker(interval) - defer ticker.Stop() - - for range ticker.C { - now := time.Now() - - m.players.Range(func(key, value interface{}) bool { - player := value.(*Player) - - if !player.lastPacket.IsZero() && now.After(player.lastPacket.Add(timeout)) { - m.players.Delete(key) - m.count.Add(-1) - player.OnDisconnect() - } - - return true - }) - } - }() - - return m + return &PlayerManager{} } -// Add adds a new player with the given address and account. +func (m *PlayerManager) Clean(timeout time.Duration) { + now := time.Now() + + m.players.Range(func(key, value interface{}) bool { + player := value.(*Player) + + if !player.lastPacket.IsZero() && now.After(player.lastPacket.Add(timeout)) { + m.players.Delete(key) + m.accounts.Delete(player.ID) + m.count.Add(-1) + player.OnDisconnect() + } + + return true + }) +} + +// Add adds a new player. func (m *PlayerManager) Add(player *Player) { m.players.Store(player.address.String(), player) + m.accounts.Store(player.ID, player) m.count.Add(1) player.OnConnect() } +// ChangeAddress changes the address of a player. +func (m *PlayerManager) ChangeAddress(player *Player, address *net.UDPAddr) { + m.players.Delete(player.address.String()) + player.address = address + m.players.Store(player.address.String(), player) +} + // Contains tells you whether the address is already a registered client. func (m *PlayerManager) Contains(address *net.UDPAddr) bool { _, exists := m.players.Load(address.String()) @@ -62,14 +63,15 @@ func (m *PlayerManager) Count() int { } // Each calls the callback function for each client. -func (m *PlayerManager) Each(callback func(*Player) bool) { +func (m *PlayerManager) Each(callback func(*Player)) { m.players.Range(func(key, value any) bool { - return callback(value.(*Player)) + callback(value.(*Player)) + return true }) } -// Get either returns a new or existing client for the requested address. -func (m *PlayerManager) Get(address *net.UDPAddr) *Player { +// ByAddress returns an existing client for the requested address. +func (m *PlayerManager) ByAddress(address *net.UDPAddr) *Player { obj, exists := m.players.Load(address.String()) if !exists { @@ -78,3 +80,14 @@ func (m *PlayerManager) Get(address *net.UDPAddr) *Player { return obj.(*Player) } + +// ByAccount returns the player with the given account ID. +func (m *PlayerManager) ByAccount(id string) *Player { + obj, exists := m.accounts.Load(id) + + if !exists { + return nil + } + + return obj.(*Player) +} diff --git a/server/game/Router.go b/server/game/Router.go new file mode 100644 index 0000000..9fc944f --- /dev/null +++ b/server/game/Router.go @@ -0,0 +1,38 @@ +package game + +import ( + "fmt" +) + +// Router processes packets by passing them to the correct handler. +type Router struct { + handlers [256]Handler +} + +// Get sets the handler for the given byte code. +func (ph *Router) Get(code byte, handler Handler) { + ph.handlers[code] = handler +} + +// handle deals with an incoming packet. +func (ph *Router) handle(p *Packet) { + defer func() { + if r := recover(); r != nil { + fmt.Println(p) + fmt.Println(r) + } + }() + + handler := ph.handlers[p.Data[0]] + + if handler == nil { + fmt.Printf("No callback registered for packet type %d\n", p.Data[0]) + return + } + + err := handler(p.Data[1:], p.Address) + + if err != nil { + fmt.Println(err) + } +} diff --git a/server/game/Server.go b/server/game/Server.go index df39e86..d606d48 100644 --- a/server/game/Server.go +++ b/server/game/Server.go @@ -5,16 +5,21 @@ import ( "net" ) +const ChannelBufferSize = 4096 + // Server represents a UDP server. type Server struct { handlers [256]Handler socket *net.UDPConn + incoming chan *Packet packetCount int } // NewServer creates a new server. func NewServer() *Server { - return &Server{} + return &Server{ + incoming: make(chan *Packet, ChannelBufferSize), + } } // SetHandler sets the handler for the given byte code. @@ -80,24 +85,9 @@ func (s *Server) read() { continue } - s.handle(buffer[:n], addr) + tmp := make([]byte, n) + copy(tmp, buffer) + s.incoming <- &Packet{Data: tmp, Address: addr} + s.packetCount++ } } - -// handle deals with an incoming packet. -func (s *Server) handle(data []byte, addr *net.UDPAddr) { - handler := s.handlers[data[0]] - - if handler == nil { - fmt.Printf("No callback registered for packet type %d\n", data[0]) - return - } - - err := handler(data[1:], addr, s) - - if err != nil { - fmt.Println(err) - } - - s.packetCount++ -}