<?xml version="1.0" encoding="gb2312"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <title>云风的 BLOG</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/" />
    <link rel="self" type="application/atom+xml" href="http://blog.codingnow.com/atom.xml" />
   <id>tag:blog.codingnow.com,2013://1</id>
    <link rel="service.post" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1" title="云风的 BLOG" />
    <updated>2013-05-16T06:16:56Z</updated>
    <subtitle>思绪来得快去得也快，偶尔会在这里停留</subtitle>
    <generator uri="http://www.sixapart.com/movabletype/">Movable Type 3.2b5</generator>
 
<entry>
    <title>招聘 Windows/Linux SA 一名</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/2013/05/sa_recruit.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1/entry_id=817" title="招聘 Windows/Linux SA 一名" />
    <id>tag:blog.codingnow.com,2013://1.817</id>
    
    <published>2013-05-16T05:33:35Z</published>
    <updated>2013-05-16T06:16:56Z</updated>
    
    <summary>最近我们代理的游戏 狂刃 开始二测了，忙坏了我们的 SA Aply 同学。 Aply 是我的大学校友，低我一届，在学校门口的网吧认识，到现在已经有十多年。前年底我们的公司 简悦 成立时，他就找到我想一起干点事情。我们正好缺一个 SA ，他便从程序员转职了。从开始的各种不熟悉，他迅速的承担了所有 SA 的工作，得到全公司同事的认可。从今年开始，狂刃一步步发展，已经不再是一两台服务器的管理工作，一个人做这些事情已经显得太繁重了。 为了让 Aply 同学能有正常的假期和休息时间，我们想再招聘一名系统管理员，协助 Aply 同学的工作。职责主要是负责狂刃游戏的服务器运维。 狂刃的服务器暂时工作在 Windows 下（我们正在协助移植到 Linux 平台，但需要一段时间），所以需要有 Windows 平台的系统管理经验。 我们公司未来自研发项目全部在 Linux 下，所以也需要用 Linux 平台的经验（不必须特别丰富，但需要有学习的热情） 对这个职位的要求是： 有系统管理员工作经验三年以上。 熟悉 Windows 平台，对 Linux 有一定了解。 懂得如何批量部署，监控多台服务器（工具不限）。 懂得如何编写脚本（不限语言和工具）进行系统管理工作。...</summary>
    <author>
        <name>云风</name>
        <uri>http://www.codingnow.com</uri>
    </author>
            <category term="简悦" />
    
    <content type="html" xml:lang="en" xml:base="http://blog.codingnow.com/">
        <![CDATA[<p>最近我们代理的游戏 <a href="http://kr.ejoy.com">狂刃</a> 开始二测了，忙坏了我们的 SA Aply 同学。</p>

<p>Aply 是我的大学校友，低我一届，在学校门口的网吧认识，到现在已经有十多年。前年底我们的公司 <a href="http://www.ejoy.com">简悦</a> 成立时，他就找到我想一起干点事情。我们正好缺一个 SA ，他便从程序员转职了。从开始的各种不熟悉，他迅速的承担了所有 SA 的工作，得到全公司同事的认可。从今年开始，狂刃一步步发展，已经不再是一两台服务器的管理工作，一个人做这些事情已经显得太繁重了。</p>

<p>为了让 Aply 同学能有正常的假期和休息时间，我们想再招聘一名系统管理员，协助 Aply 同学的工作。职责主要是负责狂刃游戏的服务器运维。</p>

<p>狂刃的服务器暂时工作在 Windows 下（我们正在协助移植到 Linux 平台，但需要一段时间），所以需要有 Windows 平台的系统管理经验。</p>

<p>我们公司未来自研发项目全部在 Linux 下，所以也需要用 Linux 平台的经验（不必须特别丰富，但需要有学习的热情）</p>

<p>对这个职位的要求是：</p>

<ol>
<li>有系统管理员工作经验三年以上。</li>
<li>熟悉 Windows 平台，对 Linux 有一定了解。</li>
<li>懂得如何批量部署，监控多台服务器（工具不限）。</li>
<li>懂得如何编写脚本（不限语言和工具）进行系统管理工作。</li>
<li>了解基本的网络协议。</li>
<li>可以 24 小时响应突发事件，并能承受突发事件带来的额外工作强度。</li>
</ol>

<p>有兴趣的同学可以 email 和我联系了解更多细节。</p>
]]>
        

    </content>
</entry>
<entry>
    <title>介绍几个和 Lua 有关的东西</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/2013/05/something_about_lua.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1/entry_id=816" title="介绍几个和 Lua 有关的东西" />
    <id>tag:blog.codingnow.com,2013://1.816</id>
    
    <published>2013-05-16T05:06:42Z</published>
    <updated>2013-05-16T05:31:35Z</updated>
    
    <summary>最近有份工作是需要把 Lua 中的数据结构以某种特定的格式输出为文本的，所以就用到了 Lust 这是个代码生成工作的利器。 可能是用的人不多，所以还略显不完整。用的时候发现一些个小问题，原来以为是 bug，读了源代码后发现是个 feature 。但是觉得这个 feature 不太合理，就上 github 上留言。作者倒挺爽快，马上表示赞同并去掉了。 第 2 个是 luaffi 。这个东西原本是 luajit 的一部分，可好多人确是冲着 ffi 库去用 luajit 的。 luajit 目前尚有不少的局限性，比如内存只能用 32 位寻址，不支持 lua 5.2 的 api 等。另外，从稳定性上来说，也不如原版的 lua 更让人放心。 据我所之，我们合作的狂刃 的服务器端就为了 ffi 使用了 luajit...</summary>
    <author>
        <name>云风</name>
        <uri>http://www.codingnow.com</uri>
    </author>
            <category term="lua与虚拟机" />
    
    <content type="html" xml:lang="en" xml:base="http://blog.codingnow.com/">
        <![CDATA[<p>最近有份工作是需要把 Lua 中的数据结构以某种特定的格式输出为文本的，所以就用到了 <a href="https://github.com/weshoke/Lust">Lust</a> 这是个代码生成工作的利器。</p>

<p>可能是用的人不多，所以还略显不完整。用的时候发现一些个小问题，原来以为是 bug，读了源代码后发现是个 feature 。但是觉得这个 feature 不太合理，就上 github 上留言。作者倒挺爽快，马上表示赞同并去掉了。</p>

<hr />

<p>第 2 个是 <a href="https://github.com/jmckaskill/luaffi">luaffi</a> 。这个东西原本是 luajit 的一部分，可好多人确是冲着 ffi 库去用 luajit 的。</p>

<p>luajit 目前尚有不少的局限性，比如内存只能用 32 位寻址，不支持 lua 5.2 的 api 等。另外，从稳定性上来说，也不如原版的 lua 更让人放心。</p>

<p>据我所之，我们合作的<a href="http://kr.ejoy.com">狂刃</a> 的服务器端就为了 ffi 使用了 luajit ，却担心稳定性问题，把 jit 功能关闭了。</p>

<p>还有 <a href="https://github.com/grrrwaaa/luaclang">luaclang</a> 这类项目，未必是稀罕 luajit 的性能，更多的是贪图用 ffi 写 binding 的便捷才启用 luajit 的。</p>
]]>
        <![CDATA[<p>把 ffi 库从 luajit 项目中拆分出来做成独立库，绝对是对 lua 社区的功劳一件。luaffi 在 windows 下，如果用 mingw 编译会遇到一些小麻烦。自己改一下 Makefile 并定义 -D<em>WIN32</em>WINNT=0x500 才能顺利 build 出来。</p>

<hr />

<p>第 3 个是 <a href="http://terralang.org/">terra</a> 。它不是一个 lua 的库。严格来说，它是混合了 lua 语法的一门新语言。</p>

<p>比较独特的是 terra 的工作方式。</p>

<p>当你用虚拟机去运行代码时，它是解释运行那些 lua 代码的。但你又可以在运行时编译（通过嵌入的 llvm 模块）代码为本地指令。并且可以保存为 .o 或执行文件。</p>

<p>编译后的代码，无论是 terra 还是 lua 部分（我猜测的，接触 terra 时间比较短，还没有深入研究）都被翻译了。</p>

<p>terra 本身是一个静态类型的编译型语言，和 C 一样，是手工管理内存的。它有很高的运行效率，又可以方便的和 Lua 交互。terra 函数直接就是 first-class 的 lua value  .</p>

<p>从语法上看， terra 受 objective-C 很多影响。我暂且认为，objective-C 发明了一套新语法，并兼容了 C ；而 terra 尽量少发明语法，而借用成熟的 Lua 语言，同时也可以方便和 C 代码混用。</p>

<p>terra 比较奇妙的特性是可以（利用 llvm ）方便的扩展语言，定制 DSL 。</p>

<p>总之，terra 是个有趣的东西，值得多花一些时间了解。btw, terra 整合了 luajit ，但目前看来，没有使用luajit 的扩展 api ，只是用了 ffi 部分。如果有必要，可以考虑用独立的 luaffi 库替换掉 luajit 。</p>
]]>
    </content>
</entry>
<entry>
    <title>招聘 Lua 开发人员一名</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/2013/05/join_us.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1/entry_id=815" title="招聘 Lua 开发人员一名" />
    <id>tag:blog.codingnow.com,2013://1.815</id>
    
    <published>2013-05-06T08:55:59Z</published>
    <updated>2013-05-16T06:03:15Z</updated>
    
    <summary>5 月 16 日 注：由于已经收到足够多的简历，所以招聘提前终止，谢谢大家的热情。 我们公司 简悦 招聘网络游戏服务端开发人员一名（截至到 2013 年 6 月 1 日）。 基本要求：有至少原创 1000 行以上 Lua 语言编程经验，一万行 C/C++ 语言编程经验。有网络服务开发经验：可以独立解决问题（包括但不限于设计合理的通讯协议，评估其效率及安全性）。 有游戏行业从业经验两年以上可以加分。 有兴趣且满足基本要求的同学，可以 email 和我联系获得更详细的信息。...</summary>
    <author>
        <name>云风</name>
        <uri>http://www.codingnow.com</uri>
    </author>
            <category term="lua与虚拟机" />
            <category term="简悦" />
    
    <content type="html" xml:lang="en" xml:base="http://blog.codingnow.com/">
        <![CDATA[<p>5 月 16 日 注：由于已经收到足够多的简历，所以招聘提前终止，谢谢大家的热情。</p>

<hr />

<p>我们公司 <a href="http://www.ejoy.com/">简悦</a> 招聘网络游戏服务端开发人员一名（截至到 2013 年 6 月 1 日）。</p>

<p>基本要求：有至少原创 1000 行以上 Lua 语言编程经验，一万行 C/C++ 语言编程经验。有网络服务开发经验：可以独立解决问题（包括但不限于设计合理的通讯协议，评估其效率及安全性）。</p>

<p>有游戏行业从业经验两年以上可以加分。</p>

<p>有兴趣且满足基本要求的同学，可以 email 和我联系获得更详细的信息。</p>
]]>
        

    </content>
</entry>
<entry>
    <title>XOR 链表</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/2013/05/xor_linked_list.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1/entry_id=814" title="XOR 链表" />
    <id>tag:blog.codingnow.com,2013://1.814</id>
    
    <published>2013-05-05T11:56:54Z</published>
    <updated>2013-05-05T12:39:41Z</updated>
    
    <summary>前两天阿楠同学想用链表实现一个消息队列，虽说是链表，但是没有从中间删除节点或添加节点的需求。只需要先压入的消息先处理。为了完成这个需求，最后实现了一个双向链表。 当然这个东西是用 Lua 写的，做一个双向链表也不算复杂。采用单向链表也可以做到，只需要多记录一个尾节点。不过今天想说的是，如果这个玩意在 C 里实现的话，其实只需要用一个指针的空间就可以搞定一个双向队列，可以从任意一头进出数据，可以以两个方向遍历，而实现需要的代码量却和单向链表类似。因为算法非常有趣，在 C 语言之外很少见到，知道并使用过的人也就不太多了。这就是所谓的 XOR Linked List 。...</summary>
    <author>
        <name>云风</name>
        <uri>http://www.codingnow.com</uri>
    </author>
            <category term="算法" />
    
    <content type="html" xml:lang="en" xml:base="http://blog.codingnow.com/">
        <![CDATA[<p>前两天阿楠同学想用链表实现一个消息队列，虽说是链表，但是没有从中间删除节点或添加节点的需求。只需要先压入的消息先处理。为了完成这个需求，最后实现了一个双向链表。</p>

<p>当然这个东西是用 Lua 写的，做一个双向链表也不算复杂。采用单向链表也可以做到，只需要多记录一个尾节点。不过今天想说的是，如果这个玩意在 C 里实现的话，其实只需要用一个指针的空间就可以搞定一个双向队列，可以从任意一头进出数据，可以以两个方向遍历，而实现需要的代码量却和单向链表类似。因为算法非常有趣，在 C 语言之外很少见到，知道并使用过的人也就不太多了。这就是所谓的 <a href="http://en.wikipedia.org/wiki/XOR_linked_list">XOR Linked List</a> 。</p>
]]>
        <![CDATA[<p>我们不需要在链表节点上保存前序和后继两个指针，而改而保存这两个指针的 XOR 值。把两个指针强制转换为 <code>uintptr_t</code> ，做位运算 ^ 即可。</p>

<p>这个数据结构可以工作起来，依赖位运算的一个特性： A^(A^B) == B ，我们知道前序地址或后继地址中的任意一个，都可以用这个值推算出另一个来。这样的链表，从前向后与从后向前遍历的算法是一样的，区别只在于出使参数。</p>

<p>每个链表对象，我们保存链表的首节点和尾节点的指针。由于首节点没有前序节点，它的链接信息只需要记录后续节点（ XOR NULL 不会改变它）即可。当链表内只有一个节点时，链接信息记录的就是它自己的地址了。</p>

<p>对于队列，我们只需要在新节点加入时，替换掉首节点，链接信息设为原来的头节点，并把原头节点的链接数据域 xor 上新加入的节点地址即可。</p>

<p>而出队列的时候，只需要用尾节点的链接域替换掉它并用 xor 更新新的尾节点链接域的数据即可。</p>

<hr />

<p>XOR 链表的实现很巧妙有趣，但并意味着实现很复杂。它比传统的双向链表实现起来要简单的多（甚至更容易实现为无锁的数据结构，传统的单向链表很容易实现为无锁数据结构，而<a href="http://people.csail.mit.edu/edya/publications/OptimisticFIFOQueue-journal.pdf">双向链表就麻烦的多</a> ）。当然，局限性也很明显。</p>

<p>它很难在 C/C++ 只外的其它语言中实现，而且链接信息很隐讳，会导致调试起来更困难一些。</p>

<p>我们几乎只能在遍历链表的过程中去插入和删除单个节点（当然很多情况下，我们也只需要在这个场合做这些事情），插入一个节点需要同时知道两个节点，我们才能把新节点插入其间，删除节点也必须有它前或后的节点信息才行。</p>

<p>但这个数据结构在某些场景毕竟是有用的，比起用 xor 交换两个变量来说，<a href="http://en.wikipedia.org/wiki/XOR_swap_algorithm">XOR 交换算法</a> 才真的像道智力题。</p>
]]>
    </content>
</entry>
<entry>
    <title>Lua 5.2.2 中的一处 Bug</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/2013/04/lua_522_bug.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1/entry_id=813" title="Lua 5.2.2 中的一处 Bug" />
    <id>tag:blog.codingnow.com,2013://1.813</id>
    
    <published>2013-04-17T08:59:51Z</published>
    <updated>2013-04-19T12:59:37Z</updated>
    
    <summary>前几天, Lua 5.2.2 发布了, 主要是修复了 4 个 Lua 5.2.1 中已知的 bug . 其中包括前段时间一个同学和我在 email 交流中讨论的一个问题. 我把 Lua 5.2.2 更新到公司项目的主干上，同时需要对我的那本 《Lua 源码欣赏》做一些更新，需要把这次的代码更改同步到书里去。这个工作很繁琐，但有它的价值。比如我发现了 Lua 5.2.2 比 5.2.1 的更改远不只官方宣布了 4 处 bugfix ，还有一些小调整，让 Lua 的源码更规整一些。 阿楠同学因为这段时间一直在维护 UniLua 这个 C# 版的 Lua 项目，我就随便和他通告了一下这次的一些代码变更，方便他同步到 UniLua...</summary>
    <author>
        <name>云风</name>
        <uri>http://www.codingnow.com</uri>
    </author>
            <category term="lua与虚拟机" />
    
    <content type="html" xml:lang="en" xml:base="http://blog.codingnow.com/">
        <![CDATA[<p>前几天, Lua 5.2.2 发布了, 主要是修复了 4 个 Lua 5.2.1 中已知的 bug . 其中包括前段时间一个同学和我在 email 交流中讨论的一个问题.</p>

<p>我把 Lua 5.2.2 更新到公司项目的主干上，同时需要对我的那本 《<a href="http://www.codingnow.com/temp/readinglua.pdf">Lua 源码欣赏</a>》做一些更新，需要把这次的代码更改同步到书里去。这个工作很繁琐，但有它的价值。比如我发现了 Lua 5.2.2 比 5.2.1 的更改远不只官方宣布了 4 处 bugfix ，还有一些小调整，让 Lua 的源码更规整一些。</p>

<p>阿楠同学因为这段时间一直在维护 <a href="https://github.com/xebecnan/UniLua">UniLua</a> 这个 C# 版的 Lua 项目，我就随便和他通告了一下这次的一些代码变更，方便他同步到 UniLua 项目中去。</p>

<p>讨论之中，他提到 <code>luaD_precall</code> 函数的实现有些诡异之处，没有看明白。我顺着他指出的位置又仔细阅读了一下，果然发现这里存在一个隐藏很深的 Bug 。</p>
]]>
        <![CDATA[<p>准确说，这不是 Lua 5.2 引入的新 Bug ， 至少在 Lua 5.1 年代就存在了。只不过很难触碰到触发条件。</p>

<p>理解了代码后，我构造了一小段 Lua 代码，可以让 Bug 暴露出来。</p>

<pre>
function f(p1,p2,p3,p4,p5,p6,p7,p8,p9,...)
  local a1,a2,a3,a4,a5,a6,a7
  local a8,a9,a10,a11,a12,a13,a14
end

f()
</pre>

<p>运行这段 Lua 代码会让 Lua 虚拟机崩溃。我的修复 patch 如下：</p>

<pre>
diff --git a/src/ldo.c b/src/ldo.c
index aafa3dc..a901e6c 100644
--- a/src/ldo.c
+++ b/src/ldo.c
@@ -324,7 +324,7 @@ int luaD_precall (lua_State *L, StkId func, int nresults) {
     case LUA_TLCL: {  /* Lua function: prepare its call */
       StkId base;
       Proto *p = clLvalue(func)->p;
-      luaD_checkstack(L, p->maxstacksize);
+      luaD_checkstack(L, p->maxstacksize + p->numparams);
       func = restorestack(L, funcr);
       n = cast_int(L->top - func) - 1;  /* number of real arguments */
       for (; n < p->numparams; n++)
</pre>

<p>已经提交到 lua mailling list 里去了。Bug 的成因是：在 Lua 函数执行时，先按编译时统计出来的需要的寄存器个数扩展了堆栈。但是，如果函数有不定个数的参数，且调用者没有给完固定参数个数，会触发一个边界条件复制传入参数，结果是让预留的栈空间有可能不够。</p>

<hr />

<p>4 月 18 日补充:</p>

<p>由于这是因为内存写越界造成的 bug , 所以是否会立即导致程序崩溃和编译环境有关.</p>

<p>我使用的是 mingw32 ，且打开了 -g 选项。</p>

<p>如果想确保看到问题，可以在 luaconf.h 里加上</p>

<pre>
#include "assert.h"
#define lua_assert assert
</pre>

<p>这样就会触发定义好的 assert 条件。</p>

<hr />

<p>4 月 19 日:</p>

<p>这个 bug 终于在 lua mailling list 上被 Roberto 确认了. 英文不好是个问题, 前后写了好几篇才说清楚.</p>

<p>不过这个修复方案还需要斟酌. 因为可以有更好的方法去掉那次加法带来的额外开销.</p>

<p>"we can avoid this little overhead in the common case (non-vararg functions).  We can either add p->numparams in the parser (so that the final maxstacksize of vararg functions already reflect
this extra need) or check for numparams inside adjust_varargs, only for vararg functions. "</p>
]]>
    </content>
</entry>
<entry>
    <title>树结构的一点想法</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/2013/04/data_structure_tree.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1/entry_id=812" title="树结构的一点想法" />
    <id>tag:blog.codingnow.com,2013://1.812</id>
    
    <published>2013-04-16T09:15:57Z</published>
    <updated>2013-04-16T09:17:06Z</updated>
    
    <summary>数据结构中的树结构在抽象复杂事物时非常常见，在图形引擎中，多用于场景以及 sprite 的层级管理。在 GUI 相关的模块中也是必备的结构。其它领域，比如对程序源码本身的解释翻译，以及对数据文件的组织管理，都离不开树结构。 我觉得，这是因为一个对象，除了它自身的属性（例如大小、形状、颜色等）之外，还需要一些外部属性（例如位置、层次、方向等）需要逐级继承。每个对象都可以分解成更细的对象组合而构成、这些对象在组成新的对象后，它们的聚合体又体现出和个体的相似性（至少具有类似的外部属性）。这使得采用树状数据结构最容易描述它们。 我最近的一些工作代码做了很多这方面的工作，回想这些年里，我不只一次的思考类似的问题（参看 2009 年的一篇 blog），而每次最后解决问题的代码的都有些不同，编程风格也有一些变化。总结一下这段时间的思考，今天再写这么一篇 blog 。 树结构的基本操作无非是遍历整棵树、遍历一层分支、添加节点、移动节点、删除节点这些。但在大部分应用环境下，我们最多用到的只是遍历，而非控制树的结构本身。...</summary>
    <author>
        <name>云风</name>
        <uri>http://www.codingnow.com</uri>
    </author>
            <category term="语言与设计" />
    
    <content type="html" xml:lang="en" xml:base="http://blog.codingnow.com/">
        <![CDATA[<p>数据结构中的树结构在抽象复杂事物时非常常见，在图形引擎中，多用于场景以及 sprite 的层级管理。在 GUI 相关的模块中也是必备的结构。其它领域，比如对程序源码本身的解释翻译，以及对数据文件的组织管理，都离不开树结构。</p>

<p>我觉得，这是因为一个对象，除了它自身的属性（例如大小、形状、颜色等）之外，还需要一些外部属性（例如位置、层次、方向等）需要逐级继承。每个对象都可以分解成更细的对象组合而构成、这些对象在组成新的对象后，它们的聚合体又体现出和个体的相似性（至少具有类似的外部属性）。这使得采用树状数据结构最容易描述它们。</p>

<p>我最近的一些工作代码做了很多这方面的工作，回想这些年里，我不只一次的思考类似的问题（<a href="http://blog.codingnow.com/2009/05/tree.html">参看 2009 年的一篇 blog</a>），而每次最后解决问题的代码的都有些不同，编程风格也有一些变化。总结一下这段时间的思考，今天再写这么一篇 blog 。</p>

<p>树结构的基本操作无非是遍历整棵树、遍历一层分支、添加节点、移动节点、删除节点这些。但在大部分应用环境下，我们最多用到的只是遍历，而非控制树的结构本身。</p>
]]>
        <![CDATA[<p>所以我认为，遍历应该是对外的 API 、而结构控制则应该是内部的 API 。也就是说，作为一个模块，对外不用显露其实现用到的数据结构，而只关心怎样取得模块内部的状态。这时，合适的遍历接口足以。典型的文件系统的接口就是这样做的：我们可以用 / 连接目录和文件名变成一个完整的路径名，用它打开一个文件；而不必一级级取得目录对象，再从中获得文件对象。</p>

<p>相较于结构控制的 API （增加、删除、移动节点这些），更重要的是树的持久化。因为最终用户关心的是最终的数据，以及怎样使用这些数据，而不大关心数据的构建过程。常见的做法是把树状结构的数据呈现为一个 XML 文件，或是生成一张 Lua 表，然后一次加载它，得以在内存中重建树结构。持久化数据的格式不重要，你可以根据性能需要优化它，也可以因为健壮性而采用易读的文本。在大多数应用需求里，一旦树被重建为编辑产生它时构建的样子，就不会再修改它了。无论多简单的结构，把构建过程直接用代码的形式写在源文件中，是最不值得做的事情。早期的 GUI 程序或许还会一行行调用 API 把窗口以及布局搭建起来，而现代 GUI 框架几乎都为界面布局定义一套 DSL ，鼓励设计人员独立描述它们了。</p>

<p>我认为，即使是动态性要求很高的场合，最好也定义出数据格式，便于和使用了树结构的模块交互。比如在 GUI 程序中，我们不建议把一个按钮被按下的行为注入到那个按钮对象中，而是从外部捕获按钮被按下的消息，忽视掉界面的层次结构而直接处理这些消息。为了做到这一点，我们可以定义出类似 HTML 的语言来定位界面上的按钮。</p>

<p>少量修改已经事先创建好的对象的需求也是普遍存在的。往往对象的结构很复杂，但可以调整的部分却很少。有一些支持对象的语言中，直接提供了蓝图对象的概念。比如早期网络游戏常用的 LPC ，让程序员实现一个对象，再给出方法复制它。</p>

<p>我最近读到 <a href="http://kr.ejoy.com/">狂刃</a> 的图形引擎也用了类似的方法。它在编辑器中编辑出需要的怪物/粒子等对象，然后持久化到文件中。运行时，加载这些数据构造出编辑得到的对象，再根据需要的数量 clone 它们。</p>

<p>对于那些需要对象中需要少量修改的部分，按蓝图复制出一个复制体，再根据需要修改即可。这是图形引擎常见的需求，比如动画节点当前播放的帧号就是一个随时间变化的节点属性，需要在运行期修改的。</p>

<hr />

<p>我想再谈一点实现上的细节以及优化。</p>

<p>采用了树结构组织起来的数据体，往往就意味着复杂且零碎的数据片段。因为每个结点下都可能有很多子节点，而子节点上保存的对象类型也可能不同，最终是大量不同尺寸内存片的组合体。但实际上，整棵树的绝大部分是不变的。</p>

<p>如果我们在编辑器里编辑出一个复杂的怪物，以它为蓝图在运行期 clone 出多份的话，会发现，这些克隆体之间的共有数据远多过易变量。所以我觉得，把这些易变量和不变量分开储存可能是个更好的方案。易变量（比如每个节点的当前空间状态等）可以平坦化储存，不变量虽然在逻辑层次上是一棵树，但在编辑完成时就决定了它是怎样的，完全可以持久化为连续的数据，并还原到连续内存中，且作为一个对象来管理。作为公有的蓝图，也不必真的复制为多个克隆体，而只需要指针引用即可。</p>

<p>在我最近的另一个项目中，我用 C + Lua 实现了类似的结构，发现代码没有我一开始想的那么复杂。Lua 和 C 的层次还是能分的很清楚。一些需要靠字符串索引储存的数据，我放到 Lua 表中，而 C 中的数据结构则专注于树结构的表述。这样不会在 Lua 中保存太多零碎的 table ，在 C 结构中保有高效的树结构索引能力，又可以把比较麻烦的字符串管理扔给 Lua GC 。而且在很多情况下，对于字符串常量，Lua 语言比 C 语言要高效的多。</p>
]]>
    </content>
</entry>
<entry>
    <title>WM_CREATE 引起的 bug 一则</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/2013/04/wm_create_exception.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1/entry_id=811" title="WM_CREATE 引起的 bug 一则" />
    <id>tag:blog.codingnow.com,2013://1.811</id>
    
    <published>2013-04-15T09:03:26Z</published>
    <updated>2013-04-15T13:28:25Z</updated>
    
    <summary>今天在维护一个 Windows 程序时，发现一个 bug　，记录一下。 这是一个简单的 Windows 程序，在注册给窗口的 WinProc 回调函数中处理了 WM_CREATE 和 WM_PAINT WM_TIMER 等消息。 bug 的现象是，WM_CREATE 的流程没有走完就开始处理 WM_TIMER 等消息了。表现起来仿佛 WinProc 被重入了。 仔细排查后发现，不知道什么奇怪的原因，我的 Win7 系统在处理 WM_CREATE 消息时，默认捕获了异常。导致消息处理的流程没有走完，但进程却没有崩溃，窗口也被正确创建出来了。然后这个窗口可以继续接收 WM_TIMER 等消息。...</summary>
    <author>
        <name>云风</name>
        <uri>http://www.codingnow.com</uri>
    </author>
            <category term="Windows" />
            <category term="调试" />
    
    <content type="html" xml:lang="en" xml:base="http://blog.codingnow.com/">
        <![CDATA[<p>今天在维护一个 Windows 程序时，发现一个 bug　，记录一下。</p>

<p>这是一个简单的 Windows 程序，在注册给窗口的 WinProc 回调函数中处理了 <code>WM_CREATE</code> 和 <code>WM_PAINT</code> <code>WM_TIMER</code> 等消息。</p>

<p>bug 的现象是，<code>WM_CREATE</code> 的流程没有走完就开始处理 <code>WM_TIMER</code> 等消息了。表现起来仿佛 WinProc 被重入了。</p>

<p>仔细排查后发现，不知道什么奇怪的原因，我的 Win7 系统在处理 <code>WM_CREATE</code> 消息时，默认捕获了异常。导致消息处理的流程没有走完，但进程却没有崩溃，窗口也被正确创建出来了。然后这个窗口可以继续接收 <code>WM_TIMER</code> 等消息。</p>
]]>
        <![CDATA[<p>我在不同的机器上测试了一下，确认是 Windows 的问题。貌似过去没有碰到过。目前我用的是 Windows 7 的 64 位版本，使用 mingw32 生成的 32bit 程序。</p>

<p>有兴趣的同学可以一试，在</p>

<p>WinProc 里加几行：</p>

<pre>
case  WM_CREATE: {
    int *p = NULL;
    *p = 0;
    break;
}
</pre>

<p>这个非法地址写指令不会让进程崩溃。</p>

<p>google 了一下，似乎没有人反应类似问题。</p>
]]>
    </content>
</entry>
<entry>
    <title>动态字体的贴图管理</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/2013/04/dfont.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1/entry_id=810" title="动态字体的贴图管理" />
    <id>tag:blog.codingnow.com,2013://1.810</id>
    
    <published>2013-04-02T08:28:45Z</published>
    <updated>2013-04-03T02:37:41Z</updated>
    
    <summary>汉字的显示，是基于 3d api 的图形引擎必须处理的问题。和西方文字不同，汉字的字形很难全部放在一张贴图上，尤其是游戏中有大小不同的字体的需求更是如此。即使放下，也很浪费内存或显存。如果不想申请很大的贴图来存放汉字字形，图形引擎往往需要做动态字形贴图的处理。 即，动态生成一张贴图，把最近常用的汉字画在上面。几乎所有成熟的基于 3d api 的图形引擎都需要有相关的模块才可以对汉字更好的支持。但我到目前为止，还没有看到有把这个模块独立出来的。大多数开源引擎都是在自己的框架内来实现差不多的功能。我觉得，这部分管理功能和如何管理贴图其实没有关系、和取得字形的方法也没有关系、不必和 3d api 打交道，也不用涉及到底用 freetype 还是 os 自带的 api 取得汉字字形，所以值得独立实现。 我们的需求本质上是对一张贴图的区块进行管理。每个汉字都占据其中的一小块。当贴图填满时，最久没有用过的汉字块可以被淘汰掉，让新的汉字覆盖上去。同样的字体的最大高度是相同的，可以排列在一行，但宽度可以不同。横向排列时，少许的空洞的允许的。 我设计了如下接口： struct dfont_rect { int x; int y; int w; int h; }; struct dfont * dfont_create(int width, int height); void dfont_release(struct...</summary>
    <author>
        <name>云风</name>
        <uri>http://www.codingnow.com</uri>
    </author>
            <category term="游戏开发" />
    
    <content type="html" xml:lang="en" xml:base="http://blog.codingnow.com/">
        <![CDATA[<p>汉字的显示，是基于 3d api 的图形引擎必须处理的问题。和西方文字不同，汉字的字形很难全部放在一张贴图上，尤其是游戏中有大小不同的字体的需求更是如此。即使放下，也很浪费内存或显存。如果不想申请很大的贴图来存放汉字字形，图形引擎往往需要做动态字形贴图的处理。</p>

<p>即，动态生成一张贴图，把最近常用的汉字画在上面。几乎所有成熟的基于 3d api 的图形引擎都需要有相关的模块才可以对汉字更好的支持。但我到目前为止，还没有看到有把这个模块独立出来的。大多数开源引擎都是在自己的框架内来实现差不多的功能。我觉得，这部分管理功能和如何管理贴图其实没有关系、和取得字形的方法也没有关系、不必和 3d api 打交道，也不用涉及到底用 freetype 还是 os 自带的 api 取得汉字字形，所以值得独立实现。</p>

<p>我们的需求本质上是对一张贴图的区块进行管理。每个汉字都占据其中的一小块。当贴图填满时，最久没有用过的汉字块可以被淘汰掉，让新的汉字覆盖上去。同样的字体的最大高度是相同的，可以排列在一行，但宽度可以不同。横向排列时，少许的空洞的允许的。</p>

<p>我设计了如下接口：</p>

<pre>
struct dfont_rect {
    int x;
    int y;
    int w;
    int h;
};

struct dfont * dfont_create(int width, int height);
void dfont_release(struct dfont *);
const struct dfont_rect * dfont_lookup(struct dfont *, int c, int height);
const struct dfont_rect * dfont_insert(struct dfont *, int c, int width, int height);
void dfont_flush(struct dfont *);
</pre>
]]>
        <![CDATA[<p>用 create/release 创建/删除一个管理对象，管理一张指定大小的贴图。贴图本身并不在这个管理对象内，它只负责管理贴图的空间划分。</p>

<p>可以用 insert 来创建一个指定宽高的矩形空间，并为这个空间指定一个 id 。这个 id 一般用汉字的 unicode 即可。insert 会返回一个矩形区域，如果贴图满了，且无法腾出空间，则返回空。禁止创建高度相同的相同 id 。</p>

<p>lookup 可以用于查询一个指定高度的 id ，如果贴图上不存在，则返回空。lookup 会记录这个字最近被使用过，不会被立刻淘汰。</p>

<p>贴图上创建的字型块，在调用 <code>dfont_flush</code> 之前都一定保留。调用 <code>dfont_flush</code> 后，如果贴图满，会淘汰最久没有使用的等高的字形空间。</p>

<hr />

<p>我把<a href="https://github.com/cloudwu/dfont">一个开源实现放在 github</a> 上了。不过暂时还没有放在任何项目中用，程序结构略微复杂，所以很可能有 bug 。</p>

<p>具体使用时，每次渲染一个汉字，可以先 lookup 查询过去是否创建过，如果没有，则算出这个字的尺寸，调用 insert 得到一个新的矩形空间，然后取得汉字的位图，上传到贴图的指定区域。每帧渲染完毕，调用一次 <code>dfont_flush</code> 即可。</p>

<p>一般情况下，创建一张 2048 宽的 dfont 对象大约就够用了。但是在字形的淘汰过程中，可能会导致贴图内有空洞（如果能保证尺寸相同，则不会产生这个结果），所以可能会比预想的少排一些字在贴图上。所以实际用的时候，可以考虑创建多创建一个 dfont 对象，交替使用，定期切换。</p>
]]>
    </content>
</entry>
<entry>
    <title>Objective-C 的对象模型</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/2013/03/objective_c.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1/entry_id=809" title="Objective-C 的对象模型" />
    <id>tag:blog.codingnow.com,2013://1.809</id>
    
    <published>2013-03-19T05:43:31Z</published>
    <updated>2013-03-19T08:36:37Z</updated>
    
    <summary>最近稍微学习了一点 Objective-C ，做笔记和做编码练习都是巩固学习的好方法。整理记录脑子里的新知识有助于理清思路，发现知识盲点以及错误的理解。 Objective-C 和 C++ 同样从兼容 C 语言开始，以给 C 语言增加面向对象为初衷，他们的出现的时间都很类似（1983 年左右）。但面向对象编程的源头却不同：C++ 受 Simula 和 Ada 的影响比较多，而 Objective-C 的相关思想源至 Smalltalk ，最终的结果是他们在对象模型上有不小的差异。 以我这些天粗浅的了解，Objective-C 似乎比 C++ 更强调类型的动态性，而牺牲了一些执行性能。不过这些牺牲，由于模型清晰，可以在今天，由更先进的编译技术来弥补了。 我对 C++ 的认知比 Objective-C 要多的多，所以对 C++ 开发中会遇到的问题的了解也多的多。在学习 Objective-C 的过程中，我发现很多地方都可以填上曾经在 C++ 开发中遇到的问题。当然，Objective-C 一定也有它自己的坑，只是我才刚开始，没有踩到过罢了。 ObjC 的类方法调用的形式，更接近于向对象发送消息。语法写作：...</summary>
    <author>
        <name>云风</name>
        <uri>http://www.codingnow.com</uri>
    </author>
            <category term="语言与设计" />
    
    <content type="html" xml:lang="en" xml:base="http://blog.codingnow.com/">
        <![CDATA[<p>最近稍微学习了一点 Objective-C ，做笔记和做编码练习都是巩固学习的好方法。整理记录脑子里的新知识有助于理清思路，发现知识盲点以及错误的理解。</p>

<p>Objective-C 和 C++ 同样从兼容 C 语言开始，以给 C 语言增加面向对象为初衷，他们的出现的时间都很类似（1983 年左右）。但面向对象编程的源头却不同：C++ 受 Simula 和 Ada 的影响比较多，而 Objective-C 的相关思想源至 Smalltalk ，最终的结果是他们在对象模型上有不小的差异。</p>

<p>以我这些天粗浅的了解，Objective-C 似乎比 C++ 更强调类型的动态性，而牺牲了一些执行性能。不过这些牺牲，由于模型清晰，可以在今天，由更先进的编译技术来弥补了。</p>

<p>我对 C++ 的认知比 Objective-C 要多的多，所以对 C++ 开发中会遇到的问题的了解也多的多。在学习 Objective-C 的过程中，我发现很多地方都可以填上曾经在 C++ 开发中遇到的问题。当然，Objective-C 一定也有它自己的坑，只是我才刚开始，没有踩到过罢了。</p>

<hr />

<p>ObjC 的类方法调用的形式，更接近于向对象发送消息。语法写作：</p>
]]>
        <![CDATA[<p>[obj message]</p>

<p>如果方法带有参数，那么就写作</p>

<p>[obj param:value]</p>

<p>方法和名称和参数的名称是一体的，参数决定了方法是什么。如果有多个参数，那么写作：</p>

<p>[obj param1:value1 param2:value2]</p>

<p>注意，如果一个类有两个方法，一个有一个参数，一个有两个参数。即使两个参数的版本中有一个参数名称和单个参数版本的相同，它们也是两个不同的方法。ObjC 不支持默认参数的语法。</p>

<p>C++ 调用对象的方法就更接近于 C 的函数调用。两相比较，可以发现 ObjC 的语法让代码可读性更强。你可以很容易的理解参数的用途，也不怕方法参数过多时，一串参数写漏或写错次序了。</p>

<hr />

<p>和 C++ 一样，ObjC 的类声明和实现是分离的。但做的比 C++ 更彻底。ObjC 不能在声明的代码段中写 inline 函数。这看起来牺牲了一些运行性能，但当实现部分更好的分离。作为补充，ObjC 有 @property ，可以帮助程序员简化实现，也可以让编译器生成更好的代码。</p>

<p>声明一个类写成这样：</p>

<pre>
@interface class : baseclass {
   type a;
}

- (void) method;

- (void) messge: (type) param;

+ (id) create ;

@end
</pre>

<p>ObjC 利用了 C 语言中没有使用的符号 @ 来扩展 C 的语法，而不是用 C++ 里增加关键字的方式。这或许是一个对语言扩展更简单的做法，而不用考虑兼容性。C++ 就得精心挑选新增加的关键字，尽量回避那些已有代码中高频出现的单词。</p>

<p>类的数据段和方法是分离的。数据描述放在 {} 中，方法写在其后，在 @end 之前。</p>

<p>"-" 开头的方法是实例方法，也就是 C++ 中的成员方法。成员方法中可以通过 self 取到实例指针，也就是 C++ 中的 this 指针。</p>

<p>同样，ObjC 也支持类方法，也就是 C++ 中的 static 成员方法。通常是用来构造实例。声明方法是在方法名前写一个 + 号。</p>

<p>和 C++ 不同，ObjC 是有类对象的。类对象里有超类指针、类名、类方法列表指针，还有类对象的字节大小等元信息。而 C++ 中是用 RTTI 类实现不完全的类似功能的。</p>

<p>调用类方法和调用实例方法在语法上没有什么不同。类名就是类对象的名字。</p>

<p>ObjC 不支持多继承，没有私有、公开这些修饰符。</p>

<p>ObjC 的类方法实现必须写在同一个源文件里。不像 C++ 有 :: 操作符，ObjC 在实现方法时不写类的名字，而是把所有实现都写在 @implementation class ... @end 之间。访问基类，也可以方便的使用 super 关键字。</p>

<p>那么，如果一个类的方法太多，不适合写在同一个源文件中怎么办？</p>

<hr />

<p>ObjC 提供了 category 这个概念。</p>

<p>可以通过 category 为一个类添加一些方法。category 和继承是不同的，不能为类添加新的成员变量，所以它不会改变类对象的内存布局。添加了方法的类还是原来那个类。</p>

<p>category 的语法是这样的：</p>

<pre>
@interface class (category) 

- newmethod;

@end
</pre>

<p>这样，就给 class 类添加了一个方法 newmethod ，并归类在 category 下。</p>

<p>和 C++ 不同，ObjC 的方法更具动态性。你可以在运行时任意调用一个对象的方法，而不用管它是否存在。ObjC 支持 id 这个类型。 id 其实就是对象指针，任何类型的对象都可以被 id 引用，并可以方便的向其发送消息（方法调用）。如果方法不存在，会抛出运行时错误。</p>

<p>向一个指定类型发送一个不存在的消息，会得到一个编译期警告，而不是编译错误。当然，我们不能随便忽略编译期警告，如果我们清楚的知道运行期这个对象可以处理这个消息，那么可以给类加一个 category 但不必实现它。这样，编译器就能了解新的方法了。</p>

<p>利用 category 可以方便的一个庞大的类拆分成独立的模块。在 C++ 中，比较接近的概念是 friend ，不过 friend 不易被优雅的使用。</p>

<p>既然方法可以被运行期检查，那么方法本身在 ObjC 中也可以被当成一种类型来处理。比较接近的 C++ 中的概念是 成员方法指针。回顾学习 C++ 的经历就能回忆起当年使用 ::* 或是 ->* 的头痛经历。ObjC 中的方法可以运行期绑定， @selector(method:) 的语法也简单的多。</p>

<p>在 NSObject 中就提供了一个叫 respondsToSelector: 的方法，接受一个 selector 用来检查自己是否可以接受这个消息。</p>

<hr />

<p>ObjC 也提供了类似 Java 的 interface 或是 C++ 的纯虚类的东西，在 ObjC 中被称为 @protocol 。</p>

<p>@protocol 可以看成是一种没有数据成员的虚类。一个实际的类可以声明自己实现了某些协议，语法是</p>

<pre>
@interface class : base &lt;protocol&gt;
{
   // variables
}

// methods
@end
</pre>

<p>和继承不同，一个类可以声明多个协议。然后在 @implementation 中必须一一实现它们。</p>

<p>如上所述，ObjC 已经做到了运行期的方法绑定，所以 @protocol 只是做了更严格的编译检查。在新版的 ObjC 2.0 中，追加了 @optional 和 @required 用来描述那些方法的实现是可选的，哪些必须实现。</p>

<hr />

<p>ObjC 的基础库比 C++ 更完整，标准化要好的多，也和语言结合的更紧密。</p>

<p>比如 NSString 是一个基础类，用于处理字符串。同时，语言也提供 @"string" 的语法方便的生成 NSString 对象。</p>

<p>ObjC 保留了 C 中的 printf 式的字符串操作形式，对比 C++ 重载移位操作符的形式，我想要更清爽一些。</p>

<p>对于 ObjC 对象，使用 %@ 来表示。给对象增加 description 方法就可以让处理函数知道该如何处理这个对象的 %@ 行为。</p>
]]>
    </content>
</entry>
<entry>
    <title>最近一些心得</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/2013/03/iueoaea.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1/entry_id=808" title="最近一些心得" />
    <id>tag:blog.codingnow.com,2013://1.808</id>
    
    <published>2013-03-11T03:14:18Z</published>
    <updated>2013-03-11T03:54:29Z</updated>
    
    <summary>最近特别忙, 每天写程序的时间都不够。有些东西在做完之前不想公开谈，所以只把一些笔记发在公司内部的周报里了。等这段时间过去，再贴到这里来。 不过还是有一些泛泛的心得可以写写的。 前几天遇到一个优化的问题。我想采用定期计算路图的方式优化寻路的算法。而不用每次每个单位在想查找目标的时候都去做一次运算并记录下路径结果。一切都看起来很顺利，算法的正确性很快就被验证了。可是最后实际跑的时候，发现在生成路图的地方会稍微卡一下影响流畅性。...</summary>
    <author>
        <name>云风</name>
        <uri>http://www.codingnow.com</uri>
    </author>
            <category term="优化与技巧" />
            <category term="语言与设计" />
            <category term="杂记" />
    
    <content type="html" xml:lang="en" xml:base="http://blog.codingnow.com/">
        <![CDATA[<p>最近特别忙, 每天写程序的时间都不够。有些东西在做完之前不想公开谈，所以只把一些笔记发在公司内部的周报里了。等这段时间过去，再贴到这里来。</p>

<p>不过还是有一些泛泛的心得可以写写的。</p>

<p>前几天遇到一个优化的问题。我想采用定期计算路图的方式优化寻路的算法。而不用每次每个单位在想查找目标的时候都去做一次运算并记录下路径结果。一切都看起来很顺利，算法的正确性很快就被验证了。可是最后实际跑的时候，发现在生成路图的地方会稍微卡一下影响流畅性。</p>
]]>
        <![CDATA[<p>遇到这类问题，第一反应通常是考虑如何优化。比如是不是应该换用多线程，是不是要分摊每次的计算量，是否可以简化问题减少运算的次数等等。</p>

<p>我在这些方面都尝试做了几个小时的努力。结果发现，如果我简单的把 O3 优化加上的话，可以直接加速 2 倍，相当于我其它方面做的努力的提升（当然其它方面的努力也是有效的）。</p>

<p>这不太符合我的平常遇到的情况。大部分情况下，C 语言编写的程序，编译器优化能做到 25% 的性能提升已经足够好了（通常 C++ 风格的代码可以提升更多）。或许是因为纯算法代码的因素导致这次的结果吧。</p>

<p>但是，采用多线程这样的方式来改进程序，不符合我的直觉。我不认为把程序结构改的复杂来获得需要的性能并不是第一选择。而且，在设计算法之初，我已经做过估算，认为在当前的硬件环境下，并太可能造成容忍不了的延迟的。</p>

<p>最后自己检查了自己的代码实现。发现我不小心把一个 O(n) 复杂度的算法实现成 O(n2) 了。修改正确后，速度一下就提高了 100 倍。</p>

<p>这次是个不多见的教训。程序结果的正确性被验证，但是依然存在 bug 。只是 bug 仅仅导致了代码运行缓慢，把一段需要 1/1000 秒应该完成的任务，减慢到了 1/10 秒的数量级。</p>

<hr />

<p>另一点心得：</p>

<p>在外部条件不多变的问题上，适当的采用一些常量是很有效的。比如断定某个字符串长度不超过 64 ，数组的元素不超过  16 个等等。</p>

<p>对于 C 风格的代码，我们总能看到一些 MAXxxx 的宏来直接定义数组的大小。在 C++ 教科书上总是把他们当成反面教材，要求换用 std::vector 。</p>

<p>但实际上，如果能对目标问题做精确的评估，并在代码中做出合理的断言。固定长度的数组的好处也很明显。数据结构比较简单，程序会更加健壮。尤其是在多线程环境中更能体现出来。</p>

<p>即使是一些动态分配的部分，如果能估算出大致的总量，那么把内存池定义在结构中也更简洁一些。即，不额外用 malloc 分配一块新内存，再用指针去引用它。而是直接在结构中声明出内存块，让结构内的动态指针引用自身内存池中的内存。</p>

<p>这样，复杂的对象在内存中的数据布局依然是连续的。管理它们(生命期)的代码成本也比较低。对并发支持也更简单。不过，这同时也要求对需求的估算更明确，适合于用在严格定义好的内聚性很高的模块内部。</p>

<hr />

<p>前段时间读过一本 Objective-C 的书。影响比较深刻的是 cocoa 框架对内存的管理方案。</p>

<p>虽然 cocoa 也使用引用计数。但是却不是在对象引用计数到 0 时就立刻释放内存。而是结合了 autorelease pool 。</p>

<p>这可以减少在当前栈帧访问无效对象的 bug ，而不需要引入 C++  中的 RAII ，以比较清晰可显的法则定义出内存管理的方案。</p>
]]>
    </content>
</entry>
<entry>
    <title>Clash of Clans</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/2013/02/clash_of_clans.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1/entry_id=807" title="Clash of Clans" />
    <id>tag:blog.codingnow.com,2013://1.807</id>
    
    <published>2013-02-22T03:20:01Z</published>
    <updated>2013-02-22T04:04:17Z</updated>
    
    <summary>最近在玩一款 iOS 游戏, 叫做 Clash of Clans 。这款游戏让我发现，手机/平板的网络游戏和传统网络游戏、网页游戏，除了 UI 方面，设计也是可以有很大的不同的。 对于移动平台，最大的不同在于，玩家的游戏时间碎片化。所以能集中精力的游戏持续时间不宜过长。应该在五分钟以下。当然，也需要提供玩家长时间持续游戏的途径。 玩家的游戏时间很自然的分为了在线时间和离线时间。所以，离线玩法相当重要。 COC 开创了一种：在线创造内容，离线后供其他玩家娱乐的模式。这是以往的游戏所不具备的。...</summary>
    <author>
        <name>云风</name>
        <uri>http://www.codingnow.com</uri>
    </author>
            <category term="游戏" />
            <category term="游戏开发" />
    
    <content type="html" xml:lang="en" xml:base="http://blog.codingnow.com/">
        <![CDATA[<p>最近在玩一款 iOS 游戏, 叫做 Clash of Clans 。这款游戏让我发现，手机/平板的网络游戏和传统网络游戏、网页游戏，除了 UI 方面，设计也是可以有很大的不同的。</p>

<p>对于移动平台，最大的不同在于，玩家的游戏时间碎片化。所以能集中精力的游戏持续时间不宜过长。应该在五分钟以下。当然，也需要提供玩家长时间持续游戏的途径。</p>

<p>玩家的游戏时间很自然的分为了在线时间和离线时间。所以，离线玩法相当重要。</p>

<p>COC 开创了一种：在线创造内容，离线后供其他玩家娱乐的模式。这是以往的游戏所不具备的。</p>
]]>
        <![CDATA[<p>在线的时候，除了攻打其它玩家的城市外，你可以充分享受城建的乐趣。研究如何布局设置陷阱，而不被别的玩家攻打下来。这使整个游戏的内容极大的丰富。比简单的离线比拼数值要有趣的多。</p>

<p>COC 的随机撮合对手的方式也是对以往传统网页战略游戏是一个重大的改进。</p>

<p>它不再允许主动攻击特定玩家，而是系统尽可能的找到实力相当的对手。这使得玩家不至于过于轻松而失去兴趣，也不会被频繁虐待导致流失。免费玩家可以在游戏中找到和 RMB 玩家相同的乐趣以及公平性，而他们虽然不付费，但却创造了高质量的游戏内容。玩家永远可以让自己的城市逐步发展起来，失败的挫败感很弱。而战争无论输赢都有相当的开销，以至于不可能压制性的横扫世界。</p>

<p>COC 简化了所有可以简化的设计，只留下了核心乐趣。上手非常简单，但在简单规则下可以挖掘的技巧又非常多。这些都是值得游戏设计者学习的地方。我相信我们这个山寨大国已经有很多团队想微创新一个中文版的 COC 了。一个不经过打榜，没有宣传的游戏，可以坐稳各个地区 app store  top 10 的位置，而迟迟没有类似游戏出现。可想而之，山寨的诱惑该有多大。</p>

<p>不过我认为想抄一个 COC 并不简单。过年在玩游戏的同时一直在想，移动平台的开发到底和传统网络游戏有什么区别？</p>

<p>我认为主要有两点:</p>

<ol>
<li><p>移动平台的网络注定处于一种不稳定状态。玩家会在地铁上游戏，会在游戏时突然接一个电话，会因为坐电梯失去信号…… 所以在游戏设计时，必须解决这个问题。不能默认网络是稳定的，需要适当的在没有网络连接的时候还可以流畅操作。</p></li>
<li><p>移动设备的内存资源有限、下载客户端的体积也有限。现在在 iphone 上做的游戏所受的限制，不比 10 年前我们做梦幻西游时强太多。单就 COC 而言，10 个兵种加上升级的表现不同，以及建筑本身各个级别的动画，如果不想办法，这些序列帧图片可以轻易超过 100M ，但它在 iPAD 1 上却可以流畅运行。这都是一般开发公司想复制一个有同样图像表现力的游戏所难以克服的技术障碍。</p></li>
</ol>
]]>
    </content>
</entry>
<entry>
    <title>C# 版的 Lua</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/2013/02/unilua.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1/entry_id=806" title="C# 版的 Lua" />
    <id>tag:blog.codingnow.com,2013://1.806</id>
    
    <published>2013-02-01T06:04:46Z</published>
    <updated>2013-02-01T06:13:46Z</updated>
    
    <summary>我们游戏客户端使用了 Unity3D , 我们不打算给它写 C 插件, 所有的开发都在 mono 中进行的。 由于某些需求，我们需要在客户端解析一些 Lua 脚本（这些脚本同时供我们的服务器使用）。所以，就有了阿楠同学开发的 UniLua 。 这个世界上已经有了很多的 .net 版的 Lua 实现，但是都不完整。它们大多是基于 Lua 5.1 甚至更老的版本的。还有一些只能解析 Lua 的字节码（这样很容易实现），而不能让 Lua 源代码直接工作起来。这使得在 Lua 中很常见的 meta 编程变得不可用了。...</summary>
    <author>
        <name>云风</name>
        <uri>http://www.codingnow.com</uri>
    </author>
            <category term="lua与虚拟机" />
    
    <content type="html" xml:lang="en" xml:base="http://blog.codingnow.com/">
        <![CDATA[<p>我们游戏客户端使用了 Unity3D , 我们不打算给它写 C 插件, 所有的开发都在 mono 中进行的。</p>

<p>由于某些需求，我们需要在客户端解析一些 Lua 脚本（这些脚本同时供我们的服务器使用）。所以，就有了阿楠同学开发的 <a href="https://github.com/xebecnan/UniLua">UniLua</a> 。</p>

<p>这个世界上已经有了很多的 .net 版的 Lua 实现，但是都不完整。它们大多是基于 Lua 5.1 甚至更老的版本的。还有一些只能解析 Lua 的字节码（这样很容易实现），而不能让 Lua 源代码直接工作起来。这使得在 Lua 中很常见的 meta 编程变得不可用了。</p>
]]>
        <![CDATA[<p>这个版本实现了 Lua 5.2 的大部分特性。特别是对 parser 和 coroutine 的支持，是许多其它实现所不具备的。</p>

<p>Lua 的基本类型：string / table 都用 C # 自己的原生实现对应，gc 也直接依赖 .net 的环境。由于工作在 mono 环境中，所以原来对于 Lua 很重要的一部分：和 C 交互的 API 变成了和 C  # 交互。</p>

<p>UniLua 着重于在 Unity3D 所嵌入的 mono 中实现 Lua 5.2 。而 mono 所支持的是官方版本的 C# 的一个子集。所以理论上 UniLua 也是可以用于官方的 .net 环境中的。</p>
]]>
    </content>
</entry>
<entry>
    <title>内存异常排查</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/2013/01/memory_bug.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1/entry_id=805" title="内存异常排查" />
    <id>tag:blog.codingnow.com,2013://1.805</id>
    
    <published>2013-01-30T09:11:17Z</published>
    <updated>2013-02-03T07:46:40Z</updated>
    
    <summary>今天开电话会议，帮助合作方排查 C++ 开发的程序崩溃的原因。 现象是这样的： 有一个 C++ 对象，其中有一个 vector ，里面存放了大量对象指针。这个 C++ 对象在构造完毕后，没有任何改写操作，全部是只读的。 在某个退出环境中，C++ 对象被析构，析构函数需要调用 vector 中所有对象指针的删除方法。这时，这个长度大约为 100 多的 vector 第 95 个指针是错的。比它应该的指针位置偏移了一到三字节（多次运行现象不一，出错几率也不大）。 整个数组只有这一个指针错误。因为这个错误，引发了程序崩溃。 从电话中的描述，我推断： 这不太可能是由错误的调用此 C++ 对象的方法改写 vector 的内容导致的，因为这个对象全部是读方法，并加了 const 修饰。且，这个数组保存的是对象地址。一个由正常的内存分配器分配出来的地址都是对齐的，但此处错误的被改写为奇数。...</summary>
    <author>
        <name>云风</name>
        <uri>http://www.codingnow.com</uri>
    </author>
            <category term="调试" />
    
    <content type="html" xml:lang="en" xml:base="http://blog.codingnow.com/">
        <![CDATA[<p>今天开电话会议，帮助合作方排查 C++ 开发的程序崩溃的原因。</p>

<p>现象是这样的：</p>

<p>有一个 C++ 对象，其中有一个 vector ，里面存放了大量对象指针。这个 C++ 对象在构造完毕后，没有任何改写操作，全部是只读的。</p>

<p>在某个退出环境中，C++ 对象被析构，析构函数需要调用 vector 中所有对象指针的删除方法。这时，这个长度大约为 100 多的 vector 第 95 个指针是错的。比它应该的指针位置偏移了一到三字节（多次运行现象不一，出错几率也不大）。</p>

<p>整个数组只有这一个指针错误。因为这个错误，引发了程序崩溃。</p>

<p>从电话中的描述，我推断：</p>

<p>这不太可能是由错误的调用此 C++ 对象的方法改写 vector 的内容导致的，因为这个对象全部是读方法，并加了 const 修饰。且，这个数组保存的是对象地址。一个由正常的内存分配器分配出来的地址都是对齐的，但此处错误的被改写为奇数。</p>
]]>
        <![CDATA[<p>这个错误也不太可能是由内存越界写造成的，因为在这一长片内存中，只有一个指针异常。</p>

<p>最大的可能是：某个对象被删除，但其指针还在使用造成的，所谓悬空指针问题。</p>

<p>那么，导致是这个出问题的对象指针悬空，还是另一个对象指针悬空影响的呢？</p>

<p>最大的可能是，这个出问题对象所占据的内存空间，曾经被别的对象使用过。但是前任使用者释放了内存，却在某处保留了指针。然后出问题的对象复用了这块地址。</p>

<p>做这个判断是因为，如果出问题的对象本身是悬空指针，那么后来者占用了它的内存的话，应该成片的改写。</p>

<p>从现象可以推断，内存异常一定是对原有数据进行递减造成的，而不是简单的覆盖了一个新的值。</p>

<p>从退出时出错可以判断，这个 C++ 对象在有效生命期间很有可能一直是正常的，否则指针错误很可能很早就暴露出来了。</p>

<p>那么，我认为最大的可能性就是：有另一个 C++ 采用了引用计数的机制。这个对象曾经引用到 0 而被正确的析构并释放掉了。但某种原因在另一个地方还保留了针对它的 raw 指针。</p>

<p>这个被释放掉的对象的内存被后来的出问题的这个 C++ 对象复用，一系列的退出析构操作导致了一系列的对象析构函数的调用。那个悬空的 raw 指针被调用了。这个指针指向的对象没有虚表，所以它的析构函数可以正确的执行。引用计数这个量存在的位置恰巧在后来出问题的 C++ 对象的中间。</p>

<p>减引用的函数把这个位置的值，也就是另一个对象的指针作为数值减了 1 ，发现不到 0 就跳过了。接下来的析构操作到这个位置时，访问了不正确的指针。这里期盼的指针引用的对象有虚表，偏移的差别导致跳转到虚析构函数出错。</p>

<p>从电话里可以获得的信息有限，我的推断只能到这里了。</p>

<p>为了进一步的盘查错误，我建议：把所有的对引用计数的操作，包括标准库中的智能指针的代码，都加上 assert 判断。断言所有的引用计数的值都应该在 0 到 10000 之间。</p>

<p>我并不是说智能指针的实现会有问题，尤其是采用标准库的实现一定没有问题。</p>

<p>但是，当一个对象本身被释放后，它的（悬空）指针还可以被调用析构函数却是一个隐患。悬空指针调用的对象中如果有一个智能指针的话，那么这个智能指针的析构函数依旧是会做递减操作的。</p>

<hr />

<p>顺便吐槽 C++ 。</p>

<p>我现在看到的这个 C++ 项目，如果用一个标准的 windows 下的 malloc ，也就是一个性能比较低下的内存管理器，性能简直不能接受。你必须换一个非常优秀的内存管理器才能正常工作。这样的依赖内存分配器的性能，在我见过的 C 开发的项目中几乎不可能存在。</p>

<p>这是因为 C++ 的项目多半层次混乱。我说的混乱，不一定指开发逻辑层次上的混乱，而是假借高性能之名，看起来在源代码层次把软件的层次分清楚了，但是在二进制层面却是混杂在一起的。</p>

<p>一个小小的内存管理模块，就穿插于最底层到最上层。</p>

<p>一个层次分明的系统，在物理上就应该是相互隔离的，这种隔离，仅仅存在于人阅读的源代码层是绝对不够的。这就好比 OS 管理下的应用进程，它绝对不依赖应用进程的程序的工作正常，不依赖应用进程准确的申请和释放资源。而是当应用进程结束后，干净的回收它申请过的所有东西。</p>

<p>小到同一进程下的软件，也理所当然要按这个思路进行才对。C++ 不强调这一点，反而鼓励程序员生产出庞大的软件，并美其名曰：信任程序员。但却把用 C++ 的程序员引向一条邪路。信任程序员和放任程序员是两码事。</p>
]]>
    </content>
</entry>
<entry>
    <title>温故而知新</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/2013/01/reading_lua_vm.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1/entry_id=804" title="温故而知新" />
    <id>tag:blog.codingnow.com,2013://1.804</id>
    
    <published>2013-01-30T05:25:47Z</published>
    <updated>2013-01-30T08:38:16Z</updated>
    
    <summary>我上次通读 Lua 的源代码时，Lua 还在 5.1 。当然 Lua 5.0 我也读过，4.0 和 3.2 则读的不多。 最近有一点空闲，想续写我那本 Lua 源码欣赏。按我心里的计划，还有大约 6 章。虚拟机、字节码持久化、C API 、解释器、GC、库函数。 新添了一章关于虚拟机的，所以重新读了一遍相关源码。发现 Lua 5.2 比上一版修改了不少，几乎每个位置都有修订。 自己读代码和写出来给人看又是不同，真的逐行推敲的话，之前的理解也是经不起琢磨的。为什么要写这一行；为什么这一行在这个位置，而不是在后面；为什么要这么实现，而不是那样实现…… 一边写，一边发现对别处的引用会引发新的疑问，继而需要对之前已完成的章节做一些修补。 上一次发布 pdf 时，采用的是日后纸质书的版式。留白太多对于电子阅读其实是很浪费的，读代码尤其不好。所以这次重新排了一下。 这次主要是增加了关于 VM 的新章节。 有兴趣的同学可以下载：《Lua 源码欣赏》。但我不建议现在开始阅读，尤其是对不仅仅想随便翻翻的同学。因为我经常修改它，今天看到的版本，可能写完后已经改了不少了。 btw, 在我写完后，发现最近有另一个同学也在写类似的文章。这里给出一个链接，有兴趣的同学可以看看。...</summary>
    <author>
        <name>云风</name>
        <uri>http://www.codingnow.com</uri>
    </author>
            <category term="lua与虚拟机" />
    
    <content type="html" xml:lang="en" xml:base="http://blog.codingnow.com/">
        <![CDATA[<p>我上次通读 Lua 的源代码时，Lua 还在  5.1 。当然 Lua 5.0 我也读过，4.0 和 3.2 则读的不多。</p>

<p>最近有一点空闲，想续写我那本 Lua 源码欣赏。按我心里的计划，还有大约 6 章。虚拟机、字节码持久化、C  API 、解释器、GC、库函数。</p>

<p>新添了一章关于虚拟机的，所以重新读了一遍相关源码。发现 Lua 5.2 比上一版修改了不少，几乎每个位置都有修订。</p>

<p>自己读代码和写出来给人看又是不同，真的逐行推敲的话，之前的理解也是经不起琢磨的。为什么要写这一行；为什么这一行在这个位置，而不是在后面；为什么要这么实现，而不是那样实现……</p>

<p>一边写，一边发现对别处的引用会引发新的疑问，继而需要对之前已完成的章节做一些修补。</p>

<p>上一次发布 pdf 时，采用的是日后纸质书的版式。留白太多对于电子阅读其实是很浪费的，读代码尤其不好。所以这次重新排了一下。</p>

<p>这次主要是增加了关于 VM 的新章节。</p>

<p>有兴趣的同学可以下载：《<a href="http://www.codingnow.com/temp/readinglua.pdf">Lua 源码欣赏</a>》。但我不建议现在开始阅读，尤其是对不仅仅想随便翻翻的同学。因为我经常修改它，今天看到的版本，可能写完后已经改了不少了。</p>

<p>btw, 在我写完后，发现最近有另一个同学也在写类似的文章。这里给出<a href="http://blog.csdn.net/yuanlin2008/article/category/1307277">一个链接</a>，有兴趣的同学可以看看。 </p>
]]>
        

    </content>
</entry>
<entry>
    <title>内存泄露排查小记</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/2013/01/memory_leak.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1/entry_id=803" title="内存泄露排查小记" />
    <id>tag:blog.codingnow.com,2013://1.803</id>
    
    <published>2013-01-22T07:40:25Z</published>
    <updated>2013-01-22T08:05:07Z</updated>
    
    <summary>最近我们的游戏服务器又发生了一起内存泄露事故。 由于泄露速度极其缓慢，所以有很大的隐蔽性。 Bug 最后是这样确认并解决的： 由于 Skynet 本质上是由唯一的时钟模块驱动的，我们首先修改了时钟部分的代码，让系统可以以 10 倍速运作。这样，原本耗时几天的泄露现象，可以在半天内就确定了：一定存在某种内存泄露的 Bug, 而不是正常流程导致的内存占用持续上升。...</summary>
    <author>
        <name>云风</name>
        <uri>http://www.codingnow.com</uri>
    </author>
            <category term="调试" />
    
    <content type="html" xml:lang="en" xml:base="http://blog.codingnow.com/">
        <![CDATA[<p>最近我们的游戏服务器又发生了一起内存泄露事故。 由于泄露速度极其缓慢，所以有很大的隐蔽性。</p>

<p>Bug 最后是这样确认并解决的：</p>

<p>由于 <a href="http://blog.codingnow.com/2012/09/the_design_of_skynet.html">Skynet</a> 本质上是由唯一的时钟模块驱动的，我们首先修改了时钟部分的代码，让系统可以以 10 倍速运作。这样，原本耗时几天的泄露现象，可以在半天内就确定了：一定存在某种内存泄露的 Bug, 而不是正常流程导致的内存占用持续上升。</p>
]]>
        <![CDATA[<p>我们原本就预留了各个独立服务的调试接口，大部分服务都是用 Lua 编写，利用调试接口，可以清楚的看出每个 Lua 虚拟机占用了多少内存。</p>

<p>但是原有的 Lua 调试接口有一定的缺陷，它以来虚拟机本身要能对外提供服务。一旦服务阻塞，就不能自行报告调试信息。这次事故和这点无关，但这次我们改进了 Lua 内部的内存占用汇报机制。</p>

<p>我们给 Lua 虚拟机定制了内存分配器，把使用报告汇总到一个全局的 C 数组中。这样，直接用 gdb 切入进程，也能直接看到报告。调试接口也不依赖服务自身的响应来汇报内存状态了。</p>

<p>这次排查事故的过程，我们排除了 Lua 对象的泄露可能。因为 Lua 虚拟机中占用的内存都没有呈现不正常上升的状态，所以利用之前<a href="http://blog.codingnow.com/2012/12/lua_snapshot.html">编写的小工具</a> 很难派上用场。</p>

<p>在 C 代码层面，我们使用的 <a href="http://www.canonware.com/jemalloc/">jemalloc</a> 可以很好的辅助分析。</p>

<p>这次的现象比较典型，在服务器正常关闭后，几乎所有的内存都正确释放了。我们给服务器退出过程增加了更详尽的 log ，确认在一张地图退出时，关闭 Lua state 的同时，C 里面有大量的内存被释放掉了，远超 Lua 占用的内存数量。</p>

<p>这几乎可以确定，在 Lua State 中大量引用了 C 对象，在运行过程中没有解引用，却在退出时正确释放了。可是，我们又没有观察到运行过程中 Lua State 内存的暴增。一开始，这种现象颇能迷惑人。</p>

<p>经过一番思考，我确定了问题。这是因为，地图服务应用了一个 AOI 管理的 C 模块。这个 C 模块以整数 handle 的形式对内部对象做了封装。在写 Lua 封装层的时候，简单的提供了几个 api ，用于创建和删除 handle 。所以，C 对象在 Lua 虚拟机中是以 handle 的形式存在的。使用的同学不慎忘记了在合适的时机，显式去删除这个 handle 。</p>

<p>具体到这个问题上，应该在怪物死亡的时候删除怪物的 aoi 对象 handle 。这个 bug 存留了很久，之所以没有被发现，是因为怪物死亡频率不高，且 AOI 模块中相应的 C 对象消耗内存很小的缘故。直到策划重新布了怪，不同阵营的怪相互厮杀，血流成河的缘故。</p>

<hr />

<p>这次事故提醒我，永远不要把显式销毁对象的责任追加到动态语言使用者上。尽量利用 gc 的机制，当然还需要更贴切的使用。这对 lua 的 C 绑定库的实现者要提高要求。</p>

<p>比如这次 C API 使用的是整数 handle 来引用对象，为这些 handle 创建独立的 userdata 虽然运行成本略高，但更安全一些。即，我们分配 4 字节的 userdata ，里面放上 C handle ，并给出 <code>__gc</code> 方法确保对象即时销毁。</p>

<p>关于封装库的话题，恰巧<a href="http://blog.codingnow.com/2013/01/binding_c_object_for_lua.html">最近刚刚写过</a>。</p>
]]>
    </content>
</entry>

</feed> 

