java lua热更新_Lua游戏脚本热更新机制

设计要点¶

能够在服务器运行期间更新程序逻辑代码以实现修正程序Bug、修改游戏数据的目的。游戏框架代码的热更新暂不考虑。例如:

某个业务处理函数逻辑有Bug,服务器运营期间发现,在不停机情况下及时更新代码修复。

技能数值表策划填写出现手误错误,导致玩家战斗异常,在不停机情况下及时更新内存中的数值表。

更新代码时要保护非代码数据,尽量不用重新载入存盘数据。例如:

登陆用户列表保存在内存中,用户登录模块出现bug需要跟新代码,此段代码更新后,用户列表依然有效,在线用户不受影响。

常见方案¶

lua的module机制¶

require 会阻止你重新加载相同的模块。当需要更新系统的时候,卸载掉自己编写的模块。方法是:把 package.loaded 里对应模块名下设置为 nil (这样可以保证下次 require 重新加载一次)并把全局表中的对应的模块表置 nil 。

同时把数据记录在专用的全局表下,并用 local 去引用它。初始化这些数据的时候,首先应该检查他们是否被初始化过了。这样来保证数据不被更新过程重置。

Module B中 local shortFunc = ModuleA.longFunc 在更新ModuleA后更新不到,需在之前加上local shortFunc = nil。比较麻烦

此方法用到了module,在Lua5.2中已经弃用module机制,Lua建议用户自己实现更简单的方法。

loadfile机制¶

通过loadfile来模拟module机制,可以灵活的根据业务需求加入更多的热更新需求。

loadfile的使用基础¶

-------------------------

--代码动态加载模块 v0.1

--

--基于Lua 5.2版本的loadfile

------------------------

----模块管理表,类似于package.loaded

local Modules = {}

--通过loadfile将新代码载入到newModule

function Load(pathname, modulename)

local newModule = {}

--_ENV被设置为空表,载入文件的访问空间被限制在模块内部

local func, err = loadfile(pathname, "bt", newModule)

if not func then

print("ERROR: " .. err)

else

func()

Modules[moduleName] = newModule

end

end

测试模块 Game.lua

---------------------------------

---- 模拟一个游戏定时活动玩法逻辑

----

---- 活动中获奖用户记录下来,活动结束后发奖

----

---- 此代码有逻辑错误

-----------------------------------

-- 活动中获奖用户列表

rewardedUser = {}

-- 活动开始结束时间设置

local starttime = {hour = 10, min = 0, sec = 0}

local endtime = {hour = 12, min = 0, sec = 0}

-- 记录活动中中奖用户

function RecordReward(charId)

rewardedUser[char] = true -- 报错,char = nil

--更新后的正确代码

-- rewardedUser[charId] = true

end

测试驱动模块 Main.lua

require("Modules.lua")

GAME = Load("Game.lua")

GAME.RecordReward(1001) --报错

-- 更新错误以后重新加载

GAME = Load("Game.lua")

GAME.RecordReward(1001) -- rewardedUser = { [1001] = true}

local data =GAME.rewardedUser

local func = GAME.RecordReward

--local 导致当前GAME.RecordReward、GAME.rewardedUser引用被当作upvalue绑定在 closure 里

GAME = Load("Game.lua")

-- GAME.rewardedUser = {} 被重新载入的代码所取代, { [1001] = true}数据丢失,导致用户无法领奖,业务逻辑发生异常。

GAME.RecordReward(1002) -- rewardedUser = { [1002] = true}

-- data引用的是保存在upvalue中的旧引用,值为{ [1001] = true} ,与当前GAME.RecordReward不同步了,出现逻辑异常

func()

-- 仍然报错,因为其引用的是保存在upvalue中的旧引用,而不是更新后的GAME.RecordReward

loadfile的使用进阶¶

通过上面的代码实验,我们发现以下几个问题

上面的Game.lua 不能访问_G空间

需要持久化的内容没有得到保存

upvalue导致热更新失效

同时我们也可以看到,通过loadfile加载的代码,是以table形式引用的,这就为我们解决上述问题打开了思路。

-------------------------

--代码动态加载模块 v0.1

--

--基于Lua 5.2版本的loadfile

------------------------

--模块管理表,类似于package.loaded

local Modules = {}

-- 参数:

-- string pathname 加载模块文件名,含路径

-- string name 模块名,一般和pathname一直即可

-- boolean reload 是否强制更新重载

-- 返回:

-- table module 模块,如果加载失败返回nil

-- string err 如果module为nil,返回错误信息

function Load(pathname, moduleName, reload)

moduleName = moduleName or pathname

local oldModule = Modules[moduleName]

if not oldModule then -- 第一次加载模块,全新加载

local newModule = {}

--通过metatable机制允许模块环境访问_G

setmetatable(newModule, {__index = _G})

local func, err = loadfile(pathname, "bt", newModule)

if not func then

print("ERROR: " .. err)

return nil, err

end

func()

Modules[moduleName] = newModule

return newModule

else -- 重复加载,不需要更新时直接返回缓存

if not reload then

return oldModule

else

--先缓存更新前模块内的table数据

local oldCache = {}

for k, v in pairs(oldModule) do

if type(v) == "table" then

oldCache[k] = v

end

oldModule[k] = nil

end

--原模块直接作为新的环境使用

local newModule = oldModule

--原模块被完全更新

local func, err = loadfile(pathname, "bt", newModule)

if not func then

print("ERROR: " .. err)

return nil, err

end

func()

--恢复table数据,既保持原有数据,也保持了其他模块的既有引用

--因为此引用机制只能作用于table,函数upvalue依然得不到更新

for k, v in pairs(oldCache) do

--将metatable换成新的即可实现函数段更新

local mt = getmetatable(newModule[k])

if mt then setmetatable(v, mt) end

--对于已存在的table,数据段保持不变

newModule[k] = v

end

return newModule

end

end

end

小结:

此方法可实现模块内非local table的数据保持和外部对其引用的保持。同时不需要业务模块对此进行特殊处理

模块内local table数据不能保持,建议用来保存固定数据,例如数值表,活动时间等。

其他模块通过 local func = MODULE.Func 的形式引用模块函数无法得到更新,建议不用此形式。或者在每个引用之前加上 local func = nil,强制脚本每次执行时去更新引用。

再次基础上还可扩展模块的初始化和unload方法,实现特殊逻辑需求

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值