系统架构层面流量控制功能完善

This commit is contained in:
flswld
2023-02-05 07:18:43 +08:00
parent cfb001c18a
commit 94c8db402a
51 changed files with 1049 additions and 2408 deletions

View File

@@ -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)

View File

@@ -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
}
}

View File

@@ -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 给予玩家所有武器

View File

@@ -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)
}
}
}

View File

@@ -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 {}

View File

@@ -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"
)

View File

@@ -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) {

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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"
)

View File

@@ -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,
}
}
// 玩家定时任务常量

View File

@@ -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)
}

View File

@@ -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 {

View File

@@ -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