« BenQ WiT ScreenBar 试用记录 | 返回首页 | 通过斜切变换 2d sprite 提高装箱率 »

资源文件系统的设计

上次说到,我们的引擎打算在 PC 上开发,设备上直接调试。如果是按传统的开发方式:运行前将 app 打包上载然后再运行,肯定是无法满足开发需要的。所以必须建立一套资源的同步机制。

目前,我们已经实现了基本的资源文件系统,大致是这样工作的:

所有的资源文件,包括程序代码本身(基于 Lua),都是放在开发 PC 上的。开发环境会缓存每个文件的 md5 值,文件系统将用此 md5 值为标准,默认 md5 相同的文件,其内容也是一致的,暂不考虑 md5 冲突的问题。

在设备上,用设备的本地文件系统做一个 cache ,cache 中分为两个区间,一是资源文件区,按所有资源文件的 md5 值为文件名(按 md5 的 16 进制码的前三字节散列在不同子目录中,防止单个目录文件数量过多)保存。二是目录结构区,每个子目录用一个递增数字 id 做文件名,内容则是可读文件名以及其内容对应的 md5 值或子目录编号。其中 id 0 是资源根目录。

例如,我们有一个贴图文件叫 foobar.tex ,它的 md5 值是 xxxxxx ,放在 textures/foobar.tex 。那么在 0 号目录文件中,就有一项记录为 textures 1 ,表示有 textures 这么一个子目录,其编号为 1 。

在 1 这个子目录文件中,有 foobar.tex xxxxxx 的记录,表示该目录下有 foobar.tex 这个文件, md5 值为 xxxxxx 。

如果需要打开 textures/foobar.tex 这个文件,需要先读取 0 号根目录,找到 textures 子目录的编号 1 ,再加载 1 号目录,找到 foobar.tex 的 md5 值 xxxxxx ,根据 md5 值从资源文件区加载对应文件。


游戏运行时,底层的 IO 模块负责管理资源文件。每打开文件时,若 cache 中没有需要的文件,则向编辑器发送一个文件请求;若已经有对应文件,则向编辑器发送一个文件查询请求,查询该文件的 md5 值,当 对应 md5 的数据不存在时,重新请求该文件。

ps. 未来可以做一个优化,在目录文件中记录版本号,版本号在启动时同步,再根据版本号减少文件查询请求的数量。

IO 模块在运行时是不负责删除任何文件的,即使主动删文件,也仅仅是在目录文件中把对应条目删除,而不真正删除资源文件区的数据文件。这样做的好处是,如果你需要经常在多个版本间切换:例如把你的开发机上连接的设备拔下去,插在另一台开发机上使用,而两台开发机处于不同的开发分支上;不必频繁的更新资源文件。

cache 可以主动清空,或是从根目录递归遍历,删除没有引用的数据文件。这样可以避免占用过多的空间。

直接采用 md5 值来索引数据文件,还有一个额外的好处。用户可以按更自然的方式组织资源结构。比如,不必因为两个模型需要共享同一张贴图,就把贴图文件放在独立的公共目录中。直接把贴图放在所属的模型目录下即可。只要贴图内容是一致的,最终在设备上就只有一份文件,资源管理模块也绝对不会在内存中重复加载。


补充一些实现细节:

当数据文件依赖远程加载时,如果通讯部分也是用 lua 实现,那么很可能会利用 lua 的 coroutine 做异步加载。而 lua 的 require 函数是在 C 中实现的,loader 无法 coroutine.yield 。

这里可以使用一个小技巧,自定义一个 require 从 lua 代码实现部分逻辑。在 lua 中判断 package.loaded 以及使用 package.searchpath 判断文件有效性。等异步加载完成后,再转入默认的 require 函数。

另外,IO 模块提供了 prefetch 指令,可以按照需要,发送文件请求,而不阻塞程序运行。这可以用于更上层的资源管理。例如,了解了资源的相互依赖关系的话,可以再加载完一个资源文件后,查询到它可能依赖的其它资源,一次性提交所有的依赖请求。特别是,如果这个资源是 lua 文件,我们可以在运行前,分析出里面是否有新的 require 请求,提前把所有 require 的相关文件请求都发送出去。

Comments

数据在磁盘上的格式是枝梢末节的东西,不值得现在考虑。 重要的是接口。我认为对于资源系统,文件 api (基于文件名索引)就不错,sql 多余了。
最近一直对 sqlite 比较着迷,想问一下,这样的游戏资源会考虑用 sqlite 来存储么? 看文章的意思似乎是要自己搞一个格式,就是想知道 sqlite 能不能满足要求。
这不就是前端的热更新流程么,哈哈
时刻关注
现在的游戏引擎专业化方面做的比较深了,但用户上手容易度还有很大的提升空间。希望能集成一些常用的功能类,例如 Toast提示,并且开放图形化自定义功能。还有各种引用、依赖,能不能都傻瓜式操作,并且集成常用的SDK,如统计、登录、分享,用户只需设置自定义数据就能用。这样的引擎很容易就打开市场了吧。
为什么不用rsync的方式来同步呢?
local ret = {} for i=1,500000 do table.insert(ret, {}) end ret = nil local function sleep(n) os.execute("sleep " .. n) end while(true) do print(collectgarbage("count")) collectgarbage("collect") sleep(5) end 云大请帮助解答一下,谢谢,这段代码print第二次打印,显示17k,lua这边说明已经内存回收了,但是ps aux,查看进程消耗了20M内存。 lua5.3 centos7.1 32位系统。
LUA 5.4 Released! https://github.com/lua/lua.git
风神的engine能导出成wasm格式吗?
关注下 啥时候放到github上
云风大哥 python感觉也值得考虑
准备从这一篇开始逆向阅读打卡
云风大哥设计的游戏引擎满满的服务器风2333
要不直接基于libSVN二次开发23333
要不直接基于libSVN二次开发23333
lua是时候弄个typelua啦,233
用什么lua 用python 啊 lua 工程大了的话 维护起来 小 心肝都碎了
完本没看懂什么意思,为什么打包上传再运行不能满足开发需要?
文件和目录管理,版本号管理这块就是Git啊,感觉基于Git来做更好
表示正在用lua结合unity搞这么一套东西,实现AR SDK,过程异常痛苦
时刻关注
存储索引方式比较类似 docker registry的文件存储方式,可以考虑用 0 有点类似meta data, 1... 是具体文件,可以考虑 用 二级目录的方式存储 类似这样 01 -> 01000000001 01000000002 ... 02 -> 02000000001 02000000002.. 这样文件很多的时候效率会高点

Post a comment

非这个主题相关的留言请到:留言本