Merge upstream/main into main

Resolve conflict in handlers_stage.go: keep lock-free packet
building pattern (copy session list, release lock, then build)
over upstream's in-lock QueueSendMHF approach.

Fix test compilation: remove objectIDs field references after
upstream removed it from Server struct.

Resync vendor directory with updated go.mod dependencies.
This commit is contained in:
Houmgaor
2026-02-14 15:58:02 +01:00
7 changed files with 27 additions and 36 deletions

View File

@@ -337,7 +337,6 @@ func logoutPlayer(s *Session) {
s.server.Lock()
delete(s.server.sessions, s.rawConn)
s.rawConn.Close()
delete(s.server.objectIDs, s)
s.server.Unlock()
// Stage cleanup

View File

@@ -12,7 +12,7 @@ func handleMsgSysCreateObject(s *Session, p mhfpacket.MHFPacket) {
s.stage.Lock()
newObj := &Object{
id: s.NextObjectID(),
id: s.getObjectId(),
ownerCharID: s.charID,
x: pkt.X,
y: pkt.Y,

View File

@@ -8,6 +8,7 @@ import (
"erupe-ce/common/byteframe"
ps "erupe-ce/common/pascalstring"
"erupe-ce/network/mhfpacket"
"go.uber.org/zap"
)
@@ -65,19 +66,18 @@ func doStageTransfer(s *Session, ackHandle uint32, stageID string) {
// Confirm the stage entry.
doAckSimpleSucceed(s, ackHandle, []byte{0x00, 0x00, 0x00, 0x00})
var temp mhfpacket.MHFPacket
newNotif := byteframe.NewByteFrame()
// Cast existing user data to new user
if !s.userEnteredStage {
s.userEnteredStage = true
if !s.loaded {
s.loaded = true
// Lock server to safely iterate over sessions map
// We need to copy the session list first to avoid holding the lock during packet building
s.server.Lock()
var sessionList []*Session
for _, session := range s.server.sessions {
if s == session {
if s == session || !session.loaded {
continue
}
sessionList = append(sessionList, session)
@@ -85,6 +85,7 @@ func doStageTransfer(s *Session, ackHandle uint32, stageID string) {
s.server.Unlock()
// Build packets for each session without holding the lock
var temp mhfpacket.MHFPacket
for _, session := range sessionList {
temp = &mhfpacket.MsgSysInsertUser{CharID: session.charID}
newNotif.WriteUint16(uint16(temp.Opcode()))

View File

@@ -582,7 +582,6 @@ func createTestServerWithDB(t *testing.T, db *sqlx.DB) *Server {
db: db,
sessions: make(map[net.Conn]*Session),
stages: make(map[string]*Stage),
objectIDs: make(map[*Session]uint16),
userBinaryParts: make(map[userBinaryPartID][]byte),
semaphore: make(map[string]*Semaphore),
erupeConfig: _config.ErupeConfig,

View File

@@ -49,7 +49,6 @@ type Server struct {
acceptConns chan net.Conn
deleteConns chan net.Conn
sessions map[net.Conn]*Session
objectIDs map[*Session]uint16
listener net.Listener // Listener that is created when Server.Start is called.
isShuttingDown bool
@@ -155,7 +154,6 @@ func NewServer(config *Config) *Server {
acceptConns: make(chan net.Conn),
deleteConns: make(chan net.Conn),
sessions: make(map[net.Conn]*Session),
objectIDs: make(map[*Session]uint16),
stages: make(map[string]*Stage),
userBinaryParts: make(map[userBinaryPartID][]byte),
semaphore: make(map[string]*Semaphore),
@@ -280,6 +278,20 @@ func (s *Server) manageSessions() {
}
}
func (s *Server) getObjectId() uint16 {
ids := make(map[uint16]struct{})
for _, sess := range s.sessions {
ids[sess.objectID] = struct{}{}
}
for i := uint16(1); i < 100; i++ {
if _, ok := ids[i]; !ok {
return i
}
}
s.logger.Warn("object ids overflowed", zap.Int("sessions", len(s.sessions)))
return 0
}
func (s *Server) invalidateSessions() {
for !s.isShuttingDown {

View File

@@ -56,7 +56,6 @@ func createTestServer() *Server {
ID: 1,
logger: logger,
sessions: make(map[net.Conn]*Session),
objectIDs: make(map[*Session]uint16),
stages: make(map[string]*Stage),
semaphore: make(map[string]*Semaphore),
questCacheData: make(map[int][]byte),

View File

@@ -37,8 +37,10 @@ type Session struct {
clientContext *clientctx.ClientContext
lastPacket time.Time
objectIndex uint16
userEnteredStage bool // If the user has entered a stage before
objectID uint16
objectIndex uint16
loaded bool
stage *Stage
reservationStage *Stage // Required for the stateful MsgSysUnreserveStage packet.
stagePass string // Temporary storage
@@ -84,12 +86,12 @@ func NewSession(server *Server, conn net.Conn) *Session {
sendPackets: make(chan packet, 20),
clientContext: &clientctx.ClientContext{}, // Unused
lastPacket: time.Now(),
objectID: server.getObjectId(),
sessionStart: TimeAdjusted().Unix(),
stageMoveStack: stringstack.New(),
ackStart: make(map[uint32]time.Time),
semaphoreID: make([]uint16, 2),
}
s.SetObjectID()
return s
}
@@ -312,30 +314,9 @@ func (s *Session) logMessage(opcode uint16, data []byte, sender string, recipien
}
}
func (s *Session) SetObjectID() {
for i := uint16(1); i < 127; i++ {
exists := false
for _, j := range s.server.objectIDs {
if i == j {
exists = true
break
}
}
if !exists {
s.server.objectIDs[s] = i
return
}
}
s.server.objectIDs[s] = 0
}
func (s *Session) NextObjectID() uint32 {
bf := byteframe.NewByteFrame()
bf.WriteUint16(s.server.objectIDs[s])
func (s *Session) getObjectId() uint32 {
s.objectIndex++
bf.WriteUint16(s.objectIndex)
bf.Seek(0, 0)
return bf.ReadUint32()
return uint32(s.objectID)<<16 | uint32(s.objectIndex)
}
func (s *Session) GetSemaphoreID() uint32 {