mirror of
https://github.com/FlourishingWorld/hk4e.git
synced 2026-02-13 15:02:27 +08:00
拆分战斗服务器
This commit is contained in:
@@ -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()
|
||||
|
||||
|
||||
@@ -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 通用返回错误码
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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 {
|
||||
|
||||
86
gs/mq/mq.go
86
gs/mq/mq.go
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user