Laya Timer原理 & 源码解析
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); } }