Hive是一个简单的LUA沙盒,除了基本的LUA解释器的功能以外,还提供了诸如热加载等功能。 了解HIVE的工作原理有利于了解Lua虚拟机的底层实现机理。 本文从是什么-怎么用-为什么三个维度介绍HIVE。
hive是一个简单的LUA应用框架,目前基于LUA 5.3.4。
主要提供了文件沙盒,文件热加载以及一些基础的服务程序底层支持.
HIVE源码:hive - master - gems / hive-framework - 工蜂内网版 (woa.com)
编译luna
# at the hive-framework root directory
cd luna && make
cp luan.so ../hive/
编译hive
# at the hive-framework root directory
cd hive && make
Hive本身只提供基础的热加载功能,并没有太多的功能。
你可以把Hive看作是一个简单的lua
启动器,正如你使用lua file_name.lua
一样,你也可以使用如下的命令行启动你的lua
代码——
# make sure the lua binary is under your PATH
hive file_name.lua
# just like lua file_name.lua!
你也可以传递一些命令行参数进去,这些命令行参数会被打包放进一个表里,然后传递给你的脚本文件。
你可以使用hive.args
在file_name.lua
中来获取这些参数。
# ok,you can obtain
hive file_name.lua arg1 arg2 arg3
例如在你自己的业务代码里,你可以写这样的代码:
-- print the args
-- test.lua
for k,v in ipairs(hive.args) do
print(string.format('%d:%s\n',k,v))
end
保存为test.lua
,然后在命令行中使用:
hive test.lua arg1 arg2 arg3
1:arg1
2:arg2
3:arg3
业务代码
被Hive启动的Lua的业务代码中,至少应该提供一个hive.run
的实现——
--test.lua
hive.run = function()
print('Hello world')
Hive.run会被反复执行,效果如下所示——
hive test.lua
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
...
除此以外,Hive还提供了一些feature,这些feature可以在Lua的环境中用,即,在你自己的业务代码中使用。
本章从使用的角度介绍Hive的特性,如果需要详细了解实现原理,欢迎参阅源码剖析和Hive接口手册章节。
所谓文件热加载是指但凡使用import
函数引入的文件(包括入口的文件,例如上面的test.lua)都会被Hive检测是否有更新。当Hive
检测到文件时间戳改变以后,会自动重新进行读取。
请观察下面的示例——
-- test.lua
l = import('lobby.lua')
hive.run = function()
print('in test.lua')
print('the imported l is:',l.str)
hive.sleep_ms(2000);
end
其中lobby.lua是另外一个lua程序
-- lobby.lua
str = 'Yes, I am a lobby'
输入hive test.lua
运行
hive test.lua
the imported l is: Yes, I am a lobby
in test.lua
the imported l is: Yes, I am a lobby
in test.lua
the imported l is: Yes, I am a lobby
in test.lua
...
在另外一个终端打开并修改lobby.lua
文件,保存
--lobby.lua
str = 'No!I am not a lobby!'
原终端中的输出发生改变
the imported l is: No, I am not a lobby!
in test.lua
the imported l is: No, I am not a lobby!
in test.lua
the imported l is: No, I am not a lobby!
使用import导入的文件,Hive会为之创建了一个沙盒环境这使得各个文件中的变量名不会冲突。
下面的例子说明了import函数的行为——
-- lobby.lua
function m_add(a,b)
return a + b
end
function m_sub(a,b)
return a - b
end
return 1123
在test.lua
中引入该文件:
l = import('lobby.lua')
print(type(l))
for k,v in pairs(l) do
print(k,':',v)
end
-- for k,v in pairs(_G) do
-- print(k,':',v)
-- end
print(l.m_add(1,2))
print(l.m_sub(1000,2))
得到结果如下所示:可见,之前的定义都放在一个表中,且返回值被忽略了。
table
m_add : function: 0x228faa0
m_sub : function: 0x229be80
3
998
如果使用require,则有所不同
> require('lobby')
1123
> v = require('lobby')
> m_add
function: 0xa5c340
> m_sub
function: 0xa5c010
有何不同?
如果使用自带的require函数,这里会有两个区别。
- 你可以获取到require的返回值
- 全局的作用域被影响,即这里的_G
本章节不涉及原理部分的阐述,如有需要,请参阅Hive接口手册。
这个主要和Hive::run
代码有关:
...
if(!lua_call_object_function(L, &err, this, "import", std::tie(), filename))
die(err);
while (lua_get_object_function(L, this, "run")) {
if(!lua_call_function(L, &err, 0, 0))
die(err);
int64_t now = ::get_time_ms();
if (m_reload_time > 0 && now > last_check + m_reload_time) {
lua_call_object_function(L, nullptr, this, "reload");
last_check = now;
}
lua_settop(L, top);
}
lua_close(L);
}
import
函数进行加载。如果没有错,则继续进入一个循环。这个import
函数,是Hive提前定义好的一个函数。后面还会介绍。import
的文件是否有更新,如果有,则调用reload
函数重新进行加载。因此,我们可以说,如果业务代码中没有定义RUN函数,则系统会直接返回。
所谓沙盒环境,其实就是不管怎么加载代码,都用一个table给装起来,而不污染全局的环境(详情可见上面的特性章节)。
沙盒的实现原理主要和Hive提前定义的load函数有关。
static const char* g_sandbox = u8R"__(
hive.files = {};
hive.meta = {__index=function(t, k) return _G[k]; end};
hive.print = function(...) end; --do nothing
local get_filenode = function(filename)
local rootpath = os.getenv("LUA_ROOT");
local withroot = rootpath and hive.get_full_path(rootpath).."/"..filename or filename;
local fullpath = hive.get_full_path(withroot) or withroot;
local node = hive.files[fullpath];
if node then
return node;
end
local env = {};
setmetatable(env, hive.meta);
node = {env=env, fullpath=fullpath, filename=filename};
hive.files[fullpath] = node;
return node;
end
function import(filename)
local node = get_filenode(filename);
if not node.time then
node.time = hive.get_file_time(node.fullpath);
try_load(node);
end
return node.env;
end
hive.reload = function()
local now = os.time();
local update = true;
while update do
update = false;
for path, node in pairs(hive.files) do
local filetime = hive.get_file_time(node.fullpath);
if filetime ~= node.time and filetime ~= 0 and math.abs(now - filetime) > 1 then
node.time = filetime;
update = true;
try_load(node);
end
end
end
end
)__";
上面的定义,是Hive在加载用户的文件之前会调用的。
主要关注import
函数——
get_filenode
函数。该函数首先到全局的hive.files
表中进行查找,如果找到了,则直接返回该node
,这里的node
,其实就是一个表,一个虚拟的沙箱。否则就新建一张表,这张表的元表是固定的hive.meta
,即如果在该表中无法找到,则到_G
中进行查找。这里的_G
事实上就是导入的文件的环境。除了前面章节中所提到的内容,Hive其实还有一些其他的接口暴露给了用户。使用hive.xxx
即可访问。
API | 作用 |
---|---|
get_version | 返回版本号 |
get_file_time | 获取文件的修改时间 |
get_full_path | 获取文件的绝对路径 |
get_time_ms/get_time_ns | 获取当前的时间(Epoch格式) |
sleep_ms | 睡眠 |
daemon | 服务作为后台运行 |