惰性编译资源仓库中的源文件

我们的 3d engine 的资源仓库使用 Merkle tree 储存在本地文件系统中,我们称呼它为 vfs ,虚拟文件系统,其结构和 git 的仓库非常类似。关于这部分的设计,之前已写过好几篇 blog 了。

现阶段已完成的版本,已经做到把 lua 虚拟机和所有 C/C++ 实现的 lua 库静态编译打包为一个执行文件,可以零配置启动运行,通过网络远程访问一个 vfs 仓库,完成自举更新和运行远程仓库里的项目。

最近在开发的过程中,发现了一点 Merkle tree 的局限性,我做了一些改进。

由于 Merkle tree 的根结点记录着整棵树的 hash 值,所有树上任何一个地方的增删修改都会影响根 hash 。这也是为什么 git 可以不依赖版本号,只用一个 hash 就能指代任何一个分支的快照的算法基础。我们这个 vfs 也是如此。

但是,对于游戏开发来说,却有一个麻烦。

开发者维护的数据资源往往只是数据源,而不是目标平台的数据。例如,你可以只维护 shader 源文件,再根据你的目标平台是 windows 还是 ios 或是 andriod 生成不同平台的 shader ;你可以只维护通用 png 格式的贴图,再针对不同平台压缩为 dxt/etc/pvr 等格式。

如果我们依旧把最终平台所用的资源全部生成储存在 Merkle tree 中,那么就需要在修改源文件后,立刻生成所有平台的版本;或者为多个不同平台维护多棵树。无论是哪个方案,都比较影响使用体验。稍微做一些修改,就要进行编译流程。Unity 在这方面就做的不太好,虽然后来用 cache 服务解决了一部分问题,但是切目标平台这事还是和重编译 C++ 工程一样,让人等得烦心。

我们现在的 vfs 天生是 C/S 结构的,所有文件都放在资源仓库服务器上,运行时客户端只要按需同步。服务器可以采用高配置,多线程传输和编译数据文件,供多个客户端使用。编译资源的工作我认为还是惰性按需进行比较好。这就需要对 Merkle tree 结构做一点改进。

所以,我们还在在原始目录中储存源文件,比如贴图就储存 .png 文件。对于需要编译的资源,再附加一个 .lk 后缀(类似 unity 的 .meta 文件) 描述这个资源需要做什么额外编译。.lk 文件和源文件放在一起。例如一张贴图可能就是 foobar.png 和 foobar.png.lk 两个文件。

当程序需要读取 foobar.png 这张贴图时,它会从 vfs 中发现同时还有 foobar.png.lk 这个文件。它便把 foobar.png 的 hash 值,加上 foobar.png.lk 的 hash 值,以及自己当前平台名,三者连接成一个长串再计算一次 hash (下文称其为 LHASH) ,作为索引,向文件服务器请求。

也就是说,源文件内容以及 lk 文件(如何编译)内容于平台名,三者决定了一个目标数据块。最终的数据块还是以 hash 为索引储存在 vfs 仓库中,但是会在本地多建立一个 LHASH 的索引文件,指向这个真正的 hash 块。下次再需要时,根据 LHASH 就能直接从 vfs 仓库中取得编译好的数据了。

真正的编译后数据依旧保存在仓库中,但却是储存在 merkle tree 之外的。这样未生成的数据并不会影响 merkle tree 的结构。额外的 LHASH 可以保证源文件或 lk 文件(里面放置着编译参数)修改时,能更新到最新版本。

另外,编译数据只存放在资源数据仓库中,以 cache 的形式存在。也方便了开发者做源文件的版本管理。

发表我的评论
取消评论
表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址