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实现可将字符转换成大写的tcp服务器实例
Apr 29 Python
python处理两种分隔符的数据集方法
Dec 12 Python
python解压TAR文件至指定文件夹的实例
Jun 10 Python
十行代码使用Python写一个USB病毒
Jun 21 Python
python3用PIL把图片转换为RGB图片的实例
Jul 04 Python
Python shelve模块实现解析
Aug 28 Python
python实现批量文件重命名
Oct 31 Python
Windows下实现将Pascal VOC转化为TFRecords
Feb 17 Python
python的pip有什么用
Jun 17 Python
如何卸载python插件
Jul 08 Python
用python批量下载apk
Dec 29 Python
Python中time与datetime模块使用方法详解
Mar 31 Python
深度学习tensorflow基础mnist
Python 多线程之threading 模块的使用
Apr 14 #Python
教你如何用python开发一款数字推盘小游戏
深度学习详解之初试机器学习
正确的理解和使用Django信号(Signals)
Apr 14 #Python
编写python程序的90条建议
Apr 14 #Python
Python基础知识之变量的详解
You might like
PHP 中的类
2006/10/09 PHP
解析PHP中的正则表达式以及模式匹配
2013/06/19 PHP
php警告Creating default object from empty value 问题的解决方法
2014/04/02 PHP
php使用ZipArchive提示Fatal error: Class ZipArchive not found in的解决方法
2014/11/04 PHP
PHP中的Session对象如何使用
2015/09/25 PHP
PHP微信红包API接口
2015/12/05 PHP
用javascript实现页面打印的三种方法
2007/03/05 Javascript
javascript Array.sort() 跨浏览器下需要考虑的问题
2009/12/07 Javascript
原生JavaScript实现瀑布流布局
2020/06/28 Javascript
关于JS中的apply,call,bind的深入解析
2016/04/05 Javascript
Node.js的npm包管理器基础使用教程
2016/05/26 Javascript
JavaScript实现图片切换效果
2017/08/12 Javascript
node使用promise替代回调函数
2018/05/07 Javascript
Angular使用动态加载组件方法实现Dialog的示例
2018/05/11 Javascript
JavaScript中filter的用法实例分析
2019/02/27 Javascript
node微信开发之获取access_token+自定义菜单
2019/03/17 Javascript
vue-cli的build的文件夹下没有dev-server.js文件配置mock数据的方法
2019/04/17 Javascript
微信小程序HTTP请求从0到1封装
2019/09/09 Javascript
Vue+ElementUI 中级联选择器Bug问题的解决
2020/07/31 Javascript
javascript实现点击按钮切换轮播图功能
2020/09/23 Javascript
python正则匹配抓取豆瓣电影链接和评论代码分享
2013/12/27 Python
Python数据结构之单链表详解
2017/09/12 Python
Python实现的栈、队列、文件目录遍历操作示例
2019/05/06 Python
PyCharm搭建Spark开发环境实现第一个pyspark程序
2019/06/13 Python
python实现五子棋游戏
2019/06/18 Python
Python3.6+selenium2.53.6自动化测试_读取excel文件的方法
2019/09/06 Python
pyinstaller打包成无控制台程序时运行出错(与popen冲突的解决方法)
2020/04/15 Python
美国男士内衣品牌:Tommy John
2017/12/22 全球购物
总经理岗位职责
2013/11/09 职场文书
大学校园生活自我鉴定
2014/01/13 职场文书
旅游市场营销方案
2014/03/09 职场文书
家长寄语大全
2014/04/02 职场文书
财务负责人任命书
2014/06/06 职场文书
学前班幼儿评语大全
2014/12/29 职场文书
先进事迹材料怎么写
2014/12/30 职场文书
幼儿园老师新年寄语
2015/08/17 职场文书