拆分战斗服务器

This commit is contained in:
huangxiaolei
2022-12-19 22:11:55 +08:00
parent cf4804c444
commit 0dc45708d6
21 changed files with 793 additions and 349 deletions

View File

@@ -9,15 +9,14 @@ import (
"time"
"hk4e/common/config"
"hk4e/common/mq"
"hk4e/gdconf"
gdc "hk4e/gs/config"
"hk4e/gs/constant"
"hk4e/gs/dao"
"hk4e/gs/game"
"hk4e/gs/mq"
"hk4e/gs/service"
"hk4e/pkg/logger"
"hk4e/protocol/cmd"
"github.com/nats-io/nats.go"
)
@@ -46,14 +45,10 @@ func Run(ctx context.Context, configFile string) error {
}
defer db.CloseDao()
netMsgInput := make(chan *cmd.NetMsg, 10000)
netMsgOutput := make(chan *cmd.NetMsg, 10000)
messageQueue := mq.NewMessageQueue(conn, netMsgInput, netMsgOutput)
messageQueue.Start()
messageQueue := mq.NewMessageQueue(mq.GS, "1")
defer messageQueue.Close()
gameManager := game.NewGameManager(db, netMsgInput, netMsgOutput)
gameManager := game.NewGameManager(db, messageQueue)
gameManager.Start()
defer gameManager.Stop()

View File

@@ -3,6 +3,7 @@ package game
import (
"time"
"hk4e/common/mq"
"hk4e/gs/dao"
"hk4e/gs/model"
"hk4e/pkg/alg"
@@ -24,16 +25,14 @@ var COMMAND_MANAGER *CommandManager = nil
type GameManager struct {
dao *dao.Dao
netMsgInput chan *cmd.NetMsg
netMsgOutput chan *cmd.NetMsg
messageQueue *mq.MessageQueue
snowflake *alg.SnowflakeWorker
}
func NewGameManager(dao *dao.Dao, netMsgInput chan *cmd.NetMsg, netMsgOutput chan *cmd.NetMsg) (r *GameManager) {
func NewGameManager(dao *dao.Dao, messageQueue *mq.MessageQueue) (r *GameManager) {
r = new(GameManager)
r.dao = dao
r.netMsgInput = netMsgInput
r.netMsgOutput = netMsgOutput
r.messageQueue = messageQueue
r.snowflake = alg.NewSnowflakeWorker(1)
GAME_MANAGER = r
LOCAL_EVENT_MANAGER = NewLocalEventManager()
@@ -49,20 +48,62 @@ func (g *GameManager) Start() {
ROUTE_MANAGER.InitRoute()
USER_MANAGER.StartAutoSaveUser()
go func() {
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 := <-g.netMsgOutput:
case netMsg := <-g.messageQueue.GetNetMsg():
// 接收客户端消息
start := time.Now().UnixNano()
ROUTE_MANAGER.RouteHandle(netMsg)
end := time.Now().UnixNano()
routeCost += end - start
case <-TICK_MANAGER.ticker.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
}
}
}()
@@ -82,19 +123,22 @@ func (g *GameManager) SendMsg(cmdId uint16, userId uint32, clientSeq uint32, pay
if userId < 100000000 || payloadMsg == nil {
return
}
netMsg := new(cmd.NetMsg)
netMsg.UserId = userId
netMsg.EventId = cmd.NormalMsg
netMsg.CmdId = cmdId
netMsg.ClientSeq = clientSeq
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
}
netMsg.PayloadMessageData = payloadMessageData
g.netMsgInput <- netMsg
gameMsg.PayloadMessageData = payloadMessageData
g.messageQueue.SendToGate("1", &mq.NetMsg{
MsgType: mq.MsgTypeGame,
EventId: mq.NormalMsg,
GameMsg: gameMsg,
})
}
// CommonRetError 通用返回错误码

View File

@@ -1,6 +1,7 @@
package game
import (
"hk4e/common/mq"
"hk4e/gs/model"
"hk4e/pkg/logger"
"hk4e/protocol/cmd"
@@ -116,19 +117,23 @@ func (r *RouteManager) InitRoute() {
r.registerRouter(cmd.VehicleInteractReq, GAME_MANAGER.VehicleInteractReq)
}
func (r *RouteManager) RouteHandle(netMsg *cmd.NetMsg) {
func (r *RouteManager) RouteHandle(netMsg *mq.NetMsg) {
if netMsg.MsgType != mq.MsgTypeGame {
return
}
gameMsg := netMsg.GameMsg
switch netMsg.EventId {
case cmd.NormalMsg:
r.doRoute(netMsg.CmdId, netMsg.UserId, netMsg.ClientSeq, netMsg.PayloadMessage)
case cmd.UserRegNotify:
GAME_MANAGER.OnReg(netMsg.UserId, netMsg.ClientSeq, netMsg.PayloadMessage)
case cmd.UserLoginNotify:
GAME_MANAGER.OnLogin(netMsg.UserId, netMsg.ClientSeq)
case cmd.UserOfflineNotify:
GAME_MANAGER.OnUserOffline(netMsg.UserId)
case cmd.ClientRttNotify:
GAME_MANAGER.ClientRttNotify(netMsg.UserId, netMsg.ClientRtt)
case cmd.ClientTimeNotify:
GAME_MANAGER.ClientTimeNotify(netMsg.UserId, netMsg.ClientTime)
case mq.NormalMsg:
r.doRoute(gameMsg.CmdId, gameMsg.UserId, gameMsg.ClientSeq, gameMsg.PayloadMessage)
case mq.UserRegNotify:
GAME_MANAGER.OnReg(gameMsg.UserId, gameMsg.ClientSeq, gameMsg.PayloadMessage)
case mq.UserLoginNotify:
GAME_MANAGER.OnLogin(gameMsg.UserId, gameMsg.ClientSeq)
case mq.UserOfflineNotify:
GAME_MANAGER.OnUserOffline(gameMsg.UserId)
case mq.ClientRttNotify:
GAME_MANAGER.ClientRttNotify(gameMsg.UserId, gameMsg.ClientRtt)
case mq.ClientTimeNotify:
GAME_MANAGER.ClientTimeNotify(gameMsg.UserId, gameMsg.ClientTime)
}
}

View File

@@ -132,32 +132,6 @@ func (t *TickManager) onTick10Second(now int64) {
GAME_MANAGER.SendMsg(cmd.PlayerTimeNotify, player.PlayerID, 0, playerTimeNotify)
}
}
if !world.IsBigWorld() && (world.multiplayer || !world.owner.Pause) {
// 刷怪
scene := world.GetSceneById(3)
monsterEntityCount := 0
for _, entity := range scene.entityMap {
if entity.entityType == uint32(proto.ProtEntityType_PROT_ENTITY_TYPE_MONSTER) {
monsterEntityCount++
}
}
if monsterEntityCount < 30 {
monsterEntityId := t.createMonster(scene)
bigWorldOwner := USER_MANAGER.GetOnlineUser(1)
GAME_MANAGER.AddSceneEntityNotify(bigWorldOwner, proto.VisionType_VISION_TYPE_BORN, []uint32{monsterEntityId}, true, false)
}
}
for _, player := range world.playerMap {
if world.multiplayer || !world.owner.Pause {
// 改面板
for _, worldAvatar := range world.GetPlayerWorldAvatarList(player) {
avatar := player.AvatarMap[worldAvatar.avatarId]
avatar.FightPropMap[uint32(constant.FightPropertyConst.FIGHT_PROP_CUR_ATTACK)] = 1000000
avatar.FightPropMap[uint32(constant.FightPropertyConst.FIGHT_PROP_CRITICAL)] = 1.0
GAME_MANAGER.UpdateUserAvatarFightProp(player.PlayerID, worldAvatar.avatarId)
}
}
}
}
}
@@ -236,6 +210,20 @@ func (t *TickManager) onTickSecond(now int64) {
}
GAME_MANAGER.SendMsg(cmd.WorldPlayerRTTNotify, player.PlayerID, 0, worldPlayerRTTNotify)
}
if !world.IsBigWorld() && world.owner.SceneLoadState == model.SceneEnterDone {
// 刷怪
scene := world.GetSceneById(3)
monsterEntityCount := 0
for _, entity := range scene.entityMap {
if entity.entityType == uint32(proto.ProtEntityType_PROT_ENTITY_TYPE_MONSTER) {
monsterEntityCount++
}
}
if monsterEntityCount < 30 {
monsterEntityId := t.createMonster(scene)
GAME_MANAGER.AddSceneEntityNotify(world.owner, proto.VisionType_VISION_TYPE_BORN, []uint32{monsterEntityId}, true, false)
}
}
}
}

View File

@@ -94,7 +94,7 @@ func (g *GameManager) CombatInvocationsNotify(player *model.Player, payloadMsg p
for _, entry := range req.InvokeList {
switch entry.ArgumentType {
case proto.CombatTypeArgument_COMBAT_TYPE_ARGUMENT_EVT_BEING_HIT:
player.CombatInvokeHandler.AddEntry(entry.ForwardType, entry)
continue
case proto.CombatTypeArgument_COMBAT_TYPE_ARGUMENT_ENTITY_MOVE:
entityMoveInfo := new(proto.EntityMoveInfo)
err := pb.Unmarshal(entry.CombatData, entityMoveInfo)

View File

@@ -115,7 +115,7 @@ func (g *GameManager) PathfindingEnterSceneReq(player *model.Player, payloadMsg
}
func (g *GameManager) QueryPathReq(player *model.Player, payloadMsg pb.Message) {
logger.Debug("user query path, uid: %v", player.PlayerID)
// logger.Debug("user query path, uid: %v", player.PlayerID)
req := payloadMsg.(*proto.QueryPathReq)
queryPathRsp := &proto.QueryPathRsp{

View File

@@ -4,6 +4,7 @@ import (
"math"
"time"
"hk4e/common/mq"
"hk4e/gs/constant"
"hk4e/gs/game/aoi"
"hk4e/gs/model"
@@ -73,6 +74,13 @@ func (w *WorldManager) CreateWorld(owner *model.Player) *World {
}
world.mpLevelEntityId = world.GetNextWorldEntityId(constant.EntityIdTypeConst.MPLEVEL)
w.worldMap[worldId] = world
GAME_MANAGER.messageQueue.SendToFight("1", &mq.NetMsg{
MsgType: mq.MsgTypeFight,
EventId: mq.AddFightRoutine,
FightMsg: &mq.FightMsg{
FightRoutineId: world.id,
},
})
return world
}
@@ -83,6 +91,13 @@ func (w *WorldManager) DestroyWorld(worldId uint32) {
player.WorldId = 0
}
delete(w.worldMap, worldId)
GAME_MANAGER.messageQueue.SendToFight("1", &mq.NetMsg{
MsgType: mq.MsgTypeFight,
EventId: mq.DelFightRoutine,
FightMsg: &mq.FightMsg{
FightRoutineId: world.id,
},
})
}
// GetBigWorld 获取本服务器的AI世界
@@ -624,6 +639,17 @@ func (s *Scene) CreateEntityAvatar(player *model.Player, avatarId uint32) uint32
if avatarId == s.world.GetPlayerActiveAvatarId(player) {
s.world.aoiManager.AddEntityIdToGridByPos(entity.id, float32(entity.pos.X), float32(entity.pos.Y), float32(entity.pos.Z))
}
GAME_MANAGER.messageQueue.SendToFight("1", &mq.NetMsg{
MsgType: mq.MsgTypeFight,
EventId: mq.FightRoutineAddEntity,
FightMsg: &mq.FightMsg{
FightRoutineId: s.world.id,
EntityId: entity.id,
FightPropMap: entity.fightProp,
Uid: entity.avatarEntity.uid,
AvatarGuid: player.AvatarMap[avatarId].Guid,
},
})
return entity.id
}
@@ -661,6 +687,15 @@ func (s *Scene) CreateEntityMonster(pos *model.Vector, level uint8, fightProp ma
}
s.entityMap[entity.id] = entity
s.world.aoiManager.AddEntityIdToGridByPos(entity.id, float32(entity.pos.X), float32(entity.pos.Y), float32(entity.pos.Z))
GAME_MANAGER.messageQueue.SendToFight("1", &mq.NetMsg{
MsgType: mq.MsgTypeFight,
EventId: mq.FightRoutineAddEntity,
FightMsg: &mq.FightMsg{
FightRoutineId: s.world.id,
EntityId: entity.id,
FightPropMap: entity.fightProp,
},
})
return entity.id
}
@@ -771,6 +806,14 @@ func (s *Scene) DestroyEntity(entityId uint32) {
}
s.world.aoiManager.RemoveEntityIdFromGridByPos(entity.id, float32(entity.pos.X), float32(entity.pos.Y), float32(entity.pos.Z))
delete(s.entityMap, entityId)
GAME_MANAGER.messageQueue.SendToFight("1", &mq.NetMsg{
MsgType: mq.MsgTypeFight,
EventId: mq.FightRoutineDelEntity,
FightMsg: &mq.FightMsg{
FightRoutineId: s.world.id,
EntityId: entity.id,
},
})
}
func (s *Scene) GetEntity(entityId uint32) *Entity {

View File

@@ -1,86 +0,0 @@
package mq
import (
"hk4e/pkg/logger"
"hk4e/protocol/cmd"
"github.com/nats-io/nats.go"
"github.com/vmihailenco/msgpack/v5"
pb "google.golang.org/protobuf/proto"
)
type MessageQueue struct {
natsConn *nats.Conn
natsMsgChan chan *nats.Msg
netMsgInput chan *cmd.NetMsg
netMsgOutput chan *cmd.NetMsg
cmdProtoMap *cmd.CmdProtoMap
}
func NewMessageQueue(conn *nats.Conn, netMsgInput chan *cmd.NetMsg, netMsgOutput chan *cmd.NetMsg) (r *MessageQueue) {
r = new(MessageQueue)
r.natsConn = conn
r.natsMsgChan = make(chan *nats.Msg, 10000)
_, err := r.natsConn.ChanSubscribe("GS_CMD_HK4E", r.natsMsgChan)
if err != nil {
logger.Error("nats subscribe error: %v", err)
return nil
}
r.netMsgInput = netMsgInput
r.netMsgOutput = netMsgOutput
r.cmdProtoMap = cmd.NewCmdProtoMap()
return r
}
func (m *MessageQueue) Start() {
go m.startRecvHandler()
go m.startSendHandler()
}
func (m *MessageQueue) Close() {
m.natsConn.Close()
}
func (m *MessageQueue) startRecvHandler() {
for {
natsMsg := <-m.natsMsgChan
// msgpack NetMsg
netMsg := new(cmd.NetMsg)
err := msgpack.Unmarshal(natsMsg.Data, netMsg)
if err != nil {
logger.Error("parse bin to net msg error: %v", err)
continue
}
if netMsg.EventId == cmd.NormalMsg || netMsg.EventId == cmd.UserRegNotify {
// protobuf PayloadMessage
payloadMessage := m.cmdProtoMap.GetProtoObjByCmdId(netMsg.CmdId)
err = pb.Unmarshal(netMsg.PayloadMessageData, payloadMessage)
if err != nil {
logger.Error("parse bin to payload msg error: %v", err)
continue
}
netMsg.PayloadMessage = payloadMessage
}
m.netMsgOutput <- netMsg
}
}
func (m *MessageQueue) startSendHandler() {
for {
netMsg := <-m.netMsgInput
// protobuf PayloadMessage 已在上一层完成
// msgpack NetMsg
netMsgData, err := msgpack.Marshal(netMsg)
if err != nil {
logger.Error("parse net msg to bin error: %v", err)
continue
}
natsMsg := nats.NewMsg("GATE_CMD_HK4E")
natsMsg.Data = netMsgData
err = m.natsConn.PublishMsg(natsMsg)
if err != nil {
logger.Error("nats publish msg error: %v", err)
continue
}
}
}