Skip to main content

资产预加载模块

在 World 构造之后,可以通过 World 的 getPreload 方法获取到预加载模块,对 World 下所有用到的资产进行预加载。

包含能力

预加载模块在运行时由三个部分组成:资产下载、预处理、预创建。

1. 资产下载

资产下载时会把资产都存储到浏览器的 IndexedDB 中,在 World 运行使用资产的时候就优先使用 IndexedDB 中的缓存资产,如果 IndexedDB 中未查询到资产会降级到使用网络资源,这会大大降低用户使用体验。所以建议使用预加载功能。

2.资产预处理

资产预处理会提前缓存角色相关的 3D VAT 资源和 shader 预热,减缓进入场景之后的性能消耗。该功能支持单独关闭。

3.资产预创建

资产预创建会提前创建场景内的前景资产,包括 richSurface、subSequence 类型等,可以很大程度上减缓进入场景后的低帧率和走动过程中的卡顿问题。该功能需要配合 setPrecreateClasses 接口使用,否则默认关闭该功能。

setPrecreateClasses 传入 actor 的构造类列表 SDK 会拉取当前actor配置文件内容,按照其 actor 类型及其构造类依次创建 actor。

如何生成 actor配置文件

通过在业务链接上拼写 open_collect_actor=1 开启 actor 扫描收集模式,此时 sdk 会按照真实业务场景收集到 actor 的配置信息,扫描完成之后自动上传云端。文件区分 sit/uat/prod 环境。

使用介绍

1.开始执行预加载

class Preload {
start(onProgress?: IPreloadProgress, options?: IPreloadStartOptions): Promise<void>
}

调用 start 方法就可以进行预加载,preload 异步调用完成,就代表着预加载完成。可以传入 onProgress 方法作为预加载进度的回调,每秒都会回调 1 次。

world
.getPreload()
.start((current, total) => {
console.log(current, total)
})
.then(() => {
// 完成
})

// 也可以await调用
await world.getPreload().on('progress', ({ current, total }) => {
const progressText = `(${current}/${total})`
})
// 完成

预加载进度也支持以事件形式通知:

world.getPreload().on('progress', ({ current, total }) => {
const progressText = `(${current}/${total})`
})

指定待加载资产

通常情况下,一个项目下初始使用资产是少于该项目全部资产的。例如在有多个房间的情况下,首次进入页面只会消费当前房间的资产,如果用户此时推出应用,那么永远都不会使用到其他房间的资产。因此为了优化首次进房速度,我们可以选择加载首次必要的资源来节约一部分资产下载时间。

SDK 提供了三种方式从不同的资产维度来指定待加载资产,可以通过在 start 方法传入 options 选项来进行设置。

interface IPreloadStartOptions {
/**
* 符合该filter方法的资产会被过滤掉 方式1
*/
filterCallback?: (item: IPreloadItemConfig, index: number, array: IPreloadItemConfig[]) => boolean
/**
* 已经加载过的资产url列表,如果不为空的数组,则用它来和需要预加载的资源进行对比 方式2
*/
preloadedList?: string[]

/**
* 启用新版Xconsole分包 方式3
*/
openConsolePack?: false
/**
* 指定加载某一个 roomId 下的所有资产
*/
roomId?: string
}

a. 设置资产筛选方法

通过 options 里的 filterCallback 字段传入,filterCallback 的背后是基于 Array.prototype.filter,它就是数组 filter 方法的参数,用于在一次下载过程中符合该过滤条件的资产。filterCallback 的第一个参数是单个资源的描述信息,它的数据结构如下,一般情况下可以根据 roomIdavatarId 就能实现一次预加载过程只下载某几个房间或者某几个 Avatar。后续分包过程可以按照自己的业务逻辑来过滤不同的资源。

//这里需要注意的是所有 Avatar 的资产的 `roomId` 属性为空字符串。所有房间资产的 `avatarId` 属性都为空字符串
export interface IPreloadItemConfig {
assetPath: string
assetSize: number
assetType: string
assetUrl: string
packName: string
roomId: string
avatarId: string
}

b. 指定不再加载的资产列表

通过传入options 里的 preloadedList 字段设置,preloadedList 是一个string数组类型,表示已经加载过的资产 url 列表,如果不为空的数组,则用它来和需要预加载的资源进行对比,最终的加载列表会去除这些指定资产。

c. 指定房间来选择预加载资源包

通过传入 options 里的 openConsolePack 字段设置为 true 开启按照房间分包功能,同时需要指定正确的 roomId来设置加载指定房间下的资产。注意在使用该功能时需要提前在 console 应用管理平台进行拆包配置。同样的,如果在 console 应用管理平台上进行了拆包配置,则业务开发使用预加载模块时务必传入正确的 roomId,否则将报错。 同时该方法与方法 1 不同时使用。

2.预加载报错处理

预加载出错情况包括 indexDB 打开异常、资产下载失败(一般是网络原因),等原因,这些情况都会在start()方法直接 throw err:

world
.getPreload()
.on('progress', ({ current, total }) => {
const progressText = `(${current}/${total})`
})
.then(() => {
// 完成
})
.catch((err) => {
console.error(err)
// 切b面/重试
})

当然也可以await调用,这完全取决于开发者的业务需求:

try {
await world.getPreload().on('progress', ({ current, total }) => {
const progressText = `(${current}/${total})`
})
} catch (error) {
console.error(err)
// 切b面/重试
}

// 完成

3.事件监听

弱网事件

在运行中,存在网络较差,但又不会导致 http 下载失败的情况,这种情况下用户可能会陷入无休止的等待,体验非常差。因此 SDK 提供旁路检测机制,当连续15s没有一个资产下载完成时,就会触发poorNet事件,终止预加载行为。因此开发者必须监听该方法,并做相应处理:

world.getPreload().on('poorNet', (err) => {
// 切b面/重试
})

可以通过修改poorNetJudge参数来设置判定poorNet的秒数:

// 连续30s没有一个资产下载完成,才触发`poorNet`事件
world.getPreload().poorNetJudge = 30

预加载单个资产下载超时事件

预加载在单个资产即将超时时会以事件形式通知,注意在网络情况差的场景下,可能会多次触发事件,业务侧在使用时可以根据使用场景进行节流等处理方式。

world.getPreload().on('singleAssetNearlyTimeout', ({ url, size }) => {
// do something
})

其他

设置资产加载并发数

支持通过修改 preload 里的concurrence成员来设置资产下载最大并发数,设置后将同时最多发起对应数量的http请求,具体做法为 world.getPreload().concurrence = n,若不传入,默认并发数为 6 个。

在不同 releaseId 之间切换

因为 IndexedDB 的存储容量有限,所以在使用了预加载的不同应用或者不同 releaseId 之间切换时,SDK 内部会根据 IndexedDB 中已下载过的资产和当下需要下载的资产进行对比,并且清除掉不在该 releaseId 中的资产,只下载需要下载的资产。