From 408171bda686844beca85301757904e0ca4d93dc Mon Sep 17 00:00:00 2001 From: TwistedAsylumMC Date: Thu, 15 Aug 2024 12:29:29 +0100 Subject: [PATCH] server/session.go: Handle new server bound packets --- server/player/diagnostics/diagnostics.go | 29 +++++++++++++++++ server/player/handler.go | 6 ++++ server/player/player.go | 11 +++++++ server/session/controllable.go | 3 ++ .../handler_server_bound_diagnostics.go | 29 +++++++++++++++++ .../handler_server_bound_loading_screen.go | 32 +++++++++++++++++++ server/session/session.go | 19 ++++++++++- 7 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 server/player/diagnostics/diagnostics.go create mode 100644 server/session/handler_server_bound_diagnostics.go create mode 100644 server/session/handler_server_bound_loading_screen.go diff --git a/server/player/diagnostics/diagnostics.go b/server/player/diagnostics/diagnostics.go new file mode 100644 index 000000000..1d0d7e62d --- /dev/null +++ b/server/player/diagnostics/diagnostics.go @@ -0,0 +1,29 @@ +package diagnostics + +// Diagnostics represents the latest diagnostics data of the client. +type Diagnostics struct { + // AverageFramesPerSecond is the average amount of frames per second that the client has been + // running at. + AverageFramesPerSecond float64 + // AverageServerSimTickTime is the average time that the server spends simulating a single tick + // in milliseconds. + AverageServerSimTickTime float64 + // AverageClientSimTickTime is the average time that the client spends simulating a single tick + // in milliseconds. + AverageClientSimTickTime float64 + // AverageBeginFrameTime is the average time that the client spends beginning a frame in + // milliseconds. + AverageBeginFrameTime float64 + // AverageInputTime is the average time that the client spends processing input in milliseconds. + AverageInputTime float64 + // AverageRenderTime is the average time that the client spends rendering in milliseconds. + AverageRenderTime float64 + // AverageEndFrameTime is the average time that the client spends ending a frame in milliseconds. + AverageEndFrameTime float64 + // AverageRemainderTimePercent is the average percentage of time that the client spends on + // tasks that are not accounted for. + AverageRemainderTimePercent float64 + // AverageUnaccountedTimePercent is the average percentage of time that the client spends on + // unaccounted tasks. + AverageUnaccountedTimePercent float64 +} diff --git a/server/player/handler.go b/server/player/handler.go index 60112f75c..981bfc9f6 100644 --- a/server/player/handler.go +++ b/server/player/handler.go @@ -5,6 +5,7 @@ import ( "github.com/df-mc/dragonfly/server/cmd" "github.com/df-mc/dragonfly/server/event" "github.com/df-mc/dragonfly/server/item" + "github.com/df-mc/dragonfly/server/player/diagnostics" "github.com/df-mc/dragonfly/server/player/skin" "github.com/df-mc/dragonfly/server/world" "github.com/go-gl/mathgl/mgl64" @@ -135,6 +136,10 @@ type Handler interface { // HandleQuit handles the closing of a player. It is always called when the player is disconnected, // regardless of the reason. HandleQuit() + // HandleDiagnostics handles the latest diagnostics data that the player has sent to the server. This is + // not sent by every client however, only those with the "Creator > Enable Client Diagnostics" setting + // enabled. + HandleDiagnostics(d diagnostics.Diagnostics) } // NopHandler implements the Handler interface but does not execute any code when an event is called. The @@ -178,3 +183,4 @@ func (NopHandler) HandleFoodLoss(*event.Context, int, *int) func (NopHandler) HandleDeath(world.DamageSource, *bool) {} func (NopHandler) HandleRespawn(*mgl64.Vec3, **world.World) {} func (NopHandler) HandleQuit() {} +func (NopHandler) HandleDiagnostics(d diagnostics.Diagnostics) {} diff --git a/server/player/player.go b/server/player/player.go index a9968275f..3718a3eaa 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -2,6 +2,7 @@ package player import ( "fmt" + "github.com/df-mc/dragonfly/server/player/diagnostics" "math" "math/rand" "net" @@ -2048,6 +2049,11 @@ func (p *Player) Rotation() cube.Rotation { return cube.Rotation{p.yaw.Load(), p.pitch.Load()} } +// ChangingDimension returns whether the player is currently changing dimension or not. +func (p *Player) ChangingDimension() bool { + return p.session().ChangingDimension() +} + // Collect makes the player collect the item stack passed, adding it to the inventory. The amount of items that could // be added is returned. func (p *Player) Collect(s item.Stack) int { @@ -2715,6 +2721,11 @@ func (p *Player) PunchAir() { p.World().PlaySound(p.Position(), sound.Attack{}) } +// UpdateDiagnostics updates the diagnostics of the player. +func (p *Player) UpdateDiagnostics(d diagnostics.Diagnostics) { + p.Handler().HandleDiagnostics(d) +} + // damageItem damages the item stack passed with the damage passed and returns the new stack. If the item // broke, a breaking sound is played. // If the player is not survival, the original stack is returned. diff --git a/server/session/controllable.go b/server/session/controllable.go index f14344e6c..bb144aa27 100644 --- a/server/session/controllable.go +++ b/server/session/controllable.go @@ -7,6 +7,7 @@ import ( "github.com/df-mc/dragonfly/server/item" "github.com/df-mc/dragonfly/server/item/inventory" "github.com/df-mc/dragonfly/server/player/chat" + "github.com/df-mc/dragonfly/server/player/diagnostics" "github.com/df-mc/dragonfly/server/player/form" "github.com/df-mc/dragonfly/server/player/skin" "github.com/df-mc/dragonfly/server/world" @@ -99,4 +100,6 @@ type Controllable interface { // entity looks in the world. Skin() skin.Skin SetSkin(skin.Skin) + + UpdateDiagnostics(diagnostics.Diagnostics) } diff --git a/server/session/handler_server_bound_diagnostics.go b/server/session/handler_server_bound_diagnostics.go new file mode 100644 index 000000000..90430f9aa --- /dev/null +++ b/server/session/handler_server_bound_diagnostics.go @@ -0,0 +1,29 @@ +package session + +import ( + "github.com/df-mc/atomic" + "github.com/df-mc/dragonfly/server/player/diagnostics" + "github.com/sandertv/gophertunnel/minecraft/protocol/packet" +) + +// ServerBoundDiagnosticsHandler handles diagnostic updates from the client. +type ServerBoundDiagnosticsHandler struct { + currentID atomic.Uint32 +} + +// Handle ... +func (h *ServerBoundDiagnosticsHandler) Handle(p packet.Packet, s *Session) error { + pk := p.(*packet.ServerBoundDiagnostics) + s.c.UpdateDiagnostics(diagnostics.Diagnostics{ + AverageFramesPerSecond: float64(pk.AverageFramesPerSecond), + AverageServerSimTickTime: float64(pk.AverageServerSimTickTime), + AverageClientSimTickTime: float64(pk.AverageClientSimTickTime), + AverageBeginFrameTime: float64(pk.AverageBeginFrameTime), + AverageInputTime: float64(pk.AverageInputTime), + AverageRenderTime: float64(pk.AverageRenderTime), + AverageEndFrameTime: float64(pk.AverageEndFrameTime), + AverageRemainderTimePercent: float64(pk.AverageRemainderTimePercent), + AverageUnaccountedTimePercent: float64(pk.AverageUnaccountedTimePercent), + }) + return nil +} diff --git a/server/session/handler_server_bound_loading_screen.go b/server/session/handler_server_bound_loading_screen.go new file mode 100644 index 000000000..d95cf7672 --- /dev/null +++ b/server/session/handler_server_bound_loading_screen.go @@ -0,0 +1,32 @@ +package session + +import ( + "fmt" + "github.com/df-mc/atomic" + "github.com/sandertv/gophertunnel/minecraft/protocol/packet" +) + +// ServerBoundLoadingScreenHandler handles loading screen updates from the clients. It is used to ensure that +// the server knows when the client is loading a screen, and when it is done loading it. +type ServerBoundLoadingScreenHandler struct { + currentID atomic.Uint32 + expectedID atomic.Uint32 +} + +// Handle ... +func (h *ServerBoundLoadingScreenHandler) Handle(p packet.Packet, s *Session) error { + pk := p.(*packet.ServerBoundLoadingScreen) + if h.expectedID.Load() == 0 { + return fmt.Errorf("unexpected loading screen packet") + } + v, ok := pk.LoadingScreenID.Value() + if !ok { + return fmt.Errorf("expected loading screen ID %d, got nothing", h.expectedID.Load()) + } else if v != h.expectedID.Load() { + return fmt.Errorf("expected loading screen ID %d, got %d", h.expectedID.Load(), v) + } else if pk.Type == packet.LoadingScreenTypeEnd { + s.changingDimension.Store(false) + h.expectedID.Store(0) + } + return nil +} diff --git a/server/session/session.go b/server/session/session.go index 926cabcd9..ce24c2e67 100644 --- a/server/session/session.go +++ b/server/session/session.go @@ -71,6 +71,7 @@ type Session struct { openedWindow atomic.Value[*inventory.Inventory] openedPos atomic.Value[cube.Pos] swingingArm atomic.Bool + changingDimension atomic.Bool recipes map[uint32]recipe.Recipe @@ -399,7 +400,16 @@ func (s *Session) handleWorldSwitch(w *world.World) { // changeDimension changes the dimension of the client. If silent is set to true, the portal noise will be stopped // immediately. func (s *Session) changeDimension(dim int32, silent bool) { - s.writePacket(&packet.ChangeDimension{Dimension: dim, Position: vec64To32(s.c.Position().Add(entityOffset(s.c)))}) + s.changingDimension.Store(true) + h := s.handlers[packet.IDServerBoundLoadingScreen].(*ServerBoundLoadingScreenHandler) + id := h.currentID.Add(1) + h.expectedID.Store(id) + + s.writePacket(&packet.ChangeDimension{ + Dimension: dim, + Position: vec64To32(s.c.Position().Add(entityOffset(s.c))), + LoadingScreenID: protocol.Option(id), + }) s.writePacket(&packet.StopSound{StopAll: silent}) s.writePacket(&packet.PlayStatus{Status: packet.PlayStatusPlayerSpawn}) @@ -411,6 +421,11 @@ func (s *Session) changeDimension(dim int32, silent bool) { }) } +// ChangingDimension returns whether the session is currently changing dimension or not. +func (s *Session) ChangingDimension() bool { + return s.changingDimension.Load() +} + // handlePacket handles an incoming packet, processing it accordingly. If the packet had invalid data or was // otherwise not valid in its context, an error is returned. func (s *Session) handlePacket(pk packet.Packet) error { @@ -463,6 +478,8 @@ func (s *Session) registerHandlers() { packet.IDSubChunkRequest: &SubChunkRequestHandler{}, packet.IDText: &TextHandler{}, packet.IDTickSync: nil, + packet.IDServerBoundLoadingScreen: &ServerBoundLoadingScreenHandler{}, + packet.IDServerBoundDiagnostics: &ServerBoundDiagnosticsHandler{}, } }