API  0.9.6
 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 
270  // FIXME: Hack for not doing this in CommonJS
271  if ([CFBundle.environments() indexOfObject:("Browser")] !== CPNotFound)
272  {
273  if (!_runLoopInsuranceTimer)
274  _runLoopInsuranceTimer = window.setNativeTimeout(function()
275  {
276  [self limitDateForMode:CPDefaultRunLoopMode];
277  }, 0);
278  }
279 }
280 
284 - (CPDate)limitDateForMode:(CPString)aMode
285 {
286  //simple locking to try to prevent concurrent iterating over timers
287  if (_runLoopLock)
288  return;
289 
290  _runLoopLock = YES;
291 
292  // FIXME: Hack for not doing this in CommonJS
293  if ([CFBundle.environments() indexOfObject:("Browser")] !== CPNotFound)
294  {
295  if (_runLoopInsuranceTimer)
296  {
297  window.clearNativeTimeout(_runLoopInsuranceTimer);
298  _runLoopInsuranceTimer = nil;
299  }
300  }
301 
302  var now = _effectiveDate ? [_effectiveDate laterDate:[CPDate date]] : [CPDate date],
303  nextFireDate = nil,
304  nextTimerFireDate = _nextTimerFireDatesForModes[aMode];
305 
306  // Perform Timers if necessary
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  // Loop through timers looking for ones that had fired
326  while (index--)
327  {
328  var timer = timers[index];
329 
330  if (timer._lastNativeRunLoopsForModes[aMode] < CPRunLoopLastNativeRunLoop && timer._isValid && timer._fireDate <= now)
331  [timer fire];
332 
333  // Timer may or may not still be valid
334  if (timer._isValid)
335  nextFireDate = (nextFireDate === nil) ? timer._fireDate : [nextFireDate earlierDate:timer._fireDate];
336 
337  else
338  {
339  // FIXME: Is there an issue with reseting the fire date in -fire? or adding it back to the run loop?...
340  timer._lastNativeRunLoopsForModes[aMode] = 0;
341 
342  timers.splice(index, 1);
343  }
344  }
345 
346  // Timers may have been added during the firing of timers
347  // They do NOT get a shot at firing, because they certainly
348  // haven't gone through one native timer.
349  var newTimers = _timersForModes[aMode];
350 
351  if (newTimers && newTimers.length)
352  {
353  index = newTimers.length;
354 
355  while (index--)
356  {
357  var timer = newTimers[index];
358 
359  if ([timer isValid])
360  nextFireDate = (nextFireDate === nil) ? timer._fireDate : [nextFireDate earlierDate:timer._fireDate];
361  else
362  newTimers.splice(index, 1);
363  }
364 
365  _timersForModes[aMode] = newTimers.concat(timers);
366  }
367  else
368  _timersForModes[aMode] = timers;
369 
370  _nextTimerFireDatesForModes[aMode] = nextFireDate;
371 
372  //initiate a new window.setTimeout if there are any timers
373  if (_nextTimerFireDatesForModes[aMode] !== nil)
374  _nativeTimersForModes[aMode] = window.setNativeTimeout(function()
375  {
376  _effectiveDate = nextFireDate;
377  _nativeTimersForModes[aMode] = nil;
379  [self limitDateForMode:aMode];
380  _effectiveDate = nil;
381  }, MAX(0, [nextFireDate timeIntervalSinceNow] * 1000));
382  }
383 
384  // Run loop performers
385  var performs = _orderedPerforms,
386  index = performs.length;
387 
388  _orderedPerforms = [];
389 
390  while (index--)
391  {
392  var perform = performs[index];
393 
394  if ([perform fireInMode:CPDefaultRunLoopMode])
395  {
396  [_CPRunLoopPerform _poolPerform:perform];
397 
398  performs.splice(index, 1);
399  }
400  }
401 
402  if (_orderedPerforms.length)
403  {
404  _orderedPerforms = _orderedPerforms.concat(performs);
405  _orderedPerforms.sort(_CPRunLoopPerformCompare);
406  }
407  else
408  _orderedPerforms = performs;
409 
410  _runLoopLock = NO;
411 
412  return nextFireDate;
413 }
414 
415 @end