Laya Timer原理 & 源碼解析
@author ixenos 2019-03-18 16:26:38
一、原理
1.將所有Handler注冊到池中
1.普通Handler在handlers數組中
2.callLatter的Handler在laters數組中
2.然后按定義的執行時刻(或執行幀)進行循環判斷執行
3.通過映射瀏覽器的requestAnimationFrame進行全局幀循環
4.Timer中再自行根據執行時刻(或執行幀)實現Laya框架的時間循環(或幀循環),即Laya引擎的時鍾。
二、源碼解析
(注意,本文對應Laya 2.0版本,Laya 2.0以上對Timer中的callLater的邏輯進行了解耦合,而在Laya 1.7中,是將callater的時鍾處理放在Timer中的)
(而解耦合后callLater本身已不依賴Timer,此時留接口在Timer中是為了兼容2.0以前的代碼而已)
1 package laya.utils { 2 3 /** 4 * <code>Timer</code> 是時鍾管理類。它是一個單例,不要手動實例化此類,應該通過 Laya.timer 訪問。 5 */ 6 public class Timer { 7 8 /**@private */ 9 private static var _pool:Array = []; 10 /**@private */ 11 public static var _mid:int = 1; 12 13 /*[DISABLE-ADD-VARIABLE-DEFAULT-VALUE]*/ 14 /** 時針縮放。*/ 15 public var scale:Number = 1; 16 /** 當前幀開始的時間。*/ 17 public var currTimer:Number = Browser.now(); 18 /** 當前的幀數。*/ 19 public var currFrame:int = 0; 20 /**@private 兩幀之間的時間間隔,單位毫秒。*/ 21 public var _delta:int = 0; 22 /**@private */ 23 public var _lastTimer:Number = Browser.now(); 24 /**@private */ 25 private var _map:Array = []; 26 /**@private */ 27 private var _handlers:Array = []; 28 /**@private */ 29 private var _temp:Array = []; 30 /**@private */ 31 private var _count:int = 0; 32 33 /** 34 * 創建 <code>Timer</code> 類的一個實例。 35 */ 36 public function Timer(autoActive:Boolean = true) { 37 autoActive && Laya.systemTimer && Laya.systemTimer.frameLoop(1, this, _update); 38 } 39 40 /**兩幀之間的時間間隔,單位毫秒。*/ 41 public function get delta():int { 42 return _delta; 43 } 44 45 /** 46 * @private 47 * 幀循環處理函數。 48 */ 49 public function _update():void { 50 if (scale <= 0) { 51 _lastTimer = Browser.now(); 52 return; 53 } 54 var frame:int = this.currFrame = this.currFrame + scale; 55 var now:Number = Browser.now(); 56 _delta = (now - _lastTimer) * scale; 57 var timer:Number = this.currTimer = this.currTimer + _delta; 58 _lastTimer = now; 59 60 //處理handler 61 var handlers:Array = this._handlers; 62 _count = 0; 63 for (var i:int = 0, n:int = handlers.length; i < n; i++) { 64 var handler:TimerHandler = handlers[i]; 65 if (handler.method !== null) { 66 var t:int = handler.userFrame ? frame : timer; 67 if (t >= handler.exeTime) { 68 if (handler.repeat) { 69 if (!handler.jumpFrame) { 70 handler.exeTime += handler.delay; 71 handler.run(false); 72 if (t > handler.exeTime) { 73 //如果執行一次后還能再執行,做跳出處理,如果想用多次執行,需要設置jumpFrame=true 74 handler.exeTime += Math.ceil((t - handler.exeTime) / handler.delay) * handler.delay; 75 } 76 } else { 77 while (t >= handler.exeTime) { 78 handler.exeTime += handler.delay; 79 handler.run(false); 80 } 81 } 82 } else { 83 handler.run(true); 84 } 85 } 86 } else { 87 _count++; 88 } 89 } 90 91 if (_count > 30 || frame % 200 === 0) _clearHandlers(); 92 } 93 94 /** @private */ 95 private function _clearHandlers():void { 96 var handlers:Array = this._handlers; 97 for (var i:int = 0, n:int = handlers.length; i < n; i++) { 98 var handler:TimerHandler = handlers[i]; 99 if (handler.method !== null) _temp.push(handler); 100 else _recoverHandler(handler); 101 } 102 this._handlers = _temp; 103 handlers.length = 0; 104 _temp = handlers; 105 } 106 107 /** @private */ 108 private function _recoverHandler(handler:TimerHandler):void { 109 if (_map[handler.key] == handler) _map[handler.key] = null; 110 handler.clear(); 111 _pool.push(handler); 112 } 113 114 /** @private */ 115 public function _create(useFrame:Boolean, repeat:Boolean, delay:int, caller:*, method:Function, args:Array, coverBefore:Boolean):TimerHandler { 116 //如果延遲為0,則立即執行 117 if (!delay) { 118 method.apply(caller, args); 119 return null; 120 } 121 122 //先覆蓋相同函數的計時 123 if (coverBefore) { 124 var handler:TimerHandler = _getHandler(caller, method); 125 if (handler) { 126 handler.repeat = repeat; 127 handler.userFrame = useFrame; 128 handler.delay = delay; 129 handler.caller = caller; 130 handler.method = method; 131 handler.args = args; 132 handler.exeTime = delay + (useFrame ? this.currFrame : this.currTimer + Browser.now() - _lastTimer); 133 return handler; 134 } 135 } 136 137 //找到一個空閑的timerHandler 138 handler = _pool.length > 0 ? _pool.pop() : new TimerHandler(); 139 handler.repeat = repeat; 140 handler.userFrame = useFrame; 141 handler.delay = delay; 142 handler.caller = caller; 143 handler.method = method; 144 handler.args = args; 145 handler.exeTime = delay + (useFrame ? this.currFrame : this.currTimer + Browser.now() - _lastTimer); 146 147 //索引handler 148 _indexHandler(handler); 149 150 //插入數組 151 _handlers.push(handler); 152 153 return handler; 154 } 155 156 /** @private */ 157 private function _indexHandler(handler:TimerHandler):void { 158 var caller:* = handler.caller; 159 var method:* = handler.method; 160 var cid:int = caller ? caller.$_GID || (caller.$_GID = Utils.getGID()) : 0; 161 var mid:int = method.$_TID || (method.$_TID = (_mid++) * 100000); 162 handler.key = cid + mid; 163 _map[handler.key] = handler; 164 } 165 166 /** 167 * 定時執行一次。 168 * @param delay 延遲時間(單位為毫秒)。 169 * @param caller 執行域(this)。 170 * @param method 定時器回調函數。 171 * @param args 回調參數。 172 * @param coverBefore 是否覆蓋之前的延遲執行,默認為 true 。 173 */ 174 public function once(delay:int, caller:*, method:Function, args:Array = null, coverBefore:Boolean = true):void { 175 _create(false, false, delay, caller, method, args, coverBefore); 176 } 177 178 /** 179 * 定時重復執行。 180 * @param delay 間隔時間(單位毫秒)。 181 * @param caller 執行域(this)。 182 * @param method 定時器回調函數。 183 * @param args 回調參數。 184 * @param coverBefore 是否覆蓋之前的延遲執行,默認為 true 。 185 * @param jumpFrame 時鍾是否跳幀。基於時間的循環回調,單位時間間隔內,如能執行多次回調,出於性能考慮,引擎默認只執行一次,設置jumpFrame=true后,則回調會連續執行多次 186 */ 187 public function loop(delay:int, caller:*, method:Function, args:Array = null, coverBefore:Boolean = true, jumpFrame:Boolean = false):void { 188 var handler:TimerHandler = _create(false, true, delay, caller, method, args, coverBefore); 189 if (handler) handler.jumpFrame = jumpFrame; 190 } 191 192 /** 193 * 定時執行一次(基於幀率)。 194 * @param delay 延遲幾幀(單位為幀)。 195 * @param caller 執行域(this)。 196 * @param method 定時器回調函數。 197 * @param args 回調參數。 198 * @param coverBefore 是否覆蓋之前的延遲執行,默認為 true 。 199 */ 200 public function frameOnce(delay:int, caller:*, method:Function, args:Array = null, coverBefore:Boolean = true):void { 201 _create(true, false, delay, caller, method, args, coverBefore); 202 } 203 204 /** 205 * 定時重復執行(基於幀率)。 206 * @param delay 間隔幾幀(單位為幀)。 207 * @param caller 執行域(this)。 208 * @param method 定時器回調函數。 209 * @param args 回調參數。 210 * @param coverBefore 是否覆蓋之前的延遲執行,默認為 true 。 211 */ 212 public function frameLoop(delay:int, caller:*, method:Function, args:Array = null, coverBefore:Boolean = true):void { 213 _create(true, true, delay, caller, method, args, coverBefore); 214 } 215 216 /** 返回統計信息。*/ 217 public function toString():String { 218 return " handlers:" + _handlers.length + " pool:" + _pool.length; 219 } 220 221 /** 222 * 清理定時器。 223 * @param caller 執行域(this)。 224 * @param method 定時器回調函數。 225 */ 226 public function clear(caller:*, method:Function):void { 227 var handler:TimerHandler = _getHandler(caller, method); 228 if (handler) { 229 _map[handler.key] = null; 230 handler.key = 0; 231 handler.clear(); 232 } 233 } 234 235 /** 236 * 清理對象身上的所有定時器。 237 * @param caller 執行域(this)。 238 */ 239 public function clearAll(caller:*):void { 240 if (!caller) return; 241 for (var i:int = 0, n:int = _handlers.length; i < n; i++) { 242 var handler:TimerHandler = _handlers[i]; 243 if (handler.caller === caller) { 244 _map[handler.key] = null; 245 handler.key = 0; 246 handler.clear(); 247 } 248 } 249 } 250 251 /** @private */ 252 private function _getHandler(caller:*, method:*):TimerHandler { 253 var cid:int = caller ? caller.$_GID || (caller.$_GID = Utils.getGID()) : 0; 254 var mid:int = method.$_TID || (method.$_TID = (_mid++) * 100000); 255 return _map[cid + mid]; 256 } 257 258 /** 259 * 延遲執行。 260 * @param caller 執行域(this)。 261 * @param method 定時器回調函數。 262 * @param args 回調參數。 263 */ 264 public function callLater(caller:*, method:Function, args:Array = null):void { 265 CallLater.I.callLater(caller, method, args); 266 } 267 268 /** 269 * 立即執行 callLater 。 270 * @param caller 執行域(this)。 271 * @param method 定時器回調函數。 272 */ 273 public function runCallLater(caller:*, method:Function):void { 274 CallLater.I.runCallLater(caller, method); 275 } 276 277 /** 278 * 立即提前執行定時器,執行之后從隊列中刪除 279 * @param caller 執行域(this)。 280 * @param method 定時器回調函數。 281 */ 282 public function runTimer(caller:*, method:Function):void { 283 var handler:TimerHandler = _getHandler(caller, method); 284 if (handler && handler.method != null) { 285 _map[handler.key] = null; 286 handler.run(true); 287 } 288 } 289 290 /** 291 * 暫停時鍾 292 */ 293 public function pause():void { 294 this.scale = 0; 295 } 296 297 /** 298 * 恢復時鍾 299 */ 300 public function resume():void { 301 this.scale = 1; 302 } 303 } 304 } 305 306 /** @private */ 307 class TimerHandler { 308 public var key:int; 309 public var repeat:Boolean; 310 public var delay:int; 311 public var userFrame:Boolean; 312 public var exeTime:int; 313 public var caller:* 314 public var method:Function; 315 public var args:Array; 316 public var jumpFrame:Boolean; 317 318 public function clear():void { 319 caller = null; 320 method = null; 321 args = null; 322 } 323 324 public function run(withClear:Boolean):void { 325 var caller:* = this.caller; 326 if (caller && caller.destroyed) return clear(); 327 var method:Function = this.method; 328 var args:Array = this.args; 329 withClear && clear(); 330 if (method == null) return; 331 args ? method.apply(caller, args) : method.call(caller); 332 } 333 }
1.創建實例的時候:
/** * 創建 <code>Timer</code> 類的一個實例。 */ public function Timer(autoActive:Boolean = true) { autoActive && Laya.systemTimer && Laya.systemTimer.frameLoop(1, this, _update); }
將_update注冊為幀循環函數,在frameLoop中會將其包裹為一個TimerHandler
2.而_update本身是處理TimerHandler的函數:
/** * @private * 幀循環處理函數。 */ public function _update():void { if (scale <= 0) { _lastTimer = Browser.now(); return; } var frame:int = this.currFrame = this.currFrame + scale; var now:Number = Browser.now(); _delta = (now - _lastTimer) * scale; var timer:Number = this.currTimer = this.currTimer + _delta; _lastTimer = now; //處理handler var handlers:Array = this._handlers; _count = 0; for (var i:int = 0, n:int = handlers.length; i < n; i++) { var handler:TimerHandler = handlers[i]; if (handler.method !== null) { var t:int = handler.userFrame ? frame : timer; if (t >= handler.exeTime) { if (handler.repeat) { if (!handler.jumpFrame) { handler.exeTime += handler.delay; handler.run(false); if (t > handler.exeTime) { //如果執行一次后還能再執行,做跳出處理,如果想用多次執行,需要設置jumpFrame=true handler.exeTime += Math.ceil((t - handler.exeTime) / handler.delay) * handler.delay; } } else { while (t >= handler.exeTime) { handler.exeTime += handler.delay; handler.run(false); } } } else { handler.run(true); } } } else { _count++; } } if (_count > 30 || frame % 200 === 0) _clearHandlers(); }
在一次執行中,會遍歷所有handlers,判斷執行條件(時刻、幀等)進行執行
3._update由Stage.render調用,Stage.render通過Stage._loop調用,Stage._loop由是Render中的enter_frame處理器調用,
.........................................................................................
Laya.Stage
.........................................................................................
/**@private */
public
function _loop():
Boolean {
render(Render._context, 0, 0);
return true;
}
......
/**@inheritDoc */ override public function render(context:Context, x:Number, y:Number):void { if (_frameRate === FRAME_SLEEP) { var now:Number = Browser.now(); if (now - _frameStartTime >= 1000) _frameStartTime = now; else return; } _renderCount++; if (!this._visible) { if (_renderCount % 5 === 0) { CallLater.I._update(); Stat.loopCount++; Laya.systemTimer._update(); Laya.startTimer._update(); Laya.physicsTimer._update(); Laya.updateTimer._update(); Laya.lateTimer._update(); Laya.timer._update(); } return; } ....... ......................................................................................... Laya.Stage .........................................................................................
.........................................................................................
Laya.Render
.........................................................................................
public function Render(width:Number, height:Number) { //創建主畫布。改到Browser中了,因為為了runtime,主畫布必須是第一個 _mainCanvas.source.id = "layaCanvas"; _mainCanvas.source.width = width; _mainCanvas.source.height = height; Browser.container.appendChild(_mainCanvas.source); RunDriver.initRender(_mainCanvas, width, height); Browser.window.requestAnimationFrame(loop); function loop(stamp:Number):void { Laya.stage._loop(); Browser.window.requestAnimationFrame(loop); } Laya.stage.on("visibilitychange", this, _onVisibilitychange); } /**@private */ private var _timeId:int = 0; /**@private */ private function _onVisibilitychange():void { if (!Laya.stage.isVisibility) { _timeId = Browser.window.setInterval(this._enterFrame, 1000); } else if (_timeId != 0) { Browser.window.clearInterval(_timeId); } }
......................................................................................... Laya.Render .........................................................................................
4.順便看看callLater唄:
package laya.utils { /** * @private */ public class CallLater { public static var I:CallLater =/*[STATIC SAFE]*/ new CallLater(); /**@private */ private var _pool:Array = []; /**@private */ private var _map:Array = []; /**@private */ private var _laters:Array = []; /** * @private * 幀循環處理函數。 */ public function _update():void { var laters:Array = this._laters; var len:int = laters.length; if (len > 0) { for (var i:int = 0, n:int = len - 1; i <= n; i++) { var handler:LaterHandler = laters[i]; _map[handler.key] = null; if (handler.method !== null) { handler.run(); handler.clear(); } _pool.push(handler); i === n && (n = laters.length - 1); } laters.length = 0; } } /** @private */ private function _getHandler(caller:*, method:*):LaterHandler { var cid:int = caller ? caller.$_GID || (caller.$_GID = Utils.getGID()) : 0; var mid:int = method.$_TID || (method.$_TID = (Timer._mid++) * 100000); return _map[cid + mid]; } /** * 延遲執行。 * @param caller 執行域(this)。 * @param method 定時器回調函數。 * @param args 回調參數。 */ public function callLater(caller:*, method:Function, args:Array = null):void { if (_getHandler(caller, method) == null) { if (_pool.length) var handler:LaterHandler = _pool.pop(); else handler = new LaterHandler(); //設置屬性 handler.caller = caller; handler.method = method; handler.args = args; //索引handler var cid:int = caller ? caller.$_GID : 0; var mid:int = method["$_TID"]; handler.key = cid + mid; _map[handler.key] = handler //插入隊列 _laters.push(handler); } } /** * 立即執行 callLater 。 * @param caller 執行域(this)。 * @param method 定時器回調函數。 */ public function runCallLater(caller:*, method:Function):void { var handler:LaterHandler = _getHandler(caller, method); if (handler && handler.method != null) { _map[handler.key] = null; handler.run(); handler.clear(); } } } } /** @private */ class LaterHandler { public var key:int; public var caller:* public var method:Function; public var args:Array; public function clear():void { caller = null; method = null; args = null; } public function run():void { var caller:* = this.caller; if (caller && caller.destroyed) return clear(); var method:Function = this.method; var args:Array = this.args; if (method == null) return; args ? method.apply(caller, args) : method.call(caller); } }
