Refactored server flow

This commit is contained in:
Eduard Urbach 2024-01-29 17:51:31 +01:00
parent 6f49400770
commit bf08205c7d
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
15 changed files with 232 additions and 154 deletions

View File

@ -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")

View File

@ -5,6 +5,7 @@ extends Camera3D
func _ready():
Global.camera = self
look_at(center.position)
func _process(delta):
if Global.player == null:

View File

@ -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
}

View File

@ -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},
},
}

View File

@ -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()
}
}

View File

@ -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

View File

@ -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
}

View File

@ -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) {

View File

@ -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
}

View File

@ -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
}

View File

@ -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

View File

@ -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)
}

View File

@ -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)
}

38
server/game/Router.go Normal file
View File

@ -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)
}
}

View File

@ -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++
}