CocosCreator ScrollView优化系列之分帧加载

2022-04-15 0 664
目录
  • 一、 前言
  • 二、卡死问题分析
  • 三、解决方案(理论篇)
  • 四、解决方案(代码篇)
    • 4.1 利用 Generator 将代码拆分为多个小段
    • 4.2 分配每一帧的一些时间去执行
  • 五、总结

    一、 前言

    JS是单线程的,也就意味着所有任务需要排队,只有当前一个任务结束了,后一个任务才会执行。如果前一个任务耗时很长,后一个任务就不得不一直等着。

    Cocos Creator 是采用 Java Script/Type Script语言开发,本质上是JS,同样会拥有以上特征。特别地,如果使用不当,极有可能导致界面卡顿。

    比如:在为一个ScrollView的Content创建500个节点的的时候,可能就会出现下面界面卡死的问题

    PS:本来加载过程中有一个loading对话框,因为卡死了,就感觉从来没出现

    CocosCreator ScrollView优化系列之分帧加载

    通过阅读本文,你将了解到如何利用「分帧加载」技术解决上述问题,最终效果对比如下:

    CocosCreator ScrollView优化系列之分帧加载

    二、卡死问题分析

    在正常情况下,我们为ScrollView创建一定数量的子节点的时候,代码可能是这样子的

    public directLoad(length: number) {
        for (let i = 0; i < length; i++) {
            this._initItem(i);
        }
    }
     
    private _initItem(itemIndex: number) {
        let itemNode = cc.instantiate(this.itemPrefab);
        itemNode.width = this.scrollView.content.width / 10;
        itemNode.height = itemNode.width;
        itemNode.parent = this.scrollView.content;
        itemNode.setPosition(0, 0);
    }

    一般而言,当length的值很小,比如10个的时候,程序跑起来的时候,看上去可能会没什么问题,但其实如果仔细一点观察,就发现其实也是会卡死一会,只是很快就结束了。

    特别地,如果length的值到一点量级,比如50+个,那么这段代码就会出现上面截图那样子—— 卡死

    归根到底,问题在于通过 cc.instantiate 创建节点以及为这个节点 setParent 时,所需要的时间并没有想象中那么小,当然,也没有想象中那么大。但是当连续创建一定数量的时候,问题就会被放大,也就是说,这个创建节点的时间可能需要一段时间。

    可视化一点去理解这个问题的话,恩,大概就是下图这样子

    CocosCreator ScrollView优化系列之分帧加载

    Direct Load

    很明显,按照上图,第1到4帧都被完成占用了,导致这期间所有的其他逻辑都会不能执行(Loading对话框出不来,旋转动画卡死等等)。

    那么怎么解决呢?

    三、解决方案(理论篇)

    可能有同学第一时间想到用Promise异步解决,但是在这个问题上,Promise只是把红色的这段连续创建节点的代码放到后面一点的时间去执行,但是当红色的代码执行的时候,它依旧会卡死那段时间,所以Promise是不能应对这种场合的。

    那么应该怎么解决呢?

    其中,一种解决方案,就是我们今天要讲的 「分帧加载」 ,怎么理解「分帧加载」呢?

    惯例,先上图:

    CocosCreator ScrollView优化系列之分帧加载

    Framing Load

    配合上图,就比较好理解「分帧加载」了,具体执行过程为

    1. 先将耗时卡死的代码拆分为很多小段
    2. 然后每一帧,分配一点时间去执行这些小段
    3. 这样子一来,每一帧,我们就留了时间给其他逻辑去跑(那么Loading对话框也可以出来了,旋转动画也可以继续了)

    OK,理论说清楚了,那么实际怎么弄呢?

    比如:

    1. 怎么拆分代码为很多小段?
    2. 怎么分配每一帧的一些时间去执行这些小段呢?

    这个时候,我们需要用到 ES6(ES2015)的协程——Generator,去帮助我们实现。

    四、解决方案(代码篇)

    以我们第二节举例用到的代码(为ScrollView创建一定数量的子节点)为例子,我们将 实现代码为多个小段 以及 分配每一帧的一些时间去执行这些小段 。

    4.1 利用 Generator 将代码拆分为多个小段

    拆分前:

    public directLoad(length: number) {
        for (let i = 0; i < length; i++) {
            this._initItem(i);
        }
    }
     
    private _initItem(itemIndex: number) {
        let itemNode = cc.instantiate(this.itemPrefab);
        itemNode.width = this.scrollView.content.width / 10;
        itemNode.height = itemNode.width;
        itemNode.parent = this.scrollView.content;
        itemNode.setPosition(0, 0);
    }

    拆分后:

    /**
     * (新增代码)获取生成子节点的Generator
     */
    private *_getItemGenerator(length: number) {
        for (let i = 0; i < length; i++) {
            yield this._initItem(i);
        }
    }
     
    /**
     * (和拆分前的代码一致)
     */
    private _initItem(itemIndex: number) {
        let itemNode = cc.instantiate(this.itemPrefab);
        itemNode.width = this.scrollView.content.width / 10;
        itemNode.height = itemNode.width;
        itemNode.parent = this.scrollView.content;
        itemNode.setPosition(0, 0);
    }

    这里的原理就是 利用 Generator 将一次 for 循环里创建所有节点,改为拆分 for 循环的每一步为一个小段

    当然,这份「拆分后」的代码并不能跑起来,因为它只是实现了拆分步骤,要让它跑起来,我们要上下面的第二段代码

    4.2 分配每一帧的一些时间去执行

    在看一次我们刚才的图

    CocosCreator ScrollView优化系列之分帧加载

    Framing Load

    配合图,得出的代码

    /**
     * 实现分帧加载
     */
    async framingLoad(length: number) {
        await this.executePreFrame(this._getItemGenerator(length), 1);
    }
     
    /**
     * 分帧执行 Generator 逻辑
     *
     * @param generator 生成器
     * @param duration 持续时间(ms)
     *          每次执行 Generator 的操作时,最长可持续执行时长。
     *          假设值为8ms,那么表示1帧(总共16ms)下,分出8ms时间给此逻辑执行
     */
    private executePreFrame(generator: Generator, duration: number) {
        return new Promise((resolve, reject) => {
            let gen = generator;
            // 创建执行函数
            let execute = () => {
     
                // 执行之前,先记录开始时间戳
                let startTime = new Date().getTime();
     
                // 然后一直从 Generator 中获取已经拆分好的代码段出来执行
                for (let iter = gen.next(); ; iter = gen.next()) {
     
                    // 判断是否已经执行完所有 Generator 的小代码段
                    // 如果是的话,那么就表示任务完成
                    if (iter == null || iter.done) {
                        resolve();
                        return;
                    }
     
                    // 每执行完一段小代码段,都检查一下是否
                    // 已经超过我们分配给本帧,这些小代码端的最大可执行时间
                    if (new Date().getTime() - startTime > duration) {
                        
                        // 如果超过了,那么本帧就不在执行,开定时器,让下一帧再执行
                        this.scheduleOnce(() => {
                            execute();
                        });
                        return;
                    }
                }
            };
     
            // 运行执行函数
            execute();
        });
    }
     

    代码中已经附有大量注释,但还是有几个点需要说明一下:

    1. 为了方便知道这些小任务是否已经都执行完了,我采用了Promise,当都完成了的时候,resolve 一下
    2. 每一个小代码段的执行时间可能不固定的,可能会超出占用我们的一些期望时间。比如我们期望每一帧分配1ms 去执行这些小代码段,假设前3段小代码段,每一段的执行时间假设为 0.2ms,0.5ms, 0.4ms,那么在我给出的这段代码中,是会执行完这3段小代码段,然后就终止本帧继续执行这些小代码段,因为这里的耗时已经是 1.1ms,比我设定的 1ms 已经多出了 0.1ms 。当然你可以自行改动代码,让这些执行严格按照最大1ms去执行,以实现不超时执行(即不再执行第3个小段)

    至此,我们一定程度上已经实现了「分帧加载」了~

    本项目中所有图示、代码都在Github仓库中,如果需要运行验证,可直接拉下项目即可,不用自己手撸代码验证

    https://github.com/zhitaocai/CocosCreator-ScrollVIewPlus

    五、总结

    1. 尽管我们标题是 「ScrollView 优化系列」,但我更加倾向于,「利用分帧加载去优化ScrollView」。在这篇文章上,我们举的例子是创建节点,但是我刻意不说「分帧创建」,这是因为我认为 「分帧加载」是一种性能优化方案 ,可以「分帧创建」、「分帧运行」、「分帧计算」、「分帧渲染」等。
    2. 在实现分帧上,我们用到了 this.scheduleOnce函数,但是其实可以尝试在 update(dt:number) 上执行,不妨尝试修改我的 「测试项目」去验证呢~
    3. TypeScript 要用上 Generator 还需要需改一下Cocos项目中的 tsconfig.jsoncompilerOptions.lib 数组中添加 es2015

    以上就是CocosCreator ScrollView优化系列之分帧加载的详细内容,更多关于CocosCreator ScrollView优化分帧加载的资料,请关注NICE源码其它相关文章!

    免责声明:
    1、本网站所有发布的源码、软件和资料均为收集各大资源网站整理而来;仅限用于学习和研究目的,您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。 不得使用于非法商业用途,不得违反国家法律。否则后果自负!

    2、本站信息来自网络,版权争议与本站无关。一切关于该资源商业行为与www.niceym.com无关。
    如果您喜欢该程序,请支持正版源码、软件,购买注册,得到更好的正版服务。
    如有侵犯你版权的,请邮件与我们联系处理(邮箱:skknet@qq.com),本站将立即改正。

    NICE源码网 JavaScript CocosCreator ScrollView优化系列之分帧加载 https://www.niceym.com/33216.html