前言我一直对Lua
了解较少,并且项目中有很多与Lua相关的要点,因此请尝试阅读Lua源代码(版本5.1)。
主题流
Lua 代码的执行可以分为两部分。
1.编译部分:
Lua 编译器将编译 Lua
代码转换成Lua字节码(本文只是简单介绍,不对其格式进行详细描述),执行Luac -L测试.lua可以直观地查看Lua编译器生成的字节码。
例如:
print('hello lua!')生成以下字节码(不是真正的字节码,是字节码的可视文本)。1
[1] GETGLOBAL 0 -1 ; 打印
2 [1] 负载K1 -2 ;“你好路亚”
3 [1] 致电 0 2 1
4 [1] 返回 0 1
可以看出,上面的printf('hello lua')被编译成4个lua字节码。
二、执行部分的解释:
Lua 解释器逐字解释字节码并相应地操作。拿上面的打印('你好lua!以执行过程为例:
第一个字节码是 GETGLOBAL,lua 类库会在全局表中查找要打印对应的函数;
第二条指令从常量表中加载 hello lua 字符串;
第三条指令调用与打印对应的函数;
第四个子句以 RETURN 结尾。
调用 print 函数时luaL_openlibs在全局表中注册,其对应的源码如下:
静态常量 luaL_Reg base_funcs[] = {
{“断言”, luaB_assert},
{“收集垃圾”, luaB_collectgarbage},
{“dofile”, luaB_dofile},
{“错误”, luaB_error},
{“gcinfo”, luaB_gcinfo},
{“getfenv”, luaB_getfenv},
{“getmetatable”, luaB_getmetatable},
{“loadfile”, luaB_loadfile},
{“load”, luaB_load},
{“loadstring”, luaB_loadstring},
{“下一个”, luaB_next},
{“pcall”, luaB_pcall},
{“打印”, luaB_print},
{“rawequal”, luaB_rawequal},
{“rawget”, luaB_rawget},
{“rawset”, luaB_rawset},
{“选择”, luaB_select},
{“setfenv”, luaB_setfenv},
{“setmetatable”, luaB_setmetatable},
{“tonumber”, luaB_tonumber},
{“tostring”, luaB_tostring},
{“type”, luaB_type},
{“解包”, luaB_unpack},
{“xpcall”, luaB_xpcall},
{空,空}
};
当 Lua 代码调用 Print 时,实际解释为执行的函数最终出现在luaB_print中。
Lua 类库的主要功能
从上面,我们已经知道 Lua 类库逐字解释 Lua 字节码并相应地操作。
它的核心功能是luaV_execute,它的源代码是:
对于 (; ;){
const 指令 i = *pc++;
StkId ra;
ra = RA(i);
开关 (GET_OPCODE(i)) {
案例OP_GETGLOBAL:{
电视 g;
TValue *rb = KBx(i);
sethvalue(L, &g, cl->env);
lua_assert(ttisstring(rb));
Protect(luaV_gettable(L, &g, rb,ra));
继续;
案例OP_LOADK:{
setobj2s(L游戏源码 lua, ra, KBx(i));
继续;
。这里省略了 10,000 行....
从上面的代码可以看出,Lua 类库循环获取 Lua 操作码,然后调用相应的 C 函数进行具体操作。
例如 GETGLOBAL,调用luaV_gettable从全局表中获取数据。
闭 包
至此,Lua实现过程的介绍甚至已经完成。让我们简要看一下 lua 闭包(这些是 C/C++ 没有的功能)。Lua 闭包的实现比较复杂,涉及 Lua 编译器和协程,这里举一个简单的例子。
函数 A()
局部 a = 1
函数 B()
a = a+1
返回一个
结束
返回 B
结束
局部 c = A()
打印 (c())
打印 (c())
输出自然是2,3。经常挠C的人可能会认为,A函数返回后,本地a就会出栈无效,除非在A返回之前复制到一个特殊的地方。
路亚
实现机制类似,Lua 会对 upval 做特殊处理(upval 是一个闭包变量,当 B 函数访问外部函数变量 a 时生成一个 upval),在解释OP_CLOSURE指令的执行时,会在 luaState 中的 openupval 数组中添加一个新的 upval。
在返回 A 函数之前,链表中的节点引脚指向 a 变量,当 A 返回时,
upval 引脚将被调整为指向数组节点本身,因此闭包变量虽然存储在 luaState 中,但在函数返回时不会被破坏。
要理解闭包,需要更仔细地阅读 Lua 编译器部分的代码,这里只是一个介绍,Lua 编译器在编译 Lua 代码时,编译后的每个函数都会生成一个 Proto 对象,这个对象有 upvalues 成员,还有一个成员 nups,upvalues 是一个链表,用来存储函数引用的外部变量, 这些信息将在编译阶段获得。
比如上面的代码编译时,nups=1 在 Proto 中由 B 函数生成,索引的变量寄存器的值也存储在 upvalues 链表中(指向 A 函数的变量 a),然后,编译完 B 函数后,会生成OP_CLOSURE字节码, 并根据原型信息生成相应的OP_MOVE(OP_GETUPVAL)字节码。具体细节可以看到OP_CLOSURE字节码相关的处理过程,这里就不赘述了。
协程
协程是轻量级线程,轮询的实现实际上是在luaV_execute中由特殊的返回值控制的,下面是一个简单的lua代码来说明执行流程:
local co = coroutine.create(function ()
打印(“X”)
屈服
完)
coroutine.resume(co)
编译 coroutine.create 会在全局表中查找协程,最后找到 create 对应的函数,lua 源码对应如下:
静态常量 luaL_Reg co_funcs[] = {
{“创建”, luaB_cocreate},
{“简历”, luaB_coresume},
{“运行”, luaB_corunning},
{“状态”, luaB_costatus},
{“wrap”, luaB_cowrap},
{“yield”, luaB_yield},
{空,空}
};
。
LUALIB_API int luaopen_base (lua_State *L) {
base_open(L);
luaL_register(L, “协程”, co_funcs);
返回 2;
。
static int luaB_cocreate (lua_State *L) {
lua_State *NL = lua_newthread(L);luaL_argcheck(L,lua_isfunction(L游戏源码 lua, 1
) && !lua_iscfunction(L, 1), 1,
“预期的 Lua 函数”);
lua_pushvalue(L, 1); /* 将函数移动到顶部 */
lua_xmove(L, NL, 1); /* 将函数从 L 移动到 NL */
返回 1;
从上面的代码可以看出,在创建轮询时,会创建一个 luaState,并且 lua 调用堆栈的信息保存在 luaState 中,
并且可以通过切换luaState来切换lua调用栈,从而实现轮询。
总结
本文简要介绍了 Lua 代码执行流、闭包和轮询的实现机制。后续文章将介绍 Lua 编译器执行流程、Lua 调试器原理等,谢谢。
关于游牧品质西山居质量测试
+团队是金山西山居工作室的专业质量团队,在游戏领域拥有超过18年的测试经验,专注于为手机游戏和终端游戏提供各种自动化测试。
Quality test+团队主要提供崩溃信息采集与跟踪、远程移动调试服务、自动化客户端/服务器性能优化服务、项目全面的客户端/服务器测试服务。
按住指纹
文章描述HTML达到蛇源码,酷炫界面疗效,点击开始游戏,通过鼠标上下左右按钮,操作连接方向,代码备注详解,一目了然。
1. 实现蛇 1.1 界面设计
以下是
主界面,其实是动态的,下面是图片,所以看不到疗效html5 的游戏源码下载,主要以游戏为主,有四个常用链接,点击即可开始游戏。
点击开始游戏后,就可以玩贪吃蛇了,相信大家都玩过了,这是通过键盘的上下左右键盘来控制蛇头的方向,吃掉漂亮的猎物。
如果挑战失败,将给出友好的提醒,游戏将重新启动。这就是游戏的全过程,代码很简单html5 的游戏源码下载,我们一起来看看动态的疗效。
1.2 界面动态效能
因为GIF资源最多只能上传5M,所以记录了一个一般的效果,从游戏开始到游戏结束,循环播放。
1.3 接口主代码
这是主界面的代码,其他JS+CSS代码,见下面的源码下载
xcLeigh - 趣玩贪吃蛇
2. 资源目录
源代码下载
贪吃蛇游戏的html实现(源代码) 点击下载