API  1.0.0
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  CPArray _observers;
199 }
200 
201 /*
202  @ignore
203 */
204 + (void)initialize
205 {
206  if (self !== [CPRunLoop class])
207  return;
208 
209  CPMainRunLoop = [[CPRunLoop alloc] init];
210 }
211 
212 - (id)init
213 {
214  self = [super init];
215 
216  if (self)
217  {
218  _orderedPerforms = [];
219 
220  _timersForModes = {};
221  _nativeTimersForModes = {};
222  _nextTimerFireDatesForModes = {};
223  _observers = nil;
224  }
225 
226  return self;
227 }
228 
232 + (CPRunLoop)currentRunLoop
233 {
234  return CPMainRunLoop;
235 }
236 
240 + (CPRunLoop)mainRunLoop
241 {
242  return CPMainRunLoop;
243 }
244 
253 - (void)performSelector:(SEL)aSelector target:(id)aTarget argument:(id)anArgument order:(int)anOrder modes:(CPArray)modes
254 {
255  var perform = [_CPRunLoopPerform performWithSelector:aSelector target:aTarget argument:anArgument order:anOrder modes:modes],
256  count = _orderedPerforms.length;
257 
258  // We sort ourselves in reverse because we iterate this list backwards.
259  while (count--)
260  if (anOrder < [_orderedPerforms[count] order])
261  break;
262 
263  _orderedPerforms.splice(count + 1, 0, perform);
264 }
265 
266 
270 - (void)performBlock:(Function)aBlock argument:(id)anArgument order:(int)anOrder modes:(CPArray)modes
271 {
272  var perform = [_CPRunLoopPerform performWithBlock:aBlock argument:anArgument order:anOrder modes:modes],
273  count = _orderedPerforms.length;
274 
275  // We sort ourselves in reverse because we iterate this list backwards.
276  while (count--)
277  if (anOrder < [_orderedPerforms[count] order])
278  break;
279 
280  _orderedPerforms.splice(count + 1, 0, perform);
281 }
282 
289 - (void)cancelPerformSelector:(SEL)aSelector target:(id)aTarget argument:(id)anArgument
290 {
291  var count = _orderedPerforms.length;
292 
293  while (count--)
294  {
295  var perform = _orderedPerforms[count];
296 
297  if ([perform selector] === aSelector && [perform target] == aTarget && [perform argument] == anArgument)
298  [_orderedPerforms[count] invalidate];
299  }
300 }
301 
302 /*
303  @ignore
304 */
305 - (void)performSelectors
306 {
307  [self limitDateForMode:CPDefaultRunLoopMode];
308 }
309 
313 - (void)addTimer:(CPTimer)aTimer forMode:(CPString)aMode
314 {
315  // FIXME: Timer already added...
316  if (_timersForModes[aMode])
317  _timersForModes[aMode].push(aTimer);
318  else
319  _timersForModes[aMode] = [aTimer];
320 
321  _didAddTimer = YES;
322 
323  if (!aTimer._lastNativeRunLoopsForModes)
324  aTimer._lastNativeRunLoopsForModes = {};
325 
326  aTimer._lastNativeRunLoopsForModes[aMode] = CPRunLoopLastNativeRunLoop;
327 
328  // FIXME: Hack for not doing this in CommonJS
329  if ([CFBundle.environments() indexOfObject:("Browser")] !== CPNotFound)
330  {
331  if (!_runLoopInsuranceTimer)
332  _runLoopInsuranceTimer = window.setNativeTimeout(function()
333  {
334  [self limitDateForMode:CPDefaultRunLoopMode];
335  }, 0);
336  }
337 }
338 
342 - (CPDate)limitDateForMode:(CPString)aMode
343 {
344  //simple locking to try to prevent concurrent iterating over timers
345  if (_runLoopLock)
346  return;
347 
348  _runLoopLock = YES;
349 
350  // FIXME: Hack for not doing this in CommonJS
351  if ([CFBundle.environments() indexOfObject:("Browser")] !== CPNotFound)
352  {
353  if (_runLoopInsuranceTimer)
354  {
355  window.clearNativeTimeout(_runLoopInsuranceTimer);
356  _runLoopInsuranceTimer = nil;
357  }
358  }
359 
360  var now = _effectiveDate ? [_effectiveDate laterDate:[CPDate date]] : [CPDate date],
361  nextFireDate = nil,
362  nextTimerFireDate = _nextTimerFireDatesForModes[aMode];
363 
364  // Perform Timers if necessary
365 
366  if (_didAddTimer || nextTimerFireDate && nextTimerFireDate <= now)
367  {
368  _didAddTimer = NO;
369 
370  // Cancel existing window.setTimeout
371  if (_nativeTimersForModes[aMode] !== nil)
372  {
373  window.clearNativeTimeout(_nativeTimersForModes[aMode]);
374 
375  _nativeTimersForModes[aMode] = nil;
376  }
377 
378  // Empty timers to avoid catastrophe if a timer is added during a timer fire.
379  var timers = _timersForModes[aMode],
380  index = timers.length;
381 
382  _timersForModes[aMode] = nil;
383 
384  // If we're running in CommonJS (unit tests) we shouldn't wait for at least 1 native run loop
385  // since those will never happen.
386  var hasNativeTimers = [CFBundle.environments() indexOfObject:("Browser")] !== CPNotFound;
387 
388  // Loop through timers looking for ones that had fired
389  while (index--)
390  {
391  var timer = timers[index];
392 
393  if ((!hasNativeTimers || timer._lastNativeRunLoopsForModes[aMode] < CPRunLoopLastNativeRunLoop) && timer._isValid && timer._fireDate <= now)
394  [timer fire];
395 
396  // Timer may or may not still be valid
397  if (timer._isValid)
398  nextFireDate = (nextFireDate === nil) ? timer._fireDate : [nextFireDate earlierDate:timer._fireDate];
399 
400  else
401  {
402  // FIXME: Is there an issue with reseting the fire date in -fire? or adding it back to the run loop?...
403  timer._lastNativeRunLoopsForModes[aMode] = 0;
404 
405  timers.splice(index, 1);
406  }
407  }
408 
409  // Timers may have been added during the firing of timers
410  // They do NOT get a shot at firing, because they certainly
411  // haven't gone through one native timer.
412  var newTimers = _timersForModes[aMode];
413 
414  if (newTimers && newTimers.length)
415  {
416  index = newTimers.length;
417 
418  while (index--)
419  {
420  var timer = newTimers[index];
421 
422  if ([timer isValid])
423  nextFireDate = (nextFireDate === nil) ? timer._fireDate : [nextFireDate earlierDate:timer._fireDate];
424  else
425  newTimers.splice(index, 1);
426  }
427 
428  _timersForModes[aMode] = newTimers.concat(timers);
429  }
430  else
431  _timersForModes[aMode] = timers;
432 
433  _nextTimerFireDatesForModes[aMode] = nextFireDate;
434 
435  //initiate a new window.setTimeout if there are any timers
436  if (_nextTimerFireDatesForModes[aMode] !== nil)
437  _nativeTimersForModes[aMode] = window.setNativeTimeout(function()
438  {
439  _effectiveDate = nextFireDate;
440  _nativeTimersForModes[aMode] = nil;
442  [self limitDateForMode:aMode];
443  _effectiveDate = nil;
444  }, MAX(0, [nextFireDate timeIntervalSinceNow] * 1000));
445  }
446 
447  // Run loop performers
448  var performs = _orderedPerforms,
449  index = performs.length;
450 
451  _orderedPerforms = [];
452 
453  while (index--)
454  {
455  var perform = performs[index];
456 
457  if ([perform fireInMode:CPDefaultRunLoopMode])
458  {
459  [_CPRunLoopPerform _poolPerform:perform];
460 
461  performs.splice(index, 1);
462  }
463  }
464 
465  if (_orderedPerforms.length)
466  {
467  _orderedPerforms = _orderedPerforms.concat(performs);
468  _orderedPerforms.sort(_CPRunLoopPerformCompare);
469  }
470  else
471  _orderedPerforms = performs;
472 
473  if (_observers)
474  {
475  var count = _observers.length;
476  while(count--)
477  {
478  var obs = _observers[count];
479  obs.callout();
480 
481  if (!obs.repeats)
482  _observers.splice(count, 1);
483  }
484  }
485 
486  _runLoopLock = NO;
487 
488  return nextFireDate;
489 }
490 
491 @end
492 
493 function CFRunLoopObserver(activities, repeats, order, callout, context)
494 {
495  this.activities = activities;
496  this.repeats = repeats;
497  this.order = order;
498  this.callout = callout;
499  this.context = context;
500 
501  this.isvalid = true;
502 };
503 
504 function CFRunLoopObserverCreate(activities, repeats, order, callout, context)
505 {
506  return new CFRunLoopObserver(activities, repeats, order, callout, context);
507 };
508 
509 function CFRunLoopAddObserver(runloop, observer, mode)
510 {
511  var observers = runloop._observers;
512 
513  if (!observers)
514  observers = (runloop._observers = []);
515 
516  if (observers.indexOf(observer) == -1)
517  observers.push(observer);
518 };
519 
520 function CFRunLoopObserverInvalidate(runloop, observer, mode)
521 {
522  CFRunLoopRemoveObserver(runloop, observer, mode);
523 };
524 
525 function CFRunLoopRemoveObserver(runloop, observer, mode)
526 {
527  var observers = runloop._observers;
528  if (observers)
529  {
530  var idx = observers.indexOf(observer);
531  if (idx !== -1)
532  {
533  observers.splice(idx, 1);
534 
535  if (observers.length == 0)
536  runloop._observers = nil;
537  }
538  }
539 };
var CPRunLoopLastNativeRunLoop
Definition: CPRunLoop.j:176
id init()
Definition: CALayer.j:126
function CFRunLoopAddObserver(runloop, observer, mode)
Definition: CPRunLoop.j:509
function CFRunLoopObserverInvalidate(runloop, observer, mode)
Definition: CPRunLoop.j:520
A representation of a single point in time.
Definition: CPDate.h:2
The main run loop for the application.
Definition: CPRunLoop.h:2
function CFRunLoopObserver(activities, repeats, order, callout, context)
Definition: CPRunLoop.j:493
function CFRunLoopRemoveObserver(runloop, observer, mode)
Definition: CPRunLoop.j:525
An immutable string (collection of characters).
Definition: CPString.h:2
CPDate limitDateForMode:(CPString aMode)
Definition: CPRunLoop.j:342
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
CompletionHandlerAgent prototype invalidate
id date()
Definition: CPDate.j:42
id alloc()
Definition: CPObject.j:130
function CFRunLoopObserverCreate(activities, repeats, order, callout, context)
Definition: CPRunLoop.j:504