PIXI.JS源碼解析:Ticker.js

  • 一個渲染引擎如何實現loop方法?
  • 一個監聽回調隊列如何實現添加和刪除?
  • 監聽回調隊列前後關係如何實現?
  • 若是在監聽回調函數中更新隊列,本輪迴調如何正確地執行?

今天咱們就借用PIXI.JS的源碼來講說以上功能的實現(本文不打算講解TS,爲了方(tou)便(lan)就是用v4版本的代碼)canvas

本文要講解的是core/ticker目錄中的Ticker和TickerListener他們用來處理畫面的動態更新,以及執行每次更新時的回調。app

從Application進入Ticker

在閱讀PIXI.JS的上手文檔時,你第一個接觸的就是一下這行代碼:函數

var app = new PIXI.Application();oop

這裏咱們實例化的app,後續會用來添加精靈圖,製做動畫。動畫

咱們都知道,依賴canvas的動畫是會不斷循環執行loop函數,把圖像繪製在畫布上,以此實現動畫。那麼Application是如何實現的?this

在構造函數中,咱們能夠看到一個內部變量this._ticker變量,這個變量就是實現loop的核心對象,它經過this.ticker對外暴露。spa

經過set方法,咱們知道,對ticker賦值實際上是更新了this._ticker(用新值覆蓋了前一次的this._ticker)。而start方法也是執行了this._ticker.start。code

而經過ticker.add方法,application將render函數添加到了渲染隊列中,此後每次執行loop是都會促發一個render函數的執行:對象

那麼這裏的add,start,remove三個方法都幹了什麼?這個UPDATE_PRIORITY.LOW又是什麼?blog

記住這幾個問題本篇咱們會一一解答。

那咱們就看看Ticker.js都實現了什麼邏輯。

ps:這裏的shared其實也是Ticker的實例:

Ticker.js

Ticker.js在core/ticker文件夾下:

core文件夾是用來存放核心代碼的,好比diaplay中存放處理顯示盒的代碼,sprites中是處理精靈圖的代碼,renderer中存放處理渲染的代碼。這些咱們往後都會說到。上一節咱們說的new Application()的邏輯代碼就來源於core根目錄的Application.js。

core/ticker文件夾下除了Ticker外,還有TickerListener和index。上一節說的shared就是在index中定義的:

而TickerListener是專門用來處理Ticker中的監聽回調隊列的(後面會講到)

話很少說讓咱們看看Ticker(這裏我刪除一些代碼,只留下核型邏輯):

這裏最重要的就是內部變量this._head和this._tick;

  • this._head

咱們以前說過Ticker內部實現了一個渲染回調隊列,在每次loop是都須要執行一遍,而這個this._head就是這個這個隊列的源頭,至於爲何須要單獨製做一個源頭,等咱們說完TickerListener你就能理解其中精妙了。而咱們在appliction中傳進來的render函數,也會做爲TickerListener插入到這個隊列之中:

而這個顯然目前這個隊列中除了head外只有一個Listener。這些回調會在每次loop後執行一邊以更新畫布上的圖像。而這個loop就由this._tick開啓。

this._tick會不斷被requestAnimationFrame調用執行,以實現loop的邏輯。可是循環開啓有三個條件:this.started爲真,this._requestId = null以及this._head.next存在,這三個條件是否都成立?

  • this.started爲真

還記得咱們在Appliction.js中有調用this.start方法?

Application.js

this.start實際上是對_ticker.start的封裝。

這裏的start是一個單例,未開始時會執行一次循環,並把this.started設爲真,這樣即使重複執行start方法也不會在促發更新。這樣咱們解答了上文的第一個問題,start函數的邏輯(還剩下addremove邏輯,和UPDATE_PRIORITY.LOW的意義)

  • this._requestId = null;

每次this._tick 執行時都會把他設爲null,因此loop的第二個條件也知足,

  • this._head.next

這裏的next也是一個TickerListener,咱們前面說過TickerListener是一個回調函數鏈,不斷經過next找到下一個回調知道執行完回調函數鏈。這個咱們在說到TickerListener會詳細說明。當咱們在Application.js中調用add方法時,咱們就已經往插入了TickerListener,因此this._head.next也存在。那麼這個loop就會一直循環下去,直到有邏輯觸發關閉。

那麼this.update(time);處理了什麼邏輯?

這裏經過next遍歷回調函數鏈來執行emit方法。這些邏輯都被寫在TickerListener中,不過在進入TickerListener以前咱們還須要弄懂add和remove函數邏輯以及UPDATE_PRIORITY.LOW的意義。

add函數

add函數和它的姐妹函數addOnce底層都調用了內部方法this._addListener。

這裏TickerListener能夠接受4個參數,第四個參數用於控制回調是執行一次仍是可以反覆執行。

而此前傳入UPDATE_PRIORITY.LOW實際上是能夠控制回調函數權重的:

一共有5檔,默認狀況下回調是按順序添加,先添加的回調先執行,但也能夠經過控制傳入的權重大小影響回調函數插入的位置(下面會解釋),但若是PRIORITY一致時,就是先添加的先執行。

而TickerListener具體邏輯在TickerListener源碼解析一篇中會詳細解釋。那麼咱們就來看看內部方法_addListener:

在前面分析update邏輯時,咱們能夠看到TickerListener回調隊列是不斷經過找尋下一個next來完成鏈式執行。而具體TickerListener的位置由previous和next決定,除了_head外TickerListener能夠沒有next卻不能沒有previous。(甚至兩個TickerListener的previous有多是同一個)。

而執行完插入邏輯後,會執行this._startIfPossible();

這保證了,若是autoStart爲真,當咱們往_header後添加了TickerListener後,但即使咱們沒有執行start函數(this.started爲false),邏輯上也會開啓loop。

但若是咱們已經執行完start後(this.started爲true),此時再執行_requestIfNeeded;

由於每次執行完_tick後this._requestId = requestAnimationFrame(this._tick);因此_requestIfNeeded的條件語句不會執行。

remove

最後讓咱們來看看remove函數都幹了什麼:

簡而言之就是刪除fn,而這些邏輯都是由TickerListener完成的。那麼下一節咱們就來看看TickerListener是如何工做的。

相關文章
相關標籤/搜索