From 59caded2efc150a851013ca86557f90814380eab Mon Sep 17 00:00:00 2001 From: Tnze Date: Tue, 21 Jun 2022 01:12:55 +0800 Subject: [PATCH] adjust PlayerList and KeepAlive in go-mc/server --- server/client.go | 37 --------------- server/dimension.go | 56 ---------------------- server/gameplay.go | 60 ------------------------ server/keepalive.go | 107 +++++++++++++++++-------------------------- server/ping.go | 22 ++++----- server/playerlist.go | 51 ++++++++------------- 6 files changed, 70 insertions(+), 263 deletions(-) delete mode 100644 server/dimension.go diff --git a/server/client.go b/server/client.go index 5ab67ea..a560726 100644 --- a/server/client.go +++ b/server/client.go @@ -5,34 +5,14 @@ import ( "strconv" "sync" - "github.com/google/uuid" - - "github.com/Tnze/go-mc/net" pk "github.com/Tnze/go-mc/net/packet" ) -type Client struct { - *net.Conn - Protocol int32 - packetQueue *PacketQueue - errChan chan error -} - -type Player struct { - uuid.UUID - Name string -} - // Packet758 is a packet in protocol 757. // We are using type system to force programmers to update packets. type Packet758 pk.Packet type Packet757 pk.Packet -// WritePacket to player client. The type of parameter will update per version. -func (c *Client) WritePacket(packet Packet758) { - c.packetQueue.Push(pk.Packet(packet)) -} - type WritePacketError struct { Err error ID int32 @@ -46,23 +26,6 @@ func (s WritePacketError) Unwrap() error { return s.Err } -func (c *Client) PutErr(err error) { - select { - case c.errChan <- err: - default: - // previous error exist, ignore this. - } -} - -func (c *Client) GetErr() error { - select { - case err := <-c.errChan: - return err - default: - return nil - } -} - type PacketQueue struct { queue *list.List closed bool diff --git a/server/dimension.go b/server/dimension.go deleted file mode 100644 index 670bd6a..0000000 --- a/server/dimension.go +++ /dev/null @@ -1,56 +0,0 @@ -package server - -import ( - "github.com/Tnze/go-mc/data/packetid" - "github.com/Tnze/go-mc/level" - pk "github.com/Tnze/go-mc/net/packet" -) - -type Level interface { - Init(g *Game) - Info() LevelInfo - PlayerJoin(p *Client) - PlayerQuit(p *Client) -} - -type LevelInfo struct { - Name string - HashedSeed uint64 -} - -type SimpleDim struct { - numOfSection int - columns map[level.ChunkPos]*level.Chunk -} - -func (s *SimpleDim) Init(*Game) {} - -func NewSimpleDim(secs int) *SimpleDim { - return &SimpleDim{ - numOfSection: secs, - columns: make(map[level.ChunkPos]*level.Chunk), - } -} - -func (s *SimpleDim) LoadChunk(pos level.ChunkPos, c *level.Chunk) { - s.columns[pos] = c -} - -func (s *SimpleDim) Info() LevelInfo { - return LevelInfo{ - Name: "minecraft:overworld", - HashedSeed: 1234567, - } -} - -func (s *SimpleDim) PlayerJoin(p *Client) { - for pos, column := range s.columns { - packet := pk.Marshal( - packetid.ClientboundLevelChunkWithLight, - pos, column, - ) - p.WritePacket(Packet758(packet)) - } -} - -func (s *SimpleDim) PlayerQuit(*Client) {} diff --git a/server/gameplay.go b/server/gameplay.go index ce45cd0..93dde1b 100644 --- a/server/gameplay.go +++ b/server/gameplay.go @@ -1,12 +1,8 @@ package server import ( - "context" "crypto/rsa" _ "embed" - "sync" - "time" - "github.com/google/uuid" "github.com/Tnze/go-mc/net" @@ -19,59 +15,3 @@ type GamePlay interface { // You don't need to close the connection, but to keep not returning while the player is playing. AcceptPlayer(name string, id uuid.UUID, profilePubKey *rsa.PublicKey, protocol int32, conn *net.Conn) } - -type Game struct { - WorldLocker sync.Mutex - handlers map[int32][]*PacketHandler - components []Component -} - -func (g *Game) AcceptPlayer(name string, id uuid.UUID, profilePubKey *rsa.PublicKey, protocol int32, conn *net.Conn) { - conn.Close() -} - -type PacketHandler struct { - ID int32 - F packetHandlerFunc -} - -type packetHandlerFunc func(client *Client, player *Player, packet Packet758) error - -type Component interface { - Init(g *Game) - Run(ctx context.Context) - ClientJoin(c *Client, p *Player) - ClientLeft(c *Client, p *Player, reason error) -} - -func NewGame(components ...Component) *Game { - g := &Game{ - handlers: make(map[int32][]*PacketHandler), - components: components, - } - for _, c := range components { - c.Init(g) - } - return g -} - -func (g *Game) AddHandler(ph *PacketHandler) { - g.handlers[ph.ID] = append(g.handlers[ph.ID], ph) -} - -func (g *Game) Run(ctx context.Context) { - for _, c := range g.components { - go c.Run(ctx) - } - ticker := time.NewTicker(time.Second / 20) - for { - select { - case <-ticker.C: - g.WorldLocker.Lock() - //g.Dispatcher.Run(g.World) - g.WorldLocker.Unlock() - case <-ctx.Done(): - return - } - } -} diff --git a/server/keepalive.go b/server/keepalive.go index 3401d6a..244caf9 100644 --- a/server/keepalive.go +++ b/server/keepalive.go @@ -4,10 +4,8 @@ import ( "container/list" "context" "errors" + "github.com/Tnze/go-mc/chat" "time" - - "github.com/Tnze/go-mc/data/packetid" - pk "github.com/Tnze/go-mc/net/packet" ) // keepAliveInterval represents the interval when the server sends keep alive @@ -16,59 +14,49 @@ const keepAliveInterval = time.Second * 15 // keepAliveWaitInterval represents how long does the player expired const keepAliveWaitInterval = time.Second * 30 -type ClientDelay struct { - Delay time.Duration +type KeepAliveClient interface { + SendKeepAlive(id int64) + SendDisconnect(reason chat.Message) } type KeepAlive struct { - join chan *Client - quit chan *Client - tick chan *Client + join chan KeepAliveClient + quit chan KeepAliveClient + tick chan KeepAliveClient pingList *list.List waitList *list.List - listIndex map[*Client]*list.Element + listIndex map[KeepAliveClient]*list.Element listTimer *time.Timer waitTimer *time.Timer // The Notchian server uses a system-dependent time in milliseconds to generate the keep alive ID value. // We don't do that here for security reason. keepAliveID int64 - updatePlayerDelay []func(p *Client, delay time.Duration) + updatePlayerDelay []func(p KeepAliveClient, delay time.Duration) } -func NewKeepAlive() (k *KeepAlive) { +func NewKeepAlive[C KeepAliveClient]() (k *KeepAlive) { return &KeepAlive{ - join: make(chan *Client), - quit: make(chan *Client), - tick: make(chan *Client), + join: make(chan KeepAliveClient), + quit: make(chan KeepAliveClient), + tick: make(chan KeepAliveClient), pingList: list.New(), waitList: list.New(), - listIndex: make(map[*Client]*list.Element), + listIndex: make(map[KeepAliveClient]*list.Element), listTimer: time.NewTimer(keepAliveInterval), waitTimer: time.NewTimer(keepAliveWaitInterval), keepAliveID: 0, } } -func (k *KeepAlive) AddPlayerDelayUpdateHandler(f func(p *Client, delay time.Duration)) { +func (k *KeepAlive) AddPlayerDelayUpdateHandler(f func(c KeepAliveClient, delay time.Duration)) { k.updatePlayerDelay = append(k.updatePlayerDelay, f) } -// Init implement Component for KeepAlive -func (k *KeepAlive) Init(g *Game) { - g.AddHandler(&PacketHandler{ - ID: packetid.ServerboundKeepAlive, - F: func(client *Client, player *Player, packet Packet758) error { - var KeepAliveID pk.Long - if err := pk.Packet(packet).Scan(&KeepAliveID); err != nil { - return err - } - k.tick <- client - return nil - }, - }) -} +func (k *KeepAlive) ClientJoin(client KeepAliveClient) { k.join <- client } +func (k *KeepAlive) ClientTick(client KeepAliveClient) { k.tick <- client } +func (k *KeepAlive) ClientLeft(client KeepAliveClient) { k.quit <- client } // Run implement Component for KeepAlive func (k *KeepAlive) Run(ctx context.Context) { @@ -76,35 +64,29 @@ func (k *KeepAlive) Run(ctx context.Context) { select { case <-ctx.Done(): return - case p := <-k.join: - k.pushPlayer(p) - case p := <-k.quit: - k.removePlayer(p) + case c := <-k.join: + k.pushPlayer(c) + case c := <-k.quit: + k.removePlayer(c) case now := <-k.listTimer.C: k.pingPlayer(now) case <-k.waitTimer.C: k.kickPlayer() - case p := <-k.tick: - k.tickPlayer(p) + case c := <-k.tick: + k.tickPlayer(c) } } } -// ClientJoin implement Component for KeepAlive -func (k *KeepAlive) ClientJoin(client *Client, _ *Player) { k.join <- client } - -// ClientLeft implement Component for KeepAlive -func (k *KeepAlive) ClientLeft(client *Client, _ *Player, _ error) { k.quit <- client } - -func (k KeepAlive) pushPlayer(p *Client) { - k.listIndex[p] = k.pingList.PushBack( - keepAliveItem{player: p, t: time.Now()}, +func (k KeepAlive) pushPlayer(c KeepAliveClient) { + k.listIndex[c] = k.pingList.PushBack( + keepAliveItem{player: c, t: time.Now()}, ) } -func (k *KeepAlive) removePlayer(p *Client) { - elem := k.listIndex[p] - delete(k.listIndex, p) +func (k *KeepAlive) removePlayer(c KeepAliveClient) { + elem := k.listIndex[c] + delete(k.listIndex, c) if elem.Prev() == nil { // At present, it is difficult to distinguish // which linked list the player is in, @@ -118,26 +100,23 @@ func (k *KeepAlive) removePlayer(p *Client) { func (k *KeepAlive) pingPlayer(now time.Time) { if elem := k.pingList.Front(); elem != nil { - p := k.pingList.Remove(elem).(keepAliveItem).player + c := k.pingList.Remove(elem).(keepAliveItem).player // Send Clientbound KeepAlive packet. - p.WritePacket(Packet758(pk.Marshal( - packetid.ClientboundKeepAlive, - pk.Long(k.keepAliveID), - ))) + c.SendKeepAlive(k.keepAliveID) k.keepAliveID++ // Clientbound KeepAlive packet is sent, move the player to waiting list. - k.listIndex[p] = k.waitList.PushBack( - keepAliveItem{player: p, t: now}, + k.listIndex[c] = k.waitList.PushBack( + keepAliveItem{player: c, t: now}, ) } // Wait for next earliest player keepAliveSetTimer(k.pingList, k.listTimer, keepAliveInterval) } -func (k *KeepAlive) tickPlayer(p *Client) { - elem, ok := k.listIndex[p] +func (k *KeepAlive) tickPlayer(c KeepAliveClient) { + elem, ok := k.listIndex[c] if !ok { - p.PutErr(errors.New("keepalive: fail to tick player: client not found")) + panic(errors.New("keepalive: fail to tick player: client not found")) return } if elem.Prev() == nil { @@ -150,19 +129,19 @@ func (k *KeepAlive) tickPlayer(p *Client) { now := time.Now() delay := now.Sub(k.waitList.Remove(elem).(keepAliveItem).t) for _, f := range k.updatePlayerDelay { - f(p, delay) + f(c, delay) } // move the player to ping list - k.listIndex[p] = k.pingList.PushBack( - keepAliveItem{player: p, t: now}, + k.listIndex[c] = k.pingList.PushBack( + keepAliveItem{player: c, t: now}, ) } func (k *KeepAlive) kickPlayer() { if elem := k.waitList.Front(); elem != nil { - player := k.waitList.Remove(elem).(keepAliveItem).player + c := k.waitList.Remove(elem).(keepAliveItem).player k.waitList.Remove(elem) - player.PutErr(errors.New("keepalive: client did not response")) + c.SendDisconnect(chat.TranslateMsg("disconnect.timeout")) } keepAliveSetTimer(k.waitList, k.waitTimer, keepAliveWaitInterval) } @@ -180,6 +159,6 @@ func keepAliveSetTimer(l *list.List, timer *time.Timer, interval time.Duration) } type keepAliveItem struct { - player *Client + player KeepAliveClient t time.Time } diff --git a/server/ping.go b/server/ping.go index 06c232f..af77aa0 100644 --- a/server/ping.go +++ b/server/ping.go @@ -3,7 +3,6 @@ package server import ( "encoding/base64" "encoding/json" - "errors" "image" "image/png" "strings" @@ -95,38 +94,35 @@ func (s *Server) listResp() ([]byte, error) { // PingInfo implement ListPingHandler. type PingInfo struct { - name string - protocol int - *PlayerList + name string + protocol int description chat.Message favicon string } // NewPingInfo crate a new PingInfo, the icon can be nil. -func NewPingInfo(list *PlayerList, name string, protocol int, motd chat.Message, icon image.Image) (p *PingInfo, err error) { +// Panic if icon's size is not 64x64. +func NewPingInfo(name string, protocol int, motd chat.Message, icon image.Image) (p *PingInfo) { var favIcon string if icon != nil { if !icon.Bounds().Size().Eq(image.Point{X: 64, Y: 64}) { - return nil, errors.New("icon size is not 64x64") + panic("icon size is not 64x64") } // Encode icon into string "data:image/png;base64,......" format var sb strings.Builder sb.WriteString("data:image/png;base64,") w := base64.NewEncoder(base64.StdEncoding, &sb) - err = png.Encode(w, icon) - if err != nil { - return nil, err + if err := png.Encode(w, icon); err != nil { + panic(err) } - err = w.Close() - if err != nil { - return nil, err + if err := w.Close(); err != nil { + panic(err) } favIcon = sb.String() } p = &PingInfo{ name: name, protocol: protocol, - PlayerList: list, description: motd, favicon: favIcon, } diff --git a/server/playerlist.go b/server/playerlist.go index 8cb3885..ddf4afc 100644 --- a/server/playerlist.go +++ b/server/playerlist.go @@ -1,22 +1,22 @@ package server import ( - "context" - "errors" "sync" "github.com/google/uuid" "github.com/Tnze/go-mc/chat" - "github.com/Tnze/go-mc/data/packetid" - pk "github.com/Tnze/go-mc/net/packet" ) +type PlayerListClient interface { + SendDisconnect(reason chat.Message) +} + // PlayerList is a player list based on linked-list. // This struct should not be copied after used. type PlayerList struct { maxPlayer int - clients map[uuid.UUID]*Player + players map[PlayerListClient]PlayerSample // Only the field players is protected by this Mutex. // Because others field never change after created. playersLock sync.Mutex @@ -26,45 +26,33 @@ type PlayerList struct { func NewPlayerList(maxPlayers int) *PlayerList { return &PlayerList{ maxPlayer: maxPlayers, - clients: make(map[uuid.UUID]*Player), + players: make(map[PlayerListClient]PlayerSample), } } -// Init implement Component for PlayerList -func (p *PlayerList) Init(*Game) {} - -// Run implement Component for PlayerList -func (p *PlayerList) Run(context.Context) {} - // ClientJoin implement Component for PlayerList -func (p *PlayerList) ClientJoin(client *Client, player *Player) { +func (p *PlayerList) ClientJoin(client PlayerListClient, player PlayerSample) { p.playersLock.Lock() defer p.playersLock.Unlock() - if len(p.clients) >= p.maxPlayer { - client.WritePacket(Packet758(pk.Marshal( - packetid.ClientboundDisconnect, - chat.TranslateMsg("multiplayer.disconnect.server_full"), - ))) - client.PutErr(errors.New("playerlist: server full")) + if len(p.players) >= p.maxPlayer { + client.SendDisconnect(chat.TranslateMsg("multiplayer.disconnect.server_full")) return } - p.clients[player.UUID] = player + p.players[client] = player } -// ClientLeft implement Component for PlayerList -func (p *PlayerList) ClientLeft(_ *Client, player *Player, _ error) { +func (p *PlayerList) ClientLeft(client PlayerListClient) { p.playersLock.Lock() defer p.playersLock.Unlock() - delete(p.clients, player.UUID) + delete(p.players, client) } -// CheckPlayer implement LoginChecker for PlayerList -func (p *PlayerList) CheckPlayer(name string, id uuid.UUID, protocol int32) (ok bool, reason chat.Message) { +func (p *PlayerList) CheckPlayer(string, uuid.UUID, int32) (ok bool, reason chat.Message) { p.playersLock.Lock() defer p.playersLock.Unlock() - if len(p.clients) >= p.maxPlayer { + if len(p.players) >= p.maxPlayer { return false, chat.TranslateMsg("multiplayer.disconnect.server_full") } return true, chat.Message{} @@ -77,24 +65,21 @@ func (p *PlayerList) MaxPlayer() int { func (p *PlayerList) OnlinePlayer() int { p.playersLock.Lock() defer p.playersLock.Unlock() - return len(p.clients) + return len(p.players) } func (p *PlayerList) PlayerSamples() (sample []PlayerSample) { p.playersLock.Lock() defer p.playersLock.Unlock() // Up to 10 players can be returned - length := len(p.clients) + length := len(p.players) if length > 10 { length = 10 } sample = make([]PlayerSample, length) var i int - for _, v := range p.clients { - sample[i] = PlayerSample{ - Name: v.Name, - ID: v.UUID, - } + for _, player := range p.players { + sample[i] = player i++ if i >= length { break