« April 2006 | Main | June 2006 »

May 30, 2006

对象和资源的管理

用了这么长时间的 C++ 架构软件,最头痛的莫过于管理内存中的对象和资源。而在管理对象方面,最难处理的就是删除对象的时机。恐怕很多人早就意识到这一点,所以才有了 gc 技术的蓬勃发展。

当一个对象被很多地方引用的时候,通常我们会给出引用记数,当记数减到 0 的时候就删除,这是个看似完美的解决方案。但是,有多少地方会记得解除引用呢?借助 C++ 的语法糖,可以自动的完成这些工作。长期的引用关系,可以在构造和析构的时候操作;短期的引用,比如就在一个函数内获得对象,操作完毕后马上解除引用。这个时候,可以通过返回几个 warpper 对象来完成。

固然,语法糖可以减少出错的可能,但是,代码还是并隐式的加上了,于之伴随的是运行时性能的下降和代码的膨胀。这个时候,多考虑下设计上的改进会有所收获。

最近两年做的项目,我都趋向于对象统一做删除管理。可以在主线程中安排一个定期时间,扫描所有对象,把需要删除的对象删除。而平时的删除,只是做一个简单的 mark 操作。因为 mark 操作是不可逆的,即不能把一个准备删除的对象 mark 回"不必删除"的状态,所以 mark 操作本身是线程安全,不必加锁的。

为什么这样做?因为对于单种对象或是资源而言,大部分情况都是只读的,很多东西经过初始化后,不再有写操作。即使有,也是 os 保证线程安全的。例如文件 handle , socket 这些。而每个对象都会有一种特殊的操作叫做释放,释放可以看成一种写操作,它导致了对象本身的破坏。当一个对象是完全只读的时候,我们就不需要再考虑它的线程安全问题,就是因为有释放这种操作的存在,破坏了完全只读的特性。当我们把释放提取出来统一放在安全的地方处理的时候,我们就可以让大多数对象完全只读,而不用估计线程安全了。

而且这种做法在单线程环境也是有意义的,用一些简单的技巧(例如用一个指针间接访问对象),甚至不用再使用引用计数。

对于单件的处理,采用静态对象和惰性初始化的方案,简直就是 C++ 程序员的陋习。Double Checked Locking is broken 相信很多人都读过了。过于依赖语法糖,通常就会造成这种结果。其实让程序有明显的初始化和退出阶段,是很容易被规划出来的。把单件(singleton) 的处理放在正确的时机,以正确的次序来处理并非难事。

对于全局所有资源的管理,我个人的主张是一定要考虑采用树结构,而不是线性表。因为扫描所有资源这种操作会比较常见。比如以上提到的扫描所有资源,删除 mark 过的对象。采用树结构的好处是,mark 的时候,可以同时 mark 父节点(对父节点计数)。这样,任何资源树上的任何一支都可以通过 root 知道是否需要遍历分支。通常,删除这种操作并不频繁,通过检查根节点一次就可以忽略整个遍历过程了。

而且删除操作往往是可以并行的。为了在删除过程不影响资源树的结构,我们还可以只是对资源树上的节点置空,再统一压缩掉空指针。这样就可以获得最大效率的删除操作,不至于因为定期删除资源而使服务停顿过久。

May 29, 2006

opera fans

用 opera 很多年了,从 97 年上网之初到现在,似乎是从第 3 或是第 4 版开始用的。

今天找了个作为 opera fans 的旁证:别人的鼠标都是左键上磨掉了一点漆,而我的则是右键磨掉了更大的一块。

开新窗口要按住右键,切换子窗口要按住右键滚滚轮,关窗口要按住右键,刷新要按住右键,回退也要 ... 我觉得 opera 发明的按住右键做鼠标手势这种创新,可以跟微软发明鼠标双击媲美了 :D

May 27, 2006

C 有 C 的规则

最近在用 C 写程序,规模不小的程序,也谈不上太大。大约一万行之内的模块吧,关于 UI 的基础框架。我知道这个东西连用 C++ 都谈不上合适,更莫谈 C 了。可是我倔强的认为,应该用 C 把它写好。

很多人批评 C++ 没有拥有好的教育体系和方式,导致了很多 C++ 程序员在用 C 的方式写 C++ ,或是把 C++ 当成更好的 C 来用。可是受过 C++ (正确的?)教育熏陶过的程序员呢,当他拿起 C 的时候,是否把 C 当成蹩脚的 C++ 来用呢?

我希望我不是。

了解了更多的语言后,我深信,每种语言有它的游戏规则。C 有 C 的规则,C++ 有 C++ 的规则。用的越深入,越发觉得其间的差别。C++ 不是 C ,C 也不是 C++ 。它们的最大的共通点,是类似的语法,语法类似到可以用一套编译器编译。

C 没有构造函数,没有析构函数。没有虚表,没有继承。const 的使用不那么严格,强制转换不那么忌讳。没有模板,宏是强有力的工具。简单而统一的 ABI ,冗长但是有效的命名规则。

C99 以前,我们甚至不可以任意声名一个变量,这估计是最让 C++ 程序员讨厌的地方,直接违反了设计原则,但同时也让 C 程序员保持了良好且统一的风格:不会让一个函数过长,也会把函数内的模块分开,即使用蹩脚的 do {} while(0);

我曾经认为 C 在某方面比 C++ (运行时)低效,但是长时间用下来,发现正是这些“低效”,让我更小心的使用它,设计更合理的结构。

C 的潜规则里,所有数据结构的最佳默认值是 0 ,这样可以方便的使用 calloc 或者 memset 。数据结构最好是 POD ,最好减少指针的滥用,内存如果可以连续的排放在一起,那么就不惜用一些在 C++ 中看似很糟糕的方案,但这样可以方便的 free ,而且不会忘记析构。

C 做成框架中,最好减少暴露的接口,和 C++ 不同,C++ 可以一组组接口的暴露,而 C 一次只是一个 api ,这成就了无数优秀的 C 库,比 C++ 类库的使用更加便捷。但是也无形提高了设计的难度,只是 C 标准库中,就有那么些不合理的接口设计。

一不小心,我们就会用 C 把整个项目写的很糟糕,但是 C++ 不会,糟糕的项目构架很晚才会发现。而 C ,如果你设计的好,感觉就会很好,稍有不适,就需要重构了。用 C 做项目,如履薄冰。

C 就是那么一如既往的简单,简单的可爱又可恨。我用五年时间,感觉自己学会了 C++ 。但是十五年了,仍旧问自己到底可以用 C 完成多大的项目。

May 21, 2006

脏矩形演示 demo

上个世纪,我在个人主页上发布了风魂。2000 年的时候,我给它加上了脏矩形的支持。之后,就再也没有发布新的版本。

实际上,我经历了好几个游戏项目后,那么久远的代码已经被抛弃不用,但是由于工作原因,新的代码没能公开。现在回首那些老代码,居然发现如此的丑陋不堪。

不过,一些思路还是可以保留下来使用的,其中之一就是脏矩形技术。

早就想用文字写写,后来在写《游戏之旅--我的编程感悟》的时候,总算如愿。遗憾的是,当初我计划为第八章专门配一个 demo 代码,时间和精力的原因没有完成。

这个周末,出于一些考虑,我花了整整一个晚上的时间终于完成了这个 demo 。我想不能太苛求 5,6 个小时内到底能写出多少东西。不过我尽力把它写的好一点,清晰一点。而且在 C 和 C++ 之间,我选择了 C 。这是受最近两年思想的一些变化所影响,向 C 回归是我个人的一些变化。

demo 之所有叫 demo ,是因为它离实用还很远。只是为了说明方法。但是我相信,还是可以在此基础上做的更加完善的。

我把它们贴在了个人空间 里,脏矩形演示 demo 。如果你需要使用这些代码,请留意 wiki 上的版本更新记录,最新的版本一般会有一些 bugfix 。看代码之前,有一些临时组织的 FAQ 可以阅读。这个 demo 并没有追求任何视觉的效果,表象非常的简单。可能,有些代码值得一读是唯一的价值吧。

May 19, 2006

不太精准的时钟

目前,我们的游戏的同步是依赖机器时钟做预测的。这种做法的前提是,client 的机器和 server 的机器的时钟走速必须相同。这样,当我们把时刻校对了以后,可以不通过网络就可以知道时间信息。即,每个通过网络包传送过来的事件已经发生了多久。这只需要发送方为事件打上时间戳,然后接收方以自己的时候为准求出时间差即可。

我原以为,在 client 不作弊的情况下,只需要登陆校对一次时间即可。而我们的服务器组每台机器也只需要在互相握手的时候校对一次时间,然后各自依靠自己机器上的时钟就可以了。今天发生的事情让我对 PC 的时钟准确度产生了怀疑。

我们以一台 freebsd 系统的机器为基准对时服务器,其他机器连上去获取时间。对时服务的程序用 clock_gettime 获取纳秒级时间,折算成 0.05s 的最小单位供其他程序使用。今天实际测试,发现,这台对时服务器的时钟比我用的 PC (windows xp 平台) 大约每 20 分钟会快上 0.1s 。这误差也太大了点吧。

那一天岂不是会快上 6,7 秒?看来不能忽略时钟不一致的情况,保持半小时一次的对时是有必要的。

ps. 另外我怀疑是 freebsd 的问题,打算周一再仔细研究一下。


5月22日 补 周一来上班,跟同事讨论了一下。首先可能是自己的问题。我的 windows client 测试时间的时候用的函数是 clock() 。我错误理解了这个函数的含义。按标准 clock() 应该返回的是程序运行时间量,即当前进程占用系统时间的量。所以应该换成 windows 的 api GetTickCount 。不过实际测试,没有差别,或许 Windows 下定义不太一样。

而后,换了一台 freebsd 的服务器,时间基本正常了。

另外,Windows 下 QueryPerformanceCounter 是不可用的,这个根据 MSDN 的文档猜测,有可能是用 TSC 实现的。在多核和变频时代,TSC 几乎不可能取到准确的时间。

freebsd 上,使用 TSC 获取时间的优先级最低,现在一般使用 ACPI 获取时钟。在 Windows 上没有找到对应的手段。

May 15, 2006

阳朔归来

ys01.jpg

去的时候还有点担心,因为两周前,在办公室用手指做引体,把右手中指和无名指拉伤了。休息了两周似乎没有痊愈,我已经抱定决心,如果必须上指力点的时候,用食指撑一下了。

来到阳朔就开始下雨,虽然雨中的山山水水还是那么秀丽,但是如果来到这攀岩圣地不去摸一下岩壁,多少会有遗憾的。

可笑介绍我认识了西唐,他在阳朔有开了个攀岩店。据说下雨也是可以进洞爬的,只不过是 5.12 的线罢了。

第二天的下午,雨停了。阳朔的岩壁是石灰岩,碳酸氢钙溶解后留下了很多小洞,雨水渗进去表面马上就干了。但是雨停了后的时间却不早了,我们去了比较近的地方。除了我,同事都是第一次爬自然岩壁,而且人工的也玩的不多,我们就选的一条 5.9 的线路体验一下。

ys02.jpg

这条线真的不难,手点都很大,我也不需要去考虑指伤的问题了。大家都没有爬过先锋,所以由我保护西唐上去挂了顶绳。然后是我做的第一次尝试。这条线起步比较难,刚起步就需要翻一个小屋檐,好在去之前我在办公室练了好几次屋檐,还是很顺利翻过去了。

ys03.jpg

不过爬了一半就脱手掉了下来。自然岩壁还是不熟悉,老是找不到手点,体力消耗很大。接下来同事都被打击了,屋檐卡掉了所有人。不能起步,大家都很沮丧。我想再试一次的时候,西唐决定再换条更简单的线。

旁边一条 5.8 的很轻松的就登顶了,也是起步比较难,脚点有些滑,关键地方需要做引体上去。再就是快到顶的时候需要一些技巧,需要做侧身,并交叉手抓指力点,中间都跟爬楼梯一样容易 :)

登顶回头那一刻,阳朔的山水显得格外秀美,不是每个人都有机会在挂在悬崖欣赏风景,而那种感觉只能去亲身体会。明年一定再来。

《我的编程感悟》的一处技术错误

今天收到一封读者来信,指出一处非常明显的技术错误,真的是非常惭愧。在第 118 页讲解 Windows 消息处理时,对于 UpdateWindow 的解释是明显错误的。

书上是这样写的,“这里,UpdateWindow 通过调用 PostMessage 把 WMPAINT 消息推入消息队列实现的。”这是一个明显的错误,UpdateWindow 是直接处理 WMPAINT 而非把消息放入队列。当初是一个笔误,有朋友看过初稿时曾经指出这个错误。我当时觉得自己不会有这种笔误的,看了稿子后发现果真如此,就立刻改过来了。可能是跟编辑来回交换稿子的时候又错误的提交了有误的稿。

收到这封 email 的时候,一开始也是不太相信,查了一下自己的书,果真被印成了白字黑字。惭愧啊。希望下次重印的时候能够修正过来。

May 11, 2006

里程碑

明天去阳朔爬自然岩壁。

去年就定下来,今年 5 月会有一个项目的里程碑,大约是一个可以联网的 demo 吧。直到前天,对于可以看的东西,我们还是一无所有。大家摸黑干了大半年。

还真是一夜间冒出来的,我们的 demo 从 client 到 server 都有了。已经可以连入许多 client 进去互动了。3d engine 以及更基础的构架也被设计的很好。网络部分由于做了运动预测和补偿,操作手感和同步都可以被很好的解决。

居然,在最后一天,里程碑按时到达了。经过良好规划的项目就是不一样。

May 07, 2006

长假过完了

pool.jpg

五一长假,2 号去骑了下马,然后就回家陪父母。在家几天硬是没出门,看老爸打理他的花花草草,陪妈妈看看电视聊聊天。没做家务,就是在露台上的鱼池清理工作上出了点力。原本带了台 GBA 回去,玩了一天不可思忆的迷宫,在 28 层下,不小心操作失误,把该放在脚下的圣域卷轴吟唱了一遍,结果没能剧情通关。正应了同事的话,玩这个要保持清醒的头脑。侥幸用卷轴逃脱出来,不过已经没有勇气再下一次,怕自己承受不了顷刻间一贫如洗的打击了。又摆弄了一下 ps2 ,觉得没啥意思,便买了机票回来。

发现自己还是离不了编程,几天不写程序就浑身不自在。晚上在公司写代码感觉就是好啊。

courtyard.jpg

我们家的小区,我喜欢它的干净整洁。物业不准封阳台,装防盗网,让整体感觉好了很多,好几年了,还是很漂亮。

room.jpg

我的书房,装修花了父亲不少的心血。左边挂的油画和右边的旋转楼梯都是他的作品。

garden.jpg

家里的露台现在是一个小花园,每株植物都有父亲的心血。每每叫他出去旅游,他总放心不下他的花花草草。我期待明年夏天绿色爬满父亲亲手做的葡萄架

wold.jpg

另一面眺望,远远的那是东湖。城中村过几年估计会进一步压缩。楼下那片空地,据说已经规划给我高中的母校,过两年就搬过来了。现在,还是一片空地,两台推土机正在整平地面,听说已经干了很久了,不紧不慢的。依然有人见缝插针的种上几分菜田。夏天的晚上,窗外蛙声一片。