From 0432cae861318f70adf6eeaae07f55ec4b3c90d0 Mon Sep 17 00:00:00 2001 From: Sandertv Date: Tue, 12 Nov 2024 13:23:32 +0100 Subject: [PATCH] server/session: Cleaned up and finished refactoring the player/session list code. --- server/session/player.go | 81 -------------------- server/session/session.go | 50 +++--------- server/session/session_list.go | 135 +++++++++++++++++++++++++++++++++ server/session/world.go | 11 +-- 4 files changed, 146 insertions(+), 131 deletions(-) create mode 100644 server/session/session_list.go diff --git a/server/session/player.go b/server/session/player.go index 8d908659f..6c3d91acf 100644 --- a/server/session/player.go +++ b/server/session/player.go @@ -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) { diff --git a/server/session/session.go b/server/session/session.go index 79986a269..976570277 100644 --- a/server/session/session.go +++ b/server/session/session.go @@ -7,12 +7,12 @@ import ( "github.com/df-mc/dragonfly/server/block" "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" @@ -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 @@ -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 @@ -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) { @@ -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() { @@ -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) @@ -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. diff --git a/server/session/session_list.go b/server/session/session_list.go new file mode 100644 index 000000000..34c721178 --- /dev/null +++ b/server/session/session_list.go @@ -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, + } +} diff --git a/server/session/world.go b/server/session/world.go index 8225a333f..4970d6a62 100644 --- a/server/session/world.go +++ b/server/session/world.go @@ -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(),