API  0.9.8
 All Classes Files Functions Variables Typedefs Macros Groups Pages
CPRunLoop.j
Go to the documentation of this file.
1 /*
2  * CPRunLoop.j
3  * Foundation
4  *
5  * Created by Francisco Tolmasky.
6  * Copyright 2008, 280 North, Inc.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21  */
22 
23 
28 CPDefaultRunLoopMode = @"CPDefaultRunLoopMode";
29 
30 function _CPRunLoopPerformCompare(lhs, rhs)
31 {
32  return [rhs order] - [lhs order];
33 }
34 
35 var _CPRunLoopPerformPool = [],
36  _CPRunLoopPerformPoolCapacity = 5;
37 
38 /* @ignore */
39 @implementation _CPRunLoopPerform : CPObject
40 {
41  id _target;
42  SEL _selector;
43  id _argument;
44  unsigned _order;
45  CPArray _runLoopModes;
46  BOOL _isValid;
47 }
48 
49 + (void)_poolPerform:(_CPRunLoopPerform)aPerform
50 {
51  if (!aPerform || _CPRunLoopPerformPool.length >= _CPRunLoopPerformPoolCapacity)
52  return;
53 
54  _CPRunLoopPerformPool.push(aPerform);
55 }
56 
57 + (_CPRunLoopPerform)performWithSelector:(SEL)aSelector target:(id)aTarget argument:(id)anArgument order:(unsigned)anOrder modes:(CPArray)modes
58 {
59  if (_CPRunLoopPerformPool.length)
60  {
61  var perform = _CPRunLoopPerformPool.pop();
62 
63  perform._target = aTarget;
64  perform._selector = aSelector;
65  perform._argument = anArgument;
66  perform._order = anOrder;
67  perform._runLoopModes = modes;
68  perform._isValid = YES;
69 
70  return perform;
71  }
72 
73  return [[self alloc] initWithSelector:aSelector target:aTarget argument:anArgument order:anOrder modes:modes];
74 }
75 
76 - (id)initWithSelector:(SEL)aSelector target:(SEL)aTarget argument:(id)anArgument order:(unsigned)anOrder modes:(CPArray)modes
77 {
78  self = [super init];
79 
80  if (self)
81  {
82  _selector = aSelector;
83  _target = aTarget;
84  _argument = anArgument;
85  _order = anOrder;
86  _runLoopModes = modes;
87  _isValid = YES;
88  }
89 
90  return self;
91 }
92 
93 - (SEL)selector
94 {
95  return _selector;
96 }
97 
98 - (id)target
99 {
100  return _target;
101 }
102 
103 - (id)argument
104 {
105  return _argument;
106 }
107 
108 - (unsigned)order
109 {
110  return _order;
111 }
112 
113 - (BOOL)fireInMode:(CPString)aRunLoopMode
114 {
115  if (!_isValid)
116  return YES;
117 
118  if ([_runLoopModes containsObject:aRunLoopMode])
119  {
120  [_target performSelector:_selector withObject:_argument];
121 
122  return YES;
123  }
124 
125  return NO;
126 }
127 
128 - (void)invalidate
129 {
130  _isValid = NO;
131 }
132 
133 @end
134 
136 
145 @implementation CPRunLoop : CPObject
146 {
147  BOOL _runLoopLock;
148 
149  Object _timersForModes; //should be a dictionary to allow lookups by mode
150  Object _nativeTimersForModes;
151  CPDate _nextTimerFireDatesForModes;
152  BOOL _didAddTimer;
153  CPDate _effectiveDate;
154 
155  CPArray _orderedPerforms;
156  int _runLoopInsuranceTimer;
157 }
158 
159 /*
160  @ignore
161 */
162 + (void)initialize
163 {
164  if (self !== [CPRunLoop class])
165  return;
166 
167  CPMainRunLoop = [[CPRunLoop alloc] init];
168 }
169 
170 - (id)init
171 {
172  self = [super init];
173 
174  if (self)
175  {
176  _orderedPerforms = [];
177 
178  _timersForModes = {};
179  _nativeTimersForModes = {};
180  _nextTimerFireDatesForModes = {};
181  }
182 
183  return self;
184 }
185 
189 + (CPRunLoop)currentRunLoop
190 {
191  return CPMainRunLoop;
192 }
193 
197 + (CPRunLoop)mainRunLoop
198 {
199  return CPMainRunLoop;
200 }
201 
210 - (void)performSelector:(SEL)aSelector target:(id)aTarget argument:(id)anArgument order:(int)anOrder modes:(CPArray)modes
211 {
212  var perform = [_CPRunLoopPerform performWithSelector:aSelector target:aTarget argument:anArgument order:anOrder modes:modes],
213  count = _orderedPerforms.length;
214 
215  // We sort ourselves in reverse because we iterate this list backwards.
216  while (count--)
217  if (anOrder < [_orderedPerforms[count] order])
218  break;
219 
220  _orderedPerforms.splice(count + 1, 0, perform);
221 }
222 
229 - (void)cancelPerformSelector:(SEL)aSelector target:(id)aTarget argument:(id)anArgument
230 {
231  var count = _orderedPerforms.length;
232 
233  while (count--)
234  {
235  var perform = _orderedPerforms[count];
236 
237  if ([perform selector] === aSelector && [perform target] == aTarget && [perform argument] == anArgument)
238  [_orderedPerforms[count] invalidate];
239  }
240 }
241 
242 /*
243  @ignore
244 */
245 - (void)performSelectors
246 {
247  [self limitDateForMode:CPDefaultRunLoopMode];
248 }
249 
253 - (void)addTimer:(CPTimer)aTimer forMode:(CPString)aMode
254 {
255  // FIXME: Timer already added...
256  if (_timersForModes[aMode])
257  _timersForModes[aMode].push(aTimer);
258  else
259  _timersForModes[aMode] = [aTimer];
260 
261  _didAddTimer = YES;
262 
263  if (!aTimer._lastNativeRunLoopsForModes)
264  aTimer._lastNativeRunLoopsForModes = {};
265 
266  aTimer._lastNativeRunLoopsForModes[aMode] = CPRunLoopLastNativeRunLoop;
267 
268  // FIXME: Hack for not doing this in CommonJS
269  if ([CFBundle.environments() indexOfObject:("Browser")] !== CPNotFound)
270  {
271  if (!_runLoopInsuranceTimer)
272  _runLoopInsuranceTimer = window.setNativeTimeout(function()
273  {
274  [self limitDateForMode:CPDefaultRunLoopMode];
275  }, 0);
276  }
277 }
278 
282 - (CPDate)limitDateForMode:(CPString)aMode
283 {
284  //simple locking to try to prevent concurrent iterating over timers
285  if (_runLoopLock)
286  return;
287 
288  _runLoopLock = YES;
289 
290  // FIXME: Hack for not doing this in CommonJS
291  if ([CFBundle.environments() indexOfObject:("Browser")] !== CPNotFound)
292  {
293  if (_runLoopInsuranceTimer)
294  {
295  window.clearNativeTimeout(_runLoopInsuranceTimer);
296  _runLoopInsuranceTimer = nil;
297  }
298  }
299 
300  var now = _effectiveDate ? [_effectiveDate laterDate:[CPDate date]] : [CPDate date],
301  nextFireDate = nil,
302  nextTimerFireDate = _nextTimerFireDatesForModes[aMode];
303 
304  // Perform Timers if necessary
305 
306  if (_didAddTimer || nextTimerFireDate && nextTimerFireDate <= now)
307  {
308  _didAddTimer = NO;
309 
310  // Cancel existing window.setTimeout
311  if (_nativeTimersForModes[aMode] !== nil)
312  {
313  window.clearNativeTimeout(_nativeTimersForModes[aMode]);
314 
315  _nativeTimersForModes[aMode] = nil;
316  }
317 
318  // Empty timers to avoid catastrophe if a timer is added during a timer fire.
319  var timers = _timersForModes[aMode],
320  index = timers.length;
321 
322  _timersForModes[aMode] = nil;
323 
324  // If we're running in CommonJS (unit tests) we shouldn't wait for at least 1 native run loop
325  // since those will never happen.
326  var hasNativeTimers = [CFBundle.environments() indexOfObject:("Browser")] !== CPNotFound;
327 
328  // Loop through timers looking for ones that had fired
329  while (index--)
330  {
331  var timer = timers[index];
332 
333  if ((!hasNativeTimers || timer._lastNativeRunLoopsForModes[aMode] < CPRunLoopLastNativeRunLoop) && timer._isValid && timer._fireDate <= now)
334  [timer fire];
335 
336  // Timer may or may not still be valid
337  if (timer._isValid)
338  nextFireDate = (nextFireDate === nil) ? timer._fireDate : [nextFireDate earlierDate:timer._fireDate];
339 
340  else
341  {
342  // FIXME: Is there an issue with reseting the fire date in -fire? or adding it back to the run loop?...
343  timer._lastNativeRunLoopsForModes[aMode] = 0;
344 
345  timers.splice(index, 1);
346  }
347  }
348 
349  // Timers may have been added during the firing of timers
350  // They do NOT get a shot at firing, because they certainly
351  // haven't gone through one native timer.
352  var newTimers = _timersForModes[aMode];
353 
354  if (newTimers && newTimers.length)
355  {
356  index = newTimers.length;
357 
358  while (index--)
359  {
360  var timer = newTimers[index];
361 
362  if ([timer isValid])
363  nextFireDate = (nextFireDate === nil) ? timer._fireDate : [nextFireDate earlierDate:timer._fireDate];
364  else
365  newTimers.splice(index, 1);
366  }
367 
368  _timersForModes[aMode] = newTimers.concat(timers);
369  }
370  else
371  _timersForModes[aMode] = timers;
372 
373  _nextTimerFireDatesForModes[aMode] = nextFireDate;
374 
375  //initiate a new window.setTimeout if there are any timers
376  if (_nextTimerFireDatesForModes[aMode] !== nil)
377  _nativeTimersForModes[aMode] = window.setNativeTimeout(function()
378  {
379  _effectiveDate = nextFireDate;
380  _nativeTimersForModes[aMode] = nil;
382  [self limitDateForMode:aMode];
383  _effectiveDate = nil;
384  }, MAX(0, [nextFireDate timeIntervalSinceNow] * 1000));
385  }
386 
387  // Run loop performers
388  var performs = _orderedPerforms,
389  index = performs.length;
390 
391  _orderedPerforms = [];
392 
393  while (index--)
394  {
395  var perform = performs[index];
396 
397  if ([perform fireInMode:CPDefaultRunLoopMode])
398  {
399  [_CPRunLoopPerform _poolPerform:perform];
400 
401  performs.splice(index, 1);
402  }
403  }
404 
405  if (_orderedPerforms.length)
406  {
407  _orderedPerforms = _orderedPerforms.concat(performs);
408  _orderedPerforms.sort(_CPRunLoopPerformCompare);
409  }
410  else
411  _orderedPerforms = performs;
412 
413  _runLoopLock = NO;
414 
415  return nextFireDate;
416 }
417 
418 @end