Files
Erupe/server/channelserver/channel_registry.go
Houmgaor 754b5a3bff feat(channelserver): decouple channel servers for independent operation (#33)
Enable multiple Erupe instances to share a single PostgreSQL database
without destroying each other's state, fix existing data races in
cross-channel access, and lay groundwork for future distributed
channel server deployments.

Phase 1 — DB safety:
- Scope DELETE FROM servers/sign_sessions to this instance's server IDs
- Fix ci++ bug where failed channel start shifted subsequent IDs

Phase 2 — Fix data races in cross-channel access:
- Lock sessions map in FindSessionByCharID and DisconnectUser
- Lock stagesLock in handleMsgSysLockGlobalSema
- Snapshot sessions/stages under lock in TransitMessage types 1-4
- Lock channel when finding mail notification targets

Phase 3 — ChannelRegistry interface:
- Define ChannelRegistry interface with 7 cross-channel operations
- Implement LocalChannelRegistry with proper locking
- Add SessionSnapshot/StageSnapshot immutable copy types
- Delegate WorldcastMHF, FindSessionByCharID, DisconnectUser to Registry
- Migrate LockGlobalSema and guild mail handlers to use Registry
- Add comprehensive tests including concurrent access

Phase 4 — Per-channel enable/disable:
- Add Enabled *bool to EntranceChannelInfo (nil defaults to true)
- Skip disabled channels in startup loop, preserving ID stability
- Add IsEnabled() helper with backward-compatible default
- Update config.example.json with Enabled field
2026-02-19 18:13:34 +01:00

59 lines
2.0 KiB
Go

package channelserver
import (
"erupe-ce/network/mhfpacket"
"net"
)
// ChannelRegistry abstracts cross-channel operations behind an interface.
// The default LocalChannelRegistry wraps the in-process []*Server slice.
// Future implementations may use DB/Redis/NATS for multi-process deployments.
type ChannelRegistry interface {
// Worldcast broadcasts a packet to all sessions across all channels.
Worldcast(pkt mhfpacket.MHFPacket, ignoredSession *Session, ignoredChannel *Server)
// FindSessionByCharID looks up a session by character ID across all channels.
FindSessionByCharID(charID uint32) *Session
// DisconnectUser disconnects all sessions belonging to the given character IDs.
DisconnectUser(cids []uint32)
// FindChannelForStage searches all channels for a stage whose ID has the
// given suffix and returns the owning channel's GlobalID, or "" if not found.
FindChannelForStage(stageSuffix string) string
// SearchSessions searches sessions across all channels using a predicate,
// returning up to max snapshot results.
SearchSessions(predicate func(SessionSnapshot) bool, max int) []SessionSnapshot
// SearchStages searches stages across all channels with a prefix filter,
// returning up to max snapshot results.
SearchStages(stagePrefix string, max int) []StageSnapshot
// NotifyMailToCharID finds the session for charID and sends a mail notification.
NotifyMailToCharID(charID uint32, sender *Session, mail *Mail)
}
// SessionSnapshot is an immutable copy of session data taken under lock.
type SessionSnapshot struct {
CharID uint32
Name string
StageID string
ServerIP net.IP
ServerPort uint16
UserBinary3 []byte // Copy of userBinaryParts index 3
}
// StageSnapshot is an immutable copy of stage data taken under lock.
type StageSnapshot struct {
ServerIP net.IP
ServerPort uint16
StageID string
ClientCount int
Reserved int
MaxPlayers uint16
RawBinData0 []byte
RawBinData1 []byte
RawBinData3 []byte
}