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实现方法
Nov 18 Python
Windows下实现Python2和Python3两个版共存的方法
Jun 12 Python
Python中取整的几种方法小结
Jan 06 Python
Python工厂函数用法实例分析
May 14 Python
利用python打开摄像头及颜色检测方法
Aug 03 Python
python集合比较(交集,并集,差集)方法详解
Sep 13 Python
详解PyCharm+QTDesigner+PyUIC使用教程
Jun 13 Python
基于Python2、Python3中reload()的不同用法介绍
Aug 12 Python
使用pandas实现连续数据的离散化处理方式(分箱操作)
Nov 22 Python
pytorch 实现cross entropy损失函数计算方式
Jan 02 Python
python 实现Requests发送带cookies的请求
Feb 08 Python
python 递归相关知识总结
Mar 03 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/11/26 PHP
Breeze 文章管理系统 v1.0.0正式发布
2006/12/14 PHP
关于php mvc开发模式的感想
2011/06/28 PHP
PHP sleep()函数, usleep()函数
2016/08/25 PHP
php生成条形码的图片的实例详解
2017/09/13 PHP
对于Laravel 5.5核心架构的深入理解
2018/02/22 PHP
怎么让脚本或里面的函数在所有图片都载入完毕的时候执行
2006/10/17 Javascript
取键盘键位ASCII码的网页
2007/07/30 Javascript
js nextSibling属性和previousSibling属性概述及使用注意
2013/02/16 Javascript
js正则表达exec与match的区别说明
2014/01/29 Javascript
struts2+jquery组合验证注册用户是否存在
2014/04/30 Javascript
Javascript中的几种URL编码方法比较
2015/01/23 Javascript
jQuery实现带有洗牌效果的动画分页实例
2015/08/31 Javascript
js带缩略图的图片轮播效果代码分享
2015/09/14 Javascript
javascript基础知识
2016/06/07 Javascript
js获取元素下的第一级子元素的方法(推荐)
2017/03/05 Javascript
微信小程序遇到修改数据后页面不渲染的问题解决
2017/03/09 Javascript
jQuery响应滚动条事件功能示例
2017/10/14 jQuery
浅谈webpack编译vue项目生成的代码探索
2017/12/11 Javascript
CSS3 动画卡顿性能优化的完美解决方案
2018/09/20 Javascript
小程序中设置缓存过期的实现方法
2020/01/14 Javascript
vue组件暴露和.js文件暴露接口操作
2020/08/11 Javascript
antd 表格列宽自适应方法以及错误处理操作
2020/10/27 Javascript
[02:51]DOTA2英雄基础教程 风暴之灵
2013/12/23 DOTA
[43:51]2014 DOTA2国际邀请赛中国区预选赛 Dream Times VS TongFu
2014/05/22 DOTA
[57:16]2014 DOTA2华西杯精英邀请赛 5 25 LGD VS VG第二场
2014/05/26 DOTA
Python实现发送QQ邮件的封装
2017/07/14 Python
Python中numpy模块常见用法demo实例小结
2019/03/16 Python
python 标准差计算的实现(std)
2019/07/29 Python
最新版 Windows10上安装Python 3.8.5的步骤详解
2020/11/28 Python
windows下python 3.9 Numpy scipy和matlabplot的安装教程详解
2020/11/28 Python
浏览器实现移动端高性能css3动画(开启gpu加速)
2013/12/23 HTML / CSS
洲际酒店集团大中华区:IHG中国
2016/08/17 全球购物
亚洲领先的设计购物网站:Pinkoi
2020/11/26 全球购物
简易离婚协议书范本
2014/10/24 职场文书
了解Kubernetes中的Service和Endpoint
2022/04/01 Servers