【ZeloEngine】Lua源码汇总
【ZeloEngine】Lua源码汇总
本文介绍Zelo在开发过程中遇到的Lua源码层,脚本绑定的问题
脚本层问题参见《Lua脚本汇总》
Lua源码分析参见《Lua源码分析》
IO
Lua的IO功能很弱,需要引擎提供
最基本的,需要注册print
异常处理
结论
- 脚本层异常很容易处理
- C++层异常很难完善处理,而且堆栈已经没了(debugger验证)
Lua异常
- dofile,可以检查返回值,或者传入error handler,二选一即可,等价的
- protected_function.call,可以设置全局error handler
C异常
- set_exception_handler
- set_panic,几乎没用
panic可以参见《PIL》,基本是Lua C API内存爆了才会触发
其他
- 保险起见,C的ctor还是不要产生错误,只做简单初始化数值(目前没有想的很清楚)
SOL_ALL_SAFETIES_ON
默认不safe,提升运行时性能
safe API:
- 使用
lua_checkXXX
,对cast进行类型检查 - 检查ud不为nullptr
- 调用脚本使用safe版本
set_exception_handler
这是最简单的情况
C抛出std::exception
void will_throw() {
throw std::runtime_error("oh no not an exception!!!");
}
从Lua调用C函数
sol::protected_function_result pfr = lua.safe_script(
"will_throw()", &sol::script_pass_on_error);
REQUIRE(!pfr.valid());
sol::error err = pfr;
std::cout << err.what() << std::endl;
输出
// An exception occurred in a function, here's what it says (straight from the exception): oh no not an exception!!!
// oh no not an exception!!!
// stack traceback:
// [C]: in function 'will_throw'
// [string "will_throw()"]:1: in main chunk
- sol会catch到,调用exception_handler,可以把msg输出来
- pfr的error还包含lua堆栈
- script_pass_on_error什么也不做,script_throw_on_error则会抛出sol::error
try {
return f(L);
}
catch (const char* cs) {
call_exception_handler(L, optional<const std::exception&>(nullopt), string_view(cs));
}
catch (const std::string& s) {
call_exception_handler(L, optional<const std::exception&>(nullopt), string_view(s.c_str(), s.size()));
}
catch (const std::exception& e) {
call_exception_handler(L, optional<const std::exception&>(e), e.what());
}
safe_script
脚本入口,可以处理异常
auto result2 = lua.safe_script("123 bad.code",
[](lua_State *, sol::protected_function_result pfr) {
// pfr will contain things that went wrong, for
// either loading or executing the script the user
// can do whatever they like here, including
// throw. Otherwise...
sol::error err = pfr;
std::cerr << "An error (an expected one) occurred: "
<< err.what() << std::endl;
return pfr;
});
测试
dofile,脚本语法错误
[22:25:22.467] [lua-boot] [error] failed to dofile C:/Users/zoloypzuo/Documents/GitHub/ZeloEngine\Script\Lua\boot.lua
C:/Users/zoloypzuo/Documents/GitHub/ZeloEngine\Script\Lua\boot.lua:66: unexpected symbol near '1'
dofile,脚本运行时错误
[22:27:30.594] [lua-boot] [error] failed to dofile C:/Users/zoloypzuo/Documents/GitHub/ZeloEngine\Script\Lua\boot.lua
...pzuo/Documents/GitHub/ZeloEngine\Script\Lua\boot.lua:66: test error
stack traceback:
[C]: in function 'error'
...pzuo/Documents/GitHub/ZeloEngine\Script\Lua\boot.lua:66: in main chunk
注册C类型
比如Vector3类型,是典型的需求:
- 数据,xyz
- 函数,dot,length等
- 运算符,±
C类型通过元表CTypeMT来注册给Lua,Lua脚本里Vector.new,是注册的一个函数,函数用lua分配内存,然后placement new调用Vector3构造函数,完成ud构造
对ud的所有操作,都是注册在CTypeMT的函数,数据对应Get/Set函数,普通函数就是函数,运算符是元方法
注册C函数
注册Agent:ApplyForce(Vector3 force)
userdata和light userdata
目前是不用light userdata的
userdata的流程:
- C++ class Foo注册给lua
- lua脚本new Foo,lua分配内存,管理gc
light userdata则是C++分配内存和构造,一般是对应裸指针,Zelo不用这种用法
检测参数是否是字符串字面量
拼了一个空串,这样编译就会报错了
#define lua_pushliteral(L, s) \
lua_pushlstring(L, "" s, (sizeof(s)/sizeof(char))-1)
require源码分析
之前用ogre的时候,脚本要封装成资源,所以要重写require
但是Zelo目前用一种简单的方法去处理,不需要改require
不过还是扒了一下源码,确保没有问题
默认注册的loader,lua和C dll
static const lua_CFunction loaders[] =
{loader_preload, loader_Lua, loader_C, loader_Croot, NULL};
package模块初始化loaders,注册require
LUALIB_API int luaopen_package (lua_State *L) {
/* create new type _LOADLIB */
/* create `package' table */
/* create `loaders' table */
lua_createtable(L, 0, sizeof(loaders)/sizeof(loaders[0]) - 1);
/* fill it with pre-defined loaders */
for (i=0; loaders[i] != NULL; i++) {
lua_pushcfunction(L, loaders[i]);
lua_rawseti(L, -2, i+1);
}
lua_setfield(L, -2, "loaders"); /* put it in field `loaders' */
setpath(L, "path", LUA_PATH, LUA_PATH_DEFAULT); /* set field `path' */
setpath(L, "cpath", LUA_CPATH, LUA_CPATH_DEFAULT); /* set field `cpath' */
/* store config information */
lua_pushliteral(L, LUA_DIRSEP "\n" LUA_PATHSEP "\n" LUA_PATH_MARK "\n"
LUA_EXECDIR "\n" LUA_IGMARK);
return 1; /* return 'package' table */
}
require伪代码
local function require (name)
if not package.loaded[name] then -- module not loaded yet?
local loader = findloader(name)
if loader == nil then
error("unable to load module " .. name)
end
package.loaded[name] = true -- mark module as loaded
local res = loader(name) -- initialize moduleloader
if res ~= nil then
package.loaded[name] = res
end
end
return package.loaded[name]
end
ll_require
遍历loader,做一个fallback
static int ll_require (lua_State *L) {
const char *name = luaL_checkstring(L, 1);
int i;
lua_settop(L, 1); /* _LOADED table will be at index 2 */
lua_getfield(L, LUA_REGISTRYINDEX, "_LOADED");
lua_getfield(L, 2, name);
if (lua_toboolean(L, -1)) { /* is it there? */
if (lua_touserdata(L, -1) == sentinel) /* check loops */
luaL_error(L, "loop or previous error loading module " LUA_QS, name);
return 1; /* package is already loaded */
}
/* else must load it; iterate over available loaders */
lua_getfield(L, LUA_ENVIRONINDEX, "loaders");
if (!lua_istable(L, -1))
luaL_error(L, LUA_QL("package.loaders") " must be a table");
lua_pushliteral(L, ""); /* error message accumulator */
for (i=1; ; i++) {
lua_rawgeti(L, -2, i); /* get a loader */
if (lua_isnil(L, -1))
luaL_error(L, "module " LUA_QS " not found:%s",
name, lua_tostring(L, -2));
lua_pushstring(L, name);
lua_call(L, 1, 1); /* call it */
if (lua_isfunction(L, -1)) /* did it find module? */
break; /* module loaded successfully */
else if (lua_isstring(L, -1)) /* loader returned error message? */
lua_concat(L, 2); /* accumulate it */
else
lua_pop(L, 1);
}
lua_pushlightuserdata(L, sentinel);
lua_setfield(L, 2, name); /* _LOADED[name] = sentinel */
lua_pushstring(L, name); /* pass name as argument to module */
lua_call(L, 1, 1); /* run loaded module */
if (!lua_isnil(L, -1)) /* non-nil return? */
lua_setfield(L, 2, name); /* _LOADED[name] = returned value */
lua_getfield(L, 2, name);
if (lua_touserdata(L, -1) == sentinel) { /* module did not set a 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;
}
LUA_USE_APICHECK
默认关闭,开启后对lapi进行assert检查
适合查C API问题
我觉得有必要打开
分析
包装了两个函数
#define api_checkvalidindex(l,o) api_check(l, isvalid(o), "invalid index")
#define api_checkstackindex(l, i, o) \
api_check(l, isstackindex(i, o), "index not in the stack")
static TValue *index2addr (lua_State *L, int idx) {
CallInfo *ci = L->ci;
if (idx > 0) {
TValue *o = ci->func + idx;
api_check(L, idx <= ci->top - (ci->func + 1), "unacceptable index");
if (o >= L->top) return NONVALIDVALUE;
else return o;
}
else if (!ispseudo(idx)) { /* negative index */
api_check(L, idx != 0 && -idx <= L->top - (ci->func + 1), "invalid index");
return L->top + idx;
}
else if (idx == LUA_REGISTRYINDEX)
return &G(L)->l_registry;
else { /* upvalues */
idx = LUA_REGISTRYINDEX - idx;
api_check(L, idx <= MAXUPVAL + 1, "upvalue index too large");
if (ttislcf(ci->func)) /* light C function? */
return NONVALIDVALUE; /* it has no upvalues */
else {
CClosure *func = clCvalue(ci->func);
return (idx <= func->nupvalues) ? &func->upvalue[idx-1] : NONVALIDVALUE;
}
}
}
引用
Code | File | Line | Column |
---|---|---|---|
#define api_check(l, e, msg) luai_apicheck(l,(e) && msg) | llimits.h | 101 | 1 |
#define api_checkvalidindex(l,o) api_check(l, isvalid(o), “invalid index”) | lapi.c | 54 | 35 |
api_check(l, isstackindex(i, o), “index not in the stack”) | lapi.c | 57 | 2 |
api_check(L, idx <= ci->top - (ci->func + 1), “unacceptable index”); | lapi.c | 64 | 5 |
api_check(L, idx != 0 && -idx <= L->top - (ci->func + 1), “invalid index”); | lapi.c | 69 | 5 |
api_check(L, idx <= MAXUPVAL + 1, “upvalue index too large”); | lapi.c | 76 | 5 |
api_check(L, n >= 0, “negative ‘n’”); | lapi.c | 101 | 3 |
api_check(from, G(from) == G(to), “moving among independent states”); | lapi.c | 123 | 3 |
api_check(from, to->ci->top - to->top >= n, “stack overflow”); | lapi.c | 124 | 3 |
api_check(L, idx <= L->stack_last - (func + 1), “new top too large”); | lapi.c | 176 | 5 |
api_check(L, -(idx+1) <= (L->top - (func + 1)), “invalid new top”); | lapi.c | 182 | 5 |
api_check(L, (n >= 0 ? n : -n) <= (t - p + 1), “invalid ‘n’”); | lapi.c | 213 | 3 |
api_check(L, LUA_TNONE <= t && t < LUA_NUMTAGS, “invalid tag”); | lapi.c | 259 | 3 |
default: api_check(L, 0, “invalid option”); | lapi.c | 329 | 16 |
api_check(L, n <= MAXUPVAL, “upvalue index too large”); | lapi.c | 541 | 5 |
api_check(L, ttistable(t), “table expected”); | lapi.c | 651 | 3 |
api_check(L, ttistable(t), “table expected”); | lapi.c | 662 | 3 |
api_check(L, ttistable(t), “table expected”); | lapi.c | 675 | 3 |
api_check(L, ttisfulluserdata(o), “full userdata expected”); | lapi.c | 728 | 3 |
api_check(L, ttistable(o), “table expected”); | lapi.c | 807 | 3 |
api_check(L, ttistable(o), “table expected”); | lapi.c | 822 | 3 |
api_check(L, ttistable(o), “table expected”); | lapi.c | 836 | 3 |
api_check(L, ttistable(L->top - 1), “table expected”); | lapi.c | 855 | 5 |
api_check(L, ttisfulluserdata(o), “full userdata expected”); | lapi.c | 891 | 3 |
api_check(L, (nr) == LUA_MULTRET || (L->ci->top - L->top >= (nr) - (na)), \ | lapi.c | 905 | 6 |
api_check(L, k == NULL || !isLua(L->ci), | lapi.c | 913 | 3 |
api_check(L, L->status == LUA_OK, “cannot do calls on non-normal thread”); | lapi.c | 916 | 3 |
api_check(L, k == NULL || !isLua(L->ci), | lapi.c | 954 | 3 |
api_check(L, L->status == LUA_OK, “cannot do calls on non-normal thread”); | lapi.c | 957 | 3 |
api_check(L, ttistable(t), “table expected”); | lapi.c | 1128 | 3 |
api_check(L, ttisLclosure(fi), “Lua function expected”); | lapi.c | 1260 | 3 |
api_check(L, (1 <= n && n <= f->p->sizeupvalues), “invalid upvalue index”); | lapi.c | 1262 | 3 |
api_check(L, 1 <= n && n <= f->nupvalues, “invalid upvalue index”); | lapi.c | 1276 | 7 |
api_check(L, 0, “closure expected”); | lapi.c | 1280 | 7 |
#define api_incr_top(L) {L->top++; api_check(L, L->top <= L->ci->top, \ | lapi.h | 14 | 38 |
#define api_checknelems(L,n) api_check(L, (n) < (L->top - L->ci->func), \ | lapi.h | 20 | 30 |
api_check(L, ttisfunction(func), “function expected”); | ldebug.c | 319 | 5 |
api_check(L, k == NULL, “hooks cannot continue after yielding”); | ldo.c | 707 | 5 |
#define api_check(l,e,msg) luai_apicheck(l,(e) && msg) | llimits.h | 101 | 9 |
Lua版本
有几种选择:
- lua5.1,最老
- lua5.4,最新
- luajit,最快
我选择lua5.1,因为最熟悉这个版本的源码
而且为了快,可以迁移到luajit
lua5.1 vs lua5.3
Zelo开发中遇到的问题:
- lua5.1不支持位运算符
- 接口改了,unpack和table.unpack
完整差异对比清单
- table 可以设置 __gc 元表函数
- 添加了统一的环境表 _ENV 并取消了之前的函数环境表概念
- 移除了模块定义函数module推荐模块使用简单的 table 来实现
- 支持了 goto 指令
- 支持了数字整型类型,并且默认是64bit有符号整型
- 支持了 bit 操作符,可以对整型数字进行 bit 操作
- 基础的 utf8 支持
- string 库中支持了打包和解包的函数:string.pack, string.unpack
更多参考:
云风的 BLOG: 从 Lua 5.2 迁移到 5.3
云风的 BLOG: Lua 5.3 升级注意
hello world汇编
lua5_1_4_Compiler -l hello_world.lua
main <hello_world.lua:0,0> (4 instructions, 16 bytes at 000001EA73DF6390)
0+ params, 2 slots, 0 upvalues, 0 locals, 2 constants, 0 functions
1 [1] GETGLOBAL 0 -1 ; print
2 [1] LOADK 1 -2 ; "hello world"
3 [1] CALL 0 2 1
4 [1] RETURN 0 1
lua5_3_5Compiler -l hello_world.lua
main <hello_world.lua:0,0> (4 instructions at 0000014F71127680)
0+ params, 2 slots, 1 upvalue, 0 locals, 2 constants, 0 functions
1 [1] GETTABUP 0 0 -1 ; _ENV "print"
2 [1] LOADK 1 -2 ; "hello world"
3 [1] CALL 0 2 1
4 [1] RETURN 0 1
Lua ChangeLog
Lua 5.3
Lua 5.3 was released on 12 Jan 2015. Its main new features are integers, bitwise operators, a basic utf-8 library, and support for both 64-bit and 32-bit platforms.
The current release is Lua 5.3.5, released on 10 Jul 2018.
- int
- 位运算op
- utf8库
- 支持64和32位平台
Lua 5.2
Lua 5.2 was released on 16 Dec 2011. Its main new features are yieldable pcall and metamethods, new lexical scheme for globals, ephemeron tables, new library for bitwise operations, light C functions, emergency garbage collector, goto statement, and finalizers for tables.
The last release was Lua 5.2.4, released on 07 Mar 2015. There will be no further releases of Lua 5.2.
- 可yield的pcall和元方法
- 新的全局变量词法规则
- 瞬表
- 位运算lib
- 轻量c func
- 紧急gc
- goto
- 表的finalizer
Lua 5.1
Lua 5.1 was released on 21 Feb 2006. Its main new features were a new module system, incremental garbage collection, new mechanism for varargs, new syntax for long strings and comments, mod and length operators, metatables for all types, new configuration scheme via luaconf.h, and a fully reentrant parser.
The last release was Lua 5.1.5, released on 17 Feb 2012. There will be no further releases of Lua 5.1.
- mod system
- 增量式gc
- 新的vararg
- long str
- mod和len op
- 所有类型都有元表
- luaconf来配置
- 可重入parser
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)