第七回 文件系统
好长好长时间没有更新了,主要还是因为太懒,写这么一篇文章往往要花几个小时的时间,有时候要配图,有时候要配代码,感觉比较累,真佩服那些随随便便就能写一大堆的家伙们.
不过既然开始,就要继续.这篇文章写写引擎中用到的文件系统,
engine之所以需要一个文件系统的原因,最主要的是因为游戏发布的时候需要把游戏数据打包,一个大型的游戏的文件个数可能达到几万甚至十万以上,这么多文件无论安装还是卸载都会很慢,而且也会占用额外的空间,所以需要把它们放到一个或多个大文件中去.但在开发时,我们又不希望游戏数据以打包形式存放,因为对文件的各种操作会很不方便.这样就导致了游戏的数据在发布期和开发期存放的方式是不同的,所以我们需要一个文件系统来封装这种差别,暴露给上层的是统一的文件读写接口,而它的底层实现则可以处理文件的不同存取方式.比如,现在需要打开一个文件"C:\IxEngine\data\texture\aaa.dds",这个文件在开发期可能存在于操作系统的这个位置上,在发布期可能存在C:\IxEngine\data.pak这个包里,而在包里它的位置是texture\aaa.dds.我们把这个字符串交给文件系统,文件系统可以自动的搜寻路径上的每个节点,如果该节点上有一个包文件的话,则会进一步到包里面去寻找对应的文件.而这对上层来说没有区别的,可以简化上层的设计.
至于怎么打包的花样还是蛮多的,可以考虑压缩什么的,不过这个engine里只有一个很简单的实现,就是把加入的文件一个一个排在那里,然后根据最简单的hash表(以路径名为hash key)来索引文件的位置,没有用到什么压缩技术.一个问题是,文件包要有patch功能,也就是要有替换某个文件的数据的功能,如果这个文件的数据量比原来大的话,原来的位置就放不下了,只能放到末尾去,这样patch过很多次后,包里就会有很多空洞,而且包会越来越大.我考虑要增加一种incremental的清理包的功能,每次游戏运行的时候清理一部分的空洞,来使玩家觉察不到.
上面说到的文件系统是对应于操作系统的文件系统的,是树状结构,但它不太适合游戏里的一种数据,就是地图数据,因为我们的engine要能支持很大的游戏世界,所以地图必须做到可以动态载入,树状的文件系统不太适合地图的存取,所以engine中专门实现了一个mapfile的文件系统,基本上它的数据组织如下图:
地图的数据被分成很多个channel,每个channel里存储某一种类型的数据,比如channel 0存储地表数据,channel 1存储植被信息, channel 2存储水面信息,等等,这样可以使游戏中各个模块在数据上是完全独立的,每个channel又被划分成一个两维的block数组,每个block里保存游戏世界里某个小方块里的数据内容,比如目前一个block对应到游戏世界里的4x4m见方的区域.
使用这种方式来保存地图数据主要目的是为了能够动态载入地图,既然游戏对象数据的保存是基于block的,那游戏对象本身也是基于block的,目前游戏地图可以以block为单位来卸载/载入地图,也就是说可以卸载/重载某个block的地图,而不影响其它的block,这对编辑是大有好处的,比如undo/redo就受益于这种地图存储方式而变得比较简单,(只需要备份某几个修改过的block的数据就可以了).而且可以方便的实现地图数据的版本控制(CheckIn/CheckOut某个地块),分成channel的好处还在于,不同的channel的地图大小可以是不一样的,比如地表可能有1024m/1024m的范围,而植被可能只有512x512的范围,它们可以在各自的channel里动态载入数据而不会互相干扰.还有一种适合于这种地图存储方式的的技术是可以在mapfile系统中添加一些预读窗口(指定的要预读的block的范围),然后底层可以透明的采用多线程预读的方式来载入这些数据,我们可以在地图的边界处设立多个预读窗口来使地图的动态载入变得流畅.
mapfile系统的结构还是很简单的,不过实现起来还是有些小麻烦,首先不可能一个block存一个文件,那会有无数个文件的,但也不能整张地图只存一个文件,因为我们要考虑到各个地块的版本控制的问题.所以在mapfile系统的实现中引入了field的概念,一个field是一大块区域,目前为256mx256m,也就是64x64个block,一个field的数据保存为一个文件,所以我们checkin/checkout地块也是以field为单位进行的.由于一个field里包含了多个block,而每个block又要求能单独写入数据,所以具体的代码实现还是有点难度的,field文件被分割成cluster,会根据每个block的数据大小为它动态分配所需的cluster,并把这些cluster链接起来.请注意,这种存储方式的读取效率是很低的,因为一个block的数据不是以连续方式保存在文件中的,读一个block可能会使磁头更多在硬盘的不同位置上跳来跳去.此外,考虑到虽然使用了field来减少文件数量,但是对于一张很大的地图来说,文件数量还是太多了,所以,最终我又写了一套新的mapfile系统,整张地图会被打包成一个地图文件,这个mapfile系统用来读取这个地图文件.只在游戏发布时使用,由于这个包是只读的,包里的block数据就可以用连续的方式存储了,可以提高读取速度.旧的mapfile系统只用于编辑时使用.当然,这两个mapfile系统的接口是一致的,对上层是保持一致。
文件系统大致就是这样了,下回说说字符串.