1 // ==========================================================================
  2 // Project:   SproutCore - JavaScript Application Framework
  3 // Copyright: ©2006-2011 Strobe Inc. and contributors.
  4 //            Portions ©2008-2011 Apple Inc. All rights reserved.
  5 // License:   Licensed under MIT license (see license.js)
  6 // ==========================================================================
  7 
  8 // Create anonymous subclass of SC.RunLoop to add support for processing
  9 // view queues and Timers.
 10 SC.RunLoop = SC.RunLoop.extend(
 11 /** @scope SC.RunLoop.prototype */ {
 12 
 13   /**
 14     The time the current run loop began executing.
 15 
 16     All timers scheduled during this run loop will begin executing as if
 17     they were scheduled at this time.
 18 
 19     @type Number
 20   */
 21   startTime: function() {
 22     if (!this._start) { this._start = Date.now(); }
 23     return this._start ;
 24   }.property(),
 25 
 26   /*
 27 
 28     Override to fire and reschedule timers once per run loop.
 29 
 30     Note that timers should fire only once per run loop to avoid the
 31     situation where a timer might cause an infinite loop by constantly
 32     rescheduling itself every time it is fired.
 33   */
 34   endRunLoop: function() {
 35     this.fireExpiredTimers(); // fire them timers!
 36     var ret = sc_super(); // do everything else
 37     this.scheduleNextTimeout(); // schedule a timeout if timers remain
 38     return ret;
 39   },
 40 
 41   // ..........................................................
 42   // TIMER SUPPORT
 43   //
 44 
 45   /**
 46     Schedules a timer to execute at the specified runTime.  You will not
 47     usually call this method directly.  Instead you should work with SC.Timer,
 48     which will manage both creating the timer and scheduling it.
 49 
 50     Calling this method on a timer that is already scheduled will remove it
 51     from the existing schedule and reschedule it.
 52 
 53     @param {SC.Timer} timer the timer to schedule
 54     @param {Time} runTime the time offset when you want this to run
 55     @returns {SC.RunLoop} receiver
 56   */
 57   scheduleTimer: function(timer, runTime) {
 58     // if the timer is already in the schedule, remove it.
 59     this._timerQueue = timer.removeFromTimerQueue(this._timerQueue);
 60 
 61     // now, add the timer ot the timeout queue.  This will walk down the
 62     // chain of timers to find the right place to insert it.
 63     this._timerQueue = timer.scheduleInTimerQueue(this._timerQueue, runTime);
 64     return this ;
 65   },
 66 
 67   /**
 68     Removes the named timer from the timeout queue.  If the timer is not
 69     currently scheduled, this method will have no effect.
 70 
 71     @param {SC.Timer} timer the timer to schedule
 72     @returns {SC.RunLoop} receiver
 73   */
 74   cancelTimer: function(timer) {
 75     this._timerQueue = timer.removeFromTimerQueue(this._timerQueue) ;
 76     return this ;
 77   },
 78 
 79   /** @private - shared array used by fireExpiredTimers to avoid memory */
 80   TIMER_ARRAY: [],
 81 
 82   /**
 83     Invokes any timers that have expired since this method was last called.
 84     Usually you will not call this method directly, but it will be invoked
 85     automatically at the end of the run loop.
 86 
 87     @returns {Boolean} YES if timers were fired, NO otherwise
 88   */
 89   fireExpiredTimers: function() {
 90     if (!this._timerQueue || this._firing) { return NO; } // nothing to do
 91 
 92     // max time we are allowed to run timers
 93     var now = this.get('startTime'),
 94         timers = this.TIMER_ARRAY,
 95         idx, len, didFire;
 96 
 97     // avoid recursive calls
 98     this._firing = YES;
 99 
100     // collect timers to fire.  we do this one time up front to avoid infinite
101     // loops where firing a timer causes it to schedule itself again, causing
102     // it to fire again, etc.
103     this._timerQueue = this._timerQueue.collectExpiredTimers(timers, now);
104 
105     // now step through timers and fire them.
106     len = timers.length;
107     for(idx=0;idx<len;idx++) { timers[idx].fire(); }
108 
109     // cleanup
110     didFire = timers.length > 0 ;
111     timers.length = 0 ; // reset for later use...
112     this._firing = NO ;
113     return didFire;
114   },
115 
116   /** @private
117     Invoked at the end of a runloop, if there are pending timers, a timeout
118     will be scheduled to fire when the next timer expires.  You will not
119     usually call this method yourself.  It is invoked automatically at the
120     end of a run loop.
121 
122     @returns {Boolean} YES if a timeout was scheduled
123   */
124   scheduleNextTimeout: function() {
125     var ret = NO,
126       timer = this._timerQueue;
127 
128     // if no timer, and there is an existing timeout, attempt to cancel it.
129     // NOTE: if this happens to be an invokeNext based timer, it will not be
130     // cancelled.
131     if (!timer) {
132       if (this._timerTimeout) { this.unscheduleRunLoop(); }
133       this._timerTimeout = null;
134 
135     // otherwise, determine if the timeout needs to be rescheduled.
136     } else {
137       var nextTimeoutAt = timer._timerQueueRunTime ;
138       this._timerTimeout = this.scheduleRunLoop(nextTimeoutAt);
139       ret = YES;
140     }
141 
142     return ret ;
143   }
144 
145 });
146 
147 // Recreate the currentRunLoop with the new methods
148 SC.RunLoop.currentRunLoop = SC.RunLoop.create();
149 SC.RunLoop.runLoopClass = SC.RunLoop;
150