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