【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
讯享网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 0000014F) 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
- int
- 位运算op
- utf8库
- 支持64和32位平台
Lua 5.2
- 可yield的pcall和元方法
- 新的全局变量词法规则
- 瞬表
- 位运算lib
- 轻量c func
- 紧急gc
- goto
- 表的finalizer
Lua 5.1
- mod system
- 增量式gc
- 新的vararg
- long str
- mod和len op
- 所有类型都有元表
- luaconf来配置
- 可重入parser
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/126450.html