创建房间何时被触发
上一章已经提到,这里再回顾下:
- 客户端连接RoomServer
- 客户端发送MsgLogin
- RoomServer的PlayerTask::ParseMsg函数被触发,执行 PlayerTask::LoginVerify 函数
- PlayerTask::LoginVerify函数内最后,调用 RoomMgr.AddPlayer(this)
这时就触发,创建房间 或 加入房间 流程
项目中房间管理代码分析 - RoomMgr.go
RoomMgr.go,就是对房间进行管理,类似 PlayerTaskMgr.go的功能
创建房间
func (this *RoomMgr) AddPlayer(player def.IPlayerTask) bool { room := this.getRoomById(player.UData().RoomId) switch player.UData().Model { case common.UserModelJoin: if room == nil { player.RetErrorMsg(common.ErrorCodeRoom) glog.Error("[房间] 加入的房间不存在 ", player.UData().Id, ",", player.UData().Account, ",", player.UData().RoomId) return false } glog.Info("[房间] 加入模式 [", room.RoomType(), ",", room.id, "],[", player.UData().Id, ",", player.UData().Account, "]") case common.UserModelTeam: if room == nil { room = this.NewRoom(player.UData().SceneId, common.RoomTypeTeam, player.UData().RoomId, player, 0) } glog.Info("[房间] 组队模式 [", room.RoomType(), ",", room.id, "],[", player.UData().Id, ",", player.UData().Account, "],[", player.UData().TeamId, ",", player.UData().TeamName, "],", room.GetPlayerNum()) case common.UserModelQuick: if room == nil { room = this.NewRoom(player.UData().SceneId, common.RoomTypeQuick, player.UData().RoomId, player, 0) } glog.Info("[房间] 闪电模式 [", room.RoomType(), ",", room.id, "],[", player.UData().Id, ",", player.UData().Account, "],[", player.UData().TeamId, ",", player.UData().TeamName, "],", room.GetPlayerNum()) default: if room == nil { room = this.NewRoom(player.UData().SceneId, common.RoomTypeQuick, player.UData().RoomId, player, 0) } glog.Info("[房间] 闪电模式 [", room.RoomType(), ",", room.id, "],[", player.UData().Id, ",", player.UData().Account, "],[", player.UData().TeamId, ",", player.UData().TeamName, "],", room.GetPlayerNum()) } if !room.IsClosed() { player.SetRoom(room) room.IncPlayerNum(player) room.chan_AddPlayer <- player if !room.iscustom { rcenterclient.GetMe().UpdateRoom(room.RoomType(), room.id, room.GetPlayerNum(), common.UserOnline, false, 0, 0) } } glog.Info("[房间] 分配成功 [", room.RoomType(), ",", room.id, "],", player.ID(), ",", player.UData().Account, ",", player.UData().Model, ",", player.UData().IsCustom) return true }在判断没有房间时,会调用 NewRoom 函数 来说创建房间,最后到进入房间流程
加入房间
room.chan_AddPlayer <- player通过go chan通知房间主循环有玩家加入房间,触发加入房间逻辑
销毁房间
func (this *RoomMgr) timeAction() { var prooms []*Room nowTime := time.Now().Unix() this.mutex.RLock() rms := make([]*Room, 0) for _, room := range this.rooms { rms = append(rms, room) } this.mutex.RUnlock() this.frame++ for _, room := range rms { if !room.CheckInited() { continue } channum := make(chan []int, 1) room.chan_GetPlayerNum <- channum num := <-channum playernum := num[1] allpalyernum := num[0] + num[1] if room.endTime > nowTime && playernum != 0 { continue } if this.frame%50 == 0 && room.RoomType() == common.RoomTypeQuick { if allpalyernum != 0 && room.endTime > nowTime { continue } } prooms = append(prooms, room) } for _, room := range prooms { this.RemoveRoom(room.id) if !room.iscustom { rcenterclient.GetMe().RemoveRoom(room.RoomType(), room.id) } rcenterclient.GetMe().UpdateServer() room.Control(ROOM_CONTROL_STOP) glog.Info("[房间]删除房间 deleteroom [", room.RoomType(), ",", room.id, "],", this.GetNum(), ",time:", nowTime-room.endTime, " now:", nowTime, " end:", room.endTime) } }定期检查房间是否结束,调用 room.Control(ROOM_CONTROL_STOP),关闭房间。
room.Control(ROOM_CONTROL_STOP) 的内部实现,也是通过go chan通知房间主循环,触发房间关闭逻辑
MsgEndRoom
房间关闭后,会广播MsgEndRoom消息,给客户端。
项目中房间主循环代码分析 - Room.go
从 NewRoom -> room.Start() -> go this.Loop()。
Room::Loop 房间主循环,非常重要,房间主循环按照规定的频率,计算一局游戏的每一帧,并按规定的频率将结果反馈给客户端。
这里着重看下,房间主循环:
func (this *Room) Loop() {
var (
timeTicker = time.NewTicker(time.Millisecond * def.FrameTimeMS)
tloop uint64
)
defer func() {
this.Stop()
timeTicker.Stop()
if err := recover(); err != nil {
glog.Error("[异常] 房间线程出错 [", this.roomType, ",", this.id, "] ", err, "\n", string(debug.Stack()))
}
}()
this.ResetFrame()
for {
select {
case <-timeTicker.C:
this.IncreaseFrame()
this.Scene.Render()
//100ms
if tloop%def.FrameCountBy100MS == 0 {
this.Render5()
}
//400ms
if tloop%(def.FrameCountBy100MS*4) == 0 {
this.SendTeamMemPos()
}
//1s
if tloop%(def.FrameCountBy100MS*10) == 0 {
this.TimeAction()
}
if tloop%(def.FrameCountBy100MS*40) == 0 {
this.PushTopList(nil)
}
tloop += 1
case op := <-this.chan_PlayerCmd:
if !this.IsClosed() {
player, ok := this.Players[op.playerID]
if ok {
player.OnRecvPlayerCmd(op.playerID, op.cmd, op.data, op.flag)
} else {
glog.Info("chan_PlayerCmd:no player,", op.playerID, " cmd:", op.cmd)
}
}
case player := <-this.chan_AddPlayer:
this.AddPlayer(player)
case player := <-this.chan_RemovePlayer:
this.RemovePlayer(player)
case playerId := <-this.chan_RemovePlayerById:
this.RemovePlayerByIdDetail(playerId)
case ctrl := <-this.chan_Control:
switch ctrl {
case ROOM_CONTROL_END:
case ROOM_CONTROL_STOP:
glog.Info("[ctrl]", this.name, " ctrl: ", ctrl)
this.destory()
}
return
case c := <-this.chan_GetPlayerNum:
rn := int(this.GetRobotNum())
pn := len(this.Players) - rn
c <- []int{rn, pn}
}
}
}
另外,上面的加入房间、销毁房间,最终都是通过go chan进入房间主循环。这里涉及到一个Go协程的编程模型。(下节内容)