backtrace-mingw 更新
backtrace-mingw 今天更新了一下。原来的版本不能正确显示 dll 里的符号信息。现在可以了。只是打了个补丁,所以代码比较乱。
不知道 backtrace-mingw 的同学,可以看这里 。
backtrace-mingw 今天更新了一下。原来的版本不能正确显示 dll 里的符号信息。现在可以了。只是打了个补丁,所以代码比较乱。
不知道 backtrace-mingw 的同学,可以看这里 。
去年介绍过我在项目中实现的一个动态数组模块的接口。
实际上,我为它提供的接口要更多一些,比如删除一个元素。
void array_erase(struct array *, seqi iter);
原来的语义就是删除 iter 引用的元素。但这里引出一个问题:删除后,iter 是否应该保持有效?
从语义上说,iter 应该在调用完毕后变成一个无效引用。但实际应用中,往往需要在迭代 array 的过程中,删除符合条件的元素。让迭代器失效的做法,用起来很不方便。
美术同学给我们的游戏做了段片头视频,正要加到产品中去时,才发现我们的引擎居然没有提供视频播放的功能。我想这个东西开源库一大堆,那做起来还不是小菜一碟。可没想到还是折腾了一整天才搞定。
第一件事是考察 License 。结果用的人做多的 ffmpeg 是 GPL 的,不适合我这种商业应用。虽然有一部分功能可以以 LGPL 的方式使用,但是遵守起来也是麻烦一大堆。想了一下还是做罢。我可不想学暴风影音和 QQ Player 那样上耻辱榜。
最终考察结果,居然没太多的选择。还是 google 的 vp8 最好。License 最为宽松,所以就去下载了一份 libvpx 的最新版试用。
今天周末,桌游店里却没客人,昨天打电话预约的朋友没来,所以我就奔到办公室测试上周写的代码。
上周的工作主要是设计了一个新的包格式,然后整合入前段时间实现的虚拟文件系统中。
这个工作和前段实现的 zipfs 有相似之处,所以做起来也很快。不过前面没仔细测试。今天比较闲,就设计了几组复杂的测试数据,感觉覆盖了各种边界情况。一测试果然发现了 Bug 。
这个 Bug 有点启发意义,所以在解决掉之后,决定记录一下。
9 年前,我设计了网易游戏的资源包以及补丁包的数据格式。
当初的设计目的是:方便解析,快速定位资源包内的文件,方便更新、每次更新尽可能的节约带宽。这些年来,虽然各个项目修修补补的改进了资源包的格式,但本质上并没有特别大的修改。
一开始我们直接把需要打包的文件连接起来,在文件末尾附上文件索引表。当初为了快速定位文件名,文件名做了 hash 处理,可以用 hash 值直接定位文件。而资源包里并没有储存文件名信息,而是保存在一个额外的 index 文件中。这个 index 文件并不对外发布。所以直接对资源包解包是无法准确还原文件名的。
btw, 暴雪的 mpq 文件也是作类似处理的。除非你猜测出文件名,否则也很难对文件名还原。网上许多 mpq 解包工具都针对特定游戏附了一个额外的文件名列表。
和许多其它游戏 Client (比如暴雪的 MPQ 文件)不同。我们的包格式里文件与文件之间是允许有空洞的。这是考虑到资源包文件都比较大。如果用传统的打包软件运作的方式:从包内删除一个文件,就重新打包或移动内部数据。在玩家更新资源的时候,就会有大量的文件 IO 操作。比如 WOW 或 SC2 在更新的时候,下载更新包的时间往往只占整个更新时间的一小部分,大部分时间花在把补丁打在已有的资源包上。
如果频繁更新客户端,对于用户,这会有很讨厌的等待。
所以当初考虑到这个因素,我们在删除包内文件时,并不移动资源包内的数据,而是把空间留下来。如果新增加的文件较之小,就重复利用这个空间。如果利用不上,就浪费在那里。这有点像内存管理算法,时间久了,资源包内会有一些空洞,但也是可以接受的。
同时,还有另一个方式更新新的资源。那就是将需要更新的文件单独打包,以相同文件名(后缀不同)保存在用户硬盘上。游戏引擎在读取资源的时候,优先在更新的资源包内检索。这个方式在 Id soft 的 Quake/Doom 系列中也有采用。
为了保证用户补丁更新速度。我们的补丁中并不是保存的资源包内的小文件。而是在开发机上以增量方式重新打包。补丁文件其实是整个资源包的 diff 文件。由于前面所述的打包方案,这个 2 进制 diff 文件其实可以做到很小。尤其对某些文件的局部修改,对整个资源包的影响很小。
在公司,有后来的同事质疑过这种方式,觉得其对减少补丁体积的作用不大。反而增量打包增加了许多制作补丁包的时间。主张直接在补丁中放入更新的小文件,然后让最最终用户机上以小文件为单位做 patching 。
的确,2 进制 diff 的作用有限,现在很多项目改用文本数据格式,很小的修改就会影响整个文件的 diff 结果。不过原始的设计也有其历史原因。因为 10 年前硬盘 I/O 速度很慢,而大话西游在设计时又需要实现无缝加载的大地图。所以地图文件的格式是经过特别设计的。这种方式很适合地图文件的修改和更新。另外,对于未压缩的图片文件的更新也有其意义。
又仔细推敲了两天,把 lua 版的 protobuf 库完善了一下。主要是做了两个工作:
protobuf 本身的格式,google 是自描述的。定义为 google.protobuf.descriptor 。我先自己实现的 parser 图方便,用了自己的中间交换格式。为了日后更通用,稍微修改了一下,可以生成于官方相同的结构。解析的性能稍有下降,不过应该兼容性更好。
一开始实现的 api 虽然性能非常好。(经简单测试,是 python 库的 30~40 倍,和 C++ 库性能相当)但若消息格式复杂,实现起来稍有麻烦。所以我做了点小封装。即为每个消息生成一对函数,可以用来打包和解包完整的消息,映射到 lua 的 table 结构上。lua 生成代码供自己调用的技巧在 lua 社区广泛使用。比如 kepler 项目。这使得 lua 可以用很短的代码行数完成很复杂的工作,不失性能。我这个封装层只有 100 行代码左右,一大半代码是为了解决消息展开时有递归定义的情况,否则更简短。(message 中有一些 field 的类型是自己,这是一种不多见的用法,但 protobuf 似乎并没有拒绝这种用法)
Google 的 Jeff Dean 同学说,设计分布式系统一定要有 Protocol Description Language。
Google Proto Buffers 的意义在于,定义了一个不错的 PDL 。protobuffers 的实现反而不那么重要了。
这几天我一直在倒腾 lua 下的 proto buffers 的支持。一直在思考,怎样的接口才是最适合 lua 使用的。
大多数语言下的 proto buffers 实现,都是将编码的数据块展开成本地语言的数据结构。对于 C/C++ ,这是最高效的形式。但对于动态语言,那就未必了。虽然 google 为 python 做的 proto buffers 的官方实现也是如此,但我依然想考虑一下,是否有更高效的方式来做这件事。
那些日子(终) (116)
kimi :
看完了将近两点,感触很深。
感谢云风。
...
从数组里删除一个元素 (9)
zwinger :
用纯C写代码,动态容器还是习惯优先使用list类容器。...
mingw 下的 stack backtrace (16)
Cloud :
前几天的版本有点问题,只能显示 exe 里的栈,如果有额外加载的 dl...
从数组里删除一个元素 (9)
lichking :
http://code.google.com/p/libcstl/, ...
在游戏引擎中播放视频 (15)
mm :
风风,我爱你!!!...
我诅咒帮网易做 OA 系统的公司 (55)
ichaochao :
哈哈,我也来喷喷,我原来的公司用的金和的系统,太蛋疼了。现在新公司也想...
把 vfs 实现好了 (17)
zhanxw :
有支持seek的gzip: razf...
从数组里删除一个元素 (9)
lin_style :
如果是遍历需求的删除,开发者而是要封装遍历的接口。比如linux链表中...
在游戏引擎中播放视频 (15)
godness :
to glds...
在游戏引擎中播放视频 (15)
godness :
你可以偷偷D版,你可以心安理得,但你喜欢拿出来炫耀,你很喜欢被鄙视...