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. nodejs中的异步回调机制

    1.再次clear Timer定时器的作用 setTimeOut绝非是传统意义上的“sleep”功能,它做不到让主线程“熄火”指定时间,它是用来指定:某个回调在固定时间后插入执行栈!(实际执行时间略长 ...

  2. Objective-C Numbers

    In Objective-C programming language, in order to save the basic data types like int, float, bool in ...

  3. spark中产生shuffle的算子

    Spark中产生shuffle的算子 作用 算子名 能否替换,由谁替换 去重 distinct() 不能 聚合 reduceByKey() groupByKey groupBy() groupByKe ...

  4. IOSAutolayout

    21:55:33前言 1 MagicNumber -> autoresizingMask -> autolayout 以上是纯手写代码所经历的关于页面布局的三个时期 在iphone1-ip ...

  5. 图片,二进制,oracle数据库

    图片在oracle数据库中一般以二进制存在,存储类型是blob,然而clob类型一般存储的是大于4000的字符,不能用来存储图像这样的二进制内容,下面展示一下实现图像,二进制,oracle 数据库的应 ...

  6. python基础一 day13 迭代器

    # 双下方法# print([1].__add__([2]))# print([1]+[2]) # 迭代器# l = [1,2,3]# 索引# 循环 for# for i in l:# i## for ...

  7. int型除以int型

    int型除以int型得到的还是int型 就算你是这样的:float a = 5/3,虽然你定义的a是float型,但a得到的结果依旧是1.0000而不是1.66666 5/3先得到1,然后再转换成1. ...

  8. druid 配置WebStatFilter 网络统计以及监控

    WebStatFilter用于采集web-jdbc关联监控的数据. web.xml配置 <filter> <filter-name>DruidWebStatFilter< ...

  9. 修改visual studio setup 安装顺序(解决新版安装包无法自动移除老版本程序的问题)

    背景 visual studio setup 支持自动删除之前版本的安装,需要设置RemovePreviousVersions = true, DetectNewerInstalledVersion ...

  10. 拼凑json的实例

    一,拼凑  json 串 并传到后台 function writeToJson(num){ var i = 1; var jsonData = "["; for(i=1;i< ...