Unity中我们要加载Lua文件调用的函数是:LuaState类中的DoFile("xxx").我们可以看到流程是:
LuaState:DoFile()->LuaState:LoadFileBuffer()->LuaFileUtils:ReadFile()->FindFile()
public void DoFile(string fileName)
{
byte[] buffer = LoadFileBuffer(fileName);
fileName = LuaChunkName(fileName);
LuaLoadBuffer(buffer, fileName);
}
byte[] LoadFileBuffer(string fileName)
{
#if UNITY_EDITOR
if (!beStart)
{
throw new LuaException("you must call Start() first to initialize LuaState");
}
#endif
byte[] buffer = LuaFileUtils.Instance.ReadFile(fileName);
if (buffer == null)
{
string error = string.Format("cannot open {0}: No such file or directory", fileName);
error += LuaFileUtils.Instance.FindFileError(fileName);
throw new LuaException(error);
}
return buffer;
}
public virtual byte[] ReadFile(string fileName)
{
if (!beZip)
{
string path = FindFile(fileName);
byte[] str = null;
if (!string.IsNullOrEmpty(path) && File.Exists(path))
{
#if !UNITY_WEBPLAYER
str = File.ReadAllBytes(path);
#else
throw new LuaException("can't run in web platform, please switch to other platform");
#endif
}
return str;
}
else
{
return ReadZipFile(fileName);
}
}
public string FindFile(string fileName)
{
if (fileName == string.Empty)
{
return string.Empty;
}
if (Path.IsPathRooted(fileName))
{
if (!fileName.EndsWith(".lua"))
{
fileName += ".lua";
}
return fileName;
}
if (fileName.EndsWith(".lua"))
{
fileName = fileName.Substring(0, fileName.Length - 4);
}
string fullPath = null;
for (int i = 0; i < searchPaths.Count; i++)
{
fullPath = searchPaths[i].Replace("?", fileName);
if (File.Exists(fullPath))
{
return fullPath;
}
}
return null;
}
最后会在searchPaths的路径中进行搜索,是否存在指定的lua文件,来进行加载与否。那这个searchPaths变量在哪里初始化呢?
第一处是AddSearchPath:
我们获取lua中的package.path来初始化我们的搜索路径。
public void InitPackagePath()
{
LuaGetGlobal("package");
LuaGetField(-1, "path");
string current = LuaToString(-1);
string[] paths = current.Split(';');
for (int i = 0; i < paths.Length; i++)
{
if (!string.IsNullOrEmpty(paths[i]))
{
string path = paths[i].Replace('\\', '/');
LuaFileUtils.Instance.AddSearchPath(path);
}
}
LuaPushString("");
LuaSetField(-3, "path");
LuaPop(2);
}
第二处是LuaState类中的AddSearchPath:
InitLuaPath是在LuaState构造函数中调用的。
void InitLuaPath()
{
InitPackagePath();
if (!LuaFileUtils.Instance.beZip)
{
#if UNITY_EDITOR
if (!Directory.Exists(LuaConst.luaDir))
{
string msg = string.Format("luaDir path not exists: {0}, configer it in LuaConst.cs", LuaConst.luaDir);
throw new LuaException(msg);
}
if (!Directory.Exists(LuaConst.toluaDir))
{
string msg = string.Format("toluaDir path not exists: {0}, configer it in LuaConst.cs", LuaConst.toluaDir);
throw new LuaException(msg);
}
AddSearchPath(LuaConst.toluaDir);
AddSearchPath(LuaConst.luaDir);
#endif
if (LuaFileUtils.Instance.GetType() == typeof(LuaFileUtils))
{
AddSearchPath(LuaConst.luaResDir);
}
}
}
lua中我们是使用require来进行加载其他lua文件的,首先require的伪代码实现如下:
function require(name)
if not package.loaded[name] then
local loader = findloader(name)
if loader == nil then
error("unable to load module" .. name)
end
package.loaded[name] = true
local res = loader(name)
if res ~= nil then
package.loaded[name] = res
end
end
return package.loaded[name]
end
lua源码(在loadlib.c中):
1.首先是初始化require搜索路径流程
static const luaL_Reg pk_funcs[] = {
{"loadlib", ll_loadlib},
{"searchpath", ll_searchpath},
#if defined(LUA_COMPAT_MODULE)
{"seeall", ll_seeall},
#endif
/* placeholders */
{"preload", NULL},
{"cpath", NULL},
{"path", NULL},
{"searchers", NULL},
{"loaded", NULL},
{NULL, NULL}
};
static const luaL_Reg ll_funcs[] = {
#if defined(LUA_COMPAT_MODULE)
{"module", ll_module},
#endif
{"require", ll_require},
{NULL, NULL}
};
static void createsearcherstable (lua_State *L) {
static const lua_CFunction searchers[] =
{searcher_preload, searcher_Lua, searcher_C, searcher_Croot, NULL};
int i;
/* create 'searchers' table */
lua_createtable(L, sizeof(searchers)/sizeof(searchers[0]) - 1, 0);
/* fill it with predefined searchers */
for (i=0; searchers[i] != NULL; i++) {
lua_pushvalue(L, -2); /* set 'package' as upvalue for all searchers */
lua_pushcclosure(L, searchers[i], 1);
lua_rawseti(L, -2, i+1);
}
#if defined(LUA_COMPAT_LOADERS)
lua_pushvalue(L, -1); /* make a copy of 'searchers' table */
lua_setfield(L, -3, "loaders"); /* put it in field 'loaders' */
#endif
lua_setfield(L, -2, "searchers"); /* put it in field 'searchers' */
}
/*
** create table CLIBS to keep track of loaded C libraries,
** setting a finalizer to close all libraries when closing state.
*/
static void createclibstable (lua_State *L) {
lua_newtable(L); /* create CLIBS table */
lua_createtable(L, 0, 1); /* create metatable for CLIBS */
lua_pushcfunction(L, gctm);
lua_setfield(L, -2, "__gc"); /* set finalizer for CLIBS table */
lua_setmetatable(L, -2);
lua_rawsetp(L, LUA_REGISTRYINDEX, &CLIBS); /* set CLIBS table in registry */
}
LUAMOD_API int luaopen_package (lua_State *L) {
createclibstable(L);
luaL_newlib(L, pk_funcs); /* create 'package' table */
createsearcherstable(L);
/* set paths */
setpath(L, "path", LUA_PATH_VAR, LUA_PATH_DEFAULT);
setpath(L, "cpath", LUA_CPATH_VAR, LUA_CPATH_DEFAULT);
/* store config information */
lua_pushliteral(L, LUA_DIRSEP "\n" LUA_PATH_SEP "\n" LUA_PATH_MARK "\n"
LUA_EXEC_DIR "\n" LUA_IGMARK "\n");
lua_setfield(L, -2, "config");
/* set field 'loaded' */
luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE);
lua_setfield(L, -2, "loaded");
/* set field 'preload' */
luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_PRELOAD_TABLE);
lua_setfield(L, -2, "preload");
lua_pushglobaltable(L);
lua_pushvalue(L, -2); /* set 'package' as upvalue for next lib */
luaL_setfuncs(L, ll_funcs, 1); /* open lib into global table */
lua_pop(L, 1); /* pop global table */
return 1; /* return 'package' table */
}
2.require的源码
static int ll_require (lua_State *L) {
const char *name = luaL_checkstring(L, 1);
lua_settop(L, 1); /* LOADED table will be at index 2 */
lua_getfield(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE);
lua_getfield(L, 2, name); /* LOADED[name] */
if (lua_toboolean(L, -1)) /* is it there? */
return 1; /* package is already loaded */
/* else must load package */
lua_pop(L, 1); /* remove 'getfield' result */
findloader(L, name);
lua_pushstring(L, name); /* pass name as argument to module loader */
lua_insert(L, -2); /* name is 1st argument (before search data) */
lua_call(L, 2, 1); /* run loader to load module */
if (!lua_isnil(L, -1)) /* non-nil return? */
lua_setfield(L, 2, name); /* LOADED[name] = returned value */
if (lua_getfield(L, 2, name) == LUA_TNIL) { /* module set no value? */
lua_pushboolean(L, 1); /* use true as result */
lua_pushvalue(L, -1); /* extra copy to be returned */
lua_setfield(L, 2, name); /* LOADED[name] = true */
}
return 1;
}
static void findloader (lua_State *L, const char *name) {
int i;
luaL_Buffer msg; /* to build error message */
luaL_buffinit(L, &msg);
/* push 'package.searchers' to index 3 in the stack */
if (lua_getfield(L, lua_upvalueindex(1), "searchers") != LUA_TTABLE)
luaL_error(L, "'package.searchers' must be a table");
/* iterate over available searchers to find a loader */
for (i = 1; ; i++) {
if (lua_rawgeti(L, 3, i) == LUA_TNIL) { /* no more searchers? */
lua_pop(L, 1); /* remove nil */
luaL_pushresult(&msg); /* create error message */
luaL_error(L, "module '%s' not found:%s", name, lua_tostring(L, -1));
}
lua_pushstring(L, name);
lua_call(L, 1, 2); /* call it */
if (lua_isfunction(L, -2)) /* did it find a loader? */
return; /* module loader found */
else if (lua_isstring(L, -2)) { /* searcher returned error message? */
lua_pop(L, 1); /* remove extra return */
luaL_addvalue(&msg); /* concatenate error message */
}
else
lua_pop(L, 2); /* remove both returns */
}
}
根据源码知道,将所有的loader:
1.searcher_preload
搜索:package.preload 表中是否有指定lua
2.searcher_Lua
搜索:path 路径中是否有指定lua
3.searcher_C
搜索路径:cpath路径中是否有指定lua
4.searcher_Croot
搜索路径:cpath路径中是否有指定lua
都加入到了searchers表中,然后require(ll_requre)调用的时候,会先判断这个lua是否加载过,如果没有加载过那么就会调用
findloader,这个函数会遍历searchers表,依次在上面的4个函数中去找寻是否有对应的lua文件。
根据2我们知道require是遍历所有loader函数来所有lua文件,于是tolua中在searchers中加入了loader函数来进行搜索我们的lua文件,源码如下:
LuaState构造函数->ToLua.OpenLibs(L)->AddLuaLoader
static void AddLuaLoader(IntPtr L)
{
LuaDLL.lua_getglobal(L, "package");
LuaDLL.lua_getfield(L, -1, "loaders");
LuaDLL.tolua_pushcfunction(L, Loader);
for (int i = LuaDLL.lua_objlen(L, -2) + 1; i > 2; i--)
{
LuaDLL.lua_rawgeti(L, -2, i - 1);
LuaDLL.lua_rawseti(L, -3, i);
}
LuaDLL.lua_rawseti(L, -2, 2);
LuaDLL.lua_pop(L, 2);
}
[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
static int Loader(IntPtr L)
{
try
{
string fileName = LuaDLL.lua_tostring(L, 1);
fileName = fileName.Replace(".", "/");
byte[] buffer = LuaFileUtils.Instance.ReadFile(fileName);
if (buffer == null)
{
string error = LuaFileUtils.Instance.FindFileError(fileName);
LuaDLL.lua_pushstring(L, error);
return 1;
}
if (LuaConst.openLuaDebugger)
{
fileName = LuaFileUtils.Instance.FindFile(fileName);
}
if (LuaDLL.luaL_loadbuffer(L, buffer, buffer.Length, "@"+ fileName) != 0)
{
string err = LuaDLL.lua_tostring(L, -1);
throw new LuaException(err, LuaException.GetLastError());
}
return 1;
}
catch (Exception e)
{
return LuaDLL.toluaL_exception(L, e);
}
}
可以看到这个loader函数最后会调用LuaFileUtils.Instance.ReadFile(fileName);来找寻要加载的文件,于是根据1中的介绍,我们知道也就是要在searchPaths中根据路径来找对应的lua文件。