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以前的代码而已)
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
.........................................................................................
...... /**@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原理 & 源码解析的更多相关文章
- [源码解析] 并行分布式框架 Celery 之 Lamport 逻辑时钟 & Mingle
[源码解析] 并行分布式框架 Celery 之 Lamport 逻辑时钟 & Mingle 目录 [源码解析] 并行分布式框架 Celery 之 Lamport 逻辑时钟 & Ming ...
- 【nodejs原理&源码杂记(8)】Timer模块与基于二叉堆的定时器
[摘要] timers模块部分源码和定时器原理 示例代码托管在:http://www.github.com/dashnowords/blogs 一.概述 Timer模块相关的逻辑较为复杂,不仅包含Ja ...
- 【nodejs原理&源码杂记(8)】Timer模块与基于二叉堆的定时器
目录 一.概述 二. 数据结构 2.1 链表 2.2 二叉堆 三. 从setTimeout理解Timer模块源码 3.1 timers.js中的定义 3.2 Timeout类定义 3.3 active ...
- Android 热修复Nuwa的原理及Gradle插件源码解析
现在,热修复的具体实现方案开源的也有很多,原理也大同小异,本篇文章以Nuwa为例,深入剖析. Nuwa的github地址 https://github.com/jasonross/Nuwa 以及用于 ...
- 顺序线性表 ---- ArrayList 源码解析及实现原理分析
原创播客,如需转载请注明出处.原文地址:http://www.cnblogs.com/crawl/p/7738888.html ------------------------------------ ...
- dubbo源码解析五 --- 集群容错架构设计与原理分析
欢迎来我的 Star Followers 后期后继续更新Dubbo别的文章 Dubbo 源码分析系列之一环境搭建 博客园 Dubbo 入门之二 --- 项目结构解析 博客园 Dubbo 源码分析系列之 ...
- 机器学习实战(Machine Learning in Action)学习笔记————03.决策树原理、源码解析及测试
机器学习实战(Machine Learning in Action)学习笔记————03.决策树原理.源码解析及测试 关键字:决策树.python.源码解析.测试作者:米仓山下时间:2018-10-2 ...
- Spring-Session实现Session共享实现原理以及源码解析
知其然,还要知其所以然 ! 本篇介绍Spring-Session的整个实现的原理.以及对核心的源码进行简单的介绍! 实现原理介绍 实现原理这里简单说明描述: 就是当Web服务器接收到http请求后,当 ...
- 神经网络中 BP 算法的原理与 Python 实现源码解析
最近这段时间系统性的学习了 BP 算法后写下了这篇学习笔记,因为能力有限,若有明显错误,还请指正. 什么是梯度下降和链式求导法则 假设我们有一个函数 J(w),如下图所示. 梯度下降示意图 现在,我们 ...
随机推荐
- 搭建高可用mongodb集群(一)——mongodb配置主从模式
转载自:LANCEYAN.COM 在大数据的时代,传统的关系型数据库要能更高的服务必须要解决高并发读写.海量数据高效存储.高可扩展性和高可用性这些难题.不过就是因为这些问题Nosql诞生了. NOSQ ...
- plpgsql: 动态插入数据 1
--目标:1.建立一个函数实现 输入一个表名(tableName)tableName,一个JSON串{feildName1:feildVale1,feildName2:feildVale2} -- 然 ...
- Opencv内存jpg图像解码和编码[转]
CV_IMPL CvMat* cvEncodeImage( const char* ext,const CvArr* arr, const int* _params ) CV_IMPL IplImag ...
- Azure 项目构建 – 部署 Jenkins 服务器以实现持续集成(CI)
通过完整流程详细介绍了如何通过 Azure 虚拟机.虚拟网络等服务在 Azure 平台上快速搭建 Jenkins 服务器. 此系列的全部课程 https://school.azure.cn/curri ...
- codevs 爱改名的小融
都是三道水题 但我很难理解的是 string 能过 char 就WA 2967 题目描述 Description Wikioi上有个人叫小融,他喜欢改名. 他的名字都是英文,只要按顺序出现R,K,Y三 ...
- 如何找到SAP Cloud for Customer标准培训和认证方面的信息
有一些朋友询问我如何在SAP官网上找到和SAP Cloud for Customer相关的标准培训信息,我这里把步骤写出来: 登录SAP官网https://training.sap.com 输入和Cl ...
- for循环输出i为同一值的问题
使用闭包将变量i的值保护起来. //sava1:加一层闭包,i以函数参数形式传递给内层函数 for( var i=0; i<ps.length; i++ ) { (function(arg){ ...
- 解决Genymotion2.8.1在拖动安装APK文件出现ARMtranslate错误
转载文章:http://blog.csdn.net/solo_talk/article/details/68488129 在新版本的genymotion中,我们拖动安装APK文件的时候会出现一个问题, ...
- FTP文传协议的应用
我开发的项目中一直用到都是AFNetworking上传图片的方法,最近老大说要用FTP上传,网上的资料很少,毕竟这种上传方式现在用的不多了,于是花了一天时间学习了FTP文件传输协议.下面是我的个人理解 ...
- odoo10 fields.Selection 根据权限显示不同的selection内容
摘要:一般作为下拉选项,selection的选项内容是固定,针对一些特殊要求,根据权限组显示不同的selection内容的,可以参考odoo源码的. 前提:基于 odoo10.0 的源码 参考源码1: ...