引擎设计跟踪(九.9) 文件包系统(Game Package System)
很早之前,闪现过写文件包系统的想法, 但是觉得还没有到时候. 由于目前工作上在做android ndk开发, 所以业余时间趁热做了android的移植, 因为android ndk提供的mountable obb调试时不太好用,或许因为有坑还没有发现. 所以把Ogre的zip文件系统拿了过来. 因为引擎里已经有了类似Ogre的IStream抽象, 所以做起来比较简单. 把zip文件改成obb后缀上传到手机,就可以测试了. 目前除了GLES没实现,全部代码已经移植完了.移植笔记在这里:
http://hi.baidu.com/crazii_chn/item/62705798f8a76bd91b49dfd8
突然想写包系统, 是因为简单看了zziplib的内容, 感觉在文件打开/查找上效率稍微有点低, 不太适合文件多的情况. zziplib是把包内的*所有*文件(包括子文件夹内的文件), 组成线性文件表(用链表链接起来), 查找时逐个遍历. 简单想了一下, 如果用目录树的话, 效率会更高, 因为按节点匹配,可以很快定位到最终文件. 同时, 目录树内某一个节点的所有同级子节点还可以排序(如果用纯C的话,可能也用树比较方便,或者使用有序数组进行binary_seach),我直接用的map/set, 省了很多事. 总之用树的话比线性表效率要高.
同时想了下游戏中的诸多需求, 如可能频繁更新(OnlineGame), 添加/删除文件等等, 觉得基本IO功能虽然很好写,但是复杂的需求,写起来要考虑的还是蛮多蛮复杂的.
1.考虑到频繁添加文件的需求, 个人觉得包内的文件表应该放在尾部.因为文件表相对真正的数据来说很小, 放在尾部的好处就是可以很快追加文件, 追加完以后把新的表写入.
package layout:
+--------------------------------------+----------+
| Data | File Table |
+--------------------------------------+----------+
after adding a new file:
+----------------------------------+-------------+----------+
| Data |new file data | File Table |
+----------------------------------+-------------+----------+
| newly written content |
这样频繁添加的需求大致可以解决了.
2.频繁的删除: 记得某些数据库(初中时玩过FoxBASE+编程)在删除条目的时候只是打上标记而不真正删除, 我觉得这个策略也适合游戏包的删除. 因为删除文件后包内有碎片, 要想没有碎片, 每次删除一个文件都要重写数据, 重写包数据时间过长难以接受. 所以现在所做的就是把文件打上删除标记, 而不真正删除, 而且被标记的数据不会在被利用.
那么这些文件什么时候真正删除呢? 不删除的话, 积累越来越多, 利用率变得越来越低,无法接受.
当删除的数据(文件内容)总和达到一个阈值以后, 比如256M等等(用户根据情况来指定), 把包内的被删除的文件真正去掉. 这个时候可能已经有很多被删除的文件了, 相当做一次碎片整理. 当然这个碎片整理比OS的磁盘整理要简单多了.
数据包整理的方式很简单: 从第一个被删除的文件开始, 把后面的数据(至第二个被删除文件的数据开始处)往前挪动, 填补被删除的空缺, 这个时候第一个空缺被往后移, 跟第二个被删文件的空缺连在一起了, 使用同样的方法处理所有被删除的文件. 这种方法可以使数据移动最小化.
删除的时候额外要做的工作就是, 根据文件偏移来定位文件表项, 然后更新其偏移量信息.
使用如上实现方式, 对于上层用户(game/app developer)来说, 策略最好如下:
a.每次单版本更新, 先删除文件, 然后检查是否需要整理, 如果需要则整理, 整理后添加新文件. 因为先添加文件的话如果需要整理, 写入之后还要再挪动, 不管怎么说都不是太好的方法.
b.如果客户端当前版本比最新版本差的版本太多, 那么先下载所有更新包(patch), 同样先删除每个更新包内需要删除的文件, 所有的删除完以后再检查是否需要整理, 或者强制整理, 最后再逐个添加每个包内新加的内容. 原因同上.
先删除文件再添加文件, 更新包之间的依赖是个问题. 比如update1添加了一个文件, update2把它删了, 如果先删除所有文件的话, 原始包里面还没有这个文件, 怎么处理?
*按版本顺序*遍历当前版本之前的所有patch包, 直到找到一个存在该文件的patch包, 比如update1, 然后在本地把该文件做删除标记. 按版本顺序的原因是中间可能有反复的添加和删除.
这种做法最大的缺点在于不同客户端的数据, 由于更新的方式不一样(逐版本更新/一次性更新), package layout可能会不一样(感觉应该不会, 但没仔细想), 最终导致没办法做crc等校验.
当然也可以写patch合并工具, 生成任意两个版本之间的一次性更新包.
另外, 为了简单起见, 每个patch包的格式, 可以与游戏主数据包相同(使用同一种包格式), 包里面额外存放一些patch信息, 比如需要删除的文件列表的txt...
除了以上以外, diff工具也要有, 用于生成patch, 还要有package expolorer, 不过这两个还没有时间写, 目前只实现了打包工具(命令行), 和简单的IO操作, 可以完成最基本的运行需求, 数据整理功能也没有实现. 计划diff工具写成命令行, package expolorer兼有有打包和生成patch功能, 作为编辑器的插件来写, 复用基本菜单,工具和视图(虽然这种视图还没有写).
3/3/2014更新
3.包的嵌套
理论上, 有一种特殊的情况也需要支持: 一个包内放了另一个包. 比如Ogre里面也有zip格式嵌套.
首先, 个人觉得这种情况确实存在, 但是为某一种具体的包格式写具体的嵌套实现是一种不良的设计. 其次, 引擎已经有了IStream抽象, 一个包系统完全可以只依赖这个接口来做依赖的IO, 到了这个时候, 嵌套已经完全解决了. 因为读取包文件依赖的是一个抽象的接口, 这个接口的具体实现可以是native IO, 或者相同格式的包系统, 或者其他格式的包系统. 但在这个包系统内部, 不需要关系它的实现.
4.调试: Native IO fallback
我所见过的某些包系统会优先查找本地文件(比如MPQ,和WPF), 如果有本地文件的时候就加载本地文件, 否则再从包里面查找.这么做主要是方便调试, 对于调试时,频繁更新的文件可以放在在本地.
当然它有一个缺点就是很容易被玩家"篡改", 比如DiabloII中, 在游戏目录下放几个文件夹和文件, 那么游戏就会优先加载这些文件, 我记得DiabloII的简体中文字体patch就是这样. 这样究竟好不好, 允不允许,或许是另外一个话题.但至少,把这个feature做成是可配置的feature应该不难, 这样如果最终发布时想关闭也没有问题.
个人觉得这个功能, 不应该在包系统里实现.因为包系统作为一个完整的系统, 接口已经齐全. 这个功能可以放在IArchive(文件系统的抽象)里面做二次封装.目前这个功能还没有加上, 但最初设计资源管理系统的时候,考虑到文件包和本地数据路径的切换, 使用了类似URI的东西, 比如media:/test.dds, 已经可以将media映射到本地路径或者一个包文件, 但只能切换整个包, 不能单独加载某个磁盘文件.
说道二次封装,这里贴一个层次依赖关系:
IStream
| \
BPKFile IArhive
| /
BPKArchive (+Native IO fallback feature)
BPKFile依赖IStream是为了实现嵌套读取, 这是它的最小依赖. 如果不需要嵌套的话, 可以去掉这个依赖, 这样BPK的独立性更高. IStream是BPK系统的一部分, 只不过它正好和现有的接口重合.
BPKArchive是用于将包系统集成到引擎的类,是属于整个引擎框架的一部分, 理论上它可以依赖引擎中的其他模块, 可以放入引擎插件. 而Istream,IArhive和BPKFile属于基础类库,独立性比较高, 不依赖于引擎的架构.
所以说BPKArchive的独立性和抽象程度都是较低的, 具化程度更高, 所以Native IO fallback的特性放这里比较好.
由于游戏的包系统,个人只知道大致原理, 也没有看过相关代码实现, 所以可能考虑的不够周全. 有时间了看看开源的代码学习一下. 如果有新的想法的话,就及时更新以备忘.
引擎设计跟踪(九.9) 文件包系统(Game Package System)的更多相关文章
- 引擎设计跟踪(九.14.2a) 导出插件问题修复和 Tangent Space 裂缝修复
由于工作很忙, 近半年的业余时间没空搞了, 不过工作马上忙完了, 趁十一有时间修了一些小问题. 这次更新跟骨骼动画无关, 修复了一个之前的, 关于tangent space裂缝的问题: 引擎设计跟踪( ...
- 引擎设计跟踪(九.14.2j) TableView工具填坑以及多国语言
Blade的UI都是预定义的接口, 然后由插件来负责实现, 目前只有MFC的插件. 最近加上了TableView的视图, 用于一些文件的查看和编辑, 比如前面在文件包的笔记中提到需写一个package ...
- 引擎设计跟踪(九.14.2f) 最近更新: OpenGL ES & tools
之前骨骼动画的IK暂时放一放, 最近在搞GLES的实现. 之前除了GLES没有实现, Android的代码移植已经完毕: [原]跨平台编程注意事项(三): window 到 android 的 移植 ...
- 引擎设计跟踪(九.14.2i) Android GLES 3.0 完善
最近把渲染设备对应的GLES的API填上了. 主要有IRenderDevice/IShader/ITexture/IGraphicsResourceManager/IIndexBuffer/IVert ...
- 引擎设计跟踪(九.14.2g) 将GNUMake集成到Visual Studio
最近在做纹理压缩工具, 以及数据包的生成. shader编译已经在vs工程里面了, 使用custom build tool, build命令是调用BladeShaderComplier, 并且每个文件 ...
- 引擎设计跟踪(九.8) Gizmo helper实现与多国语言
最近把gizmo helper的绘制做好了. 1.为了复用代码,写了utility来创建sphere, cube, cylinder, plane, ring(line), circle(solid) ...
- 引擎设计跟踪(九.14.3.4) mile stone 2 - model和fbx导入的补漏
之前milestone2已经做完的工作, 现在趁有时间记下笔记. 1.设计 这里是指兼容3ds max导出/fbx格式转换等等一系列工作的设计. 最开始, Blade的3dsmax导出插件, 全部代码 ...
- 引擎设计跟踪(九.14.2 final) Inverse Kinematics: CCD 在Blade中的实现
因为工作忙, 好久没有记笔记了, 但是有时候发现还得翻以前的笔记去看, 所以还是尽量记下来备忘. 关于IK, 读了一些paper, 觉得之前翻译的那篇, welman的paper (http://gr ...
- 引擎设计跟踪(九.14.2e) DelayLoaded DLLs (/DELAYLOAD)
关于DLL的delay load: http://msdn.microsoft.com/en-us/library/151kt790.aspx 最近在做GLES的shader compiler, 把现 ...
随机推荐
- SLF4J日志门面
SLF4J官网:http://www.slf4j.org/ SLF4J的作用通俗点讲,就是可以让我们的项目以最小的代价更换不同的日志系统.无需修改代码,只需要添加.删除相应的jar包和配置文件. 1. ...
- shell脚本入门
什么是Shell脚本 示例 看个例子吧: #!/bin/sh cd ~ mkdir shell_tut cd shell_tut for ((i=0; i<10; i++)); do touch ...
- 【Zend Studio】10.6.0版本设置默认字体
1.打开Windows->Prefefences 2.找到General->Appearance->Colors and Fonts->Basic->Text Font- ...
- java中4中类修饰符访问范围
public:本类中可见,同包中可见,子类中可见,其他包中可见. protected:本类中可见,同包中可见,子类中可见,其他包不可见. 默认:本类中可见,同包中可见,子类不可见,其他包不可见. pr ...
- Python学习之静态页面数据抓取
1 页面信息抓取 定义getPage函数,根据传入的页码get到整个页面的html内容 getContent函数,通过正则匹配把页面中的表格部分的html内容取出 最后定义getData函数,同样是通 ...
- PHPStorm配置支持友好的Laravel代码自动提示
在项目的composer.json "barryvdh/laravel-ide-helper":"dev-master" 项目config/app.php Ba ...
- shell字符串的截取
1.变量 var 从 npos ∈ [0, length-1] 位开始,从左->右截取 num 个字符: ${var:npos:num} / ${var:npos} 小结:若 npos < ...
- ORA-08189
OS: [root@yoon ~]# more /etc/oracle-releaseOracle Linux Server release 5.7 DB: Oracle Database 11g E ...
- strcpy/strlen/strcat/strcmp面试总结
<strcpy拷贝越界问题> 一. 程序一 #include<stdio.h> #include<string.h> void main() { char s[]= ...
- hdu 2988 Dark roads
题目连接 http://acm.hdu.edu.cn/showproblem.php?pid=2988 Dark roads Description Economic times these days ...