java lua热更新_lua热更新学习
发布日期:2021-06-24 11:12:14 浏览次数:5 分类:技术文章

本文共 4677 字,大约阅读时间需要 15 分钟。

什么是热更新,对于它的理解,正如云风所说的那样,热更新更多的用途是做不停机的 bug 修复,不应用于常规的版本更新。对于热更新的博客,网上看了不少,包括云风写的一篇 热更文章。也仔细看了 snax 的热更部分实现细节。发现有不少可以吸取之处。并把核心部分抽取出来,做个简单分享。

至于怎么个热更新法,更新的是哪些内容,我的理解是,热更新最好只更新模块中的一小部分,比如其中的某个函数,而不是将这个模块都一起更新替换。尽量做到小改动,以达到最终目的。至于更新的思路,我归纳为两点:

将模块中旧的函数替换成新的函数,这个新的函数可以放到一个lua文件中,或者以字符串的形式给出。

将模块中旧的函数,当前用到的所有上值,(什么是上值,后面有讲到)保存到起来,用于新函数引用,保证新函数作为模块中的一部分能够正确运行。

下面以一个demo为例,这也是我抽取 snax 模块中热更新部分,以及和他人一起探讨写的。

目录结构:

./main.lua 调用 test.lua,做为运行文件,显示最终运行效果

./test.lua 一个简单模块文件,用于提供热更新的来源

./test_hot.lua 用于更新替换 test 模块中的某些函数,更新文件

./hotfix.lua 实现热更新机制

30416db4f649

关系结构图

通过这幅关系图,可以了解到,test 模块和 test_hot 之间的关系,test_hot 负责更新 test 模块中的某些函数,但更新后的这些函数依然属于 test 模块中的一部分,并没有脱离 test 模块的掌控,而独立出来。

30416db4f649

test.lua 模块包含的内容

现在我们看看 test.lua 包含了哪些内容,分别有 一个局部变量 index,两个函数 print_index,show ,函数体分别是圆圈1和2,两个函数都引用到了这个局部变量 index。

假设当前,我们想更新替换掉 print_index 函数,让其 index 加1 操作,并打印 index 值,那么我们可以在 test_hot.lua 文件中这么写,见下图黄色框部分:

30416db4f649

test 模块 print_index 第一次热更后

我们希望在 print_index 更新后, index 加 1 后,show 函数获取到的 index 值是 1,即把更新函数也看作是 test.lua 模块中的一部分。而不应该是 index 加 1 后,show 函数获取到的还是原值 0。

假设我们希望更新 print_index 后,再一次更新,把 index 值直接设置为 100,那么它又应该是这样子的,见下图最左侧黄色部分:

30416db4f649

test 模块 print_index 第n次热更后

通过这几幅图,我们可以大致猜想到,热更新后,应该是个什么效果。

再谈及热更之前,先要介绍几过 lua 概念。一个是 _ENV 环境变量,一个是上值 upvalue。

_ENV

在 lua 程序设计一书中有过这样的解释,lua 语言并没有全局变量,所谓的全局变量都是通过某种手段模拟出来的。

Lua 语言是在一个名为 _ENV 的预定义上值(一个外部的局部变量,upvalue)存在的情况下编译所有的代码段的。因此,所有的变量要么绑定到一个名称的局部变量,要么是 _ENV 中的一个字段,而 _ENV 本身是一个局部变量。

例如:

local z = 10

x = 0

y = 1

x = y + z

等价于

local z = 10

_ENV.x = 0

_ENV.y = 1

_ENV.x = _ENV.y + z

x,y 都是不用 local 声明,z 是 local 声明。

所以,我们用到的全局变量其实是保存到 _ENV 变量中。lua 语言在内部维护了一个表来作用全局环境(_G),通常,我们在 load 一个代码段,一个模块时,lua 会用这个表(_G)来初始化 _ENV。如果上面的几行代码是写在一个文件中,那么当 load 调用它时,又会等价于:

-- xxx.lua 文件

local _ENV = the global environment (全局环境)

return function(...)

local z = 10

_ENV.x = 0

_ENV.y = 1

_ENV.x = _ENV.y +z

end

upvalue

当一个局部变量被内层的函数中使用的时候, 它被内层函数称作 上值,或是 外部局部变量。引用 Lua 5.3 参考手册

例如:

local x = 10

function hello(a, b)

local c = a + b + x

print(c)

end

那么在这段代码中,hello 函数的上值有 变量 x,_ENV,而我们刚刚讲到,print 没有经过声明,就可以直接使用,那么它肯定是保存于 _ENV 表中,print(c) 等价于 _ENV.print(c),而变量 a、b、c 都是做为 hello 函数的局部变量。

了解这个这个上值的概率,我们才能在 hotfix 模块中理解代码的含义。下面就来看下具体 demo 的实现。

-- main.lua

local hotfix = require "hotfix"

local test = require "test"

local test_hot = require "test_hot"

print("before hotfix")

for i = 1, 5 do

test.print_index() -- 热更前,调用 print_index,打印 index 的值

end

hotfix.update(test.print_index, test_hot) -- 收集旧函数的上值,用于新函数的引用,这个对应之前说的归纳第2小点

test.print_index = test_hot -- 新函数替换旧的函数,对应之前说的归纳第1小点

print("after hotfix")

for i = 1, 5 do

test.print_index() -- 打印更新后的 index 值

end

test.show() -- show 函数没有被热更,但它获取到的 index 值应该是 最新的,即 index = 5。

接下来看看 test.lua 模块内容

-- test.lua

local test = {}

local index = 0

function test.print_index()

print(index)

end

function test.show( )

print("show:", index)

end

return test

再看看 热更文件 test_hot.lua 内容

-- test_hot.lua

local index -- 这个 index 必须声明,不用赋值,才能够引用到 test 模块中的局部变量 index

return function () -- 返回一个闭包函数,这个就是要更新替换后的原型

index = index + 1

print(index)

end

最后,再看看 hotfix.lua

-- hotfix.lua

local hotfix = {}

local function collect_uv(f, uv)

local i = 1

while true do

local name, value = debug.getupvalue(f, i)

if name == nil then -- 当所有上值收集完时,跳出循环

break

end

if not uv[name] then

uv[name] = { func = f, index = i } -- 这里就会收集到旧函数 print_index 所有的上值,包括变量 index

if type(value) == "function" then

collect_uv(value, uv)

end

end

i = i + 1

end

end

local function update_func(f, uv)

local i = 1

while true do

local name, value = debug.getupvalue(f, i)

if name == nil then -- 当所有上值收集完时,跳出循环

break

end

-- value 值为空,并且这个 name 在 旧的函数中存在

if not value and uv[name] then

local desc = uv[name]

-- 将新函数 f 的第 i 个上值引用旧模块 func 的第 index 个上值

debug.upvaluejoin(f, i, desc.func, desc.index)

end

-- 只对 function 类型进行递归更新,对基本数据类型(number、boolean、string) 不管

if type(value) == "function" then

update_func(value, uv)

end

i = i + 1

end

end

function hotfix.update(old, new)

local uv = {}

collect_uv(old, uv)

update_func(new, uv)

end

return hotfix

这个用到了 lua 的两个 api 函数,在 Lua 5.3 参考手册 中有介绍。

debug.getupvalue (f, up)

此函数返回函数 f 的第 up 个上值的名字和值。 如果该函数没有那个上值,返回 nil 。

debug.upvaluejoin (f1, n1, f2, n2)

让 Lua 闭包 f1 的第 n1 个上值 引用 Lua 闭包 f2 的第 n2 个上值。

我们可以看到, hotfix.lua 做的事也是比较简单的,主要是收集 旧函数的所有上值,更新到新函数中。最后一步替换旧函数是在 main.lua 中完成。

最后看看运行结果:

[root@instance test]# lua main.lua

before hotfix

0

0

0

0

0

after hotfix

1

2

3

4

5

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

show: 5

在了解了热更新机制后,最后来思考几个问题

在热更文件 test_hot.lua 中,如果要更新的函数有很多,那么要声明的变量就会有很多,这个繁琐的事情,应该如何解决。

如果要更新的是 test.lua 中的局部函数,而这个局部函数又同时被多个其他函数引用到,改怎么热更,才能解决其他函数引用问题。

hotfix.lua 中的 collect_uv 函数,目前只对上值是 function 类型,才继续递归收集上值。就有可能会有一些上值没办法继续收集到,比如表,在 test.lua 中加入如下内容,那么 cmd 中的 show 方法,就没办法收集到。

...

local cmd = {}

function cmd.show()

end

function test.getcmd(name)

local c = cmd[name]

if c then c() and

end

转载地址:https://blog.csdn.net/weixin_32421193/article/details/115071202 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:script执行php文件_php命令行(cli)下执行PHP脚本文件的相对路径的问题解决方法...
下一篇:java zlib 位运算_位运算入门:找出一个二进制数的最右端的第一个1;计算一个二进制数中1的个数;找出数组中唯一一个出现次数为奇数的数;找出数组中唯二两个出现次数为奇数的数...

发表评论

最新留言

不错!
[***.144.177.141]2024年04月09日 16时12分04秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章