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