资源缓存

小游戏的文件目录分为临时文件目录和用户文件目录:

  • 临时文件目录不限大小,但是会被定时清理
  • 用户文件目录限定大小 200MB,但是能够持久保留。

在加载资源时,如果设置 cacheable: true,那么文件会下载到用户文件目录,否则下载到临时文件目录。用户文件目录里的资源文件,就是资源缓存。

// 假设 SpriteFrame 的资源相对于assets/目录的路径 是 "gameTexture/zombie.spriteframe",并且在 engine.ide.json 中配置 resourceDir,配置目录下的所有资源都会成为入口资源。
engine.loader.load("gameTexture/zombie.spriteframe", {
  cacheable: true
}).promise.then((spriteFrame) => {
  console.log(spriteFrame)
})

对资源文件的缓存行为是资源加载模块内部控制和调度的,开发者无须关心。以如上代码为例,在 "gameTexture/zombie.spriteframe" 加载完成后,相应的资源文件就已缓存到了用户文件目录。

缓存目录

wx.env.USER_DATA_PATH 是小游戏用户文件目录的根目录,${wx.env.USER_DATA_PATH}/engine-cache 是资源缓存的根目录,所有的缓存文件都在这个目录下。

可以直接用 wx 文件 API 访问这个目录。

const fs = wx.getFileSystemManager();
try {
  const files = fs.readdirSync(`${wx.env.USER_DATA_PATH}/engine-cache`);
  console.log(files);
}
catch (error) {
  console.error("engine-cache 还没创建", error)
}

缓存目录的结构如下:

  • wxfile://usr
    • engine-cache(根目录)
      • res_ui_accepting_applications_bid_e61c12ce@combine_0.bin
      • res_ui_accepting_applications_bid_e61c12ce@assets_ui.png
      • dep_00131089c86875e2ff637d09ff5038a8_9a089497@combine_0.bin
    • userData.json(用户文件目录下的其他文件)
    • ...

缓存文件的文件名按照 ${资源组名}@${资源文件名} 的格式保存和读取。@ 字符是分割资源组名和资源文件名的分割符,因此文件名中不应该包含 @ 字符,否则会导致缓存模块报错。

如果资源文件名中必须包含 @ 字符,可以换一个分割符。但这一步必须在 engine.cache.init() 之前设置。

// 将分割符换成 #
engine.cache.delimiter = "#";
// 如果是手动初始化缓存模块,必须在 engine.cache.init() 之前设置
engine.cache.init();
// 如果不是手动初始化缓存模块,必须在 engine.init() 之前设置
engine.init(canvas);

缓存上限

除了资源缓存,游戏可能也需要在用户文件目录写入一些业务文件,比如玩家的离线存档。所以用户文件目录不能被资源文件写满。 可以通过设置 CacheManager.sizeLimit 来限制缓存大小。

engine.cache.sizeLimit = 180*1024*1024;

sizeLimit 只是一个 JS 层限制缓存文件写入的属性值,无法影响小游戏用户文件目录大小上限的设置。即使 sizeLimit 设置为 500*1024*1024,还是会在文件总大小超过 200 MB 时写入失败。

缓存更新

资源组名也是资源组 id,id 末尾是 8 位 hash。当资源组内的文件内容发生变化,末尾的 hash 也会改变。 hash 不同的新旧版本资源组,会被视为两个不同的资源组。旧版本的资源会因为没有被使用,在淘汰过程中被删除。

如末尾 hash 不同的两个 res_ui_accepting_applications_bid 资源组的 combine_0.bin 文件就是两个不同的缓存文件。

res_ui_accepting_applications_bid_9a089497@combine_0.bin res_ui_accepting_applications_bid_e61c12ce@combine_0.bin

缓存淘汰

当有新缓存写入且当前缓存空间不足时,会触发缓存淘汰。缓存空间不足包含两种情况:

  1. 已经有缓存大小加上新缓存大小超过 sizeLimit。
  2. 因达到小游戏用户文件目录大小上限而写入失败。

当缓存空间不足时,缓存模块会调用暴露给游戏业务侧的回调函数 onNeedRelease()。游戏业务侧返回一个数值表示要释放的缓存空间大小。

engine.cache.onNeedRelease = (res) => {
  console.log(`剩余缓存空间不足,已有缓存:${res.currentSize} B,新写入缓存 ${res.cacheSize}`// 淘汰 80 MB 缓存 
  return 80*1024*1024;
}

返回小于 0 的值表示本次不淘汰缓存,新缓存的写入会失败。

engine.cache.onNeedRelease = (res) => {
  return -1;
}

如果不设置 engine.cache.onNeedRelease,那么要淘汰的缓存大小就是新写入的缓存的大小。
如要写入的缓存文件是 5 MB,那么就释放 >= 5MB 的缓存空间。

淘汰策略

除了基本的 LRU 以外,游戏业务侧还希望自定义淘汰策略。因此缓存模块暴露 compare 函数。compare 的用法和 Array.prototype.sort 一样,排序后得到一个权重升序的资源缓存数组。缓存模块从数组头部开始依次淘汰,直到满足要腾出的空间大小。

在 compare 函数的参数里,暴露了每个缓存的资源组在本次游戏中的加载信息,游戏业务侧可以据此定制加载策略。

参数 类型 说明
size number 资源文件的大小
loadTimes number 资源文件被加载的次数(调用 engine.loader.load() 加载资源时涉及到这个资源文件的次数)
lastLoadTime Date 最近一次资源文件被加载的时间戳(调用 engine.loader.load() 加载资源时涉及到这个资源文件的时间戳)
useTimes number 资源文件被用到的次数(调用 engine.loader.getAsset() 且用到这个资源文件的内容的次数)
lastUseTime Date 最近一次资源文件被用到的时间戳(最近一次调用 engine.loader.getAsset() 且用到这个资源文件的内容的时间戳)

默认按照 useTimes 进行排序,即 LRU。