上篇说到检测Lua文件发生变化,这篇来讲怎么重载lua模块。 请配合github工程来看。

关于重载lua的功能在Hotfix.lua脚本上。这个脚本有三个函数,hotfixupdate_tableupdate_func


hotfix



function



package.loaded

package.loaded是lua内部用于记录哪些模块被require的表,如果一个模块已经被require,那么下次再调用require就不会生效。 所以第一步就是把package.loaded记录的旧模块清空并重新require,然后使用pcall捕捉异常,如果出现错误,package.loaded仍然赋值旧模块,并返回。网上的很多博客也就停留在这一步了。 下一步就是更新旧模块,调用update_table,最后把package.loaded[filename]


update_table



function



这个函数用于更新旧表,首先断言这两个参数的类型是不是table。 然后遍历表里面的元素,有三种情况:数据函数

  • 数据不做处理,让旧表保留旧的数据。
  • 函数,需要处理upvalue值,处理完之后,替换旧表里的旧函数
  • ,嵌套调用update_table

最后处理元表,把旧表的元表和新表的元表拿出来,嵌套调用update_table

为什么需要处理元表?

考虑下面的代码,这是我工程里BaseClass.lua的一段代码:



local



当我对class_type表里添加元素时,实际上是往vtbl也就是class_type的元表进行操作。这样当我遍历class_type的时候,实际上是找不到我添加的元素的,因为这个元素在元表里。所以在更新表的时候,需要把新表和旧表的元表都取出来,然后再次调用update_table


update_func



function



这个函数用于处理upvalue,什么是upvalue,就是一个函数使用了不在这个函数内的变量。如下代码:



local



其中的count就是upvalue,当我更新函数的时候需要保留upvalue的值。

getupvalue、setupvalue

lua内部提供了这两个方法,我们只需要把旧函数的upvalue拿出来再赋值给新函数的upvalue就可以了。


到这里,看似热重载的功能已经完成了,但是还有一种情况没有考虑到,就是如果旧表的旧函数在其他地方已经被存了一份该怎么办? 这个时候就算你把旧表的旧函数更新为新函数了,实际上其他地方存的还是旧函数。 比如我工程里GameMain.lua的一段代码:



local



当我更新旧表的函数后,playerMove.TestFunc() 的确是调用的新函数,但是GameMain.playerFunc() 还是旧函数。我的解决办法是在PlayerMove.lua增加OnReload这个函数,在这个函数对GameMain.playerFunc重新赋值,然后在hotfix方法里这么调用:



if



OnReload代码如下:



local



为什么需要分别考虑类和实例的情况?

我们都知道在lua里写面向对象,当我调用 [class].New() 时,实际上是生成了一个元表是[class]的实例。调用package.load[filename] 获得的是类的类型,并不是类的实例。那么我又需要调用类的实例的方法的时候怎么办?我的解决方法是类在生成一个实例时,用一个表去记录它都生成了哪些实例。同时设置成弱引用,这样当实例销毁并执行gc后,这个记录表也会自动取消对实例的引用。 在工程里的BaseClass.lua里面如下:



class_type



  • 其中class_type.instance用于记录生成的实例
  • __mode = "v",说明表里的value是弱引用
  • table.insert

这就是为什么OnReload里面判断了一下是class还是instance的原因。