API 0.9.5
Foundation/CPRunLoop.j
Go to the documentation of this file.
00001 /*
00002  * CPRunLoop.j
00003  * Foundation
00004  *
00005  * Created by Francisco Tolmasky.
00006  * Copyright 2008, 280 North, Inc.
00007  *
00008  * This library is free software; you can redistribute it and/or
00009  * modify it under the terms of the GNU Lesser General Public
00010  * License as published by the Free Software Foundation; either
00011  * version 2.1 of the License, or (at your option) any later version.
00012  *
00013  * This library is distributed in the hope that it will be useful,
00014  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00015  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
00016  * Lesser General Public License for more details.
00017  *
00018  * You should have received a copy of the GNU Lesser General Public
00019  * License along with this library; if not, write to the Free Software
00020  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
00021  */
00022 
00023 
00028 CPDefaultRunLoopMode    = @"CPDefaultRunLoopMode";
00029 
00030 function _CPRunLoopPerformCompare(lhs, rhs)
00031 {
00032     return [rhs order] - [lhs order];
00033 }
00034 
00035 var _CPRunLoopPerformPool           = [],
00036     _CPRunLoopPerformPoolCapacity   = 5;
00037 
00038 /* @ignore */
00039 @implementation _CPRunLoopPerform : CPObject
00040 {
00041     id          _target;
00042     SEL         _selector;
00043     id          _argument;
00044     unsigned    _order;
00045     CPArray     _runLoopModes;
00046     BOOL        _isValid;
00047 }
00048 
00049 + (void)_poolPerform:(_CPRunLoopPerform)aPerform
00050 {
00051     if (!aPerform || _CPRunLoopPerformPool.length >= _CPRunLoopPerformPoolCapacity)
00052         return;
00053 
00054     _CPRunLoopPerformPool.push(aPerform);
00055 }
00056 
00057 + (_CPRunLoopPerform)performWithSelector:(SEL)aSelector target:(id)aTarget argument:(id)anArgument order:(unsigned)anOrder modes:(CPArray)modes
00058 {
00059     if (_CPRunLoopPerformPool.length)
00060     {
00061         var perform = _CPRunLoopPerformPool.pop();
00062 
00063         perform._target = aTarget;
00064         perform._selector = aSelector;
00065         perform._argument = anArgument;
00066         perform._order = anOrder;
00067         perform._runLoopModes = modes;
00068         perform._isValid = YES;
00069 
00070         return perform;
00071     }
00072 
00073     return [[self alloc] initWithSelector:aSelector target:aTarget argument:anArgument order:anOrder modes:modes];
00074 }
00075 
00076 - (id)initWithSelector:(SEL)aSelector target:(SEL)aTarget argument:(id)anArgument order:(unsigned)anOrder modes:(CPArray)modes
00077 {
00078     self = [super init];
00079 
00080     if (self)
00081     {
00082         _selector = aSelector;
00083         _target = aTarget;
00084         _argument = anArgument;
00085         _order = anOrder;
00086         _runLoopModes = modes;
00087         _isValid = YES;
00088     }
00089 
00090     return self;
00091 }
00092 
00093 - (SEL)selector
00094 {
00095     return _selector;
00096 }
00097 
00098 - (id)target
00099 {
00100     return _target;
00101 }
00102 
00103 - (id)argument
00104 {
00105     return _argument;
00106 }
00107 
00108 - (unsigned)order
00109 {
00110     return _order;
00111 }
00112 
00113 - (BOOL)fireInMode:(CPString)aRunLoopMode
00114 {
00115     if (!_isValid)
00116         return YES;
00117 
00118     if ([_runLoopModes containsObject:aRunLoopMode])
00119     {
00120         [_target performSelector:_selector withObject:_argument];
00121 
00122         return YES;
00123     }
00124 
00125     return NO;
00126 }
00127 
00128 - (void)invalidate
00129 {
00130     _isValid = NO;
00131 }
00132 
00133 @end
00134 
00135 var CPRunLoopLastNativeRunLoop = 0;
00136 
00146 @implementation CPRunLoop : CPObject
00147 {
00148     BOOL    _runLoopLock;
00149 
00150     Object  _timersForModes; //should be a dictionary to allow lookups by mode
00151     Object  _nativeTimersForModes;
00152     CPDate  _nextTimerFireDatesForModes;
00153     BOOL    _didAddTimer;
00154     CPDate  _effectiveDate;
00155 
00156     CPArray _orderedPerforms;
00157     int     _runLoopInsuranceTimer;
00158 }
00159 
00160 /*
00161     @ignore
00162 */
00163 + (void)initialize
00164 {
00165     if (self != [CPRunLoop class])
00166         return;
00167 
00168     CPMainRunLoop = [[CPRunLoop alloc] init];
00169 }
00170 
00171 - (id)init
00172 {
00173     self = [super init];
00174 
00175     if (self)
00176     {
00177         _orderedPerforms = [];
00178 
00179         _timersForModes = {};
00180         _nativeTimersForModes = {};
00181         _nextTimerFireDatesForModes = {};
00182     }
00183 
00184     return self;
00185 }
00186 
00190 + (CPRunLoop)currentRunLoop
00191 {
00192     return CPMainRunLoop;
00193 }
00194 
00198 + (CPRunLoop)mainRunLoop
00199 {
00200     return CPMainRunLoop;
00201 }
00202 
00211 - (void)performSelector:(SEL)aSelector target:(id)aTarget argument:(id)anArgument order:(int)anOrder modes:(CPArray)modes
00212 {
00213     var perform = [_CPRunLoopPerform performWithSelector:aSelector target:aTarget argument:anArgument order:anOrder modes:modes],
00214         count = _orderedPerforms.length;
00215 
00216     // We sort ourselves in reverse because we iterate this list backwards.
00217     while (count--)
00218         if (anOrder < [_orderedPerforms[count] order])
00219             break;
00220 
00221     _orderedPerforms.splice(count + 1, 0, perform);
00222 }
00223 
00230 - (void)cancelPerformSelector:(SEL)aSelector target:(id)aTarget argument:(id)anArgument
00231 {
00232     var count = _orderedPerforms.length;
00233 
00234     while (count--)
00235     {
00236         var perform = _orderedPerforms[count];
00237 
00238         if ([perform selector] === aSelector && [perform target] == aTarget && [perform argument] == anArgument)
00239             [_orderedPerforms[count] invalidate];
00240     }
00241 }
00242 
00243 /*
00244     @ignore
00245 */
00246 - (void)performSelectors
00247 {
00248     [self limitDateForMode:CPDefaultRunLoopMode];
00249 }
00250 
00254 - (void)addTimer:(CPTimer)aTimer forMode:(CPString)aMode
00255 {
00256     // FIXME: Timer already added...
00257     if (_timersForModes[aMode])
00258         _timersForModes[aMode].push(aTimer);
00259     else
00260         _timersForModes[aMode] = [aTimer];
00261 
00262     _didAddTimer = YES;
00263 
00264     if (!aTimer._lastNativeRunLoopsForModes)
00265         aTimer._lastNativeRunLoopsForModes = {};
00266 
00267     aTimer._lastNativeRunLoopsForModes[aMode] = CPRunLoopLastNativeRunLoop;
00268 
00269 
00270     // FIXME: Hack for not doing this in CommonJS
00271     if ([CFBundle.environments() indexOfObject:("Browser")] !== CPNotFound)
00272     {
00273         if (!_runLoopInsuranceTimer)
00274             _runLoopInsuranceTimer = window.setNativeTimeout(function()
00275             {
00276                 [self limitDateForMode:CPDefaultRunLoopMode];
00277             }, 0);
00278     }
00279 }
00280 
00284 - (CPDate)limitDateForMode:(CPString)aMode
00285 {
00286     //simple locking to try to prevent concurrent iterating over timers
00287     if (_runLoopLock)
00288         return;
00289 
00290     _runLoopLock = YES;
00291 
00292     // FIXME: Hack for not doing this in CommonJS
00293     if ([CFBundle.environments() indexOfObject:("Browser")] !== CPNotFound)
00294     {
00295         if (_runLoopInsuranceTimer)
00296         {
00297             window.clearNativeTimeout(_runLoopInsuranceTimer);
00298             _runLoopInsuranceTimer = nil;
00299         }
00300     }
00301 
00302     var now = _effectiveDate ? [_effectiveDate laterDate:[CPDate date]] : [CPDate date],
00303         nextFireDate = nil,
00304         nextTimerFireDate = _nextTimerFireDatesForModes[aMode];
00305 
00306     // Perform Timers if necessary
00307     if (_didAddTimer || nextTimerFireDate && nextTimerFireDate <= now)
00308     {
00309         _didAddTimer = NO;
00310 
00311         // Cancel existing window.setTimeout
00312         if (_nativeTimersForModes[aMode] !== nil)
00313         {
00314             window.clearNativeTimeout(_nativeTimersForModes[aMode]);
00315 
00316             _nativeTimersForModes[aMode] = nil;
00317         }
00318 
00319         // Empty timers to avoid catastrophe if a timer is added during a timer fire.
00320         var timers = _timersForModes[aMode],
00321             index = timers.length;
00322 
00323         _timersForModes[aMode] = nil;
00324 
00325         //  Loop through timers looking for ones that had fired
00326         while (index--)
00327         {
00328             var timer = timers[index];
00329 
00330             if (timer._lastNativeRunLoopsForModes[aMode] < CPRunLoopLastNativeRunLoop && timer._isValid && timer._fireDate <= now)
00331                 [timer fire];
00332 
00333             // Timer may or may not still be valid
00334             if (timer._isValid)
00335                 nextFireDate = (nextFireDate === nil) ? timer._fireDate : [nextFireDate earlierDate:timer._fireDate];
00336 
00337             else
00338             {
00339                 // FIXME: Is there an issue with reseting the fire date in -fire? or adding it back to the run loop?...
00340                 timer._lastNativeRunLoopsForModes[aMode] = 0;
00341 
00342                 timers.splice(index, 1);
00343             }
00344         }
00345 
00346         // Timers may have been added during the firing of timers
00347         // They do NOT get a shot at firing, because they certainly
00348         // haven't gone through one native timer.
00349         var newTimers = _timersForModes[aMode];
00350 
00351         if (newTimers && newTimers.length)
00352         {
00353             index = newTimers.length;
00354 
00355             while (index--)
00356             {
00357                 var timer = newTimers[index];
00358 
00359                 if ([timer isValid])
00360                     nextFireDate = (nextFireDate === nil) ? timer._fireDate : [nextFireDate earlierDate:timer._fireDate];
00361                 else
00362                     newTimers.splice(index, 1);
00363             }
00364 
00365             _timersForModes[aMode] = newTimers.concat(timers);
00366         }
00367         else
00368             _timersForModes[aMode] = timers;
00369 
00370         _nextTimerFireDatesForModes[aMode] = nextFireDate;
00371 
00372         //initiate a new window.setTimeout if there are any timers
00373         if (_nextTimerFireDatesForModes[aMode] !== nil)
00374             _nativeTimersForModes[aMode] = window.setNativeTimeout(function() { _effectiveDate = nextFireDate; _nativeTimersForModes[aMode] = nil; ++CPRunLoopLastNativeRunLoop; [self limitDateForMode:aMode]; _effectiveDate = nil; }, MAX(0, [nextFireDate timeIntervalSinceNow] * 1000));
00375     }
00376 
00377     // Run loop performers
00378     var performs = _orderedPerforms,
00379         index = performs.length;
00380 
00381     _orderedPerforms = [];
00382 
00383     while (index--)
00384     {
00385         var perform = performs[index];
00386 
00387         if ([perform fireInMode:CPDefaultRunLoopMode])
00388         {
00389             [_CPRunLoopPerform _poolPerform:perform];
00390 
00391             performs.splice(index, 1);
00392         }
00393     }
00394 
00395     if (_orderedPerforms.length)
00396     {
00397         _orderedPerforms = _orderedPerforms.concat(performs);
00398         _orderedPerforms.sort(_CPRunLoopPerformCompare);
00399     }
00400     else
00401         _orderedPerforms = performs;
00402 
00403     _runLoopLock = NO;
00404 
00405     return nextFireDate;
00406 }
00407 
00408 @end
 All Classes Files Functions Variables Defines