      1 /**
      2  * Hilo
      3  * Copyright 2015 alibaba.com
      4  * Licensed under the MIT License
      5  */
      6
      7 /**
      8  * @class Ticker是一个定时器类。它可以按指定帧率重复运行，从而按计划执行代码。
      9  * @param {Number} fps 指定定时器的运行帧率。默认60。
     10  * @module hilo/util/Ticker
     11  * @requires hilo/core/Class
     12  * @requires hilo/util/browser
     13  */
     14 var Ticker = Class.create(/** @lends Ticker.prototype */{
     15     constructor: function(fps){
     16         this._targetFPS = fps || 60;
     17         this._interval = 1000 / this._targetFPS;
     18         this._tickers = [];
     19     },
     20
     21     _paused: false,
     22     _targetFPS: 0,
     23     _interval: 0,
     24     _intervalId: null,
     25     _tickers: null,
     26     _lastTime: 0,
     27     _tickCount: 0,
     28     _tickTime: 0,
     29     _measuredFPS: 0,
     30
     31     /**
     32      * 启动定时器。
     33      * @param {Boolean} userRAF 是否使用requestAnimationFrame，默认为true。
     34      */
     35     start: function(useRAF){
     36         if(useRAF === undefined){
     37             useRAF = true;
     38         }
     39
     40         if(this._intervalId) return;
     41         this._lastTime = +new Date();
     42
     43         var self = this, interval = this._interval,
     44             raf = window.requestAnimationFrame ||
     45                   window[browser.jsVendor + 'RequestAnimationFrame'];
     46
     47         var runLoop;
     48         if(useRAF && raf && interval < 17){
     49             this._useRAF = true;
     50             runLoop = function(){
     51                 self._intervalId = raf(runLoop);
     52                 self._tick();
     53             };
     54         }else{
     55             runLoop = function(){
     56                 self._intervalId = setTimeout(runLoop, interval);
     57                 self._tick();
     58             };
     59         }
     60
     61         this._paused = false;
     62         runLoop();
     63     },
     64
     65     /**
     66      * 停止定时器。
     67      */
     68     stop: function(){
     69         if(this._useRAF){
     70             var cancelRAF = window.cancelAnimationFrame ||
     71                   window[browser.jsVendor + 'CancelAnimationFrame'];
     72             cancelRAF(this._intervalId);
     73         }
     74         else{
     75             clearTimeout(this._intervalId);
     76         }
     77         this._intervalId = null;
     78         this._lastTime = 0;
     79         this._paused = true;
     80     },
     81
     82     /**
     83      * 暂停定时器。
     84      */
     85     pause: function(){
     86         this._paused = true;
     87     },
     88
     89     /**
     90      * 恢复定时器。
     91      */
     92     resume: function(){
     93         this._paused = false;
     94     },
     95
     96     /**
     97      * @private
     98      */
     99     _tick: function(){
    100         if(this._paused) return;
    101         var startTime = +new Date(),
    102             deltaTime = startTime - this._lastTime,
    103             tickers = this._tickers;
    104
    105         //calculates the real fps
    106         if(++this._tickCount >= this._targetFPS){
    107             this._measuredFPS = 1000 / (this._tickTime / this._tickCount) + 0.5 >> 0;
    108             this._tickCount = 0;
    109             this._tickTime = 0;
    110         }else{
    111             this._tickTime += startTime - this._lastTime;
    112         }
    113         this._lastTime = startTime;
    114
    115         var tickersCopy = tickers.slice(0);
    116         for(var i = 0, len = tickersCopy.length; i < len; i++){
    117             tickersCopy[i].tick(deltaTime);
    118         }
    119     },
    120
    121     /**
    122      * 获得测定的运行时帧率。
    123      */
    124     getMeasuredFPS: function(){
    125         return Math.min(this._measuredFPS, this._targetFPS);
    126     },
    127
    128     /**
    129      * 添加定时器对象。定时器对象必须实现 tick 方法。
    130      * @param {Object} tickObject 要添加的定时器对象。此对象必须包含 tick 方法。
    131      */
    132     addTick: function(tickObject){
    133         if(!tickObject || typeof(tickObject.tick) != 'function'){
    134             throw new Error('Ticker: The tick object must implement the tick method.');
    135         }
    136         this._tickers.push(tickObject);
    137     },
    138
    139     /**
    140      * 删除定时器对象。
    141      * @param {Object} tickObject 要删除的定时器对象。
    142      */
    143     removeTick: function(tickObject){
    144         var tickers = this._tickers,
    145             index = tickers.indexOf(tickObject);
    146         if(index >= 0){
    147             tickers.splice(index, 1);
    148         }
    149     },
    150     /**
    151      * 下次tick时回调
    152      * @param  {Function} callback
    153      * @return {tickObj}
    154      */
    155     nextTick:function(callback){
    156         var that = this;
    157         var tickObj = {
    158             tick:function(dt){
    159                 that.removeTick(tickObj);
    160                 callback();
    161             }
    162         };
    163
    164         that.addTick(tickObj);
    165         return tickObj;
    166     },
    167     /**
    168      * 延迟指定的时间后调用回调, 类似setTimeout
    169      * @param  {Function} callback
    170      * @param  {Number}   duration 延迟的毫秒数
    171      * @return {tickObj}
    172      */
    173     timeout:function(callback, duration){
    174         var that = this;
    175         var targetTime = new Date().getTime() + duration;
    176         var tickObj = {
    177             tick:function(){
    178                 var nowTime = new Date().getTime();
    179                 var dt = nowTime - targetTime;
    180                 if(dt >= 0){
    181                     that.removeTick(tickObj);
    182                     callback();
    183                 }
    184             }
    185         };
    186         that.addTick(tickObj);
    187         return tickObj;
    188     },
    189     /**
    190      * 指定的时间周期来调用函数, 类似setInterval
    191      * @param  {Function} callback
    192      * @param  {Number}   duration 时间周期，单位毫秒
    193      * @return {tickObj}
    194      */
    195     interval:function(callback, duration){
    196         var that = this;
    197         var targetTime = new Date().getTime() + duration;
    198         var tickObj = {
    199             tick:function(){
    200                 var nowTime = new Date().getTime();
    201                 var dt = nowTime - targetTime;
    202                 if(dt >= 0){
    203                     if(dt < duration){
    204                         nowTime -= dt;
    205                     }
    206                     targetTime = nowTime + duration;
    207                     callback();
    208                 }
    209             }
    210         };
    211         that.addTick(tickObj);
    212         return tickObj;
    213     }
    214 });