天地之灵

置顶随笔

[置顶]Lua.js,一个Lua2JS编译器,一个新的JS运行环境下的Lua方案

眼见为实,耳听为虚,先上live demo:
http://luajs.org

其实现在在js环境中运行lua代码的方案已经很多了,这些方案大都分为两类:
VM on VM:在js环境中移植或重新实现一个Lua VM。典型的代表是lua.vm.js和MoonShineJS。 这种方案的优势在于完整还原了整个标准lua,但主要缺陷在于,原本通过虚拟机执行的Lua VM效率就不理想,再通过JS运行,效率就更打一层折扣。
离线处理型:使用离线工具链将Lua语言翻译成JS。如LLVM-Lua 加上javascript backend,还有一个名为lua2js的项目。这个方案的优势在于能做一些较为深入的优化,缺点主要在于必须离线处理,不能直接在web上执行,或者体积庞大,以至于本身加载都需要较长时间。除此以外,只能离线处理 导致了字符串执行(如dostring等)的不可能实现,还会影响Lua中的package结构。

Lua.js采用一个不同的方案,它将lua代码转变为一个AST树,经过一系列的转换函数,最后产出一个合乎js标准的AST树,随后生成一个合法的js代码。因此转换后直接执行的代码,比VM on VM要快2-5倍,高效的执行速度有助于让你的应用或游戏展现流畅的极致体验。

另外,压缩后的Lua.js难以置信的小。这里是当前lua.js和lua.vm.js的对比:
文件大小 传输大小(gz压缩)
lua.js 22.7K 8.3K
lua.vm.js 638K 203K

尽管当前lua.js还有一些功能没有实现(如metatable、Lua标准库等),但可以预见全部实现后的尺寸也不会有大幅的增长,基本上不会超过30K/12K。

再谈论性能,这里使用了lua.vm.js官方的几个benchmark代码(稍做修改使得可以在当前版本的lua中运行)(lua.js和lua.vm.js均在chrome下运行),结果如下:

lua.js lua.vm.js lua 5.2.3 luaJIT 2.0.3 Scale What it measures
Binary Trees 8.526s 10.198s 4.006s 0.731s seconds (lower numbers are better) GC Performance
Scrimark 26.98 8.84 30.52 1249.73 MFLOPS (higher numbers are better) numeric computation performance

在当前版本中,GC仅仅比lua.vm.js略快,这是因为现在lua.js对于table模拟还处于较为原型的阶段,没有经过充足的优化。但即使这样也比lua.vm.js更快
而在数值计算上,性能远超lua.vm.js,逼近官方lua,这同样是在lua.js还没有经过充足优化的前提下。经过优化,完全有超过官方Lua的可能性。

lua.js的benchmark可以在http://luajs.org上找到,lua.vm.js的benchmark在 http://kripken.github.io/lua.vm.js/lua.vm.js.html

如果你对lua.js感兴趣,在这里可以下载独立的js文件:
https://github.com/tdzl2003/lua.js
lua.js尚处于不完善状态,如果你决定尝试使用,务必关注本项目的更新,及时替换更新的版本!

posted @ 2014-11-05 20:17 天地之灵 阅读(9715) | 评论 (2)编辑 收藏

2014年11月6日

Lua.js 与标准Lua的差异

error(e)
Will throw a `Error` object containing the error message.
load (ld [, source [, mode [, env]]])
Only ld and env has meaning now.
dofile/loadfile, io.open, io.close, os.tmpname, os.remove, os.rename
Not supported cause there's no build-in file system supported for javascript. You can reimplement these functions on specific environment.
package.searchers, package.config, package.path, package.cpath, package.loadlib, package.searchpath
No build-in searchers for Lua files. You can reimplement searcher on specific environment. Mostly use package.preload instead.
No C loader/C lib. Use inline javascript instead.
next (table, index)
Not supported. use pairs instead.
pairs(t)
Will be supported next version, but cannot check table changes while iterating.
Will be slow if you call pairs but not iterate on it.
xpcall(f, msgh[, arg1, ...])
The stack was rewinded when msgh is called. So you cannot do anything like traceback the stack or read local variables.
string.*
There's encoding issues with lua.js now. Mostly, string.* API will use unicode instead of ANSI.
string.find, string.format, string.gmatch, string.gsub, string.match
Pattern will be supported in future. string.find with "plain = true" is usable.
os.execute, os.exit, io.popen, os.getenv
No process supported in lua.js.
os.setlocale
Not supported.
coroutine.*
The whole coroutine lib will not be supported for a long time. The browser context cannot support this feature.
debug.*
The whole debug lib is not supported now. But maybe some of them will be supported in future.
metatable:
__gc was not supported.
__mode was not supported.

posted @ 2014-11-06 05:07 天地之灵 阅读(459) | 评论 (0)编辑 收藏

2014年11月5日

Lua.js,一个Lua2JS编译器,一个新的JS运行环境下的Lua方案

眼见为实,耳听为虚,先上live demo:
http://luajs.org

其实现在在js环境中运行lua代码的方案已经很多了,这些方案大都分为两类:
VM on VM:在js环境中移植或重新实现一个Lua VM。典型的代表是lua.vm.js和MoonShineJS。 这种方案的优势在于完整还原了整个标准lua,但主要缺陷在于,原本通过虚拟机执行的Lua VM效率就不理想,再通过JS运行,效率就更打一层折扣。
离线处理型:使用离线工具链将Lua语言翻译成JS。如LLVM-Lua 加上javascript backend,还有一个名为lua2js的项目。这个方案的优势在于能做一些较为深入的优化,缺点主要在于必须离线处理,不能直接在web上执行,或者体积庞大,以至于本身加载都需要较长时间。除此以外,只能离线处理 导致了字符串执行(如dostring等)的不可能实现,还会影响Lua中的package结构。

Lua.js采用一个不同的方案,它将lua代码转变为一个AST树,经过一系列的转换函数,最后产出一个合乎js标准的AST树,随后生成一个合法的js代码。因此转换后直接执行的代码,比VM on VM要快2-5倍,高效的执行速度有助于让你的应用或游戏展现流畅的极致体验。

另外,压缩后的Lua.js难以置信的小。这里是当前lua.js和lua.vm.js的对比:
文件大小 传输大小(gz压缩)
lua.js 22.7K 8.3K
lua.vm.js 638K 203K

尽管当前lua.js还有一些功能没有实现(如metatable、Lua标准库等),但可以预见全部实现后的尺寸也不会有大幅的增长,基本上不会超过30K/12K。

再谈论性能,这里使用了lua.vm.js官方的几个benchmark代码(稍做修改使得可以在当前版本的lua中运行)(lua.js和lua.vm.js均在chrome下运行),结果如下:

lua.js lua.vm.js lua 5.2.3 luaJIT 2.0.3 Scale What it measures
Binary Trees 8.526s 10.198s 4.006s 0.731s seconds (lower numbers are better) GC Performance
Scrimark 26.98 8.84 30.52 1249.73 MFLOPS (higher numbers are better) numeric computation performance

在当前版本中,GC仅仅比lua.vm.js略快,这是因为现在lua.js对于table模拟还处于较为原型的阶段,没有经过充足的优化。但即使这样也比lua.vm.js更快
而在数值计算上,性能远超lua.vm.js,逼近官方lua,这同样是在lua.js还没有经过充足优化的前提下。经过优化,完全有超过官方Lua的可能性。

lua.js的benchmark可以在http://luajs.org上找到,lua.vm.js的benchmark在 http://kripken.github.io/lua.vm.js/lua.vm.js.html

如果你对lua.js感兴趣,在这里可以下载独立的js文件:
https://github.com/tdzl2003/lua.js
lua.js尚处于不完善状态,如果你决定尝试使用,务必关注本项目的更新,及时替换更新的版本!

posted @ 2014-11-05 20:17 天地之灵 阅读(9715) | 评论 (2)编辑 收藏

2013年11月10日

pomelo实战填坑记 后端坑之server "..." "..." register master failed

使用最新的pomelo,新增服务器类型后出现错误提示:
server "account-server-1" "account" register master failed
阅读讨论帖后发现,是新版添加了adminServer.json,需要在这里添加每种服务器类型的token(默认创建的只有connector)
[
    {
        "type": "connector",
        "token": "aga...xn"
    },
    {
        "type": "account",
        "token": "aga...xn"
    }
]

添加account类型以后(token随便填,自己生成个足够长的字符串填进去即可)
不同服务器用相同的token也可以,取决于你的安全性要求程度。

根据官方的描述,在单机部署时,是可以没有这个文件的,但是如果要分布式部署,必须有这个文件,当做不同服务器间(主要是master和其它服务器的monitor组件)之间通讯的验证串来使用。

posted @ 2013-11-10 15:23 天地之灵 阅读(3023) | 评论 (0)编辑 收藏

pomelo实战填坑记 前端坑之Buffer is not defined

实战pomelo过程中,自己重新进行component build之后发现输出的网页在浏览器端报错:

Uncaught ReferenceError: Buffer is not defined

阅读代码后分析如下:
模块 pomelo-protocol 的代码,试图兼容node.js与浏览器端,其区分方法是 判断module是否是一个object。
('object' === typeof module ? module.exports : (this.Protocol = {}),'object' === typeof module ? Buffer : Uint8Array, this);

在node.js中,module是一个object,而在浏览器端,早期版本的component实现 把module的函数自身作为最后一个参数(命名为module)
  if (!module.exports) {
    module.exports = {};
    module.client = module.component = true;
    module.call(this, module.exports, require.relative(resolved), module);
  }

所以typeof(module)得到的是一个function。

但是随着component的更新,component改变了这个特性:
  if (!module._resolving && !module.exports) {
    var mod = {};
    mod.exports = {};
    mod.client = mod.component = true;
    module._resolving = true;
    module.call(this, mod.exports, require.relative(resolved), mod);
    delete module._resolving;
    module.exports = mod.exports;
  }

可以看到最后一个参数现在是一个新创建出来的Object,所以现在在浏览器上,pomelo-protocol也认为现在正在node.js环境中,于是就报错了。

一个临时的workaround办法是,在require("promelo-protocol")之前,先准备好Buffer,代码如下:

window.Buffer = Uint8Array;
var protocol = require('pomelo-protocol');
window.Protocol = protocol;
delete window.Buffer;
这样问题暂时消除了。当然,最靠谱的办法还是在pomelo-protocol中修改识别环境的办法。稍后我会向pomelo提交pull-request帮助解决这一问题。

posted @ 2013-11-10 11:17 天地之灵 阅读(7717) | 评论 (0)编辑 收藏

2013年8月11日

我们是Seed Engine研发团队,我们是最接近dev的reseacher(节选)

(我们)团队里的每个人都非常重视 迅速对一知识点寻找最低成本的方式迅速付诸实践的能力—— 我们需要知道它是做什么的,该怎么用,有何优势与劣势,但不需要知道每个细节。

每个不同的项目,我们都有可能迅速地接触一两个全新的技术并尝试之,而不用早早的就把其中一个技术的全部细节当做自己终身的事业。

我们认为,对未知东西的迅速了解、分析能力是非常重要的,这也可以认为是我们团队的天分所在。我们不会花太多时间去仔细研读一个开源项目的源代码,相反,我们迅速的阅读完它提供的tutorial,查看社区与搜索结果,和很多已经用过它们及用过同类项目的人讨论,熟悉它的风格与用法,猜测它的整体结构,思考它能做什么,不能做什么,擅长做什么,不擅长做什么,再然后,通过api reference扫描寻找我们所需要的东西。

我们崇拜并膜拜那些发明了JIT、Defered Shading的人,崇拜并膜拜boost、tbb、lfds、LuaJIT、V8、Qt、libuv、mongodb、redis等项目的贡献者。我认为把他们(及它们)的牛逼之处在我们的产品们中充分展现是我们能为他们做的最有意义的事情。

我们会尝试不断的改进我们所用的每一个技术,优化细节,通过更优雅的编码使其更具扩展性,提高性能或降低用户犯错的可能,毫无疑问我们要做相当多reseacher的工作。只是有一点,我们不发明技术本身。

(我们的)主要工作内容之一就是关注项目(以及其它人项目)的每一个过程,找出其中任何一个导致任何人工作缓慢或阻塞的部分,只要是通过技术手段可以改善的,我们都会通过我们的努力去解决它们。

这就是我们,Seed Engine团队。立志以最快的速度,做最友好的游戏引擎。我们的技术未必超前,但永不落后!

和我们有相同想法的人们,欢迎你加入我们!

posted @ 2013-08-11 19:37 天地之灵 阅读(988) | 评论 (2)编辑 收藏

2013年4月12日

Seed Engine中的输入系统(一)、底层架构、历史

今天看了叛大的 KlayGE中输入系统的改进系列文章,觉得可以谈谈Seed Engine中的输入系统。
因为Seed Engine诞生之初,就定位为主要面向Android、iOS等移动设备(直到2012年6月才有了Flash平台的内部版本,才开始正式需求鼠标事件),所以Seed的输入模块除了对键盘做了简单支持(主要是出于调试目的)外,在很长的一段时间内,鼠标事件都是被wrap成为触摸事件。所有后续关于输入的工作都是针对触摸来做的。所以这篇文章主要重点讲触摸这个方面,尤其是手势识别的做法。
在Seed发展至今的期间,面临了大量复杂的需求,不断改进完善,目前还有很多不尽善尽美的地方。这篇文章会回顾一下这个过程,重点介绍一下当前的处理机制,再展望一下将来期望进行的改进工作。

Seed目前总计支持四种输入:键盘、鼠标、触摸、重力感应。
所有的输入会被响应的处理模块封装成一个struct,这个struct的头四个字节表明了触摸的类型。在单线程模式下,这个struct会被直接传递给应用。在多线程模式下,这个struct会在堆上分配,将指针传递给逻辑线程。这样的好处是在逻辑线程忙的时候,消息处理函数可以更及时返回,避免诸如窗口拖动卡顿之类的问题。
这一部分极其简单,因为简单,所以不容易出问题。之所以把底层架构放在历史之前谈,正是因为自从2011年10月份Seed诞生以来,引擎的这部分代码几乎从未变过。
对于Lua层来说,这部分的接口一直表现为一个全局事件,input.key、input.touch等等,参数struct会被iLuaWrapper(Seed中一个神秘组件)包装成Lua可以直接访问的数据,由Lua脚本去做任何上层的处理。

裸奔时代

Seed诞生以后的一段时间内,当时的工作重心在完善2D渲染基础、2D场景管理,2D物理等等较为繁杂的模块上。因为没有具体项目的负担,当时的输入模块只以满足调试需求、实现简单交互为目的。因此,Seed在2011年10月以前,一直处在输入裸奔的状态。譬如只用键盘做操控,那就直接注册input.key去监听需要的事件。为了实现诸如简单的按钮之类的功能,当时产生了一堆垃圾代码,在事件里直接判定坐标等等。当然,这样写代码是不可能做出没有BUG的游戏来的,于是我们在一个游戏项目的原型阶段结束后不久的时间,迅速推出了第一套框架。

山寨时代

因为需求紧迫,做任何游戏至少都少不了做一堆能tap的button出来,所以我们抓紧推出了第一版的input_ex插件。
input_ex以尽可能简单的方式实现了对指定对象的触摸事件处理。场景中的node可以被注册到input_ex中,以接受tap、hold、drag事件。在touchdown的时候,input_ex会遍历所有注册的结点以找到被命中的node,之后的消息都会派发给这个node。
好吧,至少现在可以创建一堆不同的button了。input_ex的严重不足主要体现在功能上:
        1、input_ex中的手写状态机,几乎决定了除了tap、hold、drag以外,每加一个新的操作种类都是一个巨大的困难。
        2、input_ex不能很好的处理多点触摸。在多点的设备上各种出问题,以至于后来在某些项目里,强行屏蔽了除了1号手指(对应安卓里的0号手指)以外的所有操作。
        3、消息最开始就确定了对象,随后的过程不能改变消息的对象。譬如一个scrollview上面有一堆button,那么当button截获了消息的时候,scrollview就无法处理相应的拖拽事件了。
除此以外,在使用上,每个节点存在一个独立的用于处理输入的对象,其生命周期需要手动管理,错误的使用会导致各种问题。初学者几乎很难写出正确的代码。因为实现复杂,input_ex本身也在很长的时间内都存在引用关系的BUG,导致不需要的资源不能被正确的释放。总体来说,使用input_ex插件做游戏简直是一段不堪回忆的黑历史。

山寨时代之后
在使用input_ex完成了三四个界面操作简单的小游戏后,我们开始构思新的输入系统。我们理想中的输入系统应该符合如下几个条件:
1、很好的支持多点触摸。这包含两方面:第一,必须能够很好的识别利用多个手指的操作,譬如scale, pinch, rotate等。第二,我们认为对于大屏幕的触屏设备,能让多个玩家在不同的地方互不干扰的进行多个操作也是很有必要的需求,这会给游戏设计师带来很多新奇的玩法创意。
2、一定要很方便的加入各种不同的操作识别。我们希望能够实现很多有创意的小游戏,依靠触摸的操作来做很多有意思的事情。我们也希望我们的界面能够交互起来更酷,可以操作控制的地方更多。那么操作一定不能只局限于区区数种,一定要在特定的游戏里就能通过代码添加大量不同的全新操作才行。
3、操作对象的识别更智能。在scrollview 上面的button做scroll操作时,操作对象要能正确的变成scrollview。
4、根据操作对象所接受的事件有所不同,以及其父结点所接受的事件有所不同,对同一事件的处理可能会有差别。譬如一个button接受tap,当触摸并移动的时候,只要没移开范围,逻辑应等待手指松开时再判定为tap成功(正如你在windows下按住一个按钮然后小范围拖动鼠标,click并不会因此而失败)。但假如这个button有一个父结点甚至是祖先结点接受drag,那么早在刚开始移动的时候,就应该判定为tap取消,事件转为drag事件而派发给相应的祖先结点。
5、不会为了满足上面的需求,把上层逻辑代码搞的太麻烦。最理想的情况下,上层逻辑代码只要选择好自己所接受的操作种类,然后安心等待事件监听器被调用就好了。
而我们不想要:
1、像安卓那样复杂的事件分派机制,所有的触摸都被绑在一起依次分派下去,在结点上依据类型的不同写代码去做对应的操作。我们认为应该要有一个很好的手势识别底层,仅仅把结点关心的信息抛给它。
2、像HTML DOM那样的事件冒泡机制。因为触摸处理的复杂性,在touch down的时候往往根本不能确定真正用户想要进行何种操作。而如果等操作进行完了才给予反馈,那操作过程就很难得到非常及时的反馈。在上述scrollview和button的例子里,button必须首先获得事件以立即展现被按下的效果,等到用户的操作能够明确为一个scroll操作之后,再由scrollview来处理后续的事件。再加上之前所述的期望4,已经不是简单的对同一事件的冒泡足以满足的。
真的有一套框架能完美的解决我们的需求吗?下一章起,我会逐步讲解我们为此所做的努力。

posted @ 2013-04-12 01:49 天地之灵 阅读(2269) | 评论 (0)编辑 收藏

2013年2月24日

LuaJIT之callback大坑绕路记

近期在做node.jsLuaJIT portLuaJIT是当前已知最快的脚本JIT编译器,拿来做服务器再好不过。
发现node.js底层所用的库libuv简直是个神器,包含了网络、文件系统、计时器等等一堆堆的有用功能,windows、linux、MacOS等均支持,而且是纯C的API,和LuaJIT结合会比较友好,理论上不用任何额外的C代码,依靠ffi库就可以搞定,经过试验也确实如此,于此同时发现LuaJIT也真神器也,居然可以直接把Lua函数当做C函数指针传进去当回调!正当我踌躇满志的准备跑下性能测试就开始做上层封装的时候,结果楞了:

1、Lua版的idle示例,等待一个idle事件被调用1e7(一千万)次,在C下只需要区区0.1秒,在lua下需要足足30秒多!并且内存在这个过程里猛涨猛涨再猛涨,最后的gc过程耗费了更久的时间!
    原版的在这里,Lua版的在这里
2、尝试添加1000次idle事件,LuaJIT直接报错:too many callbacks
3、其他不同的尝试均体现,性能严重不过关。

然后在ffi的说明里发现了这个,提到了几个问题:
1、callback占用某些总量有限的系统资源,所以用过的callback需要释放,并且同时存在的callback只能有500-1000个。
2、callback函数不会被自动gc,需要用一些麻烦的办法手动来释放
3、callback会很慢。文中提到了类似于lua_call的消耗及argument marshalling的消耗。这点会在下面详细讲述。

总的来说,luajit里的callback,是在内存里生成了一小段代码,这小段代码的功能是把参数转换好,然后再调用对应的lua函数。(还有一些奇奇怪怪的开销,我个人认为这才是主要开销,后面会详细讲述),因此有同时存在的总量上限(虽然我也不明白为什么就因此了,但大致就是那么回事吧),并且很慢,很慢,很,慢,很……慢……

基本上,解决方法就那么几种:
1、做一些特定的封装,用C额外编写一个函数做一些处理,在这个函数里用其他方式(lua_pcall等)去调用,这样调用参数的类型会受限一些。经测试这个只能提升50%左右(距离之前的300倍差距还差得远……),主要是还有一些关键的开销(在下面详细讲述)无法避免。
2、改写被使用的C库,拒绝回调,用其他办法实现。这是LuaJIT官方所推荐的,原文如下:
For new designs avoid push-style APIs: a C function repeatedly calling a callback for each result. Instead use pull-style APIs: call a C function repeatedly to get a new result. Calls from Lua to C via the FFI are much faster than the other way round. Most well-designed libraries already use pull-style APIs (read/write, get/put).
但像libuv这样的库,改写难度有些大……关键在于重新设计整个结构为pull-style很困难,同时会导致相关文档废弃,增加了额外的工作量。
3、小幅度改写使用的C库,公开一些必须的内容,然后把其中的一部分在lua里实现,确保所有callback调用的时机均在lua中,废弃掉原始的C API。这样相对来说不用改变任何的接口,但是工作量也不小,取决于库的复杂程度。

最终我在node.lua中选择了方案3。事实证明效果确实很好,在还有一些会带来额外开销的功能没加进去的情况下,之前的test优化到了0.08s左右,预计全部完成后开销在0.15s之内,很接近纯C实现的性能。

然后我又做了若干实验,并且在freelist里和LuaJIT的创始人Mike请教了一会,得到了一些结论:

1、回调的argument marshalling是重大瓶颈之一。虽然不知道为什么,Lua对C的调用,返回值的marshalling性能很高,我推测是由于原因3。
2、把Lua-function cast成C function pointer是另一重大瓶颈,如果存在反复的类型转换,这里会很要命。这里包含了之前所说的生成指令序列的开销,但cast本身也会具有巨大的开销,我尝试将一个C function cast成 C function pointer,都带来了极大的开销。据Mike说,这个开销也是原因3导致的
3、导致程序运行很慢的原因,归根结底:某些行为会导致JIT失效!在没有JIT的情况下,本身运行性能差不多就有几十倍的损失,再加上一些额外开销会因此被放大,最后就得到了不可接受的性能损失……

最后总结,目前应该在LuaJIT的ffi库中避免使用函数指针,使用Lua本身来封装回调函数(如果接口需要),方可获得LuaJIT提供的卓越性能。

posted @ 2013-02-24 14:36 天地之灵 阅读(19319) | 评论 (7)编辑 收藏

仅列出标题  
<2024年3月>
252627282912
3456789
10111213141516
17181920212223
24252627282930
31123456

导航

统计

常用链接

留言簿(3)

随笔档案

文章档案

搜索

最新评论

阅读排行榜

评论排行榜