引擎设计跟踪(九.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, 把现 ...
随机推荐
- JS调用腾讯接口获取天气
想做个直接通过JS获取某个城市的天气.本来想通过直接调用中国气象网的接口: http://www.weather.com.cn/weather/101070201.shtml,但是跨域问题一直无法解决 ...
- 黑白棋游戏 (codevs 2743)题解
[问题描述] 黑白棋游戏的棋盘由4×4方格阵列构成.棋盘的每一方格中放有1枚棋子,共有8枚白棋子和8枚黑棋子.这16枚棋子的每一种放置方案都构成一个游戏状态.在棋盘上拥有1条公共边的2个方格称为相邻方 ...
- Android LogCat使用详解
Android的Logcat用于显示系统的调试信息,可在分别以下几个地方查看和调用logcat: 1.eclipse的Debug模式或DDMS模式下的会有一个Logcat窗口,用于显示log日志 只需 ...
- Web Design:给实验室UI们的一堂课(上)
实验室的UI越来越水,设计什么的做的一塌糊涂,所以拖了很久,就想给他们讲一下设计或者说入门吧,上周末才倒出来时间. 这里放上PPT和讲稿吧,懒得去整理板式了. 主要讲了一下Web Design怎么做, ...
- .NET开源工作流RoadFlow-快速入门
在环境搭建好之后,我们就来学习一下怎样快速创建一个流程,并执行和流转该流程(我们这里讲的只是入门,不涉及到具体流程参数设置). 创建一个流程步骤为:在数据库在创建表-->设计表单-->设置 ...
- poj 2046 Gap
题目连接 http://poj.org/problem?id=2046 Gap Description Let's play a card game called Gap. You have 28 c ...
- 2天驾驭DIV+CSS (实战篇)(转)
这是去年看到的一片文章,感觉在我的学习中,有不少的影响.于是把它分享给想很快了解css的兄弟们.本文是实战篇. 基础篇[知识一] “DIV+CSS” 的叫法是不准确的[知识二] “DIV+CSS” ...
- sql server 查询数据库所有的表名+字段
SELECT * FROM INFORMATION_SCHEMA.columns WHERE TABLE_NAME='Account' SELECT (case when a.colorder= ...
- [转]coredump简介与coredump原因总结
[转]coredump简介与coredump原因总结 http://blog.sina.com.cn/s/blog_54f82cc201013srb.html 什么是coredump? 通常情况下co ...
- Android编程: ViewPager和Dialogs组件
学习内容:ViewPager和Dialogs组件 ====ViewPager组件==== 它的作用主要是支持屏幕左右滑动切换列表元素,使用方式如下: 1.首先定义ID号信息,创建res/values/ ...