![]() |
API 0.9.5
|
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