API  0.9.9
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  Function _block;
42  id _target;
43  SEL _selector;
44  id _argument;
45  unsigned _order;
46  CPArray _runLoopModes;
47  BOOL _isValid;
48 }
49 
50 + (void)_poolPerform:(_CPRunLoopPerform)aPerform
51 {
52  if (!aPerform || _CPRunLoopPerformPool.length >= _CPRunLoopPerformPoolCapacity)
53  return;
54 
55  _CPRunLoopPerformPool.push(aPerform);
56 }
57 
58 + (_CPRunLoopPerform)performWithSelector:(SEL)aSelector target:(id)aTarget argument:(id)anArgument order:(unsigned)anOrder modes:(CPArray)modes
59 {
60  if (_CPRunLoopPerformPool.length)
61  {
62  var perform = _CPRunLoopPerformPool.pop();
63 
64  perform._block = nil;
65  perform._target = aTarget;
66  perform._selector = aSelector;
67  perform._argument = anArgument;
68  perform._order = anOrder;
69  perform._runLoopModes = modes;
70  perform._isValid = YES;
71 
72  return perform;
73  }
74 
75  return [[self alloc] initWithSelector:aSelector target:aTarget argument:anArgument order:anOrder modes:modes];
76 }
77 
78 + (_CPRunLoopPerform)performWithBlock:(Function)aBlock argument:(id)anArgument order:(unsigned)anOrder modes:(CPArray)modes
79 {
80  if (_CPRunLoopPerformPool.length)
81  {
82  var perform = _CPRunLoopPerformPool.pop();
83 
84  perform._target = nil;
85  perform._selector = nil;
86  perform._block = aBlock;
87  perform._argument = anArgument;
88  perform._order = anOrder;
89  perform._runLoopModes = modes;
90  perform._isValid = YES;
91 
92  return perform;
93  }
94 
95  return [[self alloc] initWithBlock:aBlock argument:anArgument order:anOrder modes:modes];
96 }
97 
98 - (id)initWithSelector:(SEL)aSelector target:(SEL)aTarget argument:(id)anArgument order:(unsigned)anOrder modes:(CPArray)modes
99 {
100  self = [super init];
101 
102  if (self)
103  {
104  _selector = aSelector;
105  _target = aTarget;
106  _argument = anArgument;
107  _order = anOrder;
108  _runLoopModes = modes;
109  _isValid = YES;
110  }
111 
112  return self;
113 }
114 
115 - (id)initWithBlock:(Function)aBlock argument:(id)anArgument order:(unsigned)anOrder modes:(CPArray)modes
116 {
117  self = [super init];
118 
119  if (self)
120  {
121  _block = aBlock;
122  _argument = anArgument;
123  _order = anOrder;
124  _runLoopModes = modes;
125  _isValid = YES;
126  }
127 
128  return self;
129 }
130 
131 - (SEL)selector
132 {
133  return _selector;
134 }
135 
136 - (id)target
137 {
138  return _target;
139 }
140 
141 - (id)argument
142 {
143  return _argument;
144 }
145 
146 - (unsigned)order
147 {
148  return _order;
149 }
150 
151 - (BOOL)fireInMode:(CPString)aRunLoopMode
152 {
153  if (!_isValid)
154  return YES;
155 
156  if ([_runLoopModes containsObject:aRunLoopMode])
157  {
158  if (_block)
159  _block(_argument);
160  else
161  [_target performSelector:_selector withObject:_argument];
162 
163  return YES;
164  }
165 
166  return NO;
167 }
168 
169 - (void)invalidate
170 {
171  _isValid = NO;
172 }
173 
174 @end
175 
177 
186 @implementation CPRunLoop : CPObject
187 {
188  BOOL _runLoopLock;
189 
190  Object _timersForModes; //should be a dictionary to allow lookups by mode
191  Object _nativeTimersForModes;
192  CPDate _nextTimerFireDatesForModes;
193  BOOL _didAddTimer;
194  CPDate _effectiveDate;
195 
196  CPArray _orderedPerforms;
197  int _runLoopInsuranceTimer;
198 }
199 
200 /*
201  @ignore
202 */
203 + (void)initialize
204 {
205  if (self !== [CPRunLoop class])
206  return;
207 
208  CPMainRunLoop = [[CPRunLoop alloc] init];
209 }
210 
211 - (id)init
212 {
213  self = [super init];
214 
215  if (self)
216  {
217  _orderedPerforms = [];
218 
219  _timersForModes = {};
220  _nativeTimersForModes = {};
221  _nextTimerFireDatesForModes = {};
222  }
223 
224  return self;
225 }
226 
230 + (CPRunLoop)currentRunLoop
231 {
232  return CPMainRunLoop;
233 }
234 
238 + (CPRunLoop)mainRunLoop
239 {
240  return CPMainRunLoop;
241 }
242 
251 - (void)performSelector:(SEL)aSelector target:(id)aTarget argument:(id)anArgument order:(int)anOrder modes:(CPArray)modes
252 {
253  var perform = [_CPRunLoopPerform performWithSelector:aSelector target:aTarget argument:anArgument order:anOrder modes:modes],
254  count = _orderedPerforms.length;
255 
256  // We sort ourselves in reverse because we iterate this list backwards.
257  while (count--)
258  if (anOrder < [_orderedPerforms[count] order])
259  break;
260 
261  _orderedPerforms.splice(count + 1, 0, perform);
262 }
263 
264 
268 - (void)performBlock:(Function)aBlock argument:(id)anArgument order:(int)anOrder modes:(CPArray)modes
269 {
270  var perform = [_CPRunLoopPerform performWithBlock:aBlock argument:anArgument order:anOrder modes:modes],
271  count = _orderedPerforms.length;
272 
273  // We sort ourselves in reverse because we iterate this list backwards.
274  while (count--)
275  if (anOrder < [_orderedPerforms[count] order])
276  break;
277 
278  _orderedPerforms.splice(count + 1, 0, perform);
279 }
280 
287 - (void)cancelPerformSelector:(SEL)aSelector target:(id)aTarget argument:(id)anArgument
288 {
289  var count = _orderedPerforms.length;
290 
291  while (count--)
292  {
293  var perform = _orderedPerforms[count];
294 
295  if ([perform selector] === aSelector && [perform target] == aTarget && [perform argument] == anArgument)
296  [_orderedPerforms[count] invalidate];
297  }
298 }
299 
300 /*
301  @ignore
302 */
303 - (void)performSelectors
304 {
305  [self limitDateForMode:CPDefaultRunLoopMode];
306 }
307 
311 - (void)addTimer:(CPTimer)aTimer forMode:(CPString)aMode
312 {
313  // FIXME: Timer already added...
314  if (_timersForModes[aMode])
315  _timersForModes[aMode].push(aTimer);
316  else
317  _timersForModes[aMode] = [aTimer];
318 
319  _didAddTimer = YES;
320 
321  if (!aTimer._lastNativeRunLoopsForModes)
322  aTimer._lastNativeRunLoopsForModes = {};
323 
324  aTimer._lastNativeRunLoopsForModes[aMode] = CPRunLoopLastNativeRunLoop;
325 
326  // FIXME: Hack for not doing this in CommonJS
327  if ([CFBundle.environments() indexOfObject:("Browser")] !== CPNotFound)
328  {
329  if (!_runLoopInsuranceTimer)
330  _runLoopInsuranceTimer = window.setNativeTimeout(function()
331  {
332  [self limitDateForMode:CPDefaultRunLoopMode];
333  }, 0);
334  }
335 }
336 
340 - (CPDate)limitDateForMode:(CPString)aMode
341 {
342  //simple locking to try to prevent concurrent iterating over timers
343  if (_runLoopLock)
344  return;
345 
346  _runLoopLock = YES;
347 
348  // FIXME: Hack for not doing this in CommonJS
349  if ([CFBundle.environments() indexOfObject:("Browser")] !== CPNotFound)
350  {
351  if (_runLoopInsuranceTimer)
352  {
353  window.clearNativeTimeout(_runLoopInsuranceTimer);
354  _runLoopInsuranceTimer = nil;
355  }
356  }
357 
358  var now = _effectiveDate ? [_effectiveDate laterDate:[CPDate date]] : [CPDate date],
359  nextFireDate = nil,
360  nextTimerFireDate = _nextTimerFireDatesForModes[aMode];
361 
362  // Perform Timers if necessary
363 
364  if (_didAddTimer || nextTimerFireDate && nextTimerFireDate <= now)
365  {
366  _didAddTimer = NO;
367 
368  // Cancel existing window.setTimeout
369  if (_nativeTimersForModes[aMode] !== nil)
370  {
371  window.clearNativeTimeout(_nativeTimersForModes[aMode]);
372 
373  _nativeTimersForModes[aMode] = nil;
374  }
375 
376  // Empty timers to avoid catastrophe if a timer is added during a timer fire.
377  var timers = _timersForModes[aMode],
378  index = timers.length;
379 
380  _timersForModes[aMode] = nil;
381 
382  // If we're running in CommonJS (unit tests) we shouldn't wait for at least 1 native run loop
383  // since those will never happen.
384  var hasNativeTimers = [CFBundle.environments() indexOfObject:("Browser")] !== CPNotFound;
385 
386  // Loop through timers looking for ones that had fired
387  while (index--)
388  {
389  var timer = timers[index];
390 
391  if ((!hasNativeTimers || timer._lastNativeRunLoopsForModes[aMode] < CPRunLoopLastNativeRunLoop) && timer._isValid && timer._fireDate <= now)
392  [timer fire];
393 
394  // Timer may or may not still be valid
395  if (timer._isValid)
396  nextFireDate = (nextFireDate === nil) ? timer._fireDate : [nextFireDate earlierDate:timer._fireDate];
397 
398  else
399  {
400  // FIXME: Is there an issue with reseting the fire date in -fire? or adding it back to the run loop?...
401  timer._lastNativeRunLoopsForModes[aMode] = 0;
402 
403  timers.splice(index, 1);
404  }
405  }
406 
407  // Timers may have been added during the firing of timers
408  // They do NOT get a shot at firing, because they certainly
409  // haven't gone through one native timer.
410  var newTimers = _timersForModes[aMode];
411 
412  if (newTimers && newTimers.length)
413  {
414  index = newTimers.length;
415 
416  while (index--)
417  {
418  var timer = newTimers[index];
419 
420  if ([timer isValid])
421  nextFireDate = (nextFireDate === nil) ? timer._fireDate : [nextFireDate earlierDate:timer._fireDate];
422  else
423  newTimers.splice(index, 1);
424  }
425 
426  _timersForModes[aMode] = newTimers.concat(timers);
427  }
428  else
429  _timersForModes[aMode] = timers;
430 
431  _nextTimerFireDatesForModes[aMode] = nextFireDate;
432 
433  //initiate a new window.setTimeout if there are any timers
434  if (_nextTimerFireDatesForModes[aMode] !== nil)
435  _nativeTimersForModes[aMode] = window.setNativeTimeout(function()
436  {
437  _effectiveDate = nextFireDate;
438  _nativeTimersForModes[aMode] = nil;
440  [self limitDateForMode:aMode];
441  _effectiveDate = nil;
442  }, MAX(0, [nextFireDate timeIntervalSinceNow] * 1000));
443  }
444 
445  // Run loop performers
446  var performs = _orderedPerforms,
447  index = performs.length;
448 
449  _orderedPerforms = [];
450 
451  while (index--)
452  {
453  var perform = performs[index];
454 
455  if ([perform fireInMode:CPDefaultRunLoopMode])
456  {
457  [_CPRunLoopPerform _poolPerform:perform];
458 
459  performs.splice(index, 1);
460  }
461  }
462 
463  if (_orderedPerforms.length)
464  {
465  _orderedPerforms = _orderedPerforms.concat(performs);
466  _orderedPerforms.sort(_CPRunLoopPerformCompare);
467  }
468  else
469  _orderedPerforms = performs;
470 
471  _runLoopLock = NO;
472 
473  return nextFireDate;
474 }
475 
476 @end
var CPRunLoopLastNativeRunLoop
Definition: CPRunLoop.j:176
id init()
Definition: CALayer.j:126
A representation of a single point in time.
Definition: CPDate.h:2
The main run loop for the application.
Definition: CPRunLoop.h:2
An immutable string (collection of characters).
Definition: CPString.h:2
CPDate limitDateForMode:(CPString aMode)
Definition: CPRunLoop.j:340
A timer object that can send a message after the given time interval.
Definition: CPTimer.h:2
CPNotFound
Definition: CPObjJRuntime.j:62
id init()
Definition: CPObject.j:145
CPDefaultRunLoopMode
Definition: CPRunLoop.j:28
id date()
Definition: CPDate.j:42
id alloc()
Definition: CPObject.j:130