CocosCreator ScrollView优化系列之分帧加载


Posted in Python onApril 14, 2021

一、 前言

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优化分帧加载的资料,请关注三水点靠木其它相关文章!

Python 相关文章推荐
Python的条件语句与运算符优先级详解
Oct 13 Python
Python数据结构与算法之字典树实现方法示例
Dec 13 Python
浅析Python3爬虫登录模拟
Feb 07 Python
python判断一个集合是否为另一个集合的子集方法
May 04 Python
Python从数据库读取大量数据批量写入文件的方法
Dec 10 Python
python使用Paramiko模块实现远程文件拷贝
Apr 30 Python
深入浅析Python科学计算库Scipy及安装步骤
Oct 12 Python
在pytorch中对非叶节点的变量计算梯度实例
Jan 10 Python
Python实现桌面翻译工具【新手必学】
Feb 12 Python
python判断变量是否为int、字符串、列表、元组、字典的方法详解
Feb 13 Python
Python tornado上传文件的功能
Mar 26 Python
Pycharm学生免费专业版安装教程的方法步骤
Sep 24 Python
深度学习tensorflow基础mnist
Python 多线程之threading 模块的使用
Apr 14 #Python
教你如何用python开发一款数字推盘小游戏
深度学习详解之初试机器学习
正确的理解和使用Django信号(Signals)
Apr 14 #Python
编写python程序的90条建议
Apr 14 #Python
Python基础知识之变量的详解
You might like
全国FM电台频率大全 - 8 黑龙江省
2020/03/11 无线电
SONY SRF-22W(33W)的电路分析和维修案例
2021/03/02 无线电
PHP生成静态页面详解
2006/11/19 PHP
shopex主机报错误请求解决方案(No such file or directory)
2011/12/27 PHP
php安装xdebug/php安装pear/phpunit详解步骤(图)
2013/12/22 PHP
PHP中使用memcache存储session的三种配置方法
2014/04/05 PHP
php数组冒泡排序算法实例
2016/05/06 PHP
Javascript 作用域使用说明
2009/08/13 Javascript
Javascript 面向对象 命名空间
2010/05/13 Javascript
JavaScript调用ajax获取文本文件内容实现代码
2014/03/28 Javascript
JS将滑动门改为选项卡(需鼠标点击)的实现方法
2015/09/27 Javascript
jQuery EasyUi 验证功能实例解析
2017/01/06 Javascript
详解Vue2+Echarts实现多种图表数据可视化Dashboard(附源码)
2017/03/21 Javascript
基于Require.js使用方法(总结)
2017/10/26 Javascript
基于Vue的移动端图片裁剪组件功能
2017/11/28 Javascript
使用webpack4编译并压缩ES6代码的方法示例
2019/04/24 Javascript
详解express使用vue-router的history踩坑
2019/06/05 Javascript
Vue + element 实现多选框组并保存已选id集合的示例代码
2020/06/03 Javascript
python常见字符串处理函数与用法汇总
2019/10/30 Python
python turtle 绘制太极图的实例
2019/12/18 Python
Python3 assert断言实现原理解析
2020/03/02 Python
最新Python idle下载、安装与使用教程图文详解
2020/11/28 Python
python 基于opencv去除图片阴影
2021/01/26 Python
Django和Ueditor自定义存储上传文件的文件名
2021/02/25 Python
采购助理岗位职责
2014/02/16 职场文书
航海技术专业毕业生推荐信
2014/07/09 职场文书
政风行风评议整改方案
2014/09/15 职场文书
2014年学校教学工作总结
2014/12/06 职场文书
普宁寺导游词
2015/02/04 职场文书
合同纠纷调解书
2015/05/20 职场文书
开业典礼致辞
2015/07/29 职场文书
环保建议书作文400字
2015/09/14 职场文书
MySQL令人咋舌的隐式转换
2021/04/05 MySQL
Python爬虫之爬取最新更新的小说网站
2021/05/06 Python
Python下opencv库的安装过程及问题汇总
2021/06/11 Python
使用 CSS 轻松实现一些高频出现的奇形怪状按钮
2021/12/06 HTML / CSS