      1 /**
      2  * Hilo
      3  * Copyright 2015 alibaba.com
      4  * Licensed under the MIT License
      5  */
      6
      7 /**
      8  * @class Ticker is a Timer. It can run the code at specified framerate.
      9  * @param {Number} fps The fps of ticker.Default is 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      * Start the ticker.
     33      * @param {Boolean} userRAF Whether or not use requestAnimationFrame, default is 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      * Stop the ticker.
     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      * Pause the ticker.
     84      */
     85     pause: function(){
     86         this._paused = true;
     87     },
     88
     89     /**
     90      * Resume the ticker.
     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      * Get the fps.
    123      */
    124     getMeasuredFPS: function(){
    125         return Math.min(this._measuredFPS, this._targetFPS);
    126     },
    127
    128     /**
    129      * Add tickObject. The tickObject must implement the tick method.
    130      * @param {Object} tickObject The tickObject to add.It must implement the tick method.
    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      * Remove the tickObject
    141      * @param {Object} tickObject The tickObject to remove.
    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 });