API  0.9.7
 All Classes Files Functions Variables 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 
146 @implementation CPRunLoop : CPObject
147 {
148  BOOL _runLoopLock;
149 
150  Object _timersForModes; //should be a dictionary to allow lookups by mode
151  Object _nativeTimersForModes;
152  CPDate _nextTimerFireDatesForModes;
153  BOOL _didAddTimer;
154  CPDate _effectiveDate;
155 
156  CPArray _orderedPerforms;
157  int _runLoopInsuranceTimer;
158 }
159 
160 /*
161  @ignore
162 */
163 + (void)initialize
164 {
165  if (self !== [CPRunLoop class])
166  return;
167 
168  CPMainRunLoop = [[CPRunLoop alloc] init];
169 }
170 
171 - (id)init
172 {
173  self = [super init];
174 
175  if (self)
176  {
177  _orderedPerforms = [];
178 
179  _timersForModes = {};
180  _nativeTimersForModes = {};
181  _nextTimerFireDatesForModes = {};
182  }
183 
184  return self;
185 }
186 
190 + (CPRunLoop)currentRunLoop
191 {
192  return CPMainRunLoop;
193 }
194 
198 + (CPRunLoop)mainRunLoop
199 {
200  return CPMainRunLoop;
201 }
202 
211 - (void)performSelector:(SEL)aSelector target:(id)aTarget argument:(id)anArgument order:(int)anOrder modes:(CPArray)modes
212 {
213  var perform = [_CPRunLoopPerform performWithSelector:aSelector target:aTarget argument:anArgument order:anOrder modes:modes],
214  count = _orderedPerforms.length;
215 
216  // We sort ourselves in reverse because we iterate this list backwards.
217  while (count--)
218  if (anOrder < [_orderedPerforms[count] order])
219  break;
220 
221  _orderedPerforms.splice(count + 1, 0, perform);
222 }
223 
230 - (void)cancelPerformSelector:(SEL)aSelector target:(id)aTarget argument:(id)anArgument
231 {
232  var count = _orderedPerforms.length;
233 
234  while (count--)
235  {
236  var perform = _orderedPerforms[count];
237 
238  if ([perform selector] === aSelector && [perform target] == aTarget && [perform argument] == anArgument)
239  [_orderedPerforms[count] invalidate];
240  }
241 }
242 
243 /*
244  @ignore
245 */
246 - (void)performSelectors
247 {
248  [self limitDateForMode:CPDefaultRunLoopMode];
249 }
250 
254 - (void)addTimer:(CPTimer)aTimer forMode:(CPString)aMode
255 {
256  // FIXME: Timer already added...
257  if (_timersForModes[aMode])
258  _timersForModes[aMode].push(aTimer);
259  else
260  _timersForModes[aMode] = [aTimer];
261 
262  _didAddTimer = YES;
263 
264  if (!aTimer._lastNativeRunLoopsForModes)
265  aTimer._lastNativeRunLoopsForModes = {};
266 
267  aTimer._lastNativeRunLoopsForModes[aMode] = CPRunLoopLastNativeRunLoop;
268 
269  // FIXME: Hack for not doing this in CommonJS
270  if ([CFBundle.environments() indexOfObject:("Browser")] !== CPNotFound)
271  {
272  if (!_runLoopInsuranceTimer)
273  _runLoopInsuranceTimer = window.setNativeTimeout(function()
274  {
275  [self limitDateForMode:CPDefaultRunLoopMode];
276  }, 0);
277  }
278 }
279 
283 - (CPDate)limitDateForMode:(CPString)aMode
284 {
285  //simple locking to try to prevent concurrent iterating over timers
286  if (_runLoopLock)
287  return;
288 
289  _runLoopLock = YES;
290 
291  // FIXME: Hack for not doing this in CommonJS
292  if ([CFBundle.environments() indexOfObject:("Browser")] !== CPNotFound)
293  {
294  if (_runLoopInsuranceTimer)
295  {
296  window.clearNativeTimeout(_runLoopInsuranceTimer);
297  _runLoopInsuranceTimer = nil;
298  }
299  }
300 
301  var now = _effectiveDate ? [_effectiveDate laterDate:[CPDate date]] : [CPDate date],
302  nextFireDate = nil,
303  nextTimerFireDate = _nextTimerFireDatesForModes[aMode];
304 
305  // Perform Timers if necessary
306 
307  if (_didAddTimer || nextTimerFireDate && nextTimerFireDate <= now)
308  {
309  _didAddTimer = NO;
310 
311  // Cancel existing window.setTimeout
312  if (_nativeTimersForModes[aMode] !== nil)
313  {
314  window.clearNativeTimeout(_nativeTimersForModes[aMode]);
315 
316  _nativeTimersForModes[aMode] = nil;
317  }
318 
319  // Empty timers to avoid catastrophe if a timer is added during a timer fire.
320  var timers = _timersForModes[aMode],
321  index = timers.length;
322 
323  _timersForModes[aMode] = nil;
324 
325  // If we're running in CommonJS (unit tests) we shouldn't wait for at least 1 native run loop
326  // since those will never happen.
327  var hasNativeTimers = [CFBundle.environments() indexOfObject:("Browser")] !== CPNotFound;
328 
329  // Loop through timers looking for ones that had fired
330  while (index--)
331  {
332  var timer = timers[index];
333 
334  if ((!hasNativeTimers || timer._lastNativeRunLoopsForModes[aMode] < CPRunLoopLastNativeRunLoop) && timer._isValid && timer._fireDate <= now)
335  [timer fire];
336 
337  // Timer may or may not still be valid
338  if (timer._isValid)
339  nextFireDate = (nextFireDate === nil) ? timer._fireDate : [nextFireDate earlierDate:timer._fireDate];
340 
341  else
342  {
343  // FIXME: Is there an issue with reseting the fire date in -fire? or adding it back to the run loop?...
344  timer._lastNativeRunLoopsForModes[aMode] = 0;
345 
346  timers.splice(index, 1);
347  }
348  }
349 
350  // Timers may have been added during the firing of timers
351  // They do NOT get a shot at firing, because they certainly
352  // haven't gone through one native timer.
353  var newTimers = _timersForModes[aMode];
354 
355  if (newTimers && newTimers.length)
356  {
357  index = newTimers.length;
358 
359  while (index--)
360  {
361  var timer = newTimers[index];
362 
363  if ([timer isValid])
364  nextFireDate = (nextFireDate === nil) ? timer._fireDate : [nextFireDate earlierDate:timer._fireDate];
365  else
366  newTimers.splice(index, 1);
367  }
368 
369  _timersForModes[aMode] = newTimers.concat(timers);
370  }
371  else
372  _timersForModes[aMode] = timers;
373 
374  _nextTimerFireDatesForModes[aMode] = nextFireDate;
375 
376  //initiate a new window.setTimeout if there are any timers
377  if (_nextTimerFireDatesForModes[aMode] !== nil)
378  _nativeTimersForModes[aMode] = window.setNativeTimeout(function()
379  {
380  _effectiveDate = nextFireDate;
381  _nativeTimersForModes[aMode] = nil;
383  [self limitDateForMode:aMode];
384  _effectiveDate = nil;
385  }, MAX(0, [nextFireDate timeIntervalSinceNow] * 1000));
386  }
387 
388  // Run loop performers
389  var performs = _orderedPerforms,
390  index = performs.length;
391 
392  _orderedPerforms = [];
393 
394  while (index--)
395  {
396  var perform = performs[index];
397 
398  if ([perform fireInMode:CPDefaultRunLoopMode])
399  {
400  [_CPRunLoopPerform _poolPerform:perform];
401 
402  performs.splice(index, 1);
403  }
404  }
405 
406  if (_orderedPerforms.length)
407  {
408  _orderedPerforms = _orderedPerforms.concat(performs);
409  _orderedPerforms.sort(_CPRunLoopPerformCompare);
410  }
411  else
412  _orderedPerforms = performs;
413 
414  _runLoopLock = NO;
415 
416  return nextFireDate;
417 }
418 
419 @end