1 goog.provide('lime.scheduleManager'); 2 3 goog.require('goog.array'); 4 goog.require('goog.userAgent'); 5 goog.require('lime'); 6 7 8 /** 9 * Unified timer provider class 10 * Don't create instances of this class. Used the shared instance. 11 * @this {lime.scheduleManager} 12 * @constructor 13 */ 14 lime.scheduleManager = new (function() { 15 16 /** 17 * Array of registered functions 18 * @type {Array.<lime.scheduleManager.Task>} 19 * @private 20 */ 21 this.taskStack_ = []; 22 23 /** 24 * ScheduleManager is active 25 * @type {boolean} 26 * @private 27 */ 28 this.active_ = false; 29 30 /** 31 * Internal setInterval id 32 * @type {number} 33 * @private 34 */ 35 this.intervalID_ = 0; 36 37 /** 38 * Maximum update rate in ms. 39 * @type {number} 40 * @private 41 */ 42 this.displayRate_ = 1000 / 30; 43 44 /** 45 * Timer last fire timestamp 46 * @type {number} 47 * @private 48 */ 49 this.lastRunTime_ = 0; 50 51 })(); 52 53 /** 54 * Scheduled task 55 * @param {number} maxdelta Timer wait value after iteration. 56 * @param {number=} opt_limit Number of calls. 57 * @constructor 58 */ 59 lime.scheduleManager.Task = function(maxdelta, opt_limit) { 60 this.delta = this.maxdelta = maxdelta; 61 this.limit = goog.isDef(opt_limit) ? opt_limit : -1; 62 this.functionStack_ = []; 63 }; 64 65 /** 66 * Handle iteration 67 * @param {number} dt Delta time since last iteration. 68 * @private 69 */ 70 lime.scheduleManager.Task.prototype.step_ = function(dt) { 71 if (!this.functionStack_.length) return; 72 if (this.delta > dt) { 73 this.delta -= dt; 74 } 75 else { 76 var delta = this.maxdelta + dt - this.delta; 77 this.delta = this.maxdelta - (dt - this.delta); 78 if (this.delta < 0) this.delta = 0; 79 var f; 80 var i = this.functionStack_.length; 81 while (--i >= 0) { 82 f = this.functionStack_[i]; 83 if (f && f[0] && goog.isFunction(f[1])) 84 (f[1]).call(f[2], delta); 85 } 86 if (this.limit != -1) { 87 this.limit--; 88 if (this.limit == 0) { 89 lime.scheduleManager.unschedule(f[1], f[2]); 90 } 91 } 92 } 93 }; 94 95 lime.scheduleManager.taskStack_.push(new lime.scheduleManager.Task(0)); 96 97 /** 98 * Whether to use requestAnimationFrame instead of timer events 99 * Exposed here so it could be disabled if needed. 100 * @type {boolean} 101 */ 102 lime.scheduleManager.USE_ANIMATION_FRAME = goog.global['mozRequestAnimationFrame'] || 103 goog.global['webkitRequestAnimationFrame']; 104 105 /** 106 * Returns maximum fire rate in ms. If you need FPS then use 1000/x 107 * @this {lime.scheduleManager} 108 * @return {number} Display rate. 109 */ 110 lime.scheduleManager.getDisplayRate = function() { 111 //todo: bad name 112 return this.displayRate_; 113 }; 114 115 /** 116 * Sets maximum fire rate for the scheduler in ms. 117 * If you have FPS then send 1000/x 118 * Note that if animation frame methods are used browser chooses 119 * max display rate and this value has no effect. 120 * @this {lime.scheduleManager} 121 * @param {number} value New display rate. 122 */ 123 lime.scheduleManager.setDisplayRate = function(value) { 124 this.displayRate_ = value; 125 if (this.active_) { 126 lime.scheduleManager.disable_(); 127 lime.scheduleManager.activate_(); 128 } 129 }; 130 131 /** 132 * Schedule a function. Passed function will be called on every frame 133 * with delta time from last run time 134 * @this {lime.scheduleManager} 135 * @param {function(number)} f Function to be called. 136 * @param {Object} context The context used when calling function. 137 * @param {lime.scheduleManager.Task} opt_task Task object. 138 */ 139 lime.scheduleManager.schedule = function(f, context, opt_task) { 140 var task = goog.isDef(opt_task) ? opt_task : this.taskStack_[0]; 141 goog.array.insert(task.functionStack_, [1, f, context]); 142 goog.array.insert(this.taskStack_, task); 143 if (!this.active_) { 144 lime.scheduleManager.activate_(); 145 } 146 }; 147 148 /** 149 * Unschedule a function. For functions that have be previously scheduled 150 * @this {lime.scheduleManager} 151 * @param {function(number)} f Function to be unscheduled. 152 * @param {Object} context Context used when scheduling. 153 */ 154 lime.scheduleManager.unschedule = function(f, context) { 155 var j = this.taskStack_.length; 156 while (--j >= 0) { 157 var functionStack_ = this.taskStack_[j].functionStack_, 158 fi, i = functionStack_.length; 159 while (--i >= 0) { 160 fi = functionStack_[i]; 161 if (fi[1] == f && fi[2] == context) { 162 goog.array.remove(functionStack_, fi); 163 164 } 165 } 166 if (functionStack_.length == 0 && j != 0) { 167 goog.array.remove(this.taskStack_, functionStack_); 168 } 169 } 170 // if no more functions: stop timers 171 if (this.taskStack_.length == 1 && 172 this.taskStack_[0].functionStack_.length == 0) { 173 lime.scheduleManager.disable_(); 174 } 175 }; 176 177 /** 178 * Start the internal timer functions 179 * @this {lime.scheduleManager} 180 * @private 181 */ 182 lime.scheduleManager.activate_ = function() { 183 if (this.active_) return; 184 185 this.lastRunTime_ = goog.now(); 186 187 if(lime.scheduleManager.USE_ANIMATION_FRAME){ 188 // mozilla 189 if(goog.global['mozRequestAnimationFrame']){ 190 goog.global['mozRequestAnimationFrame'](); 191 this.beforePaintHandlerBinded_ = goog.bind(lime.scheduleManager.beforePaintHandler_,this); 192 goog.global.addEventListener('MozBeforePaint',this.beforePaintHandlerBinded_, false); 193 } 194 else { // webkit 195 this.animationFrameHandlerBinded_ = goog.bind(lime.scheduleManager.animationFrameHandler_,this); 196 goog.global['webkitRequestAnimationFrame'](this.animationFrameHandlerBinded_); 197 } 198 } 199 else { 200 this.intervalID_ = setInterval(goog.bind(lime.scheduleManager.stepTimer_, this), 201 lime.scheduleManager.getDisplayRate()); 202 } 203 this.active_ = true; 204 }; 205 206 207 208 /** 209 * Stop interval timer functions 210 * @this {lime.scheduleManager} 211 * @private 212 */ 213 lime.scheduleManager.disable_ = function() { 214 if (!this.active_) return; 215 216 if(lime.scheduleManager.USE_ANIMATION_FRAME){ 217 // mozilla 218 if(goog.global['mozRequestAnimationFrame']){ 219 goog.global.removeEventListener('MozBeforePaint',this.beforePaintHandlerBinded_, false); 220 } 221 else { //webkit 222 goog.global['webkitCancelRequestAnimationFrame'](this.animationFrameHandlerBinded_); 223 } 224 } 225 else { 226 clearInterval(this.intervalID_); 227 } 228 this.active_ = false; 229 }; 230 231 /** 232 * Webkit implemtation of requestAnimationFrame handler. 233 * @this {lime.scheduleManager} 234 * @private 235 */ 236 lime.scheduleManager.animationFrameHandler_ = function(time){ 237 if(!time) time=goog.now(); // no time parameter in Chrome10beta 238 var delta = time - this.lastRunTime_; 239 lime.scheduleManager.dispatch_(delta); 240 this.lastRunTime_ = time; 241 goog.global['webkitRequestAnimationFrame'](this.animationFrameHandlerBinded_); 242 } 243 244 /** 245 * Mozilla implemtation of requestAnimationFrame handler. 246 * @this {lime.scheduleManager} 247 * @private 248 */ 249 lime.scheduleManager.beforePaintHandler_ = function(event){ 250 var delta = event.timeStamp - this.lastRunTime_; 251 lime.scheduleManager.dispatch_(delta); 252 this.lastRunTime_ = event.timeStamp; 253 goog.global['mozRequestAnimationFrame'](); 254 } 255 256 /** 257 * Timer events step function that delegates to other objects waiting 258 * @this {lime.scheduleManager} 259 * @private 260 */ 261 lime.scheduleManager.stepTimer_ = function() { 262 var t; 263 var curTime = goog.now(); 264 var delta = curTime - this.lastRunTime_; 265 if (delta < 0) delta = 1; 266 lime.scheduleManager.dispatch_(delta); 267 this.lastRunTime_ = curTime; 268 }; 269 270 /** 271 * Call all scheduled tasks 272 * @this {lime.scheduleManager} 273 * @param {number} delta Milliseconds since last run. 274 * @private 275 */ 276 lime.scheduleManager.dispatch_ = function(delta){ 277 var i = this.taskStack_.length; 278 while (--i >= 0) { 279 this.taskStack_[i].step_(delta); 280 } 281 //hack to deal with FF4 CSS transformation issue https://bugzilla.mozilla.org/show_bug.cgi?id=637597 282 if(lime.transformSet_==1 && (/Firefox\/4./).test(goog.userAgent.getUserAgentString()) && 283 !lime.FF4_USE_HW_ACCELERATION){ 284 if(lime.scheduleManager.odd_){ 285 document.body.style['MozTransform'] = ''; 286 lime.scheduleManager.odd_=0; 287 } 288 else { 289 document.body.style['MozTransform'] = 'scale(1,1)'; 290 lime.scheduleManager.odd_=1; 291 } 292 lime.transformSet_=0; 293 } 294 }; 295 296 /** 297 * Change director's activity. Used for pausing updates when director is paused 298 * @this {lime.scheduleManager} 299 * @param {lime.Director} director Director. 300 * @param {boolean} value Active or inactive? 301 */ 302 lime.scheduleManager.changeDirectorActivity = function(director, value) { 303 var t, context, f, d, i, 304 j = this.taskStack_.length; 305 while (--j >= 0) { 306 307 t = this.taskStack_[j]; 308 i = t.functionStack_.length; 309 while (--i >= 0) { 310 f = t.functionStack_[i]; 311 context = f[2]; 312 if (goog.isFunction(context.getDirector)) { 313 d = context.getDirector(); 314 if (d == director) { 315 f[0] = value; 316 } 317 } 318 } 319 } 320 }; 321 322 /** 323 * Set up function to be called once after a delay 324 * @param {function(number)} f Function to be called. 325 * @param {Object} context Context used when calling object. 326 * @param {number} delay Delay before calling. 327 */ 328 lime.scheduleManager.callAfter = function(f, context, delay) { 329 lime.scheduleManager.scheduleWithDelay(f, context, delay, 1); 330 }; 331 332 /** 333 * Set up function to be called repeatedly after a delay 334 * @param {function(number)} f Function to be called. 335 * @param {Object} context Context used when calling object. 336 * @param {number} delay Delay before calling. 337 * @param {number} opt_limit Number of times to call. 338 * @this {lime.scheduleManager} 339 */ 340 lime.scheduleManager.scheduleWithDelay = function(f, context, 341 delay, opt_limit) { 342 var task = new lime.scheduleManager.Task(delay, opt_limit); 343 lime.scheduleManager.schedule(f, context, task); 344 }; 345