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以前的代码而已)

 package laya.utils {

     /**
* <code>Timer</code> 是时钟管理类。它是一个单例,不要手动实例化此类,应该通过 Laya.timer 访问。
*/
public class Timer { /**@private */
private static var _pool:Array = [];
/**@private */
public static var _mid:int = 1; /*[DISABLE-ADD-VARIABLE-DEFAULT-VALUE]*/
/** 时针缩放。*/
public var scale:Number = 1;
/** 当前帧开始的时间。*/
public var currTimer:Number = Browser.now();
/** 当前的帧数。*/
public var currFrame:int = 0;
/**@private 两帧之间的时间间隔,单位毫秒。*/
public var _delta:int = 0;
/**@private */
public var _lastTimer:Number = Browser.now();
/**@private */
private var _map:Array = [];
/**@private */
private var _handlers:Array = [];
/**@private */
private var _temp:Array = [];
/**@private */
private var _count:int = 0; /**
* 创建 <code>Timer</code> 类的一个实例。
*/
public function Timer(autoActive:Boolean = true) {
autoActive && Laya.systemTimer && Laya.systemTimer.frameLoop(1, this, _update);
} /**两帧之间的时间间隔,单位毫秒。*/
public function get delta():int {
return _delta;
} /**
* @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();
} /** @private */
private function _clearHandlers():void {
var handlers:Array = this._handlers;
for (var i:int = 0, n:int = handlers.length; i < n; i++) {
var handler:TimerHandler = handlers[i];
if (handler.method !== null) _temp.push(handler);
else _recoverHandler(handler);
}
this._handlers = _temp;
handlers.length = 0;
_temp = handlers;
} /** @private */
private function _recoverHandler(handler:TimerHandler):void {
if (_map[handler.key] == handler) _map[handler.key] = null;
handler.clear();
_pool.push(handler);
} /** @private */
public function _create(useFrame:Boolean, repeat:Boolean, delay:int, caller:*, method:Function, args:Array, coverBefore:Boolean):TimerHandler {
//如果延迟为0,则立即执行
if (!delay) {
method.apply(caller, args);
return null;
} //先覆盖相同函数的计时
if (coverBefore) {
var handler:TimerHandler = _getHandler(caller, method);
if (handler) {
handler.repeat = repeat;
handler.userFrame = useFrame;
handler.delay = delay;
handler.caller = caller;
handler.method = method;
handler.args = args;
handler.exeTime = delay + (useFrame ? this.currFrame : this.currTimer + Browser.now() - _lastTimer);
return handler;
}
} //找到一个空闲的timerHandler
handler = _pool.length > 0 ? _pool.pop() : new TimerHandler();
handler.repeat = repeat;
handler.userFrame = useFrame;
handler.delay = delay;
handler.caller = caller;
handler.method = method;
handler.args = args;
handler.exeTime = delay + (useFrame ? this.currFrame : this.currTimer + Browser.now() - _lastTimer); //索引handler
_indexHandler(handler); //插入数组
_handlers.push(handler); return handler;
} /** @private */
private function _indexHandler(handler:TimerHandler):void {
var caller:* = handler.caller;
var method:* = handler.method;
var cid:int = caller ? caller.$_GID || (caller.$_GID = Utils.getGID()) : 0;
var mid:int = method.$_TID || (method.$_TID = (_mid++) * 100000);
handler.key = cid + mid;
_map[handler.key] = handler;
} /**
* 定时执行一次。
* @param delay 延迟时间(单位为毫秒)。
* @param caller 执行域(this)。
* @param method 定时器回调函数。
* @param args 回调参数。
* @param coverBefore 是否覆盖之前的延迟执行,默认为 true 。
*/
public function once(delay:int, caller:*, method:Function, args:Array = null, coverBefore:Boolean = true):void {
_create(false, false, delay, caller, method, args, coverBefore);
} /**
* 定时重复执行。
* @param delay 间隔时间(单位毫秒)。
* @param caller 执行域(this)。
* @param method 定时器回调函数。
* @param args 回调参数。
* @param coverBefore 是否覆盖之前的延迟执行,默认为 true 。
* @param jumpFrame 时钟是否跳帧。基于时间的循环回调,单位时间间隔内,如能执行多次回调,出于性能考虑,引擎默认只执行一次,设置jumpFrame=true后,则回调会连续执行多次
*/
public function loop(delay:int, caller:*, method:Function, args:Array = null, coverBefore:Boolean = true, jumpFrame:Boolean = false):void {
var handler:TimerHandler = _create(false, true, delay, caller, method, args, coverBefore);
if (handler) handler.jumpFrame = jumpFrame;
} /**
* 定时执行一次(基于帧率)。
* @param delay 延迟几帧(单位为帧)。
* @param caller 执行域(this)。
* @param method 定时器回调函数。
* @param args 回调参数。
* @param coverBefore 是否覆盖之前的延迟执行,默认为 true 。
*/
public function frameOnce(delay:int, caller:*, method:Function, args:Array = null, coverBefore:Boolean = true):void {
_create(true, false, delay, caller, method, args, coverBefore);
} /**
* 定时重复执行(基于帧率)。
* @param delay 间隔几帧(单位为帧)。
* @param caller 执行域(this)。
* @param method 定时器回调函数。
* @param args 回调参数。
* @param coverBefore 是否覆盖之前的延迟执行,默认为 true 。
*/
public function frameLoop(delay:int, caller:*, method:Function, args:Array = null, coverBefore:Boolean = true):void {
_create(true, true, delay, caller, method, args, coverBefore);
} /** 返回统计信息。*/
public function toString():String {
return " handlers:" + _handlers.length + " pool:" + _pool.length;
} /**
* 清理定时器。
* @param caller 执行域(this)。
* @param method 定时器回调函数。
*/
public function clear(caller:*, method:Function):void {
var handler:TimerHandler = _getHandler(caller, method);
if (handler) {
_map[handler.key] = null;
handler.key = 0;
handler.clear();
}
} /**
* 清理对象身上的所有定时器。
* @param caller 执行域(this)。
*/
public function clearAll(caller:*):void {
if (!caller) return;
for (var i:int = 0, n:int = _handlers.length; i < n; i++) {
var handler:TimerHandler = _handlers[i];
if (handler.caller === caller) {
_map[handler.key] = null;
handler.key = 0;
handler.clear();
}
}
} /** @private */
private function _getHandler(caller:*, method:*):TimerHandler {
var cid:int = caller ? caller.$_GID || (caller.$_GID = Utils.getGID()) : 0;
var mid:int = method.$_TID || (method.$_TID = (_mid++) * 100000);
return _map[cid + mid];
} /**
* 延迟执行。
* @param caller 执行域(this)。
* @param method 定时器回调函数。
* @param args 回调参数。
*/
public function callLater(caller:*, method:Function, args:Array = null):void {
CallLater.I.callLater(caller, method, args);
} /**
* 立即执行 callLater 。
* @param caller 执行域(this)。
* @param method 定时器回调函数。
*/
public function runCallLater(caller:*, method:Function):void {
CallLater.I.runCallLater(caller, method);
} /**
* 立即提前执行定时器,执行之后从队列中删除
* @param caller 执行域(this)。
* @param method 定时器回调函数。
*/
public function runTimer(caller:*, method:Function):void {
var handler:TimerHandler = _getHandler(caller, method);
if (handler && handler.method != null) {
_map[handler.key] = null;
handler.run(true);
}
} /**
* 暂停时钟
*/
public function pause():void {
this.scale = 0;
} /**
* 恢复时钟
*/
public function resume():void {
this.scale = 1;
}
}
} /** @private */
class TimerHandler {
public var key:int;
public var repeat:Boolean;
public var delay:int;
public var userFrame:Boolean;
public var exeTime:int;
public var caller:*
public var method:Function;
public var args:Array;
public var jumpFrame:Boolean; public function clear():void {
caller = null;
method = null;
args = null;
} public function run(withClear:Boolean):void {
var caller:* = this.caller;
if (caller && caller.destroyed) return clear();
var method:Function = this.method;
var args:Array = this.args;
withClear && clear();
if (method == null) return;
args ? method.apply(caller, args) : method.call(caller);
}
}

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);
}
}

Laya Timer原理 & 源码解析的更多相关文章

  1. [源码解析] 并行分布式框架 Celery 之 Lamport 逻辑时钟 & Mingle

    [源码解析] 并行分布式框架 Celery 之 Lamport 逻辑时钟 & Mingle 目录 [源码解析] 并行分布式框架 Celery 之 Lamport 逻辑时钟 & Ming ...

  2. 【nodejs原理&源码杂记(8)】Timer模块与基于二叉堆的定时器

    [摘要] timers模块部分源码和定时器原理 示例代码托管在:http://www.github.com/dashnowords/blogs 一.概述 Timer模块相关的逻辑较为复杂,不仅包含Ja ...

  3. 【nodejs原理&源码杂记(8)】Timer模块与基于二叉堆的定时器

    目录 一.概述 二. 数据结构 2.1 链表 2.2 二叉堆 三. 从setTimeout理解Timer模块源码 3.1 timers.js中的定义 3.2 Timeout类定义 3.3 active ...

  4. Android 热修复Nuwa的原理及Gradle插件源码解析

    现在,热修复的具体实现方案开源的也有很多,原理也大同小异,本篇文章以Nuwa为例,深入剖析.  Nuwa的github地址 https://github.com/jasonross/Nuwa 以及用于 ...

  5. 顺序线性表 ---- ArrayList 源码解析及实现原理分析

    原创播客,如需转载请注明出处.原文地址:http://www.cnblogs.com/crawl/p/7738888.html ------------------------------------ ...

  6. dubbo源码解析五 --- 集群容错架构设计与原理分析

    欢迎来我的 Star Followers 后期后继续更新Dubbo别的文章 Dubbo 源码分析系列之一环境搭建 博客园 Dubbo 入门之二 --- 项目结构解析 博客园 Dubbo 源码分析系列之 ...

  7. 机器学习实战(Machine Learning in Action)学习笔记————03.决策树原理、源码解析及测试

    机器学习实战(Machine Learning in Action)学习笔记————03.决策树原理.源码解析及测试 关键字:决策树.python.源码解析.测试作者:米仓山下时间:2018-10-2 ...

  8. Spring-Session实现Session共享实现原理以及源码解析

    知其然,还要知其所以然 ! 本篇介绍Spring-Session的整个实现的原理.以及对核心的源码进行简单的介绍! 实现原理介绍 实现原理这里简单说明描述: 就是当Web服务器接收到http请求后,当 ...

  9. 神经网络中 BP 算法的原理与 Python 实现源码解析

    最近这段时间系统性的学习了 BP 算法后写下了这篇学习笔记,因为能力有限,若有明显错误,还请指正. 什么是梯度下降和链式求导法则 假设我们有一个函数 J(w),如下图所示. 梯度下降示意图 现在,我们 ...

随机推荐

  1. Spring Boot运行原理

    概述 本文主要写了下Spring Boot运行原理,还有一个小例子. Spring4.x提供了基于条件来配置Bean的能力,而Spring Boot的实现也是基于这一原理的. Spring Boot关 ...

  2. Spring Cloud Gateway VS Zuul 比较,怎么选择?

    Spring Cloud Gateway 是 Spring Cloud Finchley 版推出来的新组件,用来代替服务网关:Zuul. 那 Spring Cloud Gateway 和 Zuul 都 ...

  3. silverlight GPS监控,视频监控界面

    周末闲着自己做了个玩玩

  4. PMP项目管理学习笔记引言(1)——为啥要取得认证?

    (一)为啥要取得认证? 如果你参与过很多项目,就会发现,你总是在周而复始地面对同样的一些问题.一些常见的问题目前已经有了通用解决方案.经过多年的实战,项目经理已们已经掌握了很多应验教训,而通过PMP( ...

  5. 洛谷 P2872 [USACO07DEC]道路建设Building Roads

    题目描述 Farmer John had just acquired several new farms! He wants to connect the farms with roads so th ...

  6. 运行spark自带的例子出错及解决

    以往都是用java运行spark的没问题,今天用scala在eclipse上运行spark的代码倒是出现了错误 ,记录 首先是当我把相关的包导入好后,Run,报错: Exception in thre ...

  7. Hibernate 多表查询 - Criteria添加子字段查询条件 - 出错问题解决

    Criteria 查询条件如果是子对象中的非主键字段会报 could not resolve property private Criteria getCriteria(Favorite favori ...

  8. PAT (Basic Level) Practise (中文)-1027. 打印沙漏(20)

    PAT (Basic Level) Practise (中文)-1027. 打印沙漏(20)  http://www.patest.cn/contests/pat-b-practise/1027 本题 ...

  9. 官方webupload上传多个文件或者图片的方法

    文件上传 页面代码: <!--引入CSS--> <link rel="stylesheet" type="text/css" href=&qu ...

  10. Spring框架针对dao层的jdbcTemplate操作crud之delete删除数据库操作 Spring相关Jar包下载

    首先,找齐Spring框架中IoC功能.aop功能.JdbcTemplate功能所需的jar包,当前13个Jar包 1.Spring压缩包中的四个核心JAR包,实现IoC控制反转的根据xml配置文件或 ...