Cocos Creator ScrollView 優(yōu)化系列-1-分幀加載

本系列教程指引:

  1. Cocos Creator ScrollView 優(yōu)化系列-1-分幀加載
  2. Cocos Creator ScrollView 優(yōu)化系列-2-可視區(qū)域渲染
  3. Cocos Creator ScrollView 優(yōu)化系列-3-復(fù)用實(shí)現(xiàn)(待續(xù))
  4. Cocos Creator ScrollView 優(yōu)化系列-4-合批優(yōu)化(待續(xù))

本項(xiàng)目中所有圖示、代碼都在Github倉(cāng)庫(kù)中玩讳,如果需要運(yùn)行驗(yàn)證,可直接拉下項(xiàng)目即可,不用自己手?jǐn)]代碼驗(yàn)證

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

一、 前言

JS是單線程的构韵,也就意味著所有任務(wù)需要排隊(duì)虫啥,只有當(dāng)前一個(gè)任務(wù)結(jié)束了,后一個(gè)任務(wù)才會(huì)執(zhí)行吃媒。如果前一個(gè)任務(wù)耗時(shí)很長(zhǎng),后一個(gè)任務(wù)就不得不一直等著吕喘。

Cocos Creator 是采用 Java Script/Type Script語(yǔ)言開(kāi)發(fā)赘那,本質(zhì)上是JS,同樣會(huì)擁有以上特征氯质。特別地募舟,如果使用不當(dāng),極有可能導(dǎo)致界面卡頓闻察。

比如:在為一個(gè)ScrollView的Content創(chuàng)建500個(gè)節(jié)點(diǎn)的的時(shí)候拱礁,可能就會(huì)出現(xiàn)下面界面卡死的問(wèn)題

PS:本來(lái)加載過(guò)程中有一個(gè)loading對(duì)話框,因?yàn)榭ㄋ懒嗽透杏X(jué)從來(lái)沒(méi)出現(xiàn)

卡死問(wèn)題演示

通過(guò)閱讀本文呢灶,你將了解到如何利用「分幀加載」技術(shù)解決上述問(wèn)題,最終效果對(duì)比如下:

分幀加載演示

二钮热、卡死問(wèn)題分析

在正常情況下填抬,我們?yōu)镾crollView創(chuàng)建一定數(shù)量的子節(jié)點(diǎn)的時(shí)候,代碼可能是這樣子的

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);
}

一般而言隧期,當(dāng)length的值很小飒责,比如10個(gè)的時(shí)候,程序跑起來(lái)的時(shí)候仆潮,看上去可能會(huì)沒(méi)什么問(wèn)題宏蛉,但其實(shí)如果仔細(xì)一點(diǎn)觀察,就發(fā)現(xiàn)其實(shí)也是會(huì)卡死一會(huì)性置,只是很快就結(jié)束了拾并。

特別地,如果length的值到一點(diǎn)量級(jí),比如50+個(gè)嗅义,那么這段代碼就會(huì)出現(xiàn)上面截圖那樣子—— 卡死

歸根到底屏歹,問(wèn)題在于通過(guò) cc.instantiate 創(chuàng)建節(jié)點(diǎn)以及為這個(gè)節(jié)點(diǎn) setParent 時(shí),所需要的時(shí)間并沒(méi)有想象中那么小之碗,當(dāng)然蝙眶,也沒(méi)有想象中那么大。但是當(dāng)連續(xù)創(chuàng)建一定數(shù)量的時(shí)候褪那,問(wèn)題就會(huì)被放大幽纷,也就是說(shuō),這個(gè)創(chuàng)建節(jié)點(diǎn)的時(shí)間可能需要一段時(shí)間博敬。

可視化一點(diǎn)去理解這個(gè)問(wèn)題的話友浸,恩,大概就是下圖這樣子

Direct Load

很明顯偏窝,按照上圖收恢,第1到4幀都被完成占用了,導(dǎo)致這期間所有的其他邏輯都會(huì)不能執(zhí)行(Loading對(duì)話框出不來(lái)祭往,旋轉(zhuǎn)動(dòng)畫卡死等等)派诬。

那么怎么解決呢?

三、解決方案(理論篇)

可能有同學(xué)第一時(shí)間想到用Promise異步解決链沼,但是在這個(gè)問(wèn)題上,Promise只是把紅色的這段連續(xù)創(chuàng)建節(jié)點(diǎn)的代碼放到后面一點(diǎn)的時(shí)間去執(zhí)行沛鸵,但是當(dāng)紅色的代碼執(zhí)行的時(shí)候括勺,它依舊會(huì)卡死那段時(shí)間,所以Promise是不能應(yīng)對(duì)這種場(chǎng)合的曲掰。

那么應(yīng)該怎么解決呢疾捍?

其中,一種解決方案栏妖,就是我們今天要講的 「分幀加載」 乱豆,怎么理解「分幀加載」呢?

慣例吊趾,先上圖:

Framing Load

配合上圖宛裕,就比較好理解「分幀加載」了,具體執(zhí)行過(guò)程為

  1. 先將耗時(shí)卡死的代碼拆分為很多小段
  2. 然后每一幀论泛,分配一點(diǎn)時(shí)間去執(zhí)行這些小段
  3. 這樣子一來(lái)揩尸,每一幀,我們就留了時(shí)間給其他邏輯去跑(那么Loading對(duì)話框也可以出來(lái)了屁奏,旋轉(zhuǎn)動(dòng)畫也可以繼續(xù)了)

OK岩榆,理論說(shuō)清楚了,那么實(shí)際怎么弄呢?

比如:

  1. 怎么拆分代碼為很多小段勇边?
  2. 怎么分配每一幀的一些時(shí)間去執(zhí)行這些小段呢犹撒?

這個(gè)時(shí)候,我們需要用到 ES6(ES2015)的協(xié)程——Generator粒褒,去幫助我們實(shí)現(xiàn)识颊。

ps: 我們不會(huì)在這里探討什么是Generator,怎么用怀浆,如果你對(duì)Generator感到陌生谊囚,不妨可以嘗試閱讀下面文章去了解

四、解決方案(代碼篇)

以我們第二節(jié)舉例用到的代碼(為ScrollView創(chuàng)建一定數(shù)量的子節(jié)點(diǎn))為例子执赡,我們將 實(shí)現(xiàn)代碼為多個(gè)小段 以及 分配每一幀的一些時(shí)間去執(zhí)行這些小段 镰踏。

4.1 利用 Generator 將代碼拆分為多個(gè)小段

拆分前:

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);
}

拆分后:

/**
 * (新增代碼)獲取生成子節(jié)點(diǎn)的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 循環(huán)里創(chuàng)建所有節(jié)點(diǎn),改為拆分 for 循環(huán)的每一步為一個(gè)小段

當(dāng)然沙合,這份「拆分后」的代碼并不能跑起來(lái)奠伪,因?yàn)樗皇菍?shí)現(xiàn)了拆分步驟,要讓它跑起來(lái)首懈,我們要上下面的第二段代碼

4.2 分配每一幀的一些時(shí)間去執(zhí)行

在看一次我們剛才的圖

Framing Load

配合圖绊率,得出的代碼

/**
 * 實(shí)現(xiàn)分幀加載
 */
async framingLoad(length: number) {
    await this.executePreFrame(this._getItemGenerator(length), 1);
}

/**
 * 分幀執(zhí)行 Generator 邏輯
 *
 * @param generator 生成器
 * @param duration 持續(xù)時(shí)間(ms)
 *          每次執(zhí)行 Generator 的操作時(shí),最長(zhǎng)可持續(xù)執(zhí)行時(shí)長(zhǎng)究履。
 *          假設(shè)值為8ms滤否,那么表示1幀(總共16ms)下,分出8ms時(shí)間給此邏輯執(zhí)行
 */
private executePreFrame(generator: Generator, duration: number) {
    return new Promise((resolve, reject) => {
        let gen = generator;
        // 創(chuàng)建執(zhí)行函數(shù)
        let execute = () => {

            // 執(zhí)行之前最仑,先記錄開(kāi)始時(shí)間戳
            let startTime = new Date().getTime();

            // 然后一直從 Generator 中獲取已經(jīng)拆分好的代碼段出來(lái)執(zhí)行
            for (let iter = gen.next(); ; iter = gen.next()) {

                // 判斷是否已經(jīng)執(zhí)行完所有 Generator 的小代碼段
                // 如果是的話藐俺,那么就表示任務(wù)完成
                if (iter == null || iter.done) {
                    resolve();
                    return;
                }

                // 每執(zhí)行完一段小代碼段,都檢查一下是否
                // 已經(jīng)超過(guò)我們分配給本幀泥彤,這些小代碼端的最大可執(zhí)行時(shí)間
                if (new Date().getTime() - startTime > duration) {
                    
                    // 如果超過(guò)了欲芹,那么本幀就不在執(zhí)行,開(kāi)定時(shí)器吟吝,讓下一幀再執(zhí)行
                    this.scheduleOnce(() => {
                        execute();
                    });
                    return;
                }
            }
        };

        // 運(yùn)行執(zhí)行函數(shù)
        execute();
    });
}

代碼中已經(jīng)附有大量注釋菱父,但還是有幾個(gè)點(diǎn)需要說(shuō)明一下:

  1. 為了方便知道這些小任務(wù)是否已經(jīng)都執(zhí)行完了,我采用了Promise剑逃,當(dāng)都完成了的時(shí)候浙宜,resolve 一下
  2. 每一個(gè)小代碼段的執(zhí)行時(shí)間可能不固定的,可能會(huì)超出占用我們的一些期望時(shí)間蛹磺。比如我們期望每一幀分配1ms 去執(zhí)行這些小代碼段梆奈,假設(shè)前3段小代碼段,每一段的執(zhí)行時(shí)間假設(shè)為 0.2ms称开,0.5ms, 0.4ms亩钟,那么在我給出的這段代碼中乓梨,是會(huì)執(zhí)行完這3段小代碼段,然后就終止本幀繼續(xù)執(zhí)行這些小代碼段清酥,因?yàn)檫@里的耗時(shí)已經(jīng)是 1.1ms扶镀,比我設(shè)定的 1ms 已經(jīng)多出了 0.1ms 。當(dāng)然你可以自行改動(dòng)代碼焰轻,讓這些執(zhí)行嚴(yán)格按照最大1ms去執(zhí)行臭觉,以實(shí)現(xiàn)不超時(shí)執(zhí)行(即不再執(zhí)行第3個(gè)小段)

至此,我們一定程度上已經(jīng)實(shí)現(xiàn)了「分幀加載」了~

本項(xiàng)目中所有圖示辱志、代碼都在Github倉(cāng)庫(kù)中蝠筑,如果需要運(yùn)行驗(yàn)證,可直接拉下項(xiàng)目即可揩懒,不用自己手?jǐn)]代碼驗(yàn)證

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

五什乙、總結(jié)

  1. 盡管我們標(biāo)題是 「ScrollView 優(yōu)化系列」,但我更加傾向于已球,「利用分幀加載去優(yōu)化ScrollView」臣镣。在這篇文章上,我們舉的例子是創(chuàng)建節(jié)點(diǎn)智亮,但是我刻意不說(shuō)「分幀創(chuàng)建」忆某,這是因?yàn)槲艺J(rèn)為 「分幀加載」是一種性能優(yōu)化方案 ,可以「分幀創(chuàng)建」阔蛉、「分幀運(yùn)行」弃舒、「分幀計(jì)算」、「分幀渲染」等状原。
  2. 在實(shí)現(xiàn)分幀上棒坏,我們用到了 this.scheduleOnce函數(shù),但是其實(shí)可以嘗試在 update(dt:number) 上執(zhí)行遭笋,不妨嘗試修改我的 「測(cè)試項(xiàng)目」去驗(yàn)證呢~
  3. TypeScript 要用上 Generator 還需要需改一下Cocos項(xiàng)目中的 tsconfig.jsoncompilerOptions.lib 數(shù)組中添加 es2015

六蝇裤、進(jìn)入下一個(gè)章節(jié)

至此饱苟,我們的「分幀加載」基本告一段落了,但細(xì)心的你肯定發(fā)現(xiàn)了庐椒,目前這個(gè)案例里面 Draw call 太高了测暗,這是一個(gè)能忽視的問(wèn)題央串,這個(gè)問(wèn)題,我們將會(huì)在下個(gè)章節(jié)中解決碗啄。

本系列教程指引:

  1. Cocos Creator ScrollView 優(yōu)化系列-1-分幀加載
  2. ??Cocos Creator ScrollView 優(yōu)化系列-2-可視區(qū)域渲染
  3. Cocos Creator ScrollView 優(yōu)化系列-3-復(fù)用實(shí)現(xiàn)(待續(xù))
  4. Cocos Creator ScrollView 優(yōu)化系列-4-合批優(yōu)化(待續(xù))
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末质和,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子稚字,更是在濱河造成了極大的恐慌饲宿,老刑警劉巖厦酬,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異瘫想,居然都是意外死亡仗阅,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門国夜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)减噪,“玉大人,你說(shuō)我怎么就攤上這事车吹〕镌#” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵窄驹,是天一觀的道長(zhǎng)朝卒。 經(jīng)常有香客問(wèn)我,道長(zhǎng)馒吴,這世上最難降的妖魔是什么扎运? 我笑而不...
    開(kāi)封第一講書人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮饮戳,結(jié)果婚禮上豪治,老公的妹妹穿的比我還像新娘。我一直安慰自己扯罐,他們只是感情好负拟,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著歹河,像睡著了一般掩浙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上秸歧,一...
    開(kāi)封第一講書人閱讀 49,760評(píng)論 1 289
  • 那天厨姚,我揣著相機(jī)與錄音,去河邊找鬼键菱。 笑死谬墙,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的经备。 我是一名探鬼主播拭抬,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼侵蒙!你這毒婦竟也來(lái)了造虎?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤纷闺,失蹤者是張志新(化名)和其女友劉穎算凿,沒(méi)想到半個(gè)月后份蝴,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡澎媒,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年搞乏,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片戒努。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡请敦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出储玫,到底是詐尸還是另有隱情侍筛,我是刑警寧澤,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布撒穷,位于F島的核電站匣椰,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏端礼。R本人自食惡果不足惜禽笑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蛤奥。 院中可真熱鬧佳镜,春花似錦、人聲如沸凡桥。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)缅刽。三九已至啊掏,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間衰猛,已是汗流浹背迟蜜。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留啡省,地道東北人娜睛。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像冕杠,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子酸茴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒(méi)有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,090評(píng)論 1 32
  • 那條叫‘卡卡’的惡龍頓時(shí)變得兇惡起來(lái)分预,它張牙舞爪地向我撲來(lái)。我被它撲倒了薪捍,一頭撞在了濕漉漉的石壁上笼痹。就在它伸出兩爪...
    小小夕顏花閱讀 667評(píng)論 3 5
  • 【做人智商不高沒(méi)關(guān)系配喳,情商不高也問(wèn)題不大,但做人的格局已定要大】說(shuō)白了凳干,你可以不聰明晴裹,也可以不懂交際,但一定要大氣...
    最愛(ài)的桐嘉閱讀 416評(píng)論 0 0