Lua热更新原理(2) - Upvalue

##对table的误解

对于lua新手来说,可能会把table当成了c++里的class或者struct,就像当初的我,例如:

--example.lua
local t = {}
t.data = 6
function t.func()
	print(t.data)
end
return t

可能你会觉得t.func能访问t.data是因为它们都是t里的值,当你把t认为是一个class,自然会认为t.func具有访问“数据成员”t.data的权限。你是不是觉得无论何时何地,table里的函数应该总能访问到该table里的其它“成员”?那如果是这么写的:

--example.lua
local function func()
	print(t.data)
end
local t = {}
t.data = 6
t.func = func
return t

当你调用t.func(),还会得到想要的结果吗?不会,你会得到一个报错attempt to index global 't' (a nil value),这时候的t.func压根不知道t的存在,更别说t.data了。

table对于该table里的值并不一定可见,upvalue是维系它们的桥梁。

##什么是upvalue? 我想用一句话来总结:函数里用到的定义在该函数之前的local变量,就成为了该函数的upvalue。在此我还要说一个关于local变量误区,也是因为把它类比成了其它语言的局部变量造成的,就是误解了它的生命周期。在lua里,对于local变量,只要你还能访问到,不管是通过upvalue还是通过table,那么这个local变量就不消失,它的值也不会变化除非你主动改变它。对于这个文件:

--example.lua
local t = {}
t.data = 6
function t.func()
	print(t.data)
end
return t

t.func能访问到t.data,是因为t成为了t.func的upvalue。

##热更新需要注意的 upvalue是持久的数据,为了不破坏原来的逻辑,把旧函数换成新函数时,也要记得把旧函数的upvalue复制新函数身上。例如:

--example.lua
local count = 0
local function func()
	count = count + 1
	print(count)
end
return func

count用来统计func运行了多少次,如果你用Lua热更新原理(1)的方法来热更新,那么需要把原来函数的count复制过来。可以使用debug.getupvaluedebug.setupvalue来完成,简单的代码如下:

local oldfunc = require "example"
package.loaded["example"] = nil
local newfunc = require "example"

for i = 1, math.huge do
	local name, value = debug.getupvalue(oldfunc, i)
	if not name then break end
	debug.setupvalue(newfunc, i, value)
end

通过这种方法,就能把旧函数的upvalue复制到新函数里。但如果想热更upvalue函数,那你就要注意区分,如果是函数,那就用新的upvalue。

Written on January 24, 2016