【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;
    }
  }
}
引用
CodeFileLineColumn
#define api_check(l, e, msg) luai_apicheck(l,(e) && msg)llimits.h1011
#define api_checkvalidindex(l,o) api_check(l, isvalid(o), “invalid index”)lapi.c5435
api_check(l, isstackindex(i, o), “index not in the stack”)lapi.c572
api_check(L, idx <= ci->top - (ci->func + 1), “unacceptable index”);lapi.c645
api_check(L, idx != 0 && -idx <= L->top - (ci->func + 1), “invalid index”);lapi.c695
api_check(L, idx <= MAXUPVAL + 1, “upvalue index too large”);lapi.c765
api_check(L, n >= 0, “negative ‘n’”);lapi.c1013
api_check(from, G(from) == G(to), “moving among independent states”);lapi.c1233
api_check(from, to->ci->top - to->top >= n, “stack overflow”);lapi.c1243
api_check(L, idx <= L->stack_last - (func + 1), “new top too large”);lapi.c1765
api_check(L, -(idx+1) <= (L->top - (func + 1)), “invalid new top”);lapi.c1825
api_check(L, (n >= 0 ? n : -n) <= (t - p + 1), “invalid ‘n’”);lapi.c2133
api_check(L, LUA_TNONE <= t && t < LUA_NUMTAGS, “invalid tag”);lapi.c2593
default: api_check(L, 0, “invalid option”);lapi.c32916
api_check(L, n <= MAXUPVAL, “upvalue index too large”);lapi.c5415
api_check(L, ttistable(t), “table expected”);lapi.c6513
api_check(L, ttistable(t), “table expected”);lapi.c6623
api_check(L, ttistable(t), “table expected”);lapi.c6753
api_check(L, ttisfulluserdata(o), “full userdata expected”);lapi.c7283
api_check(L, ttistable(o), “table expected”);lapi.c8073
api_check(L, ttistable(o), “table expected”);lapi.c8223
api_check(L, ttistable(o), “table expected”);lapi.c8363
api_check(L, ttistable(L->top - 1), “table expected”);lapi.c8555
api_check(L, ttisfulluserdata(o), “full userdata expected”);lapi.c8913
api_check(L, (nr) == LUA_MULTRET || (L->ci->top - L->top >= (nr) - (na)), \lapi.c9056
api_check(L, k == NULL || !isLua(L->ci),lapi.c9133
api_check(L, L->status == LUA_OK, “cannot do calls on non-normal thread”);lapi.c9163
api_check(L, k == NULL || !isLua(L->ci),lapi.c9543
api_check(L, L->status == LUA_OK, “cannot do calls on non-normal thread”);lapi.c9573
api_check(L, ttistable(t), “table expected”);lapi.c11283
api_check(L, ttisLclosure(fi), “Lua function expected”);lapi.c12603
api_check(L, (1 <= n && n <= f->p->sizeupvalues), “invalid upvalue index”);lapi.c12623
api_check(L, 1 <= n && n <= f->nupvalues, “invalid upvalue index”);lapi.c12767
api_check(L, 0, “closure expected”);lapi.c12807
#define api_incr_top(L) {L->top++; api_check(L, L->top <= L->ci->top, \lapi.h1438
#define api_checknelems(L,n) api_check(L, (n) < (L->top - L->ci->func), \lapi.h2030
api_check(L, ttisfunction(func), “function expected”);ldebug.c3195
api_check(L, k == NULL, “hooks cannot continue after yielding”);ldo.c7075
#define api_check(l,e,msg) luai_apicheck(l,(e) && msg)llimits.h1019

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.

  1. int
  2. 位运算op
  3. utf8库
  4. 支持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.

  1. 可yield的pcall和元方法
  2. 新的全局变量词法规则
  3. 瞬表
  4. 位运算lib
  5. 轻量c func
  6. 紧急gc
  7. goto
  8. 表的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.

  1. mod system
  2. 增量式gc
  3. 新的vararg
  4. long str
  5. mod和len op
  6. 所有类型都有元表
  7. luaconf来配置
  8. 可重入parser
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值