Skip to content

Commit

Permalink
server/session: Cleaned up and finished refactoring the player/sessio…
Browse files Browse the repository at this point in the history
…n list code.
  • Loading branch information
Sandertv committed Nov 12, 2024
1 parent aa5ad65 commit 0432cae
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 131 deletions.
81 changes: 0 additions & 81 deletions server/session/player.go
Original file line number Diff line number Diff line change
Expand Up @@ -518,87 +518,6 @@ func (s *Session) EnableInstantRespawn(enable bool) {
s.sendGameRules([]protocol.GameRule{{Name: "doimmediaterespawn", Value: enable}})
}

// addToPlayerList adds the player of a session to the player list of this session. It will be shown in the
// in-game pause menu screen.
func (s *Session) addToPlayerList(session *Session, c Controllable) {
runtimeID := uint64(1)
s.entityMutex.Lock()
if session != s {
s.currentEntityRuntimeID += 1
runtimeID = s.currentEntityRuntimeID
}
s.entityRuntimeIDs[session.ent] = runtimeID
s.entities[runtimeID] = session.ent
s.entityMutex.Unlock()

s.writePacket(&packet.PlayerList{
ActionType: packet.PlayerListActionAdd,
Entries: []protocol.PlayerListEntry{{
UUID: session.ent.UUID(),
EntityUniqueID: int64(runtimeID),
Username: c.Name(),
XUID: c.XUID(),
Skin: skinToProtocol(c.Skin()),
}},
})
}

// skinToProtocol converts a skin to its protocol representation.
func skinToProtocol(s skin.Skin) protocol.Skin {
var animations []protocol.SkinAnimation
for _, animation := range s.Animations {
protocolAnim := protocol.SkinAnimation{
ImageWidth: uint32(animation.Bounds().Max.X),
ImageHeight: uint32(animation.Bounds().Max.Y),
ImageData: animation.Pix,
FrameCount: float32(animation.FrameCount),
}
switch animation.Type() {
case skin.AnimationHead:
protocolAnim.AnimationType = protocol.SkinAnimationHead
case skin.AnimationBody32x32:
protocolAnim.AnimationType = protocol.SkinAnimationBody32x32
case skin.AnimationBody128x128:
protocolAnim.AnimationType = protocol.SkinAnimationBody128x128
}
protocolAnim.ExpressionType = uint32(animation.AnimationExpression)
animations = append(animations, protocolAnim)
}

return protocol.Skin{
PlayFabID: s.PlayFabID,
SkinID: uuid.New().String(),
SkinResourcePatch: s.ModelConfig.Encode(),
SkinImageWidth: uint32(s.Bounds().Max.X),
SkinImageHeight: uint32(s.Bounds().Max.Y),
SkinData: s.Pix,
CapeImageWidth: uint32(s.Cape.Bounds().Max.X),
CapeImageHeight: uint32(s.Cape.Bounds().Max.Y),
CapeData: s.Cape.Pix,
SkinGeometry: s.Model,
PersonaSkin: s.Persona,
CapeID: uuid.New().String(),
FullID: uuid.New().String(),
Animations: animations,
Trusted: true,
OverrideAppearance: true,
}
}

// removeFromPlayerList removes the player of a session from the player list of this session. It will no
// longer be shown in the in-game pause menu screen.
func (s *Session) removeFromPlayerList(session *Session) {
s.entityMutex.Lock()
delete(s.entities, s.entityRuntimeIDs[session.ent])
delete(s.entityRuntimeIDs, session.ent)
s.entityMutex.Unlock()

s.writePacket(&packet.PlayerList{
ActionType: packet.PlayerListActionRemove,
Entries: []protocol.PlayerListEntry{{UUID: session.ent.UUID()}},
})
}

// HandleInventories starts handling the inventories of the Controllable entity of the session. It sends packets when
// slots in the inventory are changed.
func (s *Session) HandleInventories(tx *world.Tx, c Controllable) (inv, offHand, enderChest *inventory.Inventory, armour *inventory.Armour, heldSlot *uint32) {
Expand Down
50 changes: 10 additions & 40 deletions server/session/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import (
"github.com/df-mc/dragonfly/server/block"

Check failure on line 7 in server/session/session.go

View workflow job for this annotation

GitHub Actions / Build

"github.com/df-mc/dragonfly/server/block" imported and not used
"github.com/df-mc/dragonfly/server/block/cube"
"github.com/df-mc/dragonfly/server/cmd"
"github.com/df-mc/dragonfly/server/internal/sliceutil"
"github.com/df-mc/dragonfly/server/item"
"github.com/df-mc/dragonfly/server/item/inventory"
"github.com/df-mc/dragonfly/server/item/recipe"
"github.com/df-mc/dragonfly/server/player/chat"
"github.com/df-mc/dragonfly/server/player/form"
"github.com/df-mc/dragonfly/server/player/skin"
"github.com/df-mc/dragonfly/server/world"
"github.com/go-gl/mathgl/mgl64"
"github.com/sandertv/gophertunnel/minecraft"
Expand Down Expand Up @@ -61,6 +61,11 @@ type Session struct {
inv, offHand, enderChest, ui *inventory.Inventory
armour *inventory.Armour

// joinSkin is the first skin that the player joined with. It is sent on
// spawn for the player list, but otherwise updated immediately when the
// player is viewed.
joinSkin skin.Skin

breakingPos cube.Pos

inTransaction, containerOpened atomic.Bool
Expand Down Expand Up @@ -116,11 +121,6 @@ type Conn interface {
// Nop represents a no-operation session. It does not do anything when sending a packet to it.
var Nop = &Session{}

// session is a slice of all open sessions. It is protected by the sessionMu, which must be locked whenever
// accessing the value.
var sessions []*Session
var sessionMu sync.Mutex

// selfEntityRuntimeID is the entity runtime (or unique) ID of the controllable that the session holds.
const selfEntityRuntimeID = 1

Expand Down Expand Up @@ -184,10 +184,6 @@ func (conf Config) New(conn Conn) *Session {
return s
}

func (s *Session) EntityHandle() *world.EntityHandle {
return s.ent
}

// Spawn makes the Controllable passed spawn in the world.World.
// The function passed will be called when the session stops running.
func (s *Session) Spawn(c Controllable, tx *world.Tx) {
Expand All @@ -206,7 +202,9 @@ func (s *Session) Spawn(c Controllable, tx *world.Tx) {
})

s.sendAvailableEntities(tx.World())
s.initPlayerList()

s.joinSkin = c.Skin()
sessions.Add(s)

c.SetGameMode(c.GameMode())
for _, e := range c.Effects() {
Expand Down Expand Up @@ -252,7 +250,7 @@ func (s *Session) close(tx *world.Tx, c Controllable) {
s.chunkLoader.Close(tx)

// This should always be called last due to the timing of the removal of entity runtime IDs.
s.closePlayerList()
sessions.Remove(s)
s.entityMutex.Lock()
clear(s.entityRuntimeIDs)
clear(s.entities)
Expand Down Expand Up @@ -507,34 +505,6 @@ func (s *Session) writePacket(pk packet.Packet) {
_ = s.conn.WritePacket(pk)
}

// initPlayerList initialises the player list of the session and sends the session itself to all other
// sessions currently open.
func (s *Session) initPlayerList() {
sessionMu.Lock()
sessions = append(sessions, s)
for _, session := range sessions {
// AddStack the player of the session to all sessions currently open, and add the players of all sessions
// currently open to the player list of the new session.
session.addToPlayerList(s)
if s != session {
s.addToPlayerList(session)
}
}
sessionMu.Unlock()
}

// closePlayerList closes the player list of the session and removes the session from the player list of all
// other sessions.
func (s *Session) closePlayerList() {
sessionMu.Lock()
for _, session := range sessions {
// Remove the player of the session from the player list of all other sessions.
session.removeFromPlayerList(s)
}
sessions = sliceutil.DeleteVal(sessions, s)
sessionMu.Unlock()
}

// actorIdentifier represents the structure of an actor identifier sent over the network.
type actorIdentifier struct {
// ID is a unique namespaced identifier for the entity.
Expand Down
135 changes: 135 additions & 0 deletions server/session/session_list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package session

import (
"github.com/df-mc/dragonfly/server/internal/sliceutil"
"github.com/df-mc/dragonfly/server/player/skin"
"github.com/google/uuid"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
"golang.org/x/exp/slices"
"sync"
)

var sessions = new(sessionList)

type sessionList struct {
mu sync.Mutex
s []*Session
}

func (l *sessionList) Add(s *Session) {
l.mu.Lock()
defer l.mu.Unlock()

l.s = append(l.s, s)
for _, other := range l.s {
// AddStack the player of the session to all sessions currently open,
// and add the players of all sessions currently open to the player list
// of the new session.
l.sendSessionTo(s, other)
if s != other {
l.sendSessionTo(other, s)
}
}
}

func (l *sessionList) Remove(s *Session) {
l.mu.Lock()
defer l.mu.Unlock()

for _, other := range l.s {
// Remove the player of the other from the player list of others.
l.unsendSessionFrom(s, other)
}
l.s = sliceutil.DeleteVal(l.s, s)
}

func (l *sessionList) Lookup(id uuid.UUID) (*Session, bool) {
l.mu.Lock()
defer l.mu.Unlock()

if index := slices.IndexFunc(l.s, func(session *Session) bool {
return session.ent.UUID() == id
}); index != -1 {
return l.s[index], true
}
return nil, false
}

func (l *sessionList) sendSessionTo(s, to *Session) {
runtimeID := uint64(1)

to.entityMutex.Lock()
if s != to {
to.currentEntityRuntimeID += 1
runtimeID = to.currentEntityRuntimeID
}
to.entityRuntimeIDs[s.ent] = runtimeID
to.entities[runtimeID] = s.ent
to.entityMutex.Unlock()

to.writePacket(&packet.PlayerList{
ActionType: packet.PlayerListActionAdd,
Entries: []protocol.PlayerListEntry{{
UUID: s.ent.UUID(),
EntityUniqueID: int64(runtimeID),
Username: s.conn.IdentityData().DisplayName,
XUID: s.conn.IdentityData().XUID,
Skin: skinToProtocol(s.joinSkin),
}},
})
}

func (l *sessionList) unsendSessionFrom(s, from *Session) {
from.entityMutex.Lock()
delete(from.entities, from.entityRuntimeIDs[s.ent])
delete(from.entityRuntimeIDs, s.ent)
from.entityMutex.Unlock()

from.writePacket(&packet.PlayerList{
ActionType: packet.PlayerListActionRemove,
Entries: []protocol.PlayerListEntry{{UUID: s.ent.UUID()}},
})
}

// skinToProtocol converts a skin to its protocol representation.
func skinToProtocol(s skin.Skin) protocol.Skin {
var animations []protocol.SkinAnimation
for _, animation := range s.Animations {
protocolAnim := protocol.SkinAnimation{
ImageWidth: uint32(animation.Bounds().Max.X),
ImageHeight: uint32(animation.Bounds().Max.Y),
ImageData: animation.Pix,
FrameCount: float32(animation.FrameCount),
}
switch animation.Type() {
case skin.AnimationHead:
protocolAnim.AnimationType = protocol.SkinAnimationHead
case skin.AnimationBody32x32:
protocolAnim.AnimationType = protocol.SkinAnimationBody32x32
case skin.AnimationBody128x128:
protocolAnim.AnimationType = protocol.SkinAnimationBody128x128
}
protocolAnim.ExpressionType = uint32(animation.AnimationExpression)
animations = append(animations, protocolAnim)
}

return protocol.Skin{
PlayFabID: s.PlayFabID,
SkinID: uuid.New().String(),
SkinResourcePatch: s.ModelConfig.Encode(),
SkinImageWidth: uint32(s.Bounds().Max.X),
SkinImageHeight: uint32(s.Bounds().Max.Y),
SkinData: s.Pix,
CapeImageWidth: uint32(s.Cape.Bounds().Max.X),
CapeImageHeight: uint32(s.Cape.Bounds().Max.Y),
CapeData: s.Cape.Pix,
SkinGeometry: s.Model,
PersonaSkin: s.Persona,
CapeID: uuid.New().String(),
FullID: uuid.New().String(),
Animations: animations,
Trusted: true,
OverrideAppearance: true,
}
}
11 changes: 1 addition & 10 deletions server/session/world.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,16 +76,7 @@ func (s *Session) ViewEntity(e world.Entity) {
id := e.Type().EncodeEntity()
switch v := e.(type) {
case Controllable:
actualPlayer := false

sessionMu.Lock()
for _, s := range sessions {
if s.ent.UUID() == v.UUID() {
actualPlayer = true
break
}
}
sessionMu.Unlock()
_, actualPlayer := sessions.Lookup(v.UUID())
if !actualPlayer {
s.writePacket(&packet.PlayerList{ActionType: packet.PlayerListActionAdd, Entries: []protocol.PlayerListEntry{{
UUID: v.UUID(),
Expand Down

0 comments on commit 0432cae

Please sign in to comment.