adjust PlayerList and KeepAlive in go-mc/server
This commit is contained in:
@ -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
|
||||
|
@ -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) {}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ package server
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"image"
|
||||
"image/png"
|
||||
"strings"
|
||||
@ -97,36 +96,33 @@ func (s *Server) listResp() ([]byte, error) {
|
||||
type PingInfo struct {
|
||||
name string
|
||||
protocol int
|
||||
*PlayerList
|
||||
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,
|
||||
}
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user