Improved reconnect flow

This commit is contained in:
2024-01-29 23:16:06 +01:00
parent bf08205c7d
commit b22d0b1367
25 changed files with 212 additions and 99 deletions

View File

@ -2,6 +2,7 @@ package game
import (
"net"
"strings"
)
// Chat is used for chat messages.
@ -12,9 +13,30 @@ func (game *Game) Chat(data []byte, address *net.UDPAddr) error {
return ErrUnknownAddress
}
message := string(data)
if game.ChatCommand(player, message) {
return nil
}
newData := []byte{}
newData = AppendString(newData, player.ID)
newData = AppendStringBytes(newData, data)
newData = AppendString(newData, message)
game.Broadcast(Chat, newData)
return nil
}
// ChatCommand executes chat commands and returns true if it was one.
func (game *Game) ChatCommand(player *Player, message string) bool {
if strings.HasPrefix(message, "/") {
switch message {
case "/logout":
game.server.Send(Logout, nil, player.address)
game.players.Remove(player)
}
return true
}
return false
}

View File

@ -10,7 +10,7 @@ import (
// Game represents the entire state of the game server.
type Game struct {
server *Server
router Router
router *Router
players *PlayerManager
}
@ -21,19 +21,26 @@ func New() *Game {
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)
game.router = NewRouter(game)
return game
}
// NewRouter creates a new router.
func NewRouter(game *Game) *Router {
router := &Router{}
router.Get(Ping, game.Ping)
router.Get(Login, game.Login)
router.Get(Move, game.Move)
router.Get(Jump, game.Jump)
router.Get(Chat, game.Chat)
return router
}
// Run starts all game systems.
func (game *Game) Run() {
physics := time.NewTicker(20 * time.Millisecond)
statistics := time.NewTicker(time.Second)
clean := time.NewTicker(time.Second)
physics := time.NewTicker(20 * time.Millisecond).C
statistics := time.NewTicker(time.Second).C
clean := time.NewTicker(time.Second).C
close := make(chan os.Signal, 1)
signal.Notify(close, os.Interrupt)
@ -44,16 +51,16 @@ func (game *Game) Run() {
case p := <-game.server.incoming:
game.router.handle(p)
case <-physics.C:
case <-physics:
game.players.Each(func(c *Player) {
c.Tick()
})
case <-statistics.C:
case <-statistics:
fmt.Printf("%d players | %d packets\n", game.players.Count(), game.server.PacketCount())
game.server.ResetPacketCount()
case <-clean.C:
case <-clean:
game.players.Clean(5 * time.Second)
case <-close:

View File

@ -50,21 +50,14 @@ func (game *Game) Login(data []byte, address *net.UDPAddr) error {
player = game.players.ByAccount(account.ID)
if player != nil {
game.reconnect(player, address)
} else {
game.connect(account, address)
game.server.Send(Login, []byte{Failure}, address)
return ErrAlreadyLoggedIn
}
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()

View File

@ -48,6 +48,7 @@ func (player *Player) State() []byte {
return state
}
// OnConnect is executed when a connection has been established.
func (player *Player) OnConnect() {
fmt.Printf("%s connected.\n", player.Name)
players := player.game.players
@ -62,6 +63,7 @@ func (player *Player) OnConnect() {
})
}
// OnDisconnect is executed when the connection has been closed.
func (player *Player) OnDisconnect() {
fmt.Printf("%s disconnected.\n", player.Name)
player.game.BroadcastOthers(PlayerRemove, []byte(player.ID), player)

View File

@ -2,92 +2,96 @@ package game
import (
"net"
"sync"
"sync/atomic"
"time"
)
// PlayerManager keeps tracks of all players.
type PlayerManager struct {
players sync.Map
accounts sync.Map
count atomic.Int64
players map[string]*Player
accounts map[string]*Player
count int
}
// NewPlayerManager creates a new player manager.
func NewPlayerManager() *PlayerManager {
return &PlayerManager{}
}
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
})
return &PlayerManager{
players: map[string]*Player{},
accounts: map[string]*Player{},
}
}
// 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)
m.players[player.address.String()] = player
m.accounts[player.ID] = player
m.count++
player.OnConnect()
}
// ChangeAddress changes the address of a player.
func (m *PlayerManager) ChangeAddress(player *Player, address *net.UDPAddr) {
m.players.Delete(player.address.String())
delete(m.players, player.address.String())
player.address = address
m.players.Store(player.address.String(), player)
m.players[player.address.String()] = player
}
// Clean checks for players who haven't responded in the timeout duration and disconnects them.
func (m *PlayerManager) Clean(timeout time.Duration) {
now := time.Now()
for _, player := range m.players {
if player.lastPacket.IsZero() || now.Before(player.lastPacket.Add(timeout)) {
continue
}
m.Remove(player)
}
}
// Remove removes a player.
func (m *PlayerManager) Remove(player *Player) {
delete(m.players, player.address.String())
delete(m.accounts, player.ID)
m.count--
player.OnDisconnect()
}
// 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())
_, exists := m.players[address.String()]
return exists
}
// Count returns the number of clients.
func (m *PlayerManager) Count() int {
return int(m.count.Load())
return m.count
}
// Each calls the callback function for each client.
func (m *PlayerManager) Each(callback func(*Player)) {
m.players.Range(func(key, value any) bool {
callback(value.(*Player))
return true
})
for _, player := range m.players {
callback(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())
player, exists := m.players[address.String()]
if !exists {
return nil
}
return obj.(*Player)
return player
}
// ByAccount returns the player with the given account ID.
func (m *PlayerManager) ByAccount(id string) *Player {
obj, exists := m.accounts.Load(id)
player, exists := m.accounts[id]
if !exists {
return nil
}
return obj.(*Player)
return player
}