本帖最后由 fightingcat 于 -7-16 00:26 编辑
上一篇讲到了引擎的入口runEgret为每一个播放器标签(就是index.html中看到的那个
之前web.WebPlayer初始化时调用了sys.Player的start方法:
[mw_shl_code=actionscript3,true]
/**
* @private
* 启动播放器
*/
public start():void {
/// ……
if (!this.root) {
this.initialize();
}
$ticker.$addPlayer(this);
}
[/mw_shl_code]
root声明的类型是DisplayObject,如果root未设置,就调用initialize初始化:
[mw_shl_code=actionscript3,true]
private initialize():void {
var rootClass;
if (this.entryClassName) {
rootClass = egret.getDefinitionByName(this.entryClassName);
}
if (rootClass) {
var rootContainer:any = new rootClass();
this.root = rootContainer;
if (rootContainer instanceof egret.DisplayObject) {
this.stage.addChild(rootContainer);
}
DEBUG && $error(1002, this.entryClassName);
}
}
else {
DEBUG && $error(1001, this.entryClassName);
}
}
[/mw_shl_code]
这里就是根据index.html中播放器标签的data-entry-class属性指定的类名获取入口类,创建一个对象并作为sys.Player的root节点,然后加入stage的子节点中。
初始化完后调用了$ticker.$addPlayer(this)注册并运行播放器。这里再一次用到了sys.$ticker,前面说过它是sys.SystemTicker的单例。事实上除了驱动sys.Player,sys.SystemTicker管理了所有需要循环更新的逻辑,以及广播ENTERFRAME事件,无论是egret.setTimeout还是Tween都使用了sys.SystemTicker来注册每帧更新事件。
源码位于 egret/player/SystemTicker.ts 中:
[mw_shl_code=actionscript3,true]
/**
* @private
* 注册一个播放器实例并运行
*/
$addPlayer(player
layer):void {
if (this.playerList.indexOf(player) != -1) {
return;
}
if (DEBUG) {
egret_stages.push(player.stage);
}
this.playerList = this.playerList.concat();
this.playerList.push(player);
}
[/mw_shl_code]
只是把sys.Player加入到列表中(不知为何用concat复制了个新的列表),那么sys.SystemTicker是如何驱动sys.Player运行的呢?还记得前面第一次用到$ticker是在引擎的入口runEgret()里吧,其中启动了一个循环不断调用$ticker.update,所以接下来看sys.SystemTicker的update方法:
[mw_shl_code=actionscript3,true]
public update():void {
var t1 = egret.getTimer();
var timeStamp = egret.getTimer();
for (var i = 0; i < this.callBackList.length; i++) {
if (this.callBackList.call(this.thisObjectList, timeStamp)) {
$requestRenderingFlag = true;
}
}
/// 接下
[/mw_shl_code]
这里遍历了一个callBackList并执行其中的回调函数,这个列表就是除了播放器外,其他逻辑注册的更新事件(见$startTick方法)。如果回调函数返回true,则表示需要立即触发渲染,$requestRenderingFlag是egret.sys命名空间下的公共变量,控制后面是否执行渲染逻辑。(code review技巧:在wing3.x或者vscode中选中一个符号(变量、属性、方法或类)并按shift+F12可以查看使用到这个符号的代码,可以自己看一下都有哪些功能注册了这个事件回调。)
[mw_shl_code=actionscript3,true]
/// 接上
this.lastCount -= 1000;
var t2 = egret.getTimer();
if (this.lastCount > 0) {
if ($requestRenderingFlag) {
this.render(false, this.costEnterFrame + t2 - t1);
}
return;
}
this.lastCount += this.frameInterval;
this.render(true, this.costEnterFrame + t2 - t1);
var t3 = egret.getTimer();
this.broadcastEnterFrame();
var t4 = egret.getTimer();
this.costEnterFrame = t4 - t3;
}
[/mw_shl_code]
代码稍微简化了一下,去掉了一些不必要的局部变量(个人觉得这种优化有限却降低可读性)。
这里的变量命名不是很好,frameInterval是指以60帧为基准相对当前帧率的倍数(因为runEgret中的循环是以60帧为准的,这里相当于跳帧处理),lastCount是一个计数器,初始为frameInterval,每次减1,当大于0时说明需要跳帧,但如果$requestRenderingFlag为真就调用this.render方法主动渲染一次。否则(lastCount<=0),说明发生了跳帧,lastCount再加上frameInterval。为了消除误差,涉及的量都乘以了1000,所以这里lastCount是-1000而不是1,frameInterval也是大于1000的整数。大概这就是egret所谓的滑动跑道模型吧,其实并没有什么高深的……
再来看$render方法(省略了一些调试相关的代码):
[mw_shl_code=actionscript3,true]
$render(triggerByFrame: boolean, costTicker: number): void {
this.callLaters();
this.callLaterAsyncs();
var dirtyList = this.stage.$displayList.updateDirtyRegions();
var drawCalls = this.stage.$displayList.drawToSurface();
/// 调试相关
}
[/mw_shl_code]
这里先调用了两个方法:callLaters和callLaterAsyncs,作用就是执行egret/player/utils/callLater.ts中callLater和$callAsync注册的延迟回调,从实现上看这两个方法完全一样,除了后者注释为私有,可能是考虑语义上的区分。另外其中有释放和创建数组的操作,不过由于对象生命周期很短可能并不会影响性能。
后面两句看函数名就可以知道,分别是更新舞台显示列表的脏区域,以及绘制舞台显示列表到sys.RenderBuffer。之前跳过了sys.DisplayList,接下来就可以分析sys.DisplayList的实现了。
小结,egret引擎启动流程
引擎从runEgret函数开始启动,先启动一个60帧每秒的循环,并调用sys.$ticker.update(),sys.$ticker是sys.SystemTicker的单例对象,负责管理所有每帧回调。
然后遍历页面上所有拥有"egret-class"CSS类的标签,并为其创建web.WebPlayer。
web.WebPlayer初始化监听用户输入事件,创建Stage和sys.RenderBuffer(实际的实现是web.CanvasRenderBuffer或web.WebGLRenderBuffer)。
web.WebPlayer把创建的RenderBuffer的canvas加入播放器标签,并设置基本的CSS属性。
web.WebPlayer用上一步创建的Stage和sys.RenderBuffer创建sys.Player,并调用其start方法启动播放器。
sys.Player在start中初始化,根据传入的选项创建主类(通常是Main),设置为播放器的根节点,并加入舞台。
sys.Player在start中初始化完毕,调用 sys.SystemTicker(通过单例sys.$ticker)的$addPlayer方法注册播放器。
sys.SystemTicker在update中处理每帧更新回调,并通过滑动跑道的方式调用所有sys.Player实例的render方法。
sys.Player在render中执行延迟回调,更新Stage的显示列表的脏区域,绘制Stage的显示列表到sys.RenderBuffer。
以上就是egret 2D引擎(Web)从启动、创建播放器到驱动播放器更新和渲染的流程框架。下一篇开始分析显示列表是如何更新和渲染DisplayObject的。