Main

August 18, 2010

继续完善 protobuf 库

又仔细推敲了两天,把 lua 版的 protobuf 库完善了一下。主要是做了两个工作:

  1. protobuf 本身的格式,google 是自描述的。定义为 google.protobuf.descriptor 。我先自己实现的 parser 图方便,用了自己的中间交换格式。为了日后更通用,稍微修改了一下,可以生成于官方相同的结构。解析的性能稍有下降,不过应该兼容性更好。

  2. 一开始实现的 api 虽然性能非常好。(经简单测试,是 python 库的 30~40 倍,和 C++ 库性能相当)但若消息格式复杂,实现起来稍有麻烦。所以我做了点小封装。即为每个消息生成一对函数,可以用来打包和解包完整的消息,映射到 lua 的 table 结构上。lua 生成代码供自己调用的技巧在 lua 社区广泛使用。比如 kepler 项目。这使得 lua 可以用很短的代码行数完成很复杂的工作,不失性能。我这个封装层只有 100 行代码左右,一大半代码是为了解决消息展开时有递归定义的情况,否则更简短。(message 中有一些 field 的类型是自己,这是一种不多见的用法,但 protobuf 似乎并没有拒绝这种用法)

August 11, 2010

Proto Buffers in Lua

Google 的 Jeff Dean 同学说,设计分布式系统一定要有 Protocol Description Language

Google Proto Buffers 的意义在于,定义了一个不错的 PDL 。protobuffers 的实现反而不那么重要了。

这几天我一直在倒腾 lua 下的 proto buffers 的支持。一直在思考,怎样的接口才是最适合 lua 使用的。

大多数语言下的 proto buffers 实现,都是将编码的数据块展开成本地语言的数据结构。对于 C/C++ ,这是最高效的形式。但对于动态语言,那就未必了。虽然 google 为 python 做的 proto buffers 的官方实现也是如此,但我依然想考虑一下,是否有更高效的方式来做这件事。

June 09, 2010

采访 Lua 发明人的一篇文章

《Masterminds of Programming: Conversations with the Creators of Major Programming Languages》是本相当不错的书。博文翻译出版了这本书,中文名叫做《编程之魂》。

书是好书,可惜翻译这本书需要对各种语言的深入研究,看起来译者有点力不从心。出版社打算重新做这本书。受编辑所托,我校对了其中第七章:有关 Lua 的一段。原文读下来拍案叫好。可惜译文许多地方看起来有些词不达意。许多在口语化交流中提到的术语被忽略了做了错误的翻译。有些部分应该是对 lua 理解不够而没能表达清楚。

仔细校对了两段后,我干脆放弃原译本,自己动手翻译了一份(保留了不到 1/4 原来的译文)。虽然个人能力有限,但也算是每句话自己都看明白了再译的。虽说有些地方没有直译,但也算没有夹带私货。

这里贴出一段,希望大家阅读愉快。

May 27, 2010

共享 lua state 中的数据

今天和倩女幽魂的同事讨论一个问题:他们的游戏 client 中,有大量策划填写的表格直接导入 lua state 中的大量数据。大约有 100M 以上。这样,如果玩家在一台机器上启动多个 client ,就会占用大量的内存。

而这些数据,一旦加载进 lua ,就不会再修改,且每个 client 中数据都是一致的,这是一种浪费。

问题是:如何利用进程间的数据共享,在多开 client 时节省这些空间。(同时也可以加快开第二个 client 的启动速度)

January 12, 2010

Lua 5.2.0 (work1)

[ANN] Lua 5.2.0 (work1) now available ,这个消息有几天了。lua 社区这两天非常热闹,各大牛都现身了。

做 LuaJIT 的牛人 Mike Pall 对 bit 库没有采用他做好的现成方案那可是相当的不客气

不过,欢呼雀跃的人还是比较多的。每次 lua 升级个小版本,改动都非常大。对成熟项目,不给你伤点筋骨,那就不是 lua 三巨头的风格了。当然,对于时不时重写代码的我,欣赏这种风格 ;) 我喜欢更健康的 lua 语言。

嗯,无论如何,lua 的源代码是非常值得阅读的。

November 01, 2009

luajit 这次终于扬眉吐气了

几个月前, Mike Pall 就在 lua 的 mailling list 里叫嚣他的 luajit 2.0 用的新算法将会大幅度提升性能。还记得 Soloist 同学当初就是眉飞色舞的跟我说这个事。所以 luajit 2.0 还真是万众期待啊。至少 lua 社区的人都等着呢。

昨晚,Mike Pall 同学终于放出了 beta 版。那个性能测试结果真是很吓人啊。

ps. 我昨天刚把项目里的几个命令行工具用 lua 改写,并把 srlua 加到了 makefile 框架里。嗯,可以考虑做一个带 jit 的 srlua 。

October 15, 2009

C/C++ 与 Lua 的混合编程

这个是我将在今年 SD 2.0 大会上进行的议题的演示稿。最近太忙了,国庆期间在家写的草稿。

C/C++ 与 Lua 的混合编程

有兴趣的同学可以看看,没兴趣的请无视。

另外我会作为嘉宾参加 SD 大会的一个所谓算法论坛。还不知道谈些啥呢。

May 26, 2009

lua 中判断字符串前缀

一个 lua 的小技巧

在写 lua debugger 的时候,我需要判断一个字符串的前缀是不是 "@" 。

有三个方案:

  1. 比较直观的是 string.sub(str,1,1) == "@"
  2. 感觉效率比较高的是 string.byte(str) == 64
  3. 或者是 string.find(str,"@") == 1

我推荐第三种。(注:在此特定运用环境下。因为用于判定 source 的文件名,大多数情况都是 @ 开头。如果结果为非,则性能较低)

第一方案 string.sub 会在生成子串的时候做一次字符串 hash ,感觉效率会略微低一些。

第二方案效率应该是最好,但是需要记住 @ 的 ascii 码 64 。如果前缀是多个字符也不适用。

May 25, 2009

lua 调试器制作注意

前两年写过一个 lua 的调试器,blog 上有截图

不过调试器设计的关键不在于界面,在于调试协议。前两年的那个是设计的不完整的。

最近同事强烈要求引擎提供一个强力的调试工具,虽然我个人不太依赖调试去写代码。甚至认为,经过反复调试才正确工作的代码不是好代码。不过周末还是花了点时间重新制作了一个 lua 调试器。

中间发现一些问题,非常让人吐血。列在这里,做个记录。

May 10, 2009

树结构的管理

要写过多少代码才能得到哪怕一点真谛?

多少年过来,我在潜意识的去追求复杂的东西。比如我自幼好玩游戏,从小到大,一直觉得玩过的游戏过于简单(无论是电子游戏还是桌面游戏),始终追寻更复杂规则的游戏,供我沉浸进去。或许是因为,有了更高的理解和控制复杂度的能力,就可以更为轻松的驾御复杂性。

这很好的解释了 2000 年到 2004 年我对 C++ 的痴迷。还有对设计模式的迷恋。

Eric S. Raymond 说:尽量不要去想一种语言或操作系统最多能做多少事情,而是尽量去想这种语言或操作系统最少能做的事情——不是带着假想行动,而是从零开始。禅称为“初心”(beginner's mind)或者叫“虚心”(empty mind) 。

代码写多了,问题见过了,甚至是同一问题解决多了。模式这种东西自在心底,不必拿出来。时时的从零去想,总能重新明白一些道理。

为什么说语言重要也不重要,算法和数据结构重要也不重要。对要解决的问题的领域的理解很重要(即明白真正要做什么)。理解了,我们才可以用面向对象,用模式去套问题;可理解了,我们又不真的需要这些繁杂的抽象。

闲话放一边,今天想谈谈树结构的管理。

May 06, 2009

回顾 Forth

第一件事就是没有钩子。不要留一个接口,想着未来的什么时候当问题变化时插入一些代码,因为问题会以你不能预见的方式变化。反正这个成本肯定是浪费了。不要预测,只解决你眼前的问题。by Charles Moore (Forth 之父)

今天也是机缘巧合,莫名其妙的翻出老资料温习 Forth 了。我想是个心结吧。19 年前,我痴迷于 Forth ,只看到了皮毛;13 年前,我进入大学的第一年,在校图书馆借出的第一本书,就是《Forth 语言》,读书笔记写了 20 多页。

只到今天,我才有机会,有能力,去仔细探究 Forth 的深层思想。当然,由于时间有限,几个小时的阅读,也只算是初窥门径。原本是想研究下 Forth 的系统实现,对同事正在设计的 3d 粒子系统,提供一些建议的。

碰巧又读到 Charles Moore 在 99 年的访谈稿 1x Forth ,颇多感慨。题头那段话,我在一周前刚好苦口婆心的对一同事说过,只差几个字而已。

May 03, 2009

树型打印一个 table

php 中有个 print_r 函数,可以递归打印一张表。很多 php 程序员喜欢用这个去调试程序。

我想,所有写过一定代码量的 lua 程序员都会写一个类似的东西放着备用吧。这两天调试 lua 程序的时候,发现以前做的简陋的 print_r 不够好用。对于复杂的 table 打印出来一大篇很不直观。结果就放下手头的工作,花了整整一个小时,写了下面几十行代码。把 table 输出成树结构。

比如:

a = {}

a.a = { 
    hello = { 
        alpha = 1 ,
        beta = 2,
    },
    world =  {
        foo = "ooxx",
        bar = "haha",
        root = a,
    },
}
a.b = { 
    test = a.a 
}
a.c = a.a.hello

print_r(a)

可以输出成:

+a+hello+alpha [1]
| |     +beta [2]
| +world+root {.}
|       +bar [haha]
|       +foo [ooxx]
+c {.a.hello}
+b+test {.a}

May 01, 2009

在 lua 中实现函数的重载

警告:记录以下内容纯粹自娱,请勿轻易用于项目。我个人也不赞同随意使用语法糖去改造语言。

我们知道 C++ 里有函数重载的特性,程序员可以为一个看起来同名的函数做多份实现,让编译器通过调用时的参数类型去指定链接器链接最为匹配的一份实现。对于死忠的 C++ 程序员,这绝对是最必不可少的利器。如果没有它,那些 template 绝对玩不出现在这么多花来,当然也就没那么多机会拿着“充满智慧” 花哨的 template 代码来 YY 自己的智商了。

哦,写 lua 的所谓脚本程序员不要沮丧,其实 lua 中可玩的花样也很多。一样可以写出让同行瞠目结舌的代码来。比如这个函数重载的问题,虽然 lua 不可能做所谓编译期运算(动态生成代码或许勉强算一个),也没有什么静态链接过程。

但 lua 是个有趣的语言,下面看我怎么模拟出一个类似的东西来。

April 06, 2009

为 lua 插件提供一个安全的环境

wow 开放了一套用户自定义的插件系统,很多人都认为,这套系统是 wow 成功的因素之一。反观国内乃至韩国的网游,至今没有一款游戏能提供相当自由度的用户自定义插件系统。

最开始,暴雪是想让用户可以由用户甚至第三方自定义操作界面。后来,这套基于 XML 和 lua 的插件系统不仅仅用来做界面了。

从我在网游行业从业这么多年的经验,游戏界面相关的开发也是颇费人力的。甚至于,Client 开发到了维护期,几乎都是在 UI 上堆砌人工。一套自由的插件系统,对于开发商和用户是双赢的。

但是,插件系统也是双面剑。最典型的负面问题就是安全。越是自由,越是给机器人制作开放了自由之门。这里暂且不提这个方面的问题。首先关注一下另一个:尽可能的保护系统中不想被插件系统访问的数据,避免利用插件编写木马。

March 28, 2009

安全的迭代一个集合

把同质的东西放入一个容器,然后用迭代器迭代这个容器,把里面的内容逐个取出来处理。这是一个非常常见的需求。但是,这个过程往往也会滋生 bug 。因为,若将容器看成一个对象,那么对其迭代的这个操作很难实现原子性。

非原子性导致了,在迭代过程中,十分有可能对容器本身进行修改。或增加若干元素,或删除若干元素。这些都容易造成迭代过程不正常。

所以,最终我们需要根据需求设计以及实现合理的容器。比如管理消息的消息队列,严格的满足尾进头出,没有删除中间数据的需求,就不会导致 bug 。

那么,如果容器是一个集合怎么办?即,允许向其中增加新的元素,也可以移除某些元素。这种数据结构非常有用。比如向某对象注册若干回调函数,一旦满足条件则依次调用。即设计模式中的 Observer 观察者模式。回调函数就极有可能增加新的观察者或某些老的观察者退出。

March 12, 2009

为 lua 封装 C 对象的生存期管理问题

把 C 里的对象封装到 lua 中,方便 lua 程序调用,是很常见的一项工作。

里面最大的问题是生命期管理问题。

通常有两种方案:

第一:编写 C 库的时候,完全针对 lua 设计,所有对象都有 lua_newuserdata 分配内存。对象和对象之间的联系可以使用 userdata 的 环境表,把对象间的引用放在里面,使得 lua 的 gc 过程可以正常进行。

第二:给 C 对象简单加一个壳。lua 的 userdata 中仅仅保存 C 对象指针。然后给 userdata 设置 gc 元方法,在被回收时,正确调用 C 对象的销毁函数。

以上两种方案都依赖 lua 的 full userdata ,这里,我想提供第三种方案,仅使用 lightuserdata 完成这项工作。

这第三方案未必比前两种都好。虽然从字面上理解 light userdata 比 full userdata 更廉价,但诚如 pil 中所言,full userdata 也非过于重量。

最终的方案选择还是要结合实际的设计,仔细考量。

March 07, 2009

降低 lua gc 的开销

周末有同事问我一个问题,说他们猜测在他们系统里 lua 的垃圾回收过程导致了系统开销过大。而其中有些开销是无谓的。

比如在他们的系统中,有大量的结构化数据是只读的,不会和其它数据产生联系。大多为策划设定的游戏逻辑参数。而偏偏这部分数据结构复杂,在 lua 的 gc 过程中会产生大量的遍历。但我们明明知道,这些数据一定不会被回收掉,且不会影响 gc 的结果。那么有什么方法可以优化呢?

October 31, 2008

给 Lua 增加参数类型描述

Lua 的函数定义是没有参数类型信息的。这些信息在跨语言的模块化设计中非常有价值。因为跨语言的方法调用通常需要做列集(Marshaling) 的操作,缺乏类型信息很难完成这个工作。同样的需求在做 RPC 调用的时候也很重要。

感谢 Lua 简洁的 metatable 的设计,我们可以用一个简单的方法自然的描述出 Lua 函数的调用参数类型,又无损性能。

一开始,我们先回顾一下在 Pil 中推荐的类实现方法。通常我们可以把方法列表(对应于 C++ 中的虚表)放在一个 table 中,然后把这个 table 作为一个 metatable 的 __index 方法,并把 metatable 附加到一个 table 上,就生成了简单的对象。我喜欢这样做:

August 15, 2008

让 lua 编译时计算

lua 里其实也颇多奇技淫巧,使用时应三思。

如果你读过 kepler 的代码,就会发现,多次编译这种技巧用的很多,甚至迭代几次使用。即,第一次加载代码时,用一段 lua 程序生成真正需要的源代码,然后再将其编译出来。

由于 lua 的编译速度相当快,而且这种迭代编译的过程仅仅在程序加载的时候进程一次,故而可以带来性能的提高:一些在系统初始化时可以决定的参数(比如从配置文件中读出来的数据)直接编译为常量置入程序中。

云风写了一小段 lua 程序,简化这种迭代编译的过程,算作周末自娱吧。

例如这样一个例子,我们在程序中需要用一个常量,这个常量可能是通过加载配置文件得到。假设允许编译期计算,我们可以这样:

function foo()
    for i=1,| config "max" | do
        execute(i)
    end
end

这个例子里,循环的终值是通过调用 config "max" 得到的,如果每次运行这个程序时都去查询 config 必然影响效率。我们需要在程序加载时,第一次得到 config "max" 的结果即可。

这里,我使用 | 夹在中间 | 表示这段代码应该在加载时运行。

有点像 php 写网页用的模版?呵呵,可以接着往下看。

August 13, 2008

Lua 不是 C++

嗯,首先,此贴不是牢骚帖。

话题从最近私人的一点工作开始。应 dingdang 的建议,我最近在帮 大唐无双 做一些程序上的工作。接手做这件事情,是因为这个内部被我们称作 dt2 的游戏 engine 关系重大。公司有至少四个项目在使用(另外三个暂处于研发期,尚未公布)。

dt2 用了大量的 lua 代码构建系统,但从系统设计上,沿袭了老的大唐的许多代码。原来的大唐是用 C++ 构建的,为了利用上这些代码(虽然我觉得这种复用非常无意义,但是其中原因复杂,就不展开谈了),dt2 engine 的开发人员做了一套非常复杂的中间层,把 lua 和 C++ 几乎无缝的联系在了一起。

May 09, 2008

The Implementation of Lua 5.0 中译

读者向海飞给我 email 了一份他翻译的《The Implementation of Lua 5.0》这篇 paper。原文可以在 lua.org 上下载

这篇由 lua 作者们写的 paper 对理解 lua 非常有帮助。有兴趣的朋友在 这里下载译文

译文最后附有译者的 email 大家可以直接向他反馈。

March 22, 2008

感觉好多了

其实我并没有用 lua 亲手写过什么大规模的项目。超过 5 千行代码的项目几乎都是 C 或是 C++ 写的。这几天算是做了点复杂的玩意了。几经修改和删减,最后接近完工的这个东西统计下来不多不少 3000 行(误差在十位数)。其中用 C 编写了基础模块 900 多行(仅仅是 socket api 的封装和 byte 流的编码解码),剩下的都是用 lua 设计并实现的。

好吧,我承认 2000 多行代码也只是一个小东西。不过用 lua 实现的一个 wiki 系统,sputnik 还不到 2000 行呢。lua 有一种特质,用的久了就容易体会到。它和 python ruby 这些更为流行的动态语言是不同的。曾经,我把选择 lua 的理由,肤浅的停留在了更轻便更高效上,虽然这些也很重要,但抓住语言的特质才是更关键的。

March 15, 2008

基于 lua 的热更新系统设计要点

很久没写 blog 了,主要是忙。项目排的很紧,一个小项目上线,发现不少问题,所以把多余精力都投进去了。最后人手不够,亲自上场写代码。在不动大体的情况下,最大能力的修改了一些设计,并把能重写的代码重新写过了。

这一周,写了三天半程序(其中通宵了一次)。平均每天写了千多行程序。基本上把原来项目里用的几万行东西替换下来了。重构总是能比老的设计更好,不是么? :) 不过这事不能老干,个人精力再充沛。故而还是找到称心的合作人比较好,也不能啥都自己做啊。程序员的效率差别可不只十倍二十倍的差别这么少。btw, 前段时间通过这里找到的新同事不错,呵呵。如果有缘,还想再找一个能干的。我相信,聪明人在一起做事可以获得更多快乐。

闲话扯到这里,总结下这两天做项目的一点经验。那就是:当我们需要一个 7*24 小时工作的系统时,实现热更新需要注意的要点。当然,这次我用的 lua 做实现,相信别的动态语言也差不多。只是我对 lua 最为熟悉,可以快速开发。

October 18, 2007

在 Lua 中管理 C 对象

今天同事在设计引擎的脚本接口时遇到一个问题:需要把 C 对象指针放到 Lua 中,允许 Lua 保存这个指针,并传递给其它模块。

这是给 Lua 写 C 扩展时常见的问题,撇开如何如何将对象的方法导入 Lua 这个更复杂的问题不谈,我主要想说说 C 对象的生命期管理的问题。

一开始的设计是把对象的销毁方法也导入 Lua ,由脚本程序员手工管理。这是很明显的 C 程序员的思路:谁构造谁释放。但在这里是不合适的,不符合带 gc 机制语言的习惯。

我们当然希望脚本更为健壮,不需要考虑对象释放的问题。所以晚上我想了一下,修改了一下这部分的实现。

September 06, 2007

玩了一下 ajax

起因是这样的:

几个同事在棋牌群里聊天,说找不到搭档打桥牌。网上也没啥好地方去,大家都比较讨厌下客户端和注册。我说,不如我做一个免客户端免注册的桥牌网站吧。然后就开始了。

直觉告诉我,ajax 技术可以实现这些。但是我没做过 web 方面的开发,仅有的一些知识只在几年前写过一个 php 留言本。一开始觉得 ajax 这些时髦玩意学一下午,然后一通宵就可以把想要的东西做出来。哪知道,结果不务正业干了半周了,中间还熬了两晚上,到今天都没做完。明天要出差,只好放一放了。

June 17, 2007

如何在 Lua 注册表中选择一个合适的 Key

Lua 提供了一个注册表(REGISTRY)让我们的 C 扩展可以安全的把一些运行时数据放进去,而不被 lua 代码碰到。为了让各个 C 扩展库之间可以相安无事的工作,并且对注册表的操作又有较高效率。Lua 大神 Roberto 在神作 Pil 里给出了一个简洁的方案:用 static 变量的地址作为 key

静态变量在当前进程中一定拥有惟一的地址,且 lightuserdata 作 key 非常高效。这无疑是一个好方法。

但是,当模块的源码规模变大了以后,我们将代码分散到不同的源文件中。或者几个子模块需要相互协作时。这个方法就有了一定的缺陷。那就是,必须将这个静态变量暴露出来供大家蹂躏;或是写一个内部函数来取得它(其实没有本质区别)。

如果你也碰到这类问题,不妨看看下面的解决方案。

June 12, 2007

魔兽世界的影响力

晚上一个小朋友在 gtalk 上问我编程语言专注哪一门好。当然这不是一个简单能回答的问题。尤其对刚上大学的小朋友不太好解释清楚。

不过睡觉前我还是八卦了一下当今世界编程语言的流行程度排名。查了下 TIOBE 的排行榜。首先映入眼帘的加黑的头条:Lua only 0.003% away from top 20 position

不得不感叹魔兽世界的影响力啊。(同事语: wow 让阿猫阿狗都开始写 lua 程序了)回想 05 年的时候,Lua 可是排在 70 多位的。

我们在 01 年底为大话西游2 选择一门嵌入式脚本语言的时候,考察了 lua python java javascript ruby 等许多开源动态语言(java 是个例外,而且 java 不开源,但还是可以找到一些 JVM 的开源代码)。最后定下 lua ,其中一个原因就是它不太为人所知。反逆向工程可能可以方便一点,真没想到今天会是这个局面。(当然,那个时候 python 和 ruby 在国内用的人也相当的少)

是金子总会闪光的 :D

May 04, 2007

正确的向 WinProc 传递 lua_State 指针

在 Windows 下写一些关于窗口的程序时,如果在软件中嵌入 lua ,那么就很有可能遇到一个棘手的问题:如果你需要用 lua 来直接响应一些 Windows 消息,那么如何向 WinProc 传递 lua_State ,也就是那个充斥于 lua 代码中的 L 。

在 Lua 的第 4 版及以前,这个问题并不突出。因为大多数情况下,我们并不需要嵌入多余一个的 Lua 虚拟机。而 L 这个指针,从 Lua 虚拟机被创建出来以后,就不会改变。那么我们只需要把 L 保存在一个全局变量中就可以了。若是你的程序是多线程的,并且每个线程都开有独立的虚拟机,把这个全局变量放到 TLS 中就可以完美的解决问题。当然一些全局变量的排斥者,会想到把 L 放到 Window 对象的 USERDATA 中,这也未尝不是一个体面的方法。

但是,从 Lua 5 开始,因为 coroutine 的引入,即使只打开一个虚拟机,我们也会面对不同 L 的问题。这个问题早在去年就困扰过我,我和同事一起也讨论并研究过这个问题,当时得到了一些解决方法。今天,我重构代码,又想起这个话题,觉得有必要把当初的思考、结论和今天的想法纪录下来。

April 18, 2007

以自定义方式加载 lua 模块

今天我们的一个小项目开始做内部测试发布前的资源打包。这个项目基本上是用 lua 做开发的。整个开发过程中,我们的代码是直接把 Lua 源代码放在项目的发布目录下的。发布版因为安全或是整洁等种种原因,我们必须给所有的脚本代码打包。

这种事情以前在大话2 里也干过,当时用的 lua 4.0 而且也没多少经验,我们是直接去修改的 lua 的代码,适应我们的打包格式。这次,不想这么干了。希望能够完全不动 lua 官方发布的源代码,来最终完成这项工作。

简单分析了一下,发现实现起来非常简单:

February 12, 2007

lua 近期的一个 bug

在 lua 的 maillist 上最近报告了一个 bug

看起来问题比较严重,因为稍具规模的 lua 程序都可能因此而出现问题。最近两周,我和我的同事都比较关注这个问题,并对 lua 的源代码做了相关的分析。

Roberto 作为 lua 委员会三巨头之一,在 mail 中已经表示追踪到 bug 的起因,但暂时还找不到合适的解决方案。直觉告诉我,这不会是一个简单的问题。如果容易修正的话,patch 早就有了,而不会只是发一个 bug report 而已。所以我们也并未尝试自己去修补这个 bug ,可以做的可能只有等待。

December 18, 2006

Lua 中 userdata 的反向映射

lua 中,我们可以用 userdata 保存一个 C 结构。当我们为 lua 写扩展时,C 函数中可以利用 lua_touserdata 将 userdata 转换为一个 C 结构指针。

但是,有时候我们却需要把一个指针转换回 lua 中的 userdata 对象。用到它的最常见的地方是封装 GUI ,通常 GUI 的底层是用 C 编码的。当 engine 把鼠标位置或是别的消息拦截到以后,消息会被传递到一个 C 对象中。这个时候,我们需要从 C 对象中得到对应的 lua 对象,并触发事件。

December 11, 2006

为 lua 配一个合适的内存分配器

以前版本的 lua 缺省是调用的 crt 的内存分配函数来管理内存的。但是修改也很方便,内部留下了宏专门用来替换。现在的 5.1 版更为方便,可以直接把外部的内存分配器塞到虚拟机里去。

有过 C/C++ 项目经验的人都知道,一个合适的内存分配器可以极大的提高整个项目的运行效率。所以 sgi 的 stl 实现中,还特别利用 free list 技术实现了一个小内存管理器以提高效率。事实证明,对于大多数程序而言,效果是很明显的。VC 自带的 stl 版本没有专门为用户提供一个优秀的内存分配器,便成了许多人诟病的对象。

其实以我自己的观点,VC 的 stl (我用的 VC6 ,没有考察更新版本的情况)还是非常优秀的,一点都不比 sgi 的版本差。至于 allocator 这种东西,成熟的项目应该根据自己的情况来实现。即使提供给我一个足够优秀的也不能保证在我的项目中表现最佳,那么还不如不提供。基础而通用的东西,减无可减的设计才符合我的审美观。sgi 版 stl 里的 allocator 就是可以被减掉的。

好了,不扯远了。今天想谈的是,如何为 lua 定制一个合适的内存分配器。

December 04, 2006

LoadLibrary 的搜索次序

今天写程序的时候发现一个问题,我为 lua 写了一个叫作 console 的 C 扩展库,可老是加载失败。郁闷了好半天后终于找到问题,那就是 lua 解释器实际找到的是 windows/system32 下的一个同名 dll 文件。原来系统也有一个 console.dll 了。

记得从前没有这个问题的,上网查了下 msdn 终于发现其缘故了。原来 windows xp sp2 以后,动态链接库的缺省搜索次序被修改了。

Dynamic-Link Library Search Order

November 30, 2006

Lua Debugger

luadbg.png

最近想做一个 visual 版本的 Lua 远程调试器。奋战了两天弄出这样一个玩意出来。如果有精力做完,就可以在 Windows 下远程调试任何地方的 Lua 代码了 :D

如果近期没精力,就开源让别人继续做好了。

November 24, 2006

lua 代码的断点调试

Lua 5.1 带了一个 debug 库,把所有的 C API 中的 debug 相关 api 都导出了。作为独立的语言使用的话,这些足够搭建一套方便的调试库。

说到最常用的断点调试法,我们能想到的最直接的方法就是利用 lua debug 库中的 hook ,然后记录一张断点位置表,设置行模式的 hook ,每次进入 hook 都检查是否是断点处,若是就停下来等待交互调试。

这个方法很有效,但是很消耗 cpu 。因为每进入一个新的代码行,都需要回调一个函数。当这个函数本身又是用 lua 写的时候,效率更低。

本文提供另一种思路,换一个方法设置断点,让没有断点时不影响运行效率。

November 17, 2006

Lua 中写 C 扩展库时用到的一些技巧

今天方舟组的同事问到我一些 lua 的问题,主要是关于 C 扩展库的。我觉得有些技巧性的东西挺值得跟大家分享一下,那么就写篇 blog 吧。

通常,C 扩展库中 C 代码会有一些数据要放在 lua 状态机中。Lua 提供的方案是放在它的 注册表 中。如文档所言,因为 Lua 的注册表是全局共享的,选择 key 的时候就要千万小心了。整数 key 已经被 reference 系统用掉了,一般我们会采用字符串作 key 。

从 C 中压入字符串的效率不是最高,这是因为外部字符串进入状态机时需要重新 hash 并检查唯一性所致。关于避免直接压入字符串的问题,以前写过一篇 blog 谈过。( btw, 以前那个方法也不是最好的解决方案,不过还是可以作为一个参考的 :)

很容易想到,最方便且能保证唯一性的 key 是一个 light userdata 。这一点,在参考手册以及 Programming in Lua 中都有提到。

November 13, 2006

Lua 5.1 中文手册

前段时间安排同事工作时间翻译了 Lua 的参考手册 。当时的目的是想让翻译的人可以借翻译的机会深入了解这门语言。另外,其他人在查手册的时候也可以轻松一点。

事与愿违,这个中译本陆续经过了几个月的翻译和校对后,可读性依然欠缺。往往我需要去手册里查点东西的时候,都发现还不如直接看英文原版。中文译文读的晦涩倒是次要的,主要是一到关键点上就译的含糊不清,甚至有错误。

这倒符合我的观点,翻译技术资料,首先要求的是对原文的理解,然后是中文的组织水平,最后才是英文水平。

周末,我突然发神经,自己动手译了一下(其实起先只是想看看翻译这个篇幅的文档大约需要多少工时)。花掉两个半天敲了大约一万两千汉字,手都快抽筋了:)把最重要的一部分关于 Lua 语言的译完。

我想我的英文水平是很糟糕的,中文能力也不怎么样,不过满足翻译技术文档的第一要点:对 Lua 本身是很熟悉的。所以这个译本当是能看吧。

Lua 5.1 中文参考手册

很多技术术语我没有按标准译法来译,尽量用一些更通俗但是更繁杂的译法。大部分译词,如果我认为大家普遍能接受,就统一用中文译词,只在第一次出现时括号注明英文原文。例如:事件 (event) 、元方法 (metamethod)。

如果觉得译词不太能被大众接受,大部分地方保留英文,而在第一次出现的地方括号注明我认为合适的中文译法。例如 metatable (元表)、closure(闭包)。


2006年11月19日 凌晨: 手册的第三部分(C API)业已完成 :D

September 21, 2006

lua cclosure 的 upvalue 数量限制

最近写的代码中出了一个奇怪的 bug ,很难调试出来。经过一个晚上的挣扎,终于发现了问题。

第一个问题,在 C 函数中,不能随意的时候 lua_State 中的虚拟机堆栈,如果需要大量使用堆栈,应该先调用 lua_checkstack 。少量使用堆栈,(在 LUA_MINSTACK 20 )之下时则没有问题。这个问题其实在文档里有写,我看过忘记了 :( 不过我个人还是觉得 lua_checkstack 的语义有点奇怪,从字面上看,这个 api 不应该有副作用。它能增加可用堆栈的大小违背了 checkstack 的词义。

第二个问题,当从 lua 调用 C 函数时,当参数数量不足的时候,并不会填入 nil 作为缺省参数。比如,写了一个 C 函数,接受两个参数。当 lua 中调用这个 C 函数时,如果仅传入一个参数,那么在 C 中 stack 上 index 2 位置的值并不一定是 nil 。这个时候我们应该用 lua_gettop 得到准确的参数个数以做适当的处理,或者直接在进入 C 函数时调用一次 lua_settop(L,2) 强制堆栈扩展到两个。

第三个问题,就是一开始最为迷惑我的问题。在生成 cclosure 的时候,upvalue 不能超过 255 个。而这一点并没在文档中说明,运行时压入超过 255 个 upvalue 也不会报错。知道仔细查看源码才发现其中的秘密。

July 27, 2006

用 lua 调用 Windows 的 API

昨天同事谈起能否给一个从 lua 中调用 Windows API 的简单方案。一开始觉得,如果是一个通用方案,那么至少需要先给出一个类似 windows.h 的原型声明,然后从 lua 来解析这些原型。大约写了几十行程序就实现了。后来又想了一下,似乎可以用一个更简单的方式,绕过原型,更简洁(但不保证安全)的方法来做到。

其间的问题就只有一个,每个 api 的参数都不一样,如何自动生成 C 中匹配的函数指针。似乎 C++ 的 template 是一个正统的解决方案。不过思考过几分钟以后,就被我否决了。实际用到的解决方案比较诡异:

先用 alloca 分配出正确的参数空间,再立刻填充这些参数,接下来以无参数的形式调用 api 。这样做,对于 __stdcall 的函数是没有问题的。好在 api 大多也是这样。

我写了这样一段程序来验证我的想法:

June 12, 2006

在 Lua 中实现面向对象

在 pil 中,lua 的作者推荐了一种方案来实现 OO,比较简洁,但是我依然觉得有些繁琐。

这里给出一种更漂亮一点的解决方案:为了贴代码和修改方便,我把它贴在了 wiki 上。

Lua 中实现面向对象

在这个方案中,只定义了一个函数 class(super) ,用这个函数,我们就可以方便的在 lua 中定义类:

base_type=class()       -- 定义一个基类 base_type

function base_type:ctor(x)  -- 定义 base_type 的构造函数
    print("base_type ctor")
    self.x=x
end

function base_type:print_x()    -- 定义一个成员函数 base_type:print_x
    print(self.x)
end

function base_type:hello()  -- 定义另一个成员函数 base_type:hello
    print("hello base_type")
end

以上是基本的 class 定义的语法,完全兼容 lua 的编程习惯。我增加了一个叫做 ctor 的词,作为构造函数的名字。

March 14, 2006

使用 closure 替代 table

前几天谈到 lua 的一些技巧,我整理在 wiki 上了。今天又加了一个,关于 point 结构的封装的。

function point (x,y)
    return function () return x,y end
end

可以用 point(1,2) 构造一个 point 。它比 {1,2} 轻量。

February 18, 2006

lua 5.1 final release

这一天等了很久,终于看到了这则消息:

Lua 5.1 (final) is now available at http://www.lua.org/ftp/lua-5.1.tar.gz

Thank you very much for your patience during this long release process. Special thanks to everyone that sent suggestions. They have helped make Lua still better.

Enjoy! We can now focus on 5.2 :-) --lhf

February 16, 2006

lua 5.1 的 module

lua 从 5.1 开始终于官方提供统一的 module 实现标准了,这是个值得庆幸的事。今天读了下相关的源码和文档,把这套机制搞清楚了,还是很巧妙的。从简洁这个角度看,要比 python 强 :)

有一点容易被忽略掉(我的同事在用的时候就忽略掉了),module 指令运行完后,整个环境被压栈,所以前面全局的东西再看不见了。比如定义了一个 test 模块,使用

module("test")

后,下面不再看的见前面的全局环境。如果在这个模块里想调用 print 输出调试信息怎么办呢?一个简单的方法是

local print=print
module("test")

这样 print 是一个 local 变量,下面也是可见的。或者可以用

local _G=_G
module("test")

那么 _G.print 也是可以用的。

当然还有一种巧妙的方式,lua 5.1 提供了一个 package.seeall 可以作为 module 的option 传入

module("test",package.seeall)

这样就 OK 了。至于它们是如何工作的,还是自己读源码会理解的清楚一些。

在读源码时可以发现很多 lua 的技巧,还有一些 undocumented 的东西,比如 newproxy :) 它是一个 unsupported 且 undocumented 的东西,但是它希望实现的却是个巧妙的玩意。

February 11, 2006

lua 终于支持了16进制数

今天 lua 5.1 rc4 发布了。看了一下,比较 rc3 只改了两个地方,一个是 luaconf.h 里的 lua_popen 的宏。还有一个是增加了 hex number 的支持。

前两天在 mailist 里讨论了这个问题,其实早就有呼声加上 16 进制数了。其实我自己也写过 patch 加上,lua maillist 里 Roberto 提了个方案,只需要修改 luaconf.h 里的 #define lua_str2number(s,p) 这个宏就可以了。我测试了一下,很巧妙,还顺手附和了两句。

不过这个方法对 16 进制数前面加了负号是有问题的(虽然我认为一般不会这么用),结果还是对词法分析代码打了 patch,改动不大,而且同样也很巧妙。Roberto 这次很大方,这么快就加到官方版本并发布了。

这次因为这么小的改变就发布新的 rc ,看来 lua 5.1 的正式 release 很近了。期待,这样 lua 就拥有了官方的模块化解决方案。

January 01, 2006

向 lua 虚拟机传递信息

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

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

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

December 25, 2005

虚拟机之比较,lua 5 的实现

前段把自己的虚拟机和编译器完成后,曾经和 lua5 做过一个比较。比较的结果很沮丧,我的虚拟机只能达到 lua 5 一半多点的速度。所以很不服气的又读了一段 lua5 的源码。而之前我是一段一段的看 lua source code 的,甚至 lua 4 和 lua5 的是在不同时期去读的,当然我也知道其间巨大的不同。

December 11, 2005

12K 的虚拟机

今天把脚本虚拟机整合到正在开发的引擎中去了,按新引擎的跨平台2进制格式 build 出来,只有 12.6K :D 比 lua 小多了 ^^ 庆祝一下。如果不是现在机器都是 32位了,在 16 位或者 8 位机上,这代码体积还能更小。唉,早几年计算机的地址空间只有 64K 的时候多痛苦啊。

突然想,我们这套引擎给手机用一定很不错 尤其是 gc 部分,比 lua python 什么的更适合小内存环境,可惜我现在对嵌入式开发没啥兴趣。

December 10, 2005

基于并行处理的垃圾回收方法

最近在做的一个虚拟机是基于垃圾回收(garbage collection)的,采用的是标记整理算法。这种算法的好处在于不需要 太多额外的内存,而且可以将内存中的 garbage 完全压缩掉。至于长期占用的内存空间,会被压到内存块的底部,整理时无须移动。

对于需要长期稳定运行的服务器程序,在 32bit 操作系统下,受限于有限的地址空间, gc 技术是根本解决内存碎片问题的最佳通用方案。

我计划在服务器程序中,全部程序逻辑都放在虚拟机内运行。由于和 client 程序不同,不用太考虑物理内存的占用,所以计划在服务启动的时候就预设 1~2G 的内存块供虚拟机使用。在这个内存块耗尽之前,所以涉及内存分配的操作都会相当的快了。但是一旦发生 gc ,光是扫描一遍内存,都是非常耗时的。所以我不得不考虑解决方案。

实现一个系统堆栈无关的虚拟机

最初设计虚拟机时,bytecode 中的函数调用会产生一个 native code 上的实际的函数调用。似乎这样写比较容易。但是这样做,想实现bytecode单步运行却很困难。只有另开一个线程监护跑虚拟机的线程,在每步运行后可以挂起,而不破坏相关的堆栈。

所以,我想实现一个系统堆栈无关的虚拟机。

December 07, 2005

给脚本加入字符串类型

最近的工作是给虚拟机加上字符串类型的支持,并让编译器可以生成相应的 bytecode 。思路很简单,就是按 lua 的方式,把所有源码中相同的字符串合并,在 bytecode 中只保留一份。所有提到这些字符串的地方直接对其引用。bytecode运行时,产生的任何新的字符串都会产生新的副本。垃圾等到 gc 时回收。

想起来容易,实现起来还是颇为麻烦的。

December 05, 2005

在脚本语言中一个取巧实现 OO 的方法

今天,脚本编译器连同前段写的虚拟机全部完工了,很有成就感。
跟 lua 一样,复杂的数据类型我只支持了 table ,这个 table 即可以做 array 也可以做 hash_map 用。一般用 lua 的人都会用 table 去模拟 class 。lua 只对这个做了非常有限的扩展:在 lua 的文档中,我们可以看到

function t.a.b.c:f (...) ... end 可以等同于 t.a.b.c.f = function (self, ...) ... end

就我用 lua 的经验,这个转换用途不是特别大,只是少写个 self 而已。

这次我自己设计脚本语言,针对脚本支持 OO 的问题,特别做了些改进。

December 01, 2005

编译器实现有感

脚本虚拟机前段时间就已经做好,如果没有跑在上面的语言,光有虚拟机没太大意义。所以脚本编译器一早就开始做了。中间因为去上海参加 C++ 大会,又去了成都做招聘,弄的心力疲惫。这几天才回来,有那么几天去实现。

编译原理的课程大学本科就应该开过吧,我不是科班出身,反正是没正经上过。不过依稀记得自己是学过的,记得是上中学的时候,跑到一个大学上课,老师教的就是编译原理。那个时候 C 语言还没玩转,最熟的是 basic 和 6502 汇编。理解那些东西很有困难。囫囵吞枣的记了一点,算是有点印象,逢人也可以吹吹牛。

November 02, 2005

实现一个虚拟机

在编程的世界中,只有你想不到的事情,懒的做的事情,没有做不到的事情。

曾经一度为使用哪种脚本嵌入游戏犯愁,lua 的源码过了几遍,python 的也看了点。lcc 也试过,还有 ch 什么的。真正用在项目里的就是 lua 和 python 了。我个人更偏好 lua 一点。越读它的 source code 越觉得它作为世界上最快的脚本语言,是名副其实。

很早以前,我觉得显示一个图片很难,好象发现很简单。曾经觉得汇编很难,结果发现很简单。曾经担心 C++ 太复杂学不会,现在已经感觉游刃有余了。还有学习 perl php 或者是 stl mfc 这样的库,不懂的时候都觉得难,懂了却是不过如此。

对于游戏,说什么掌握 3d ,网络编程,无非是些技术方向而已。肯花时间,一定没问题的。

这次是脚本虚拟机。已经想了很久了,怕太复杂需要投入过多时间和精力。可事实上,写起来却又一次发现不过如此。

October 29, 2005

lua 的 table 处理

lua 的整体效率是很高的,其中,它的 table 实现的很巧妙为这个效率贡献很大。

lua 的 table 充当了数组和映射表的双重功能,所以在实现时就考虑了这些,让 table 在做数组使用时尽量少效率惩罚。