mirror of
https://github.com/FlourishingWorld/hk4e.git
synced 2026-02-04 15:52:27 +08:00
310 lines
9.0 KiB
Go
310 lines
9.0 KiB
Go
package game
|
|
|
|
import (
|
|
"reflect"
|
|
"time"
|
|
|
|
appConfig "hk4e/common/config"
|
|
"hk4e/common/mq"
|
|
"hk4e/gate/client_proto"
|
|
"hk4e/gate/kcp"
|
|
"hk4e/gs/dao"
|
|
"hk4e/gs/model"
|
|
"hk4e/pkg/alg"
|
|
"hk4e/pkg/logger"
|
|
"hk4e/pkg/reflection"
|
|
"hk4e/protocol/cmd"
|
|
"hk4e/protocol/proto"
|
|
|
|
pb "google.golang.org/protobuf/proto"
|
|
)
|
|
|
|
var GAME_MANAGER *GameManager = nil
|
|
var LOCAL_EVENT_MANAGER *LocalEventManager = nil
|
|
var ROUTE_MANAGER *RouteManager = nil
|
|
var USER_MANAGER *UserManager = nil
|
|
var WORLD_MANAGER *WorldManager = nil
|
|
var TICK_MANAGER *TickManager = nil
|
|
var COMMAND_MANAGER *CommandManager = nil
|
|
var GCG_MANAGER *GCGManager = nil
|
|
var MESSAGE_QUEUE *mq.MessageQueue
|
|
|
|
var SELF *model.Player
|
|
|
|
type GameManager struct {
|
|
dao *dao.Dao
|
|
snowflake *alg.SnowflakeWorker
|
|
clientCmdProtoMap *client_proto.ClientCmdProtoMap
|
|
clientCmdProtoMapRefValue reflect.Value
|
|
}
|
|
|
|
func NewGameManager(dao *dao.Dao, messageQueue *mq.MessageQueue, gsId uint32) (r *GameManager) {
|
|
r = new(GameManager)
|
|
r.dao = dao
|
|
MESSAGE_QUEUE = messageQueue
|
|
r.snowflake = alg.NewSnowflakeWorker(int64(gsId))
|
|
if appConfig.CONF.Hk4e.ClientProtoProxyEnable {
|
|
r.clientCmdProtoMap = client_proto.NewClientCmdProtoMap()
|
|
r.clientCmdProtoMapRefValue = reflect.ValueOf(r.clientCmdProtoMap)
|
|
}
|
|
GAME_MANAGER = r
|
|
LOCAL_EVENT_MANAGER = NewLocalEventManager()
|
|
ROUTE_MANAGER = NewRouteManager()
|
|
USER_MANAGER = NewUserManager(dao)
|
|
WORLD_MANAGER = NewWorldManager(r.snowflake)
|
|
TICK_MANAGER = NewTickManager()
|
|
COMMAND_MANAGER = NewCommandManager()
|
|
GCG_MANAGER = NewGCGManager()
|
|
r.run()
|
|
return r
|
|
}
|
|
|
|
func (g *GameManager) run() {
|
|
go g.gameMainLoopD()
|
|
}
|
|
|
|
func (g *GameManager) gameMainLoopD() {
|
|
for times := 1; times <= 1000; times++ {
|
|
logger.Warn("start game main loop, times: %v", times)
|
|
g.gameMainLoop()
|
|
logger.Warn("game main loop stop")
|
|
}
|
|
}
|
|
|
|
func (g *GameManager) gameMainLoop() {
|
|
defer func() {
|
|
if err := recover(); err != nil {
|
|
logger.Error("!!! GAME MAIN LOOP PANIC !!!")
|
|
logger.Error("error: %v", err)
|
|
logger.Error("stack: %v", logger.Stack())
|
|
logger.Error("user: %v", SELF)
|
|
if SELF != nil {
|
|
GAME_MANAGER.DisconnectPlayer(SELF.PlayerID, kcp.EnetServerKick)
|
|
}
|
|
}
|
|
}()
|
|
intervalTime := time.Second.Nanoseconds() * 60
|
|
lastTime := time.Now().UnixNano()
|
|
routeCost := int64(0)
|
|
tickCost := int64(0)
|
|
localEventCost := int64(0)
|
|
commandCost := int64(0)
|
|
for {
|
|
now := time.Now().UnixNano()
|
|
if now-lastTime > intervalTime {
|
|
routeCost /= 1e6
|
|
tickCost /= 1e6
|
|
localEventCost /= 1e6
|
|
commandCost /= 1e6
|
|
logger.Info("[GAME MAIN LOOP] cpu time cost detail, routeCost: %vms, tickCost: %vms, localEventCost: %vms, commandCost: %vms",
|
|
routeCost, tickCost, localEventCost, commandCost)
|
|
totalCost := routeCost + tickCost + localEventCost + commandCost
|
|
logger.Info("[GAME MAIN LOOP] cpu time cost percent, routeCost: %v%%, tickCost: %v%%, localEventCost: %v%%, commandCost: %v%%",
|
|
float32(routeCost)/float32(totalCost)*100.0,
|
|
float32(tickCost)/float32(totalCost)*100.0,
|
|
float32(localEventCost)/float32(totalCost)*100.0,
|
|
float32(commandCost)/float32(totalCost)*100.0)
|
|
logger.Info("[GAME MAIN LOOP] total cpu time cost detail, totalCost: %vms",
|
|
totalCost)
|
|
logger.Info("[GAME MAIN LOOP] total cpu time cost percent, totalCost: %v%%",
|
|
float32(totalCost)/float32(intervalTime/1e6)*100.0)
|
|
lastTime = now
|
|
routeCost = 0
|
|
tickCost = 0
|
|
localEventCost = 0
|
|
commandCost = 0
|
|
}
|
|
select {
|
|
case netMsg := <-MESSAGE_QUEUE.GetNetMsg():
|
|
// 接收客户端消息
|
|
start := time.Now().UnixNano()
|
|
ROUTE_MANAGER.RouteHandle(netMsg)
|
|
end := time.Now().UnixNano()
|
|
routeCost += end - start
|
|
case <-TICK_MANAGER.globalTick.C:
|
|
// 游戏服务器定时帧
|
|
start := time.Now().UnixNano()
|
|
TICK_MANAGER.OnGameServerTick()
|
|
end := time.Now().UnixNano()
|
|
tickCost += end - start
|
|
case localEvent := <-LOCAL_EVENT_MANAGER.localEventChan:
|
|
// 处理本地事件
|
|
start := time.Now().UnixNano()
|
|
LOCAL_EVENT_MANAGER.LocalEventHandle(localEvent)
|
|
end := time.Now().UnixNano()
|
|
localEventCost += end - start
|
|
case command := <-COMMAND_MANAGER.commandTextInput:
|
|
// 处理传入的命令 (普通玩家 GM命令)
|
|
start := time.Now().UnixNano()
|
|
COMMAND_MANAGER.HandleCommand(command)
|
|
end := time.Now().UnixNano()
|
|
commandCost += end - start
|
|
}
|
|
}
|
|
}
|
|
|
|
func (g *GameManager) Stop() {
|
|
// 下线玩家
|
|
userList := USER_MANAGER.GetAllOnlineUserList()
|
|
for _, player := range userList {
|
|
g.DisconnectPlayer(player.PlayerID, kcp.EnetServerShutdown)
|
|
}
|
|
time.Sleep(time.Second * 3)
|
|
// 保存玩家数据
|
|
onlinePlayerMap := USER_MANAGER.GetAllOnlineUserList()
|
|
saveUserIdList := make([]uint32, 0, len(onlinePlayerMap))
|
|
for userId := range onlinePlayerMap {
|
|
saveUserIdList = append(saveUserIdList, userId)
|
|
}
|
|
LOCAL_EVENT_MANAGER.localEventChan <- &LocalEvent{
|
|
EventId: RunUserCopyAndSave,
|
|
Msg: saveUserIdList,
|
|
}
|
|
time.Sleep(time.Second * 3)
|
|
}
|
|
|
|
// SendMsgToGate 发送消息给客户端 指定网关
|
|
func (g *GameManager) SendMsgToGate(cmdId uint16, userId uint32, clientSeq uint32, gateAppId string, payloadMsg pb.Message) {
|
|
if userId < 100000000 {
|
|
return
|
|
}
|
|
if payloadMsg == nil {
|
|
logger.Error("payload msg is nil")
|
|
return
|
|
}
|
|
gameMsg := &mq.GameMsg{
|
|
UserId: userId,
|
|
CmdId: cmdId,
|
|
ClientSeq: clientSeq,
|
|
PayloadMessage: payloadMsg,
|
|
}
|
|
MESSAGE_QUEUE.SendToGate(gateAppId, &mq.NetMsg{
|
|
MsgType: mq.MsgTypeGame,
|
|
EventId: mq.NormalMsg,
|
|
GameMsg: gameMsg,
|
|
})
|
|
}
|
|
|
|
// SendMsg 发送消息给客户端
|
|
func (g *GameManager) SendMsg(cmdId uint16, userId uint32, clientSeq uint32, payloadMsg pb.Message) {
|
|
if userId < 100000000 {
|
|
return
|
|
}
|
|
if payloadMsg == nil {
|
|
logger.Error("payload msg is nil")
|
|
return
|
|
}
|
|
player := USER_MANAGER.GetOnlineUser(userId)
|
|
if player == nil {
|
|
logger.Error("player not exist, uid: %v", userId)
|
|
return
|
|
}
|
|
gameMsg := new(mq.GameMsg)
|
|
gameMsg.UserId = userId
|
|
gameMsg.CmdId = cmdId
|
|
gameMsg.ClientSeq = clientSeq
|
|
// 在这里直接序列化成二进制数据 防止发送的消息内包含各种游戏数据指针 而造成并发读写的问题
|
|
payloadMessageData, err := pb.Marshal(payloadMsg)
|
|
if err != nil {
|
|
logger.Error("parse payload msg to bin error: %v", err)
|
|
return
|
|
}
|
|
gameMsg.PayloadMessageData = payloadMessageData
|
|
MESSAGE_QUEUE.SendToGate(player.GateAppId, &mq.NetMsg{
|
|
MsgType: mq.MsgTypeGame,
|
|
EventId: mq.NormalMsg,
|
|
GameMsg: gameMsg,
|
|
})
|
|
}
|
|
|
|
// CommonRetError 通用返回错误码
|
|
func (g *GameManager) CommonRetError(cmdId uint16, player *model.Player, rsp pb.Message, retCode ...proto.Retcode) {
|
|
if rsp == nil {
|
|
return
|
|
}
|
|
ret := int32(proto.Retcode_RET_FAIL)
|
|
if len(retCode) == 0 {
|
|
ret = int32(proto.Retcode_RET_SVR_ERROR)
|
|
} else if len(retCode) == 1 {
|
|
ret = int32(retCode[0])
|
|
} else {
|
|
return
|
|
}
|
|
ok := reflection.SetStructFieldValue(rsp, "Retcode", ret)
|
|
if !ok {
|
|
return
|
|
}
|
|
logger.Debug("send common error: %v", rsp)
|
|
g.SendMsg(cmdId, player.PlayerID, player.ClientSeq, rsp)
|
|
}
|
|
|
|
// CommonRetSucc 通用返回成功
|
|
func (g *GameManager) CommonRetSucc(cmdId uint16, player *model.Player, rsp pb.Message) {
|
|
if rsp == nil {
|
|
return
|
|
}
|
|
ok := reflection.SetStructFieldValue(rsp, "Retcode", int32(proto.Retcode_RET_SUCC))
|
|
if !ok {
|
|
return
|
|
}
|
|
g.SendMsg(cmdId, player.PlayerID, player.ClientSeq, rsp)
|
|
}
|
|
|
|
// SendToWorldA 给世界内所有玩家发消息
|
|
func (g *GameManager) SendToWorldA(world *World, cmdId uint16, seq uint32, msg pb.Message) {
|
|
for _, v := range world.GetAllPlayer() {
|
|
GAME_MANAGER.SendMsg(cmdId, v.PlayerID, seq, msg)
|
|
}
|
|
}
|
|
|
|
// SendToWorldAEC 给世界内除自己以外的所有玩家发消息
|
|
func (g *GameManager) SendToWorldAEC(world *World, cmdId uint16, seq uint32, msg pb.Message, uid uint32) {
|
|
for _, v := range world.GetAllPlayer() {
|
|
if uid == v.PlayerID {
|
|
continue
|
|
}
|
|
GAME_MANAGER.SendMsg(cmdId, v.PlayerID, seq, msg)
|
|
}
|
|
}
|
|
|
|
// SendToWorldH 给世界房主发消息
|
|
func (g *GameManager) SendToWorldH(world *World, cmdId uint16, seq uint32, msg pb.Message) {
|
|
GAME_MANAGER.SendMsg(cmdId, world.owner.PlayerID, seq, msg)
|
|
}
|
|
|
|
func (g *GameManager) ReconnectPlayer(userId uint32) {
|
|
g.SendMsg(cmd.ClientReconnectNotify, userId, 0, new(proto.ClientReconnectNotify))
|
|
}
|
|
|
|
func (g *GameManager) DisconnectPlayer(userId uint32, reason uint32) {
|
|
player := USER_MANAGER.GetOnlineUser(userId)
|
|
if player == nil {
|
|
return
|
|
}
|
|
MESSAGE_QUEUE.SendToGate(player.GateAppId, &mq.NetMsg{
|
|
MsgType: mq.MsgTypeConnCtrl,
|
|
EventId: mq.KickPlayerNotify,
|
|
ConnCtrlMsg: &mq.ConnCtrlMsg{
|
|
KickUserId: userId,
|
|
KickReason: reason,
|
|
},
|
|
})
|
|
// g.SendMsg(cmd.ServerDisconnectClientNotify, userId, 0, new(proto.ServerDisconnectClientNotify))
|
|
}
|
|
|
|
func (g *GameManager) GetClientProtoObjByName(protoObjName string) pb.Message {
|
|
if !appConfig.CONF.Hk4e.ClientProtoProxyEnable {
|
|
logger.Error("client proto proxy func not enable")
|
|
return nil
|
|
}
|
|
fn := g.clientCmdProtoMapRefValue.MethodByName("GetClientProtoObjByName")
|
|
ret := fn.Call([]reflect.Value{reflect.ValueOf(protoObjName)})
|
|
obj := ret[0].Interface()
|
|
if obj == nil {
|
|
logger.Error("try to get a not exist proto obj, protoObjName: %v", protoObjName)
|
|
return nil
|
|
}
|
|
clientProtoObj := obj.(pb.Message)
|
|
return clientProtoObj
|
|
}
|