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抓取百度查询结果的方法
Jul 08 Python
举例讲解Python面相对象编程中对象的属性与类的方法
Jan 19 Python
python简单实现刷新智联简历
Mar 30 Python
python基础while循环及if判断的实例讲解
Aug 25 Python
Python随机生成均匀分布在三角形内或者任意多边形内的点
Dec 14 Python
对python程序内存泄漏调试的记录
Jun 11 Python
Python读取系统文件夹内所有文件并统计数量的方法
Oct 23 Python
在keras中获取某一层上的feature map实例
Jan 24 Python
Python变量格式化输出实现原理解析
Aug 06 Python
python使用scapy模块实现ping扫描的过程详解
Jan 21 Python
Python 正则模块详情
Nov 02 Python
python开发人人对战的五子棋小游戏
May 02 Python
深度学习tensorflow基础mnist
Python 多线程之threading 模块的使用
Apr 14 #Python
教你如何用python开发一款数字推盘小游戏
深度学习详解之初试机器学习
正确的理解和使用Django信号(Signals)
Apr 14 #Python
编写python程序的90条建议
Apr 14 #Python
Python基础知识之变量的详解
You might like
PHP网页游戏学习之Xnova(ogame)源码解读(二)
2014/06/23 PHP
php微信公众平台开发(四)回复功能开发
2016/12/06 PHP
php安全配置记录和常见错误梳理(总结)
2017/03/28 PHP
Laravel多用户认证系统示例详解
2018/03/13 PHP
thinkphp框架表单数组实现图片批量上传功能示例
2020/04/04 PHP
深入认识JavaScript中的函数
2007/01/22 Javascript
关于jquery ajax 调用带参数的webservice返回XML数据一个小细节
2012/07/31 Javascript
一个可拖拽列宽表格实例演示
2012/11/26 Javascript
JS根据key值获取URL中的参数值及把URL的参数转换成json对象
2015/08/26 Javascript
Jquery easyui 实现动态树
2015/11/17 Javascript
AngularJS转换响应内容
2016/01/27 Javascript
js改变css样式的三种方法推荐
2016/06/28 Javascript
jQuery联动日历的实例解析
2016/12/02 Javascript
Vue 2.0学习笔记之Vue中的computed属性
2017/10/16 Javascript
关于Angularjs中跨域设置白名单问题
2018/04/17 Javascript
详解Vue2.0配置mint-ui踩过的那些坑
2018/04/23 Javascript
对angularJs中2种自定义服务的实例讲解
2018/09/30 Javascript
微信小程序实现动态获取元素宽高的方法分析
2018/12/10 Javascript
使用Easyui实现查询条件的后端传递并自动刷新表格的两种方法
2019/09/09 Javascript
详解element-ui级联菜单(城市三级联动菜单)和回显问题
2019/10/02 Javascript
layui 数据表格 根据值(1=业务,2=机构)显示中文名称示例
2019/10/26 Javascript
[28:28]Ti4 冒泡赛第二天NEWBEE vs NaVi 2
2014/07/15 DOTA
独特的python循环语句
2016/11/20 Python
Python Json序列化与反序列化的示例
2018/01/31 Python
python编写简易聊天室实现局域网内聊天功能
2018/07/28 Python
详解pyqt5的UI中嵌入matplotlib图形并实时刷新(挖坑和填坑)
2020/08/07 Python
小学生手册家长评语
2014/04/16 职场文书
节水标语大全
2014/06/11 职场文书
小学关爱留守儿童活动方案
2014/08/25 职场文书
乡镇党建工作汇报材料
2014/10/27 职场文书
党员倡议书
2015/01/19 职场文书
毕业论文致谢信
2015/05/14 职场文书
2015年宣传思想工作总结
2015/05/22 职场文书
2015年小学实验室工作总结
2015/07/28 职场文书
MySQL 中如何归档数据的实现方法
2022/03/16 SQL Server
Python OpenCV实现图形检测示例详解
2022/04/08 Python