Hive是一個簡單的LUA沙盒,除了基本的LUA解釋器的功能以外,還提供了諸如熱加載等功能。 了解HIVE的工作原理有利于了解Lua虛擬機的底層實現機理。 本文從是什么-怎么用-為什么三個維度介紹HIVE。
Hive
Hive是什么
hive是一個簡單的LUA應用框架,目前基于LUA 5.3.4。
主要提供了文件沙盒,文件熱加載以及一些基礎的服務程序底層支持.
HIVE源碼:hive - master - gems / hive-framework - 工蜂內網版 (woa.com)
Hive的使用
編譯
-
編譯
luna
# at the hive-framework root directorycd luna && makecp luan.so ../hive/
-
編譯
hive
# at the hive-framework root directorycd hive && make
運行
- 作為啟動器的Hive
Hive本身只提供基礎的熱加載功能,并沒有太多的功能。
你可以把Hive看作是一個簡單的lua
啟動器,正如你使用lua file_name.lua
一樣,你也可以使用如下的命令行啟動你的lua
代碼——
# make sure the lua binary is under your PATHhive file_name.lua# just like lua file_name.lua!
- 命令行參數
你也可以傳遞一些命令行參數進去,這些命令行參數會被打包放進一個表里,然后傳遞給你的腳本文件。
你可以使用hive.args
在file_name.lua
中來獲取這些參數。
# ok,you can obtainhive file_name.lua arg1 arg2 arg3
例如在你自己的業務代碼里,你可以寫這樣的代碼:
-- print the args-- test.luafor k,v in ipairs(hive.args) doprint(string.format('%d:%s\n',k,v))end
保存為test.lua
,然后在命令行中使用:
hive test.lua arg1 arg2 arg31:arg12:arg23:arg3
-
業務代碼
被Hive啟動的Lua的業務代碼中,至少應該提供一個
hive.run
的實現——--test.luahive.run = function()print('Hello world')
Hive.run會被反復執行,效果如下所示——
hive test.luahello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello world...
除此以外,Hive還提供了一些feature,這些feature可以在Lua的環境中用,即,在你自己的業務代碼中使用。
Hive的特性
本章從使用的角度介紹Hive的特性,如果需要詳細了解實現原理,歡迎參閱源碼剖析和Hive接口手冊章節。
文件熱加載
所謂文件熱加載是指但凡使用import
函數引入的文件(包括入口的文件,例如上面的test.lua)都會被Hive檢測是否有更新。當Hive
檢測到文件時間戳改變以后,會自動重新進行讀取。
請觀察下面的示例——
-- test.lual = 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.luastr = 'Yes, I am a lobby'
輸入hive test.lua
運行
hive test.luathe imported l is: Yes, I am a lobbyin test.luathe imported l is: Yes, I am a lobbyin test.luathe imported l is: Yes, I am a lobbyin test.lua...
在另外一個終端打開并修改lobby.lua
文件,保存
--lobby.luastr = 'No!I am not a lobby!'
原終端中的輸出發生改變
the imported l is: No, I am not a lobby!in test.luathe imported l is: No, I am not a lobby!in test.luathe imported l is: No, I am not a lobby!
沙盒環境
使用import導入的文件,Hive會為之創建了一個沙盒環境這使得各個文件中的變量名不會沖突。
下面的例子說明了import函數的行為——
-- lobby.luafunction m_add(a,b)return a + bend function m_sub(a,b)return a - bendreturn 1123
在test.lua
中引入該文件:
l = import('lobby.lua')print(type(l))for k,v in pairs(l) doprint(k,':',v)end-- for k,v in pairs(_G) do-- print(k,':',v)-- endprint(l.m_add(1,2))print(l.m_sub(1000,2))
得到結果如下所示:可見,之前的定義都放在一個表中,且返回值被忽略了。
tablem_add : function: 0x228faa0m_sub : function: 0x229be803998
如果使用require,則有所不同
> require('lobby')1123> v = require('lobby')> m_addfunction: 0xa5c340> m_subfunction: 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);}
- 首先將用戶的代碼(#2),使用
import
函數進行加載。如果沒有錯,則繼續進入一個循環。這個import
函數,是Hive提前定義好的一個函數。后面還會介紹。 - 循環(#5)不斷地從Lua環境中獲取到Run函數的地址,然后去調用,如果獲取不到函數地址,則循環直接中止,執行完畢。
- 否則則直接進行調用獲取到的Run函數。
- 執行完畢以后,檢查
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 nothinglocal 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 thenreturn node;endlocal env = {};setmetatable(env, hive.meta);node = {env=env, fullpath=fullpath, filename=filename};hive.files[fullpath] = node;return node;endfunction import(filename)local node = get_filenode(filename);if not node.time thennode.time = hive.get_file_time(node.fullpath);try_load(node);endreturn node.env;endhive.reload = function()local now = os.time();local update = true;while update doupdate = false;for path, node in pairs(hive.files) dolocal filetime = hive.get_file_time(node.fullpath);if filetime ~= node.time and filetime ~= 0 and math.abs(now - filetime) > 1 thennode.time = filetime;update = true;try_load(node);endendendend)__";
上面的定義,是Hive在加載用戶的文件之前會調用的。
主要關注import
函數——
- 函數首先執行
get_filenode
函數。該函數首先到全局的hive.files
表中進行查找,如果找到了,則直接返回該node
,這里的node
,其實就是一個表,一個虛擬的沙箱。否則就新建一張表,這張表的元表是固定的hive.meta
,即如果在該表中無法找到,則到_G
中進行查找。這里的_G
事實上就是導入的文件的環境。 - 如果是新導入的文件,則對其進行加載即可,記錄下導入的時間(方便后面檢查是否有更新)
Hive接口手冊
除了前面章節中所提到的內容,Hive其實還有一些其他的接口暴露給了用戶。使用hive.xxx
即可訪問。
API | 作用 |
---|---|
get_version | 返回版本號 |
get_file_time | 獲取文件的修改時間 |
get_full_path | 獲取文件的絕對路徑 |
get_time_ms/get_time_ns | 獲取當前的時間(Epoch格式) |
sleep_ms | 睡眠 |
daemon | 服務作為后臺運行 |