PixiJS 源碼深度解讀:用于循環(huán)渲染的 Ticker 模塊
大家好,我是前端西瓜哥。這次來看看 PixiJS 的 Ticker 模塊源碼。
Ticker 的作用是 在下一幀繪制前調(diào)用監(jiān)聽器,PixiJS 使用它來不斷對(duì)畫面進(jìn)行重繪。
使用
在我們 實(shí)例化 PIXI.Application 時(shí),PIXI.Application 內(nèi)部注冊(cè)的插件之一的 TickerPlugin 會(huì)被初始化:
Application._plugins.forEach((plugin) => {
plugin.init.call(this, options);
});
將 Application 的渲染器 renderer 的 render 方法注冊(cè)進(jìn)去,然后每幀都會(huì)會(huì)被調(diào)用。
ticker = options.sharedTicker ? Ticker.shared : new Ticker();
ticker.add(this.render, this, UPDATE_PRIORITY.LOW);
也可以直接使用:
const ticker = new PIXI.Ticker();
// ticker.autoStart = true;
ticker.add(() => {
console.log("前端西瓜哥");
});
ticker.add(() => {
console.log("fe_watermelon");
});
ticker.start();
目錄結(jié)構(gòu)
Ticker 的代碼都在 packages/ticker 目錄下。
構(gòu)造函數(shù)
看一下 Ticker 的構(gòu)造函數(shù):
class Ticker {
constructor() {
this._head = new TickerListener(null, null, Infinity);
this.deltaMS = 1 / Ticker.targetFPMS;
this.elapsedMS = 1 / Ticker.targetFPMS;
this._tick = (time: number): void => {
this._requestId = null;
if (this.started) {
// Invoke listeners now
this.update(time);
// Listener side effects may have modified ticker state.
if (this.started && this._requestId === null && this._head.next) {
this._requestId = requestAnimationFrame(this._tick);
}
}
};
}
}
做了哪些事情:
(1)初始化一個(gè)空的監(jiān)聽器函數(shù)
this._head = new TickerListener(null, null, Infinity);
使用了鏈表來管理多個(gè)監(jiān)聽器函數(shù),所以這個(gè)空的 this._head 就是這個(gè)鏈表的頭部 哨兵節(jié)點(diǎn)(虛節(jié)點(diǎn))。
哨兵節(jié)點(diǎn)是一個(gè)鏈表編程技巧,目的是讓真正的頭部節(jié)點(diǎn)能和其他普通節(jié)點(diǎn)有一致的實(shí)現(xiàn),不需要老是判斷 head 是否為 null,對(duì)頭節(jié)點(diǎn)做特殊處理,達(dá)到降低實(shí)現(xiàn)難度的目的。
監(jiān)聽器函數(shù)會(huì)被封裝成一個(gè) TickerListener 的類,提供 priority(優(yōu)先級(jí))、once(只執(zhí)行一次) 等特性。其中的 next 指向下一個(gè)監(jiān)聽函數(shù),確保監(jiān)聽器函數(shù)能夠按照注冊(cè)順序依次執(zhí)行。
不過 TickerListener 有個(gè) 優(yōu)先級(jí)的概念,監(jiān)聽器在加入的時(shí)候,會(huì)插入合適的位置,最終保證優(yōu)先級(jí)是從高往低的。優(yōu)先級(jí)類型在 UPDATE_PRIORITY 這個(gè)枚舉變量中。
順帶一提,application 的 render 方法優(yōu)先級(jí)是 UPDATE_PRIORITY.LOW,哨兵節(jié)點(diǎn)則是突破天際的 Infinity。
關(guān)于監(jiān)聽器函數(shù)鏈表的維護(hù)相關(guān)代碼,其實(shí)就是雙向鏈表的一些常規(guī)操作,本文不會(huì)過多敘述。
(2)執(zhí)行監(jiān)聽器函數(shù)
this.update(time);
這里是執(zhí)行所有監(jiān)聽器函數(shù)的地方。
其下的核心代碼為:
while (listener)
{
listener = listener.emit(this.deltaTime);
}
listener.emit() 方法會(huì)執(zhí)行當(dāng)前的 listener 方法,并返回它的 next。不斷循環(huán),直到 next 為 null 結(jié)束。
(3)循環(huán)調(diào)用 this._tick 方法
this._requestId = requestAnimationFrame(this._tick);
這里用了瀏覽器提供的 requestAnimationFrame 方法,實(shí)現(xiàn)循環(huán)調(diào)用 this._tick 方法。為了方便描述,通常簡(jiǎn)寫為 raf。
this.head.next 表示有注冊(cè)的監(jiān)聽函數(shù)。
結(jié)尾
Ticker 模塊并不復(fù)雜的,是對(duì) requestAnimationFrame 的一層封裝,并使用鏈表做監(jiān)聽器函數(shù)的維護(hù)。