mirror of
https://github.com/FlourishingWorld/hk4e.git
synced 2026-02-14 00:22:25 +08:00
系统架构层面流量控制功能完善
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
_ "net/http/pprof"
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
@@ -49,6 +50,7 @@ func Run(ctx context.Context, configFile string) error {
|
||||
_, err := client.Discovery.KeepaliveServer(context.TODO(), &api.KeepaliveServerReq{
|
||||
ServerType: api.GS,
|
||||
AppId: APPID,
|
||||
LoadCount: uint32(atomic.LoadInt32(&game.ONLINE_PLAYER_NUM)),
|
||||
})
|
||||
if err != nil {
|
||||
logger.Error("keepalive error: %v", err)
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"hk4e/gs/model"
|
||||
"hk4e/pkg/logger"
|
||||
|
||||
"github.com/pierrec/lz4/v4"
|
||||
"github.com/vmihailenco/msgpack/v5"
|
||||
)
|
||||
|
||||
@@ -18,13 +21,28 @@ func (d *Dao) GetRedisPlayerKey(userId uint32) string {
|
||||
}
|
||||
|
||||
func (d *Dao) GetRedisPlayer(userId uint32) *model.Player {
|
||||
playerData, err := d.redis.Get(context.TODO(), d.GetRedisPlayerKey(userId)).Result()
|
||||
playerDataLz4, err := d.redis.Get(context.TODO(), d.GetRedisPlayerKey(userId)).Result()
|
||||
if err != nil {
|
||||
logger.Error("get player from redis error: %v", err)
|
||||
return nil
|
||||
}
|
||||
// 解压
|
||||
startTime := time.Now().UnixNano()
|
||||
in := bytes.NewReader([]byte(playerDataLz4))
|
||||
out := new(bytes.Buffer)
|
||||
lz4Reader := lz4.NewReader(in)
|
||||
_, err = io.Copy(out, lz4Reader)
|
||||
if err != nil {
|
||||
logger.Error("lz4 decode player data error: %v", err)
|
||||
return nil
|
||||
}
|
||||
playerData := out.Bytes()
|
||||
endTime := time.Now().UnixNano()
|
||||
costTime := endTime - startTime
|
||||
logger.Debug("lz4 decode cost time: %v ns, before len: %v, after len: %v, ratio lz4/raw: %v",
|
||||
costTime, len(playerDataLz4), len(playerData), float64(len(playerDataLz4))/float64(len(playerData)))
|
||||
player := new(model.Player)
|
||||
err = msgpack.Unmarshal([]byte(playerData), player)
|
||||
err = msgpack.Unmarshal(playerData, player)
|
||||
if err != nil {
|
||||
logger.Error("unmarshal player error: %v", err)
|
||||
return nil
|
||||
@@ -38,9 +56,29 @@ func (d *Dao) SetRedisPlayer(player *model.Player) {
|
||||
logger.Error("marshal player error: %v", err)
|
||||
return
|
||||
}
|
||||
err = d.redis.Set(context.TODO(), d.GetRedisPlayerKey(player.PlayerID), playerData, time.Hour*24*30).Err()
|
||||
// 压缩
|
||||
startTime := time.Now().UnixNano()
|
||||
in := bytes.NewReader(playerData)
|
||||
out := new(bytes.Buffer)
|
||||
lz4Writer := lz4.NewWriter(out)
|
||||
_, err = io.Copy(lz4Writer, in)
|
||||
if err != nil {
|
||||
logger.Error("set player from redis error: %v", err)
|
||||
logger.Error("lz4 encode player data error: %v", err)
|
||||
return
|
||||
}
|
||||
err = lz4Writer.Close()
|
||||
if err != nil {
|
||||
logger.Error("lz4 encode player data error: %v", err)
|
||||
return
|
||||
}
|
||||
playerDataLz4 := out.Bytes()
|
||||
endTime := time.Now().UnixNano()
|
||||
costTime := endTime - startTime
|
||||
logger.Debug("lz4 encode cost time: %v ns, before len: %v, after len: %v, ratio lz4/raw: %v",
|
||||
costTime, len(playerData), len(playerDataLz4), float64(len(playerDataLz4))/float64(len(playerData)))
|
||||
err = d.redis.Set(context.TODO(), d.GetRedisPlayerKey(player.PlayerID), playerDataLz4, time.Hour*24*30).Err()
|
||||
if err != nil {
|
||||
logger.Error("set player to redis error: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,9 +54,18 @@ func (c *CommandManager) GMAddUserAvatar(userId, avatarId uint32) {
|
||||
|
||||
// GMAddUserAllItem 给予玩家所有物品
|
||||
func (c *CommandManager) GMAddUserAllItem(userId, itemCount uint32) {
|
||||
// 猜猜这样做为啥不行?
|
||||
// for itemId := range GAME_MANAGER.GetAllItemDataConfig() {
|
||||
// c.GMAddUserItem(userId, uint32(itemId), itemCount)
|
||||
// }
|
||||
itemList := make([]*UserItem, 0)
|
||||
for itemId := range GAME_MANAGER.GetAllItemDataConfig() {
|
||||
c.GMAddUserItem(userId, uint32(itemId), itemCount)
|
||||
itemList = append(itemList, &UserItem{
|
||||
ItemId: uint32(itemId),
|
||||
ChangeCount: itemCount,
|
||||
})
|
||||
}
|
||||
GAME_MANAGER.AddUserItem(userId, itemList, false, 0)
|
||||
}
|
||||
|
||||
// GMAddUserAllWeapon 给予玩家所有武器
|
||||
|
||||
@@ -42,6 +42,8 @@ var COMMAND_MANAGER *CommandManager = nil
|
||||
var GCG_MANAGER *GCGManager = nil
|
||||
var MESSAGE_QUEUE *mq.MessageQueue
|
||||
|
||||
var ONLINE_PLAYER_NUM int32 = 0 // 当前在线玩家数
|
||||
|
||||
var SELF *model.Player
|
||||
|
||||
type GameManager struct {
|
||||
@@ -235,6 +237,7 @@ func (g *GameManager) gameMainLoop() {
|
||||
COMMAND_MANAGER.HandleCommand(command)
|
||||
end := time.Now().UnixNano()
|
||||
commandCost += end - start
|
||||
logger.Info("run gm cmd cost: %v ns", commandCost)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"hk4e/common/mq"
|
||||
"hk4e/gdconf"
|
||||
"hk4e/gs/model"
|
||||
"hk4e/pkg/logger"
|
||||
"hk4e/pkg/object"
|
||||
|
||||
"github.com/vmihailenco/msgpack/v5"
|
||||
)
|
||||
|
||||
// 本地事件队列管理器
|
||||
@@ -16,10 +18,10 @@ const (
|
||||
LoadLoginUserFromDbFinish = iota // 玩家登录从数据库加载完成回调
|
||||
CheckUserExistOnRegFromDbFinish // 玩家注册从数据库查询是否已存在完成回调
|
||||
RunUserCopyAndSave // 执行一次在线玩家内存数据复制到数据库写入协程
|
||||
ExitRunUserCopyAndSave
|
||||
UserOfflineSaveToDbFinish
|
||||
ReloadGameDataConfig
|
||||
ReloadGameDataConfigFinish
|
||||
ExitRunUserCopyAndSave // 停服时执行全部玩家保存操作
|
||||
UserOfflineSaveToDbFinish // 玩家离线保存完成
|
||||
ReloadGameDataConfig // 执行热更表
|
||||
ReloadGameDataConfigFinish // 热更表完成
|
||||
)
|
||||
|
||||
type LocalEvent struct {
|
||||
@@ -37,6 +39,20 @@ func NewLocalEventManager() (r *LocalEventManager) {
|
||||
return r
|
||||
}
|
||||
|
||||
type PlayerLastSaveTimeSortList []*model.Player
|
||||
|
||||
func (p PlayerLastSaveTimeSortList) Len() int {
|
||||
return len(p)
|
||||
}
|
||||
|
||||
func (p PlayerLastSaveTimeSortList) Less(i, j int) bool {
|
||||
return p[i].LastSaveTime < p[j].LastSaveTime
|
||||
}
|
||||
|
||||
func (p PlayerLastSaveTimeSortList) Swap(i, j int) {
|
||||
p[i], p[j] = p[j], p[i]
|
||||
}
|
||||
|
||||
func (l *LocalEventManager) LocalEventHandle(localEvent *LocalEvent) {
|
||||
switch localEvent.EventId {
|
||||
case LoadLoginUserFromDbFinish:
|
||||
@@ -51,49 +67,45 @@ func (l *LocalEventManager) LocalEventHandle(localEvent *LocalEvent) {
|
||||
case ExitRunUserCopyAndSave:
|
||||
fallthrough
|
||||
case RunUserCopyAndSave:
|
||||
saveUserIdList := localEvent.Msg.([]uint32)
|
||||
startTime := time.Now().UnixNano()
|
||||
// 拷贝一份数据避免并发访问
|
||||
insertPlayerList := make([]*model.Player, 0)
|
||||
updatePlayerList := make([]*model.Player, 0)
|
||||
for _, uid := range saveUserIdList {
|
||||
player := USER_MANAGER.GetOnlineUser(uid)
|
||||
if player == nil {
|
||||
logger.Error("try to save but user not exist or online, uid: %v", uid)
|
||||
playerList := make(PlayerLastSaveTimeSortList, 0)
|
||||
for _, player := range USER_MANAGER.playerMap {
|
||||
if player.PlayerID < 100000000 {
|
||||
continue
|
||||
}
|
||||
if uid < 100000000 {
|
||||
playerList = append(playerList, player)
|
||||
}
|
||||
sort.Stable(playerList)
|
||||
// 拷贝一份数据避免并发访问
|
||||
insertPlayerList := make([][]byte, 0)
|
||||
updatePlayerList := make([][]byte, 0)
|
||||
saveCount := 0
|
||||
for _, player := range playerList {
|
||||
totalCostTime := time.Now().UnixNano() - startTime
|
||||
if totalCostTime > time.Millisecond.Nanoseconds()*50 {
|
||||
// 总耗时超过50ms就中止本轮保存
|
||||
logger.Debug("user copy loop overtime exit, total cost time: %v ns", totalCostTime)
|
||||
break
|
||||
}
|
||||
playerData, err := msgpack.Marshal(player)
|
||||
if err != nil {
|
||||
logger.Error("marshal player data error: %v", err)
|
||||
continue
|
||||
}
|
||||
switch player.DbState {
|
||||
case model.DbNone:
|
||||
break
|
||||
case model.DbInsert:
|
||||
playerCopy := new(model.Player)
|
||||
err := object.FastDeepCopy(playerCopy, player)
|
||||
if err != nil {
|
||||
logger.Error("deep copy player error: %v", err)
|
||||
continue
|
||||
}
|
||||
insertPlayerList = append(insertPlayerList, playerCopy)
|
||||
USER_MANAGER.playerMap[uid].DbState = model.DbNormal
|
||||
insertPlayerList = append(insertPlayerList, playerData)
|
||||
USER_MANAGER.playerMap[player.PlayerID].DbState = model.DbNormal
|
||||
player.LastSaveTime = uint32(time.Now().UnixMilli())
|
||||
saveCount++
|
||||
case model.DbDelete:
|
||||
playerCopy := new(model.Player)
|
||||
err := object.FastDeepCopy(playerCopy, player)
|
||||
if err != nil {
|
||||
logger.Error("deep copy player error: %v", err)
|
||||
continue
|
||||
}
|
||||
updatePlayerList = append(updatePlayerList, playerCopy)
|
||||
delete(USER_MANAGER.playerMap, uid)
|
||||
delete(USER_MANAGER.playerMap, player.PlayerID)
|
||||
case model.DbNormal:
|
||||
playerCopy := new(model.Player)
|
||||
err := object.FastDeepCopy(playerCopy, player)
|
||||
if err != nil {
|
||||
logger.Error("deep copy player error: %v", err)
|
||||
continue
|
||||
}
|
||||
updatePlayerList = append(updatePlayerList, playerCopy)
|
||||
updatePlayerList = append(updatePlayerList, playerData)
|
||||
player.LastSaveTime = uint32(time.Now().UnixMilli())
|
||||
saveCount++
|
||||
}
|
||||
}
|
||||
saveUserData := &SaveUserData{
|
||||
@@ -107,7 +119,7 @@ func (l *LocalEventManager) LocalEventHandle(localEvent *LocalEvent) {
|
||||
USER_MANAGER.saveUserChan <- saveUserData
|
||||
endTime := time.Now().UnixNano()
|
||||
costTime := endTime - startTime
|
||||
logger.Info("run save user copy cost time: %v ns", costTime)
|
||||
logger.Debug("run save user copy cost time: %v ns, save user count: %v", costTime, saveCount)
|
||||
if localEvent.EventId == ExitRunUserCopyAndSave {
|
||||
// 在此阻塞掉主协程 不再进行任何消息和任务的处理
|
||||
select {}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"hk4e/common/constant"
|
||||
"hk4e/gdconf"
|
||||
"hk4e/gs/model"
|
||||
@@ -8,7 +10,6 @@ import (
|
||||
"hk4e/pkg/object"
|
||||
"hk4e/protocol/cmd"
|
||||
"hk4e/protocol/proto"
|
||||
"strconv"
|
||||
|
||||
pb "google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"hk4e/common/constant"
|
||||
@@ -81,6 +82,8 @@ func (g *GameManager) OnLoginOk(userId uint32, player *model.Player, clientSeq u
|
||||
|
||||
TICK_MANAGER.CreateUserGlobalTick(userId)
|
||||
TICK_MANAGER.CreateUserTimer(userId, UserTimerActionTest, 100)
|
||||
|
||||
atomic.AddInt32(&ONLINE_PLAYER_NUM, 1)
|
||||
}
|
||||
|
||||
func (g *GameManager) OnReg(userId uint32, clientSeq uint32, gateAppId string, payloadMsg pb.Message) {
|
||||
@@ -134,6 +137,8 @@ func (g *GameManager) OnUserOffline(userId uint32, changeGsInfo *ChangeGsInfo) {
|
||||
player.Online = false
|
||||
player.TotalOnlineTime += uint32(time.Now().UnixMilli()) - player.OnlineTime
|
||||
USER_MANAGER.OfflineUser(player, changeGsInfo)
|
||||
|
||||
atomic.AddInt32(&ONLINE_PLAYER_NUM, -1)
|
||||
}
|
||||
|
||||
func (g *GameManager) LoginNotify(userId uint32, player *model.Player, clientSeq uint32) {
|
||||
|
||||
@@ -175,18 +175,18 @@ func (g *GameManager) SceneKickPlayerReq(player *model.Player, payloadMsg pb.Mes
|
||||
}
|
||||
|
||||
func (g *GameManager) UserApplyEnterWorld(player *model.Player, targetUid uint32) {
|
||||
applyFailNotify := func() {
|
||||
applyFailNotify := func(reason proto.PlayerApplyEnterMpResultNotify_Reason) {
|
||||
playerApplyEnterMpResultNotify := &proto.PlayerApplyEnterMpResultNotify{
|
||||
TargetUid: targetUid,
|
||||
TargetNickname: "",
|
||||
IsAgreed: false,
|
||||
Reason: proto.PlayerApplyEnterMpResultNotify_PLAYER_CANNOT_ENTER_MP,
|
||||
Reason: reason,
|
||||
}
|
||||
g.SendMsg(cmd.PlayerApplyEnterMpResultNotify, player.PlayerID, player.ClientSeq, playerApplyEnterMpResultNotify)
|
||||
}
|
||||
world := WORLD_MANAGER.GetWorldByID(player.WorldId)
|
||||
if world.multiplayer {
|
||||
applyFailNotify()
|
||||
applyFailNotify(proto.PlayerApplyEnterMpResultNotify_PLAYER_CANNOT_ENTER_MP)
|
||||
return
|
||||
}
|
||||
targetPlayer := USER_MANAGER.GetOnlineUser(targetUid)
|
||||
@@ -194,7 +194,7 @@ func (g *GameManager) UserApplyEnterWorld(player *model.Player, targetUid uint32
|
||||
if !USER_MANAGER.GetRemoteUserOnlineState(targetUid) {
|
||||
// 全服不存在该在线玩家
|
||||
logger.Error("target user not online in any game server, uid: %v", targetUid)
|
||||
applyFailNotify()
|
||||
applyFailNotify(proto.PlayerApplyEnterMpResultNotify_PLAYER_CANNOT_ENTER_MP)
|
||||
return
|
||||
}
|
||||
gsAppId := USER_MANAGER.GetRemoteUserGsAppId(targetUid)
|
||||
@@ -224,18 +224,32 @@ func (g *GameManager) UserApplyEnterWorld(player *model.Player, targetUid uint32
|
||||
})
|
||||
return
|
||||
}
|
||||
applyTime, exist := targetPlayer.CoopApplyMap[player.PlayerID]
|
||||
if exist && time.Now().UnixNano() < applyTime+int64(10*time.Second) {
|
||||
applyFailNotify()
|
||||
if WORLD_MANAGER.multiplayerWorldNum >= MAX_MULTIPLAYER_WORLD_NUM {
|
||||
// 超过本服务器最大多人世界数量限制
|
||||
applyFailNotify(proto.PlayerApplyEnterMpResultNotify_MAX_PLAYER)
|
||||
return
|
||||
}
|
||||
targetPlayer.CoopApplyMap[player.PlayerID] = time.Now().UnixNano()
|
||||
targetWorld := WORLD_MANAGER.GetWorldByID(targetPlayer.WorldId)
|
||||
if targetWorld.multiplayer && targetWorld.owner.PlayerID != targetPlayer.PlayerID {
|
||||
// 向同一世界内的非房主玩家申请时直接拒绝
|
||||
applyFailNotify()
|
||||
applyFailNotify(proto.PlayerApplyEnterMpResultNotify_PLAYER_NOT_IN_PLAYER_WORLD)
|
||||
return
|
||||
}
|
||||
mpSetting := targetPlayer.PropertiesMap[constant.PlayerPropertyConst.PROP_PLAYER_MP_SETTING_TYPE]
|
||||
if mpSetting == 0 {
|
||||
// 房主玩家没开权限
|
||||
applyFailNotify(proto.PlayerApplyEnterMpResultNotify_SCENE_CANNOT_ENTER)
|
||||
return
|
||||
} else if mpSetting == 1 {
|
||||
g.UserDealEnterWorld(targetPlayer, player.PlayerID, true)
|
||||
return
|
||||
}
|
||||
applyTime, exist := targetPlayer.CoopApplyMap[player.PlayerID]
|
||||
if exist && time.Now().UnixNano() < applyTime+int64(10*time.Second) {
|
||||
applyFailNotify(proto.PlayerApplyEnterMpResultNotify_PLAYER_CANNOT_ENTER_MP)
|
||||
return
|
||||
}
|
||||
targetPlayer.CoopApplyMap[player.PlayerID] = time.Now().UnixNano()
|
||||
|
||||
playerApplyEnterMpNotify := new(proto.PlayerApplyEnterMpNotify)
|
||||
playerApplyEnterMpNotify.SrcPlayerInfo = g.PacketOnlinePlayerInfo(player)
|
||||
@@ -520,7 +534,7 @@ func (g *GameManager) UpdateWorldPlayerInfo(hostWorld *World, excludePlayer *mod
|
||||
func (g *GameManager) ServerUserMpReq(userMpInfo *mq.UserMpInfo, gsAppId string) {
|
||||
switch userMpInfo.OriginInfo.CmdName {
|
||||
case "PlayerApplyEnterMpReq":
|
||||
applyFailNotify := func() {
|
||||
applyFailNotify := func(reason proto.PlayerApplyEnterMpResultNotify_Reason) {
|
||||
MESSAGE_QUEUE.SendToGs(gsAppId, &mq.NetMsg{
|
||||
MsgType: mq.MsgTypeServer,
|
||||
EventId: mq.ServerUserMpRsp,
|
||||
@@ -529,6 +543,7 @@ func (g *GameManager) ServerUserMpReq(userMpInfo *mq.UserMpInfo, gsAppId string)
|
||||
OriginInfo: userMpInfo.OriginInfo,
|
||||
HostUserId: userMpInfo.HostUserId,
|
||||
ApplyOk: false,
|
||||
Reason: int32(reason),
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -536,21 +551,35 @@ func (g *GameManager) ServerUserMpReq(userMpInfo *mq.UserMpInfo, gsAppId string)
|
||||
hostPlayer := USER_MANAGER.GetOnlineUser(userMpInfo.HostUserId)
|
||||
if hostPlayer == nil {
|
||||
logger.Error("player is nil, uid: %v", userMpInfo.HostUserId)
|
||||
applyFailNotify()
|
||||
applyFailNotify(proto.PlayerApplyEnterMpResultNotify_PLAYER_CANNOT_ENTER_MP)
|
||||
return
|
||||
}
|
||||
if WORLD_MANAGER.multiplayerWorldNum >= MAX_MULTIPLAYER_WORLD_NUM {
|
||||
// 超过本服务器最大多人世界数量限制
|
||||
applyFailNotify(proto.PlayerApplyEnterMpResultNotify_MAX_PLAYER)
|
||||
return
|
||||
}
|
||||
hostWorld := WORLD_MANAGER.GetWorldByID(hostPlayer.WorldId)
|
||||
if hostWorld.multiplayer && hostWorld.owner.PlayerID != hostPlayer.PlayerID {
|
||||
// 向同一世界内的非房主玩家申请时直接拒绝
|
||||
applyFailNotify(proto.PlayerApplyEnterMpResultNotify_PLAYER_NOT_IN_PLAYER_WORLD)
|
||||
return
|
||||
}
|
||||
mpSetting := hostPlayer.PropertiesMap[constant.PlayerPropertyConst.PROP_PLAYER_MP_SETTING_TYPE]
|
||||
if mpSetting == 0 {
|
||||
// 房主玩家没开权限
|
||||
applyFailNotify(proto.PlayerApplyEnterMpResultNotify_SCENE_CANNOT_ENTER)
|
||||
return
|
||||
} else if mpSetting == 1 {
|
||||
g.UserDealEnterWorld(hostPlayer, userMpInfo.ApplyUserId, true)
|
||||
return
|
||||
}
|
||||
applyTime, exist := hostPlayer.CoopApplyMap[userMpInfo.ApplyUserId]
|
||||
if exist && time.Now().UnixNano() < applyTime+int64(10*time.Second) {
|
||||
applyFailNotify()
|
||||
applyFailNotify(proto.PlayerApplyEnterMpResultNotify_PLAYER_CANNOT_ENTER_MP)
|
||||
return
|
||||
}
|
||||
hostPlayer.CoopApplyMap[userMpInfo.ApplyUserId] = time.Now().UnixNano()
|
||||
hostWorld := WORLD_MANAGER.GetWorldByID(hostPlayer.WorldId)
|
||||
if hostWorld.multiplayer && hostWorld.owner.PlayerID != hostPlayer.PlayerID {
|
||||
// 向同一世界内的非房主玩家申请时直接拒绝
|
||||
applyFailNotify()
|
||||
return
|
||||
}
|
||||
|
||||
playerApplyEnterMpNotify := new(proto.PlayerApplyEnterMpNotify)
|
||||
playerApplyEnterMpNotify.SrcPlayerInfo = &proto.OnlinePlayerInfo{
|
||||
@@ -617,7 +646,7 @@ func (g *GameManager) ServerUserMpRsp(userMpInfo *mq.UserMpInfo) {
|
||||
TargetUid: userMpInfo.HostUserId,
|
||||
TargetNickname: "",
|
||||
IsAgreed: false,
|
||||
Reason: proto.PlayerApplyEnterMpResultNotify_PLAYER_CANNOT_ENTER_MP,
|
||||
Reason: proto.PlayerApplyEnterMpResultNotify_Reason(userMpInfo.Reason),
|
||||
}
|
||||
g.SendMsg(cmd.PlayerApplyEnterMpResultNotify, player.PlayerID, player.ClientSeq, playerApplyEnterMpResultNotify)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,10 @@ import (
|
||||
pb "google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
ENTITY_MAX_BATCH_SEND_NUM = 1000 // 单次同步的最大实体数量
|
||||
)
|
||||
|
||||
func (g *GameManager) EnterSceneReadyReq(player *model.Player, payloadMsg pb.Message) {
|
||||
logger.Debug("user enter scene ready, uid: %v", player.PlayerID)
|
||||
world := WORLD_MANAGER.GetWorldByID(player.WorldId)
|
||||
@@ -531,16 +535,14 @@ func (g *GameManager) RemoveSceneEntityNotifyBroadcast(scene *Scene, visionType
|
||||
}
|
||||
}
|
||||
|
||||
const ENTITY_BATCH_SIZE = 1000
|
||||
|
||||
func (g *GameManager) AddSceneEntityNotify(player *model.Player, visionType proto.VisionType, entityIdList []uint32, broadcast bool, aec bool) {
|
||||
world := WORLD_MANAGER.GetWorldByID(player.WorldId)
|
||||
scene := world.GetSceneById(player.SceneId)
|
||||
// 如果总数量太多则分包发送
|
||||
times := int(math.Ceil(float64(len(entityIdList)) / float64(ENTITY_BATCH_SIZE)))
|
||||
times := int(math.Ceil(float64(len(entityIdList)) / float64(ENTITY_MAX_BATCH_SEND_NUM)))
|
||||
for i := 0; i < times; i++ {
|
||||
begin := ENTITY_BATCH_SIZE * i
|
||||
end := ENTITY_BATCH_SIZE * (i + 1)
|
||||
begin := ENTITY_MAX_BATCH_SEND_NUM * i
|
||||
end := ENTITY_MAX_BATCH_SEND_NUM * (i + 1)
|
||||
if i == times-1 {
|
||||
end = len(entityIdList)
|
||||
}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"hk4e/common/constant"
|
||||
"hk4e/gdconf"
|
||||
"hk4e/gs/model"
|
||||
"hk4e/pkg/logger"
|
||||
"hk4e/protocol/cmd"
|
||||
"hk4e/protocol/proto"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
pb "google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
@@ -81,12 +81,6 @@ func (t *TickManager) onUserTickSecond(userId uint32, now int64) {
|
||||
}
|
||||
|
||||
func (t *TickManager) onUserTickMinute(userId uint32, now int64) {
|
||||
// 每分钟保存玩家数据
|
||||
saveUserIdList := []uint32{userId}
|
||||
LOCAL_EVENT_MANAGER.localEventChan <- &LocalEvent{
|
||||
EventId: RunUserCopyAndSave,
|
||||
Msg: saveUserIdList,
|
||||
}
|
||||
}
|
||||
|
||||
// 玩家定时任务常量
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"hk4e/gs/dao"
|
||||
"hk4e/gs/model"
|
||||
"hk4e/pkg/logger"
|
||||
"hk4e/pkg/object"
|
||||
"hk4e/protocol/proto"
|
||||
|
||||
"github.com/vmihailenco/msgpack/v5"
|
||||
)
|
||||
|
||||
// 玩家管理器
|
||||
@@ -167,14 +170,19 @@ type PlayerOfflineInfo struct {
|
||||
|
||||
// OfflineUser 玩家离线
|
||||
func (u *UserManager) OfflineUser(player *model.Player, changeGsInfo *ChangeGsInfo) {
|
||||
playerCopy := new(model.Player)
|
||||
err := object.FastDeepCopy(playerCopy, player)
|
||||
playerData, err := msgpack.Marshal(player)
|
||||
if err != nil {
|
||||
logger.Error("deep copy player error: %v", err)
|
||||
logger.Error("marshal player data error: %v", err)
|
||||
return
|
||||
}
|
||||
playerCopy.DbState = player.DbState
|
||||
go func() {
|
||||
playerCopy := new(model.Player)
|
||||
err := msgpack.Unmarshal(playerData, playerCopy)
|
||||
if err != nil {
|
||||
logger.Error("unmarshal player data error: %v", err)
|
||||
return
|
||||
}
|
||||
playerCopy.DbState = player.DbState
|
||||
u.SaveUserToDbSync(playerCopy)
|
||||
u.SaveUserToRedisSync(playerCopy)
|
||||
LOCAL_EVENT_MANAGER.localEventChan <- &LocalEvent{
|
||||
@@ -336,14 +344,19 @@ func (u *UserManager) SaveTempOfflineUser(player *model.Player) {
|
||||
// 主协程同步写入redis
|
||||
u.SaveUserToRedisSync(player)
|
||||
// 另一个协程异步的写回db
|
||||
playerCopy := new(model.Player)
|
||||
err := object.FastDeepCopy(playerCopy, player)
|
||||
playerData, err := msgpack.Marshal(player)
|
||||
if err != nil {
|
||||
logger.Error("deep copy player error: %v", err)
|
||||
logger.Error("marshal player data error: %v", err)
|
||||
return
|
||||
}
|
||||
playerCopy.DbState = player.DbState
|
||||
go func() {
|
||||
playerCopy := new(model.Player)
|
||||
err := msgpack.Unmarshal(playerData, playerCopy)
|
||||
if err != nil {
|
||||
logger.Error("unmarshal player data error: %v", err)
|
||||
return
|
||||
}
|
||||
playerCopy.DbState = player.DbState
|
||||
u.SaveUserToDbSync(playerCopy)
|
||||
}()
|
||||
}
|
||||
@@ -351,21 +364,56 @@ func (u *UserManager) SaveTempOfflineUser(player *model.Player) {
|
||||
// db和redis相关操作
|
||||
|
||||
type SaveUserData struct {
|
||||
insertPlayerList []*model.Player
|
||||
updatePlayerList []*model.Player
|
||||
insertPlayerList [][]byte
|
||||
updatePlayerList [][]byte
|
||||
exitSave bool
|
||||
}
|
||||
|
||||
func (u *UserManager) saveUserHandle() {
|
||||
for {
|
||||
saveUserData := <-u.saveUserChan
|
||||
u.SaveUserListToDbSync(saveUserData)
|
||||
u.SaveUserListToRedisSync(saveUserData)
|
||||
if saveUserData.exitSave {
|
||||
// 停服落地玩家数据完毕 通知APP主协程关闭程序
|
||||
EXIT_SAVE_FIN_CHAN <- true
|
||||
go func() {
|
||||
ticker := time.NewTicker(time.Minute)
|
||||
for {
|
||||
<-ticker.C
|
||||
// 保存玩家数据
|
||||
LOCAL_EVENT_MANAGER.localEventChan <- &LocalEvent{
|
||||
EventId: RunUserCopyAndSave,
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
for {
|
||||
saveUserData := <-u.saveUserChan
|
||||
insertPlayerList := make([]*model.Player, 0)
|
||||
updatePlayerList := make([]*model.Player, 0)
|
||||
setPlayerList := make([]*model.Player, 0)
|
||||
for _, playerData := range saveUserData.insertPlayerList {
|
||||
player := new(model.Player)
|
||||
err := msgpack.Unmarshal(playerData, player)
|
||||
if err != nil {
|
||||
logger.Error("unmarshal player data error: %v", err)
|
||||
continue
|
||||
}
|
||||
insertPlayerList = append(insertPlayerList, player)
|
||||
setPlayerList = append(setPlayerList, player)
|
||||
}
|
||||
for _, playerData := range saveUserData.updatePlayerList {
|
||||
player := new(model.Player)
|
||||
err := msgpack.Unmarshal(playerData, player)
|
||||
if err != nil {
|
||||
logger.Error("unmarshal player data error: %v", err)
|
||||
continue
|
||||
}
|
||||
updatePlayerList = append(updatePlayerList, player)
|
||||
setPlayerList = append(setPlayerList, player)
|
||||
}
|
||||
u.SaveUserListToDbSync(insertPlayerList, updatePlayerList)
|
||||
u.SaveUserListToRedisSync(setPlayerList)
|
||||
if saveUserData.exitSave {
|
||||
// 停服落地玩家数据完毕 通知APP主协程关闭程序
|
||||
EXIT_SAVE_FIN_CHAN <- true
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (u *UserManager) LoadUserFromDbSync(userId uint32) *model.Player {
|
||||
@@ -395,18 +443,18 @@ func (u *UserManager) SaveUserToDbSync(player *model.Player) {
|
||||
}
|
||||
}
|
||||
|
||||
func (u *UserManager) SaveUserListToDbSync(saveUserData *SaveUserData) {
|
||||
err := u.dao.InsertPlayerList(saveUserData.insertPlayerList)
|
||||
func (u *UserManager) SaveUserListToDbSync(insertPlayerList []*model.Player, updatePlayerList []*model.Player) {
|
||||
err := u.dao.InsertPlayerList(insertPlayerList)
|
||||
if err != nil {
|
||||
logger.Error("insert player list error: %v", err)
|
||||
return
|
||||
}
|
||||
err = u.dao.UpdatePlayerList(saveUserData.updatePlayerList)
|
||||
err = u.dao.UpdatePlayerList(updatePlayerList)
|
||||
if err != nil {
|
||||
logger.Error("update player list error: %v", err)
|
||||
return
|
||||
}
|
||||
logger.Info("save user finish, insert user count: %v, update user count: %v", len(saveUserData.insertPlayerList), len(saveUserData.updatePlayerList))
|
||||
logger.Info("save user finish, insert user count: %v, update user count: %v", len(insertPlayerList), len(updatePlayerList))
|
||||
}
|
||||
|
||||
func (u *UserManager) LoadUserFromRedisSync(userId uint32) *model.Player {
|
||||
@@ -418,13 +466,6 @@ func (u *UserManager) SaveUserToRedisSync(player *model.Player) {
|
||||
u.dao.SetRedisPlayer(player)
|
||||
}
|
||||
|
||||
func (u *UserManager) SaveUserListToRedisSync(saveUserData *SaveUserData) {
|
||||
setPlayerList := make([]*model.Player, 0, len(saveUserData.insertPlayerList)+len(saveUserData.updatePlayerList))
|
||||
for _, player := range saveUserData.insertPlayerList {
|
||||
setPlayerList = append(setPlayerList, player)
|
||||
}
|
||||
for _, player := range saveUserData.updatePlayerList {
|
||||
setPlayerList = append(setPlayerList, player)
|
||||
}
|
||||
func (u *UserManager) SaveUserListToRedisSync(setPlayerList []*model.Player) {
|
||||
u.dao.SetRedisPlayerList(setPlayerList)
|
||||
}
|
||||
|
||||
@@ -16,11 +16,18 @@ import (
|
||||
|
||||
// 世界管理器
|
||||
|
||||
const (
|
||||
ENTITY_NUM_UNLIMIT = false // 是否不限制场景内实体数量
|
||||
ENTITY_MAX_SEND_NUM = 300 // 场景内最大实体数量
|
||||
MAX_MULTIPLAYER_WORLD_NUM = 10 // 本服务器最大多人世界数量
|
||||
)
|
||||
|
||||
type WorldManager struct {
|
||||
worldMap map[uint32]*World
|
||||
snowflake *alg.SnowflakeWorker
|
||||
aiWorld *World // 本服的Ai玩家世界
|
||||
sceneBlockAoiMap map[uint32]*alg.AoiManager // 全局各场景地图的aoi管理器
|
||||
worldMap map[uint32]*World
|
||||
snowflake *alg.SnowflakeWorker
|
||||
aiWorld *World // 本服的Ai玩家世界
|
||||
sceneBlockAoiMap map[uint32]*alg.AoiManager // 全局各场景地图的aoi管理器
|
||||
multiplayerWorldNum uint32 // 本服务器的多人世界数量
|
||||
}
|
||||
|
||||
func NewWorldManager(snowflake *alg.SnowflakeWorker) (r *WorldManager) {
|
||||
@@ -136,6 +143,7 @@ func NewWorldManager(snowflake *alg.SnowflakeWorker) (r *WorldManager) {
|
||||
}
|
||||
r.sceneBlockAoiMap[uint32(sceneConfig.Id)] = aoiManager
|
||||
}
|
||||
r.multiplayerWorldNum = 0
|
||||
return r
|
||||
}
|
||||
|
||||
@@ -176,6 +184,9 @@ func (w *WorldManager) DestroyWorld(worldId uint32) {
|
||||
player.WorldId = 0
|
||||
}
|
||||
delete(w.worldMap, worldId)
|
||||
if world.multiplayer {
|
||||
w.multiplayerWorldNum--
|
||||
}
|
||||
}
|
||||
|
||||
// GetAiWorld 获取本服务器的Ai世界
|
||||
@@ -618,6 +629,7 @@ func (w *World) GetChatList() []*proto.ChatInfo {
|
||||
|
||||
// ChangeToMultiplayer 转换为多人世界
|
||||
func (w *World) ChangeToMultiplayer() {
|
||||
WORLD_MANAGER.multiplayerWorldNum++
|
||||
w.multiplayer = true
|
||||
}
|
||||
|
||||
@@ -665,10 +677,10 @@ type Scene struct {
|
||||
world *World
|
||||
playerMap map[uint32]*model.Player
|
||||
entityMap map[uint32]*Entity
|
||||
objectIdEntityMap map[int64]*Entity
|
||||
gameTime uint32 // 游戏内提瓦特大陆的时间
|
||||
createTime int64
|
||||
meeoIndex uint32 // 客户端风元素染色同步协议的计数器
|
||||
objectIdEntityMap map[int64]*Entity // 用于标识配置档里的唯一实体是否已被创建
|
||||
gameTime uint32 // 游戏内提瓦特大陆的时间
|
||||
createTime int64 // 场景创建时间
|
||||
meeoIndex uint32 // 客户端风元素染色同步协议的计数器
|
||||
}
|
||||
|
||||
func (s *Scene) GetAllPlayer() map[uint32]*model.Player {
|
||||
@@ -873,7 +885,7 @@ func (s *Scene) CreateEntityAvatar(player *model.Player, avatarId uint32) uint32
|
||||
avatarId: avatarId,
|
||||
},
|
||||
}
|
||||
s.entityMap[entity.id] = entity
|
||||
s.CreateEntity(entity, 0)
|
||||
MESSAGE_QUEUE.SendToFight(s.world.owner.FightAppId, &mq.NetMsg{
|
||||
MsgType: mq.MsgTypeFight,
|
||||
EventId: mq.FightRoutineAddEntity,
|
||||
@@ -903,7 +915,7 @@ func (s *Scene) CreateEntityWeapon() uint32 {
|
||||
entityType: uint32(proto.ProtEntityType_PROT_ENTITY_WEAPON),
|
||||
level: 0,
|
||||
}
|
||||
s.entityMap[entity.id] = entity
|
||||
s.CreateEntity(entity, 0)
|
||||
return entity.id
|
||||
}
|
||||
|
||||
@@ -931,8 +943,7 @@ func (s *Scene) CreateEntityMonster(pos, rot *model.Vector, monsterId uint32, le
|
||||
configId: configId,
|
||||
objectId: objectId,
|
||||
}
|
||||
s.entityMap[entity.id] = entity
|
||||
s.objectIdEntityMap[objectId] = entity
|
||||
s.CreateEntity(entity, objectId)
|
||||
MESSAGE_QUEUE.SendToFight(s.world.owner.FightAppId, &mq.NetMsg{
|
||||
MsgType: mq.MsgTypeFight,
|
||||
EventId: mq.FightRoutineAddEntity,
|
||||
@@ -976,8 +987,7 @@ func (s *Scene) CreateEntityNpc(pos, rot *model.Vector, npcId, roomId, parentQue
|
||||
configId: configId,
|
||||
objectId: objectId,
|
||||
}
|
||||
s.entityMap[entity.id] = entity
|
||||
s.objectIdEntityMap[objectId] = entity
|
||||
s.CreateEntity(entity, objectId)
|
||||
return entity.id
|
||||
}
|
||||
|
||||
@@ -1010,8 +1020,7 @@ func (s *Scene) CreateEntityGadgetNormal(pos, rot *model.Vector, gadgetId uint32
|
||||
configId: configId,
|
||||
objectId: objectId,
|
||||
}
|
||||
s.entityMap[entity.id] = entity
|
||||
s.objectIdEntityMap[objectId] = entity
|
||||
s.CreateEntity(entity, objectId)
|
||||
return entity.id
|
||||
}
|
||||
|
||||
@@ -1047,8 +1056,7 @@ func (s *Scene) CreateEntityGadgetGather(pos, rot *model.Vector, gadgetId uint32
|
||||
configId: configId,
|
||||
objectId: objectId,
|
||||
}
|
||||
s.entityMap[entity.id] = entity
|
||||
s.objectIdEntityMap[objectId] = entity
|
||||
s.CreateEntity(entity, objectId)
|
||||
return entity.id
|
||||
}
|
||||
|
||||
@@ -1081,7 +1089,7 @@ func (s *Scene) CreateEntityGadgetClient(pos, rot *model.Vector, entityId uint32
|
||||
},
|
||||
},
|
||||
}
|
||||
s.entityMap[entity.id] = entity
|
||||
s.CreateEntity(entity, 0)
|
||||
}
|
||||
|
||||
func (s *Scene) CreateEntityGadgetVehicle(uid uint32, pos, rot *model.Vector, vehicleId uint32) uint32 {
|
||||
@@ -1119,10 +1127,21 @@ func (s *Scene) CreateEntityGadgetVehicle(uid uint32, pos, rot *model.Vector, ve
|
||||
},
|
||||
},
|
||||
}
|
||||
s.entityMap[entity.id] = entity
|
||||
s.CreateEntity(entity, 0)
|
||||
return entity.id
|
||||
}
|
||||
|
||||
func (s *Scene) CreateEntity(entity *Entity, objectId int64) {
|
||||
if len(s.entityMap) >= ENTITY_MAX_SEND_NUM && !ENTITY_NUM_UNLIMIT {
|
||||
logger.Error("above max scene entity num limit: %v, id: %v, pos: %v", ENTITY_MAX_SEND_NUM, entity.id, entity.pos)
|
||||
return
|
||||
}
|
||||
if objectId != 0 {
|
||||
s.objectIdEntityMap[objectId] = entity
|
||||
}
|
||||
s.entityMap[entity.id] = entity
|
||||
}
|
||||
|
||||
func (s *Scene) DestroyEntity(entityId uint32) {
|
||||
entity := s.GetEntity(entityId)
|
||||
if entity == nil {
|
||||
|
||||
@@ -57,6 +57,7 @@ type Player struct {
|
||||
GCGInfo *GCGInfo `bson:"gcgInfo"` // 七圣召唤信息
|
||||
IsGM uint8 `bson:"isGM"` // 管理员权限等级
|
||||
// 在线数据 请随意 记得加忽略字段的tag
|
||||
LastSaveTime uint32 `bson:"-" msgpack:"-"` // 上一次保存时间
|
||||
EnterSceneToken uint32 `bson:"-" msgpack:"-"` // 玩家的世界进入令牌
|
||||
DbState int `bson:"-" msgpack:"-"` // 数据库存档状态
|
||||
WorldId uint32 `bson:"-" msgpack:"-"` // 所在的世界id
|
||||
|
||||
Reference in New Issue
Block a user