Files
zt-online-game-backend/internal/room/manager.go
T

131 lines
4.4 KiB
Go

package room // 包名声明:房间管理器与房间同包
import (
"errors" // 标准库:房间数超限、游戏未注册等错误
"log/slog" // 标准库:记录创建与回收日志
// 标准库:生成房间 ID 时去除 UUID 连字符
"sync" // 标准库:保护房间表与用户绑定表
"git.lzcwy.cn/zhengchiliu/zt-online-game-backend/global"
"git.lzcwy.cn/zhengchiliu/zt-online-game-backend/internal/game"
"git.lzcwy.cn/zhengchiliu/zt-online-game-backend/internal/wsgateway"
)
// Manager 管理全部房间:创建、查询、用户绑定与回收。
type Manager struct {
mu sync.RWMutex // 保护下面两张表
rooms map[string]*Room // roomID -> 房间
userRoom map[int64]string // userID -> roomID(一个用户同时只在一个房间)
hub *wsgateway.Hub // 下发通道,透传给房间
}
// NewManager 创建房间管理器。
func NewManager(hub *wsgateway.Hub) *Manager {
return &Manager{
rooms: make(map[string]*Room), // 初始化房间表
userRoom: make(map[int64]string), // 初始化绑定表
hub: hub, // 保存下发通道
}
}
// Create 创建一个房间并让指定用户入座(匹配成功后由匹配器调用)。
func (m *Manager) Create(gameID string, uids []int64) (*Room, error) {
meta, err := game.LookupMeta(gameID) // 校验玩法是否已注册,并取人数
if err != nil {
return nil, err
}
if m.Count() >= maxRooms() { // 房间数达到上限
return nil, errors.New("too many rooms")
}
m.mu.Lock() // 加写锁
r, err := newRoom(newRoomID(), gameID, m.hub, m.onRoomClosed) // 建房间并启动事件循环
if err != nil { // 创建失败
m.mu.Unlock() // 解锁
return nil, err
}
m.rooms[r.ID()] = r // 登记房间
m.mu.Unlock() // 解锁
for _, uid := range uids { // 逐个用户入座
if err := r.Join(uid); err != nil { // 入座失败(如人数超出座位)
slog.Warn("room join failed on create", "room_id", r.ID(), "user_id", uid, "error", err)
continue // 跳过该用户,房间继续可用
}
m.bind(uid, r.ID()) // 绑定用户与房间
}
slog.Info("room created", "room_id", r.ID(), "game_id", gameID, "seats", meta.MinSeats)
return r, nil
}
// Get 按 ID 取房间。
func (m *Manager) Get(id string) (*Room, bool) {
m.mu.RLock() // 加读锁
defer m.mu.RUnlock() // 返回时解锁
r, ok := m.rooms[id] // 查表
return r, ok
}
// RoomOf 取用户当前所在房间(用于重连与防止重复匹配)。
func (m *Manager) RoomOf(uid int64) (*Room, bool) {
m.mu.RLock() // 加读锁
id, ok := m.userRoom[uid] // 查绑定表
m.mu.RUnlock() // 解锁
if !ok { // 未绑定
return nil, false
}
return m.Get(id) // 取房间
}
// Count 返回当前房间数。
func (m *Manager) Count() int {
m.mu.RLock() // 加读锁
defer m.mu.RUnlock() // 返回时解锁
return len(m.rooms) // 表长度
}
// bind 绑定用户与房间。
func (m *Manager) bind(uid int64, roomID string) {
m.mu.Lock() // 加写锁
defer m.mu.Unlock() // 返回时解锁
m.userRoom[uid] = roomID // 记录绑定关系
}
// unbind 解除用户绑定。
func (m *Manager) unbind(uid int64) {
m.mu.Lock() // 加写锁
defer m.mu.Unlock() // 返回时解锁
delete(m.userRoom, uid) // 删除绑定
}
// onRoomClosed 是房间关闭回调:从表中移除并解绑全部成员。
func (m *Manager) onRoomClosed(roomID string, uids []int64) {
m.mu.Lock() // 加写锁
delete(m.rooms, roomID) // 移除房间
m.mu.Unlock() // 解锁
for _, uid := range uids { // 解绑成员
m.unbind(uid)
}
}
// OnUserDisconnected 处理连接断开:通知所在房间把该用户标记为掉线(进入重连宽限)。
// 由 boot 注册到 Hub 的断连回调上。
func (m *Manager) OnUserDisconnected(uid int64) {
r, ok := m.RoomOf(uid) // 查用户所在房间
if !ok { // 不在任何房间(可能只是普通断开)
return
}
if err := r.MarkOffline(uid); err != nil { // 通知房间标记掉线
slog.Debug("mark offline failed", "user_id", uid, "error", err)
}
}
// maxRooms 返回房间总数上限。
func maxRooms() int {
if global.WSSetting != nil && global.WSSetting.MaxRooms > 0 { // 配置了
return global.WSSetting.MaxRooms
}
return 1000 // 兜底
}