« 当编辑器也成为游戏 | 返回首页 | 在中学100强中看到了母校的名字 »

向 lua 虚拟机传递信息

当程序逻辑交给脚本跑了以后,C/C++ 层就只需要把必要的输入信息传入虚拟机就够了。当然,我们也需要一个高效的传递方法。

以向 lua 虚拟机传递鼠标坐标信息为例,我们容易想到的方法是,定义一个 C 函数 get_mouse_pos 。当 lua 脚本中需要取得鼠标坐标的时候,就可以调用这个函数。

但这并不是一个好方法,因为每次获取鼠标坐标,都需要在虚拟机和 native code 间做一次切换。我们应该寻求更高效的方案。

编写脚本的人可以只获取一次鼠标坐标,然后把数据放进一组全局变量。在一个运行片内,不再调用 get_mouse_pos 函数,而是通过访问全局变量来得到鼠标的位置。

从这个方案,我们可以引申开,其实这个全局变量可以由 C 程序主动设置,在 native code 的运行片中,Windows 消息处理完后,直接讲鼠标信息设入 lua 虚拟机。代码类似这样的。

lua_pushstring(L,"MOUSE_X");
lua_pushnumber(L,mouse_x);
lua_settable(L,LUA_GLOBALSINDEX);

lua_pushstring(L,"MOUSE_Y");
lua_pushnumber(L,mouse_y);
lua_settable(L,LUA_GLOBALSINDEX);

但是这里,依旧存在一个效率问题,那就是 lua_pushstring 。我们知道,lua 虚拟机中,每次产生一个 string ,都需要查对 string 在虚拟机中是否存在相同的拷贝,如果存在,就直接引用已有的;如果不存在,则产生一份新的拷贝。

这里,MOUSE_X 和 MOUSE_Y 两个 string 除了第一次运行,以后都是存在于 lua 虚拟机中的,虽然不会产生新的 string,但查找和比较字符串依然会消耗一定的时间。下面,我们来优化这个 lua_pushstring 操作。

我们可以在程序开始阶段,创建出这两个 string ,并且在 C 中保留引用。
lua_pushstring(L,"MOUSE_X");
_mouse_x_ref=lua_ref(L,-1);
lua_pushstring(L,"MOUSE_Y");
_mouse_y_ref=lua_ref(L,-1);

那么,以后运行时就不需要再做 lua_pushstring 操作了,而改成相对较快的 lua_getref 操作。

lua_getref(L,_mouse_x_ref);
lua_pushnumber(L,mouse_x);
lua_settable(L,LUA_GLOBALSINDEX);

lua_getref(L,_mouse_y_ref);
lua_pushnumber(L,mouse_y);
lua_settable(L,LUA_GLOBALSINDEX);

lua_getref 之所以相对快一些,是因为 lua 对数字做 key 的 table 操作有优化处理,直接变成一次指针操作。而 ref 就是记在一张全局表中的。而且 lua_getref 不需要 lua_pushstring 做过的 strcmp 操作。

那么这个方法还没有优化余地呢?答案还是有。

我们其实可以写一个 lua 程序,放在一个单独的文件(mouse.lua)中,程序很短:

return funtion(mx,my) MOUSE_X,MOUSE_Y=mx,my end

我们在程序启动的时候运行
lua_dofile(L,"mouse.lua");
_mouse_set_ref=lua_ref(L,-1);

那么,在设置鼠标坐标的时候就可以简单的做如下操作:
lua_getref(L,_mouse_set_ref);
lua_pushnumber(L,mouse_x);
lua_pushnumber(L,mouse_y);
lua_call(L,2,0);

这个方案只需要保留一个函数的 ref ,并且把设置的工作交给了虚拟机中的伪指令。单从这个例子(仅仅 MOUSE_X,MOUSE_Y两个需要传递的信息)来看,不能说明后者的效率更高一些,毕竟 lua_call 也有额外的消耗。但是,最后一个方案更加灵活,对于native code 向虚拟机更多数据的交换采用这种方案更加简洁。

ps. lua_ref 的东西,最后要记得调用 lua_unref 解引用。

Comments

真好, 很感谢.

我写了个lua鼠标脚本,但如果速度点击鼠标,结果产生堆栈溢出,不知道有什么处理办法?

Lua 讨论群
60028358

随着这个思考的深入,我有时候甚至怀疑这样的设计是否本身就存在问题,使用脚本语言进行流程的控制的项目究竟应该是一个什么样的架构(我现在的做法是程序完成初始化后,就直接一个dofile运行一大段Lua程序,由Lua程序负责整个流程,期间除了调用C API,不回到自己写的C代码。如果这个问题1-2句话说不清楚的话,是否有什么架构不错的开源项目(最好不要太大:))或者demo可以参考?谢谢!

实际上,碰到这个问题,我首先想到的也是post到单线程来进行处理,但实际操作时发现我的Lua脚本完成的是一个耗时很长的流程(引入Lua也就是希望这个过程可配置)总控工作,这个流程走完了,我的程序也就结束了(总的感觉有点像LuaPlayer的过程)。而这期间如果发生异线程事件,即使我把消息post过来,调用L的线程也还在lua_pcall中没有返回,还在堵塞状态,根本无从recv这个消息,也更无从响应了。

更进一步来说,即使采取某种神秘的方法把事件通知给了Lua,Lua的主流程又该如何知道什么时候该把“主线程”yield,去响应这个异步事件呢?除了轮询。

抱歉公司的网络下,我无法一次性post太多的内容,只能分帖了。

我实际碰到的当然不是在GUI单线程中鼠标事件(或者我并不是在GUI线程中调用的Lua:) ),实际上win采取的就是你推荐的方法,在中断中捕捉鼠标事件,然后post到了win线程,所以更本质上说,它还是该异步事件,只不过win把它post过一次了:)

lua 默认是不能在多个线程里操作一个 L 的。在 luaconfig 里可以配置一个 lock 解决这个问题。但是我个人不推荐。

处理鼠标消息这种,用 coroutine 加单线程足够解决了。

ps. windows 的窗口模型中,也不是多线程的。如果没有特别设计,消息处理并不在独立线程中。如果你使用了独立的消息线程,可以把消息再 post 回逻辑线程中再处理。

不知道你会不会看这么旧的文章的留言,但实际上我正好碰到一个有些相关的问题。

文中你提到“Windows 消息处理完后,直接讲鼠标信息设入 lua 虚拟机”,但这个时候,“程序逻辑交给脚本跑”,也就是说这个L正在调用lua_pcall或者类似的线程中运行,而忽然在另一个线程中(win消息线程)把鼠标信息设入到了同一个L中,这显然是在两个不同的线程中使用了同一个L,Lua可以应付多线程调用的情况吗?据我所知,似乎是不行的。

刚才我写了个测试程序,做了个简单的速度测试。首先, lua_call 没有 holimion 说的那么慢。
其次,第 3 个方案跟第一个方案比,其实是只用来传递 2 个变量,速度依然可以快上 50% 到 100% 。

我的测试程序写的很简单,就是把以上代码重复执行 10000 次。这种测试方法不够客观,但是至少可以比较出速度。

传递一两个变量显然效率很低,如果是大量的数据,我想作一次函数调用还是值得的。

而且经测试,C中调用Lua函数的效率,和Lua调用C函数的效率差别非常大,后者比前者快2个数量级

调用一次lua_call的开销不是一般的大,在10000个cycle以上,最后一个办法不如第二个好,甚至连第一个都不如。

这两天正在想这个lua 通过 string name 访问优化问题,被你做掉了, i 服了 u now!

Post a comment

非这个主题相关的留言请到:留言本