--- 此脚本只是给客户端回放使用 --- 目的:回放时不再是一次性读取所有帧放入到内存中,而是按需读取,减少内存占用 --- 做法:从ServerDatas中将所有帧数据保存到本地文件ClientProfilerFrameDatas,回放时读取部分文件,放入到内存中 --- 使用:玩家初始信息还是从ServerDatas读取,但是在获取帧命令时从ClientProfilerFrameDatas读取。 local ClientProfilerSaveAndRideCmdManager = {} local battle_common_pb = require 'Proto/battle_common_pb' local replay_pb = require('Proto/fair_play_replay_pb') local ServerInfo = "ReplayData/ServerDatas" local FrameFileDirPath = "ReplayData/ClientProfilerFrameDatas" -- 存储的协议结构是 CmdB2CFramePBInfos local private = { CmdB2CFramePBInfos = nil, -- 所有的帧数据集合 frameDataList = {}, -- 一次从服务器拿到的帧数据 curWriteFrame = 0, -- 当前储存的帧号 readFile = nil -- 读取文件时的文件句柄 } --region 外部接口 - 将回放数据中的每帧数据保存到ClientProfilerFrameDatas中 -- 检查是否有回放文件,没有则生成一个 function ClientProfilerSaveAndRideCmdManager.CheckHasReplayFile(callBack) if not LuaExt.FileExists(FrameFileDirPath) then UTL.MessageBox.ShowOnlyOkNoCloseBox("", "ReplayData/ClientProfilerFrameDatas文件不存在,现在开始生成!\n 完毕后会自动GC一次", function() ClientProfilerSaveAndRideCmdManager.CreatSplitFrameFileByMsg() collectgarbage("collect") callBack() end) else callBack() end end function ClientProfilerSaveAndRideCmdManager.CreatSplitFrameFileByMsg() private.curWriteFrame = 1 private.CmdB2CFramePBInfos = replay_pb.CmdB2CFramePBInfos() private.frameDataList = {} -- 读取服务器原始的记录文件 local dataBytes = LuaExt.FileReadBytes(ServerInfo) local msg = battle_common_pb.ForCheckBattleAllInfo() msg:ParseFromString(dataBytes) local serverFrameDatas = msg.battleInfo BattleExt.ParseCmdB2CFrames(serverFrameDatas) BattleExt.SetLuaKcpFrameCB(private.OnFrameCallback) --从缓存中拿去数据 BattleExt.LuaGetB2CFrameList() -- 存储在private.frameDataList中 local filePath = LuaExt.ToUserDataFilePath(FrameFileDirPath) local file, err = io.open (filePath, "ab+") --将 private.frameDataList 中的数据解析到 private.CmdB2CFramePBInfos 中保存(分批次) for i = 1, #private.frameDataList do local framedata = private.frameDataList[i] --如发现帧号提升了,则将之前的 private.CmdB2CFramePBInfos 写入到磁盘(每帧的cmd写一次) if private.curWriteFrame < framedata.FrameNum then private.curWriteFrame = framedata.FrameNum local frameBytes = private.CmdB2CFramePBInfos:SerializeToString() local count = #frameBytes file:write(tostring(count)) file:write(frameBytes) private.CmdB2CFramePBInfos = replay_pb.CmdB2CFramePBInfos() end ClientProfilerSaveAndRideCmdManager.TrySaveB2CFrame(framedata.FrameNum, framedata.PlayerIds, framedata.Framedatas) end file:close() BattleExt.Clear() BattleExt.SetLuaKcpFrameCB(nil) private.CmdB2CFramePBInfos = nil end --endregion 外部接口 --region 组织数据、保存到本地文件 --新版本的回调函数的参数playerIds和frameDatas是作为buffer复用的,不可以持有他们的引用,需要将数据读取出来自行保存 function private.OnFrameCallback(frameNum, playerIdsBuffer, frameDatasBuffer) local playerIds = { } local frameDatas = { } local newFrame = {FrameNum = frameNum,PlayerIds = playerIds,Framedatas = frameDatas} for i = 1, playerIdsBuffer[0] do playerIds[i] = playerIdsBuffer[i] end local length = frameDatasBuffer[0] * 8 for i = 1, length do frameDatas[i] = frameDatasBuffer[i] end table.insert(private.frameDataList,newFrame) end -- 保存到游戏运行时内存中 function ClientProfilerSaveAndRideCmdManager.TrySaveB2CFrame(frameNum, playerIds, frameDatas ) local cmdB2CFramePB = replay_pb.CmdB2CFramePB() cmdB2CFramePB.FrameNum = frameNum for i,playerId in ipairs(playerIds) do table.insert(cmdB2CFramePB.OnlinePlayers, playerId) end local frameCount = #frameDatas / 8 for j = 1, frameCount do -- frameDatas = { 1-8,9-16, }每8个是一个cmd local playerFrameDataPB = replay_pb.PlayerFrameDataPB() local index = (j - 1) * 8 playerFrameDataPB.PlayerId = frameDatas[index + 1] playerFrameDataPB.data.SkillIdx = frameDatas[index + 2] playerFrameDataPB.data.MoveType = frameDatas[index + 3] playerFrameDataPB.data.DirAngleMulTen = frameDatas[index + 4] playerFrameDataPB.data.Spec = frameDatas[index + 5] playerFrameDataPB.data.UseJoystick = frameDatas[index + 6] playerFrameDataPB.data.SkillJoystickPosX = frameDatas[index + 7] playerFrameDataPB.data.SkillJoystickPosZ = frameDatas[index + 8] table.insert(cmdB2CFramePB.PlayerFrameDatas, playerFrameDataPB) end table.insert(private.CmdB2CFramePBInfos.frames, cmdB2CFramePB) end --endregion 组织数据、保存到本地文件 --region 用文件中的数据组织出帧领命数据 function ClientProfilerSaveAndRideCmdManager.ReadOneFrameData() if LogicUpdateManager._clientHandleFrame == 0 then if private.readFile ~= nil then--首次开始时先检查是否异常没关闭之前的逻辑 private.readFile:close() private.readFile = nil private.fileNextCount = nil end private.readFile = io.open (FrameFileDirPath, "rb") private.fileNextCount = private.readFile:read("*n") end if not private.fileNextCount then -- 没有数据了 logging.error(LTAG.ERROR, "[gws]: 分帧回放的存储帧文件读取完毕,已无数据! frame = ", LogicUpdateManager._clientHandleFrame) return end local bytes = private.readFile:read(private.fileNextCount) SaveSyncBattleInfoManager.GetFrameDatasFromBytes(bytes) private.fileNextCount = private.readFile:read("*n") if not private.fileNextCount then -- 没有数据了 private.readFile:close() private.readFile = nil private.fileNextCount = nil logging.crit(LTAG.CRIT, "[gws]: 分帧回放的存储帧文件读取完毕 ! frame = ", LogicUpdateManager._clientHandleFrame) end end --endregion 用文件中的数据组织出帧领命数据 return ClientProfilerSaveAndRideCmdManager