要实现Lua的热更新,首先要了解Lua的模块加载机制,热更新的核心就是替换package.loaded中的模块。
加载规则:
包管理库提供了从Lua中加载模块的基础库。只有一个导出函数直接放在全局环境中:require。所有其他的部分都导出在表package中。
require(modname)
查询全局缓存表package.loaded:这个函数首先查找package.loaded表,检测modname是否被加载过。如果被加载过,require返回package.loaded[modname]中保存的值。否则,它试着为模块寻找加载器。
通过package.searchers查找加载器:require遵循package.searchers序列的指引来查找加载器。如果改变package.searchers中的序列,也会改变require如何查找一个模块的方式。
查找加载器:首先require查找package.preload[modname]。如果这里有一个值(必须是一个函数),则为加载器。否则require会使用Lua加载器去查找package.path的路径。如果查找失败,接着使用C加载器去查找package.cpath的路径。如果都失败了,再尝试一体化加载器(参见package.searchers详解)
加载器调用规则:每当找到一个加载器,require都用两个参数调用加载器:modname和一个在获取加载器过程中得到的参数(如果是通过查找文件得到的加载器,这个额外的参数即为文件名)。如果加载器返归非空值,require将这个值赋给package.loaded[modname]。如果加载器没能返回一个非空值用于赋给package.loaded[modname],require 会将true赋给 package.loaded[modname]。 无论加载器返回什么结果,require都会为package.loaded[modname]
设置最终值。
报错:在加载或运行模块时有错误,或是无法为模块找到加载器,require都会抛出错误。
package.loaded
存储已经被加载的模块:当require一个modname模块得到的结果不为假时,require返回这个存储的值。require从package.loader中获得的值仅仅是对那张表(模块)的引用,改变这个值并不会改变require使用的表(模块)。
package.preload
保存一些特殊模块的加载器:这里面的值仅仅是对那张表(模块)的引用,改变这个值并不会改变require使用的表(模块)。
package.path
Lua加载器的搜索路径:使用环境变量LUA_PATH_5_3或LUA_PATH初始化。或者采用luaconf.h中的默认路径。环境变量中的所有";;"都会被替换为默认路径。
package.cpath
C加载器的搜索路径:使用环境变量LUA_CPATH_5_3或LUA_CPATH初始化。或者采用luaconf.h中定义的默认路径。
package.searchers
require查找加载器的表:这个表内的每一项都是一个查找器函数。当加载一个模块时,require按次序调用这些查找器,传入modname作为唯一参数。此方法会返回一个函数(模块的加载器)和一个传给这个加载器的参数。或返回一个描述为什么没有找到这个模块的字符串或者nil。
Lua共有四个查找器函数:
第一个查找器就是简单的在package.preload表中查找;
第二个查找器用于查找Lua库的加载库。它使用存储在package.path中的路径查找工作。查找过程和函数package.searchpath描述的一致;
第三个查找器用于查找C库的加载库。它使用存储在package.coath中的路径查找工作。查找过程和函数package.searchpath描述的一致;
第四个搜索器是一体化加载器,从C路径中查找指定模块的根名字。
除了第一个搜索器外,每个搜索器都会返回找到的模块的文件名。这和package.searchpath的返回值一样,第一个搜索器没有返回值。
package.searchpath(name, path[,sep[,,rep]])
在指定的path中搜索指定的name:路径是一个包含一些列分号分隔的模板构成的字符串。对于每个模板,都会用name替换其中的每个问号(前提是有问号)。且将其中的sep(点".")替换为rep(系统的目录分隔符"/")。谈候场时打开这个文件名。
例如,如果路径是字符串 "./?.lua;./?.lc;/usr/local/?/init.lua"
搜索foo.a 这个名字将一次尝试打开文件./foo/a.lua ./foo/a.lc 以及 /usr/local/foo/a/init.lua
返回第一个可以用读模式打开(并马上关闭该文件)的文件的名字。如果不存在这样的文件,返回nil加上错误的消息。(错误消息列出了所有尝试打开的文件名)