summary refs log tree commit diff stats
path: root/libs/cocos2d/CCScheduler.m
diff options
context:
space:
mode:
Diffstat (limited to 'libs/cocos2d/CCScheduler.m')
-rwxr-xr-xlibs/cocos2d/CCScheduler.m657
1 files changed, 657 insertions, 0 deletions
diff --git a/libs/cocos2d/CCScheduler.m b/libs/cocos2d/CCScheduler.m new file mode 100755 index 0000000..a14ca10 --- /dev/null +++ b/libs/cocos2d/CCScheduler.m
@@ -0,0 +1,657 @@
1/*
2 * cocos2d for iPhone: http://www.cocos2d-iphone.org
3 *
4 * Copyright (c) 2008-2010 Ricardo Quesada
5 * Copyright (c) 2011 Zynga Inc.
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a copy
8 * of this software and associated documentation files (the "Software"), to deal
9 * in the Software without restriction, including without limitation the rights
10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 * copies of the Software, and to permit persons to whom the Software is
12 * furnished to do so, subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 * THE SOFTWARE.
24 */
25
26
27// cocos2d imports
28#import "CCScheduler.h"
29#import "ccMacros.h"
30#import "Support/uthash.h"
31#import "Support/utlist.h"
32#import "Support/ccCArray.h"
33
34//
35// Data structures
36//
37#pragma mark -
38#pragma mark Data Structures
39
40// A list double-linked list used for "updates with priority"
41typedef struct _listEntry
42{
43 struct _listEntry *prev, *next;
44 TICK_IMP impMethod;
45 id target; // not retained (retained by hashUpdateEntry)
46 NSInteger priority;
47 BOOL paused;
48 BOOL markedForDeletion; // selector will no longer be called and entry will be removed at end of the next tick
49} tListEntry;
50
51typedef struct _hashUpdateEntry
52{
53 tListEntry **list; // Which list does it belong to ?
54 tListEntry *entry; // entry in the list
55 id target; // hash key (retained)
56 UT_hash_handle hh;
57} tHashUpdateEntry;
58
59// Hash Element used for "selectors with interval"
60typedef struct _hashSelectorEntry
61{
62 struct ccArray *timers;
63 id target; // hash key (retained)
64 unsigned int timerIndex;
65 CCTimer *currentTimer;
66 BOOL currentTimerSalvaged;
67 BOOL paused;
68 UT_hash_handle hh;
69} tHashSelectorEntry;
70
71
72
73//
74// CCTimer
75//
76#pragma mark -
77#pragma mark - CCTimer
78
79@implementation CCTimer
80
81@synthesize interval;
82
83-(id) init
84{
85 NSAssert(NO, @"CCTimer: Init not supported.");
86 [self release];
87 return nil;
88}
89
90+(id) timerWithTarget:(id)t selector:(SEL)s
91{
92 return [[[self alloc] initWithTarget:t selector:s] autorelease];
93}
94
95+(id) timerWithTarget:(id)t selector:(SEL)s interval:(ccTime) i
96{
97 return [[[self alloc] initWithTarget:t selector:s interval:i] autorelease];
98}
99
100-(id) initWithTarget:(id)t selector:(SEL)s
101{
102 return [self initWithTarget:t selector:s interval:0];
103}
104
105-(id) initWithTarget:(id)t selector:(SEL)s interval:(ccTime) seconds
106{
107 if( (self=[super init]) ) {
108#if COCOS2D_DEBUG
109 NSMethodSignature *sig = [t methodSignatureForSelector:s];
110 NSAssert(sig !=0 , @"Signature not found for selector - does it have the following form? -(void) name: (ccTime) dt");
111#endif
112
113 // target is not retained. It is retained in the hash structure
114 target = t;
115 selector = s;
116 impMethod = (TICK_IMP) [t methodForSelector:s];
117 elapsed = -1;
118 interval = seconds;
119 }
120 return self;
121}
122
123- (NSString*) description
124{
125 return [NSString stringWithFormat:@"<%@ = %08X | target:%@ selector:(%@)>", [self class], self, [target class], NSStringFromSelector(selector)];
126}
127
128-(void) dealloc
129{
130 CCLOGINFO(@"cocos2d: deallocing %@", self);
131 [super dealloc];
132}
133
134-(void) update: (ccTime) dt
135{
136 if( elapsed == - 1)
137 elapsed = 0;
138 else
139 elapsed += dt;
140 if( elapsed >= interval ) {
141 impMethod(target, selector, elapsed);
142 elapsed = 0;
143 }
144}
145@end
146
147//
148// CCScheduler
149//
150#pragma mark -
151#pragma mark - CCScheduler
152
153@interface CCScheduler (Private)
154-(void) removeHashElement:(tHashSelectorEntry*)element;
155@end
156
157@implementation CCScheduler
158
159static CCScheduler *sharedScheduler;
160
161@synthesize timeScale = timeScale_;
162
163+ (CCScheduler *)sharedScheduler
164{
165 if (!sharedScheduler)
166 sharedScheduler = [[CCScheduler alloc] init];
167
168 return sharedScheduler;
169}
170
171+(id)alloc
172{
173 NSAssert(sharedScheduler == nil, @"Attempted to allocate a second instance of a singleton.");
174 return [super alloc];
175}
176
177+(void)purgeSharedScheduler
178{
179 [sharedScheduler release];
180 sharedScheduler = nil;
181}
182
183- (id) init
184{
185 if( (self=[super init]) ) {
186 timeScale_ = 1.0f;
187
188 // used to trigger CCTimer#update
189 updateSelector = @selector(update:);
190 impMethod = (TICK_IMP) [CCTimer instanceMethodForSelector:updateSelector];
191
192 // updates with priority
193 updates0 = NULL;
194 updatesNeg = NULL;
195 updatesPos = NULL;
196 hashForUpdates = NULL;
197
198 // selectors with interval
199 currentTarget = nil;
200 currentTargetSalvaged = NO;
201 hashForSelectors = nil;
202 updateHashLocked = NO;
203 }
204
205 return self;
206}
207
208- (void) dealloc
209{
210 CCLOG(@"cocos2d: deallocing %@", self);
211
212 [self unscheduleAllSelectors];
213
214 sharedScheduler = nil;
215
216 [super dealloc];
217}
218
219
220#pragma mark CCScheduler - Custom Selectors
221
222-(void) removeHashElement:(tHashSelectorEntry*)element
223{
224 ccArrayFree(element->timers);
225 [element->target release];
226 HASH_DEL(hashForSelectors, element);
227 free(element);
228}
229
230-(void) scheduleSelector:(SEL)selector forTarget:(id)target interval:(ccTime)interval paused:(BOOL)paused
231{
232 NSAssert( selector != nil, @"Argument selector must be non-nil");
233 NSAssert( target != nil, @"Argument target must be non-nil");
234
235 tHashSelectorEntry *element = NULL;
236 HASH_FIND_INT(hashForSelectors, &target, element);
237
238 if( ! element ) {
239 element = calloc( sizeof( *element ), 1 );
240 element->target = [target retain];
241 HASH_ADD_INT( hashForSelectors, target, element );
242
243 // Is this the 1st element ? Then set the pause level to all the selectors of this target
244 element->paused = paused;
245
246 } else
247 NSAssert( element->paused == paused, @"CCScheduler. Trying to schedule a selector with a pause value different than the target");
248
249
250 if( element->timers == nil )
251 element->timers = ccArrayNew(10);
252 else
253 {
254 for( unsigned int i=0; i< element->timers->num; i++ ) {
255 CCTimer *timer = element->timers->arr[i];
256 if( selector == timer->selector ) {
257 CCLOG(@"CCScheduler#scheduleSelector. Selector already scheduled. Updating interval from: %.2f to %.2f", timer->interval, interval);
258 timer->interval = interval;
259 return;
260 }
261 }
262 ccArrayEnsureExtraCapacity(element->timers, 1);
263 }
264
265 CCTimer *timer = [[CCTimer alloc] initWithTarget:target selector:selector interval:interval];
266 ccArrayAppendObject(element->timers, timer);
267 [timer release];
268}
269
270-(void) unscheduleSelector:(SEL)selector forTarget:(id)target
271{
272 // explicity handle nil arguments when removing an object
273 if( target==nil && selector==NULL)
274 return;
275
276 NSAssert( target != nil, @"Target MUST not be nil");
277 NSAssert( selector != NULL, @"Selector MUST not be NULL");
278
279 tHashSelectorEntry *element = NULL;
280 HASH_FIND_INT(hashForSelectors, &target, element);
281
282 if( element ) {
283
284 for( unsigned int i=0; i< element->timers->num; i++ ) {
285 CCTimer *timer = element->timers->arr[i];
286
287
288 if( selector == timer->selector ) {
289
290 if( timer == element->currentTimer && !element->currentTimerSalvaged ) {
291 [element->currentTimer retain];
292 element->currentTimerSalvaged = YES;
293 }
294
295 ccArrayRemoveObjectAtIndex(element->timers, i );
296
297 // update timerIndex in case we are in tick:, looping over the actions
298 if( element->timerIndex >= i )
299 element->timerIndex--;
300
301 if( element->timers->num == 0 ) {
302 if( currentTarget == element )
303 currentTargetSalvaged = YES;
304 else
305 [self removeHashElement: element];
306 }
307 return;
308 }
309 }
310 }
311
312 // Not Found
313// NSLog(@"CCScheduler#unscheduleSelector:forTarget: selector not found: %@", selString);
314
315}
316
317#pragma mark CCScheduler - Update Specific
318
319-(void) priorityIn:(tListEntry**)list target:(id)target priority:(NSInteger)priority paused:(BOOL)paused
320{
321 tListEntry *listElement = malloc( sizeof(*listElement) );
322
323 listElement->target = target;
324 listElement->priority = priority;
325 listElement->paused = paused;
326 listElement->impMethod = (TICK_IMP) [target methodForSelector:updateSelector];
327 listElement->next = listElement->prev = NULL;
328 listElement->markedForDeletion = NO;
329
330 // empty list ?
331 if( ! *list ) {
332 DL_APPEND( *list, listElement );
333
334 } else {
335 BOOL added = NO;
336
337 for( tListEntry *elem = *list; elem ; elem = elem->next ) {
338 if( priority < elem->priority ) {
339
340 if( elem == *list )
341 DL_PREPEND(*list, listElement);
342 else {
343 listElement->next = elem;
344 listElement->prev = elem->prev;
345
346 elem->prev->next = listElement;
347 elem->prev = listElement;
348 }
349
350 added = YES;
351 break;
352 }
353 }
354
355 // Not added? priority has the higher value. Append it.
356 if( !added )
357 DL_APPEND(*list, listElement);
358 }
359
360 // update hash entry for quicker access
361 tHashUpdateEntry *hashElement = calloc( sizeof(*hashElement), 1 );
362 hashElement->target = [target retain];
363 hashElement->list = list;
364 hashElement->entry = listElement;
365 HASH_ADD_INT(hashForUpdates, target, hashElement );
366}
367
368-(void) appendIn:(tListEntry**)list target:(id)target paused:(BOOL)paused
369{
370 tListEntry *listElement = malloc( sizeof( * listElement ) );
371
372 listElement->target = target;
373 listElement->paused = paused;
374 listElement->markedForDeletion = NO;
375 listElement->impMethod = (TICK_IMP) [target methodForSelector:updateSelector];
376
377 DL_APPEND(*list, listElement);
378
379
380 // update hash entry for quicker access
381 tHashUpdateEntry *hashElement = calloc( sizeof(*hashElement), 1 );
382 hashElement->target = [target retain];
383 hashElement->list = list;
384 hashElement->entry = listElement;
385 HASH_ADD_INT(hashForUpdates, target, hashElement );
386}
387
388-(void) scheduleUpdateForTarget:(id)target priority:(NSInteger)priority paused:(BOOL)paused
389{
390 tHashUpdateEntry * hashElement = NULL;
391 HASH_FIND_INT(hashForUpdates, &target, hashElement);
392 if(hashElement)
393 {
394#if COCOS2D_DEBUG >= 1
395 NSAssert( hashElement->entry->markedForDeletion, @"CCScheduler: You can't re-schedule an 'update' selector'. Unschedule it first");
396#endif
397 // TODO : check if priority has changed!
398
399 hashElement->entry->markedForDeletion = NO;
400 return;
401 }
402
403 // most of the updates are going to be 0, that's way there
404 // is an special list for updates with priority 0
405 if( priority == 0 )
406 [self appendIn:&updates0 target:target paused:paused];
407
408 else if( priority < 0 )
409 [self priorityIn:&updatesNeg target:target priority:priority paused:paused];
410
411 else // priority > 0
412 [self priorityIn:&updatesPos target:target priority:priority paused:paused];
413}
414
415- (void) removeUpdateFromHash:(tListEntry*)entry
416{
417 tHashUpdateEntry * element = NULL;
418
419 HASH_FIND_INT(hashForUpdates, &entry->target, element);
420 if( element ) {
421 // list entry
422 DL_DELETE( *element->list, element->entry );
423 free( element->entry );
424
425 // hash entry
426 [element->target release];
427 HASH_DEL( hashForUpdates, element);
428 free(element);
429 }
430}
431
432-(void) unscheduleUpdateForTarget:(id)target
433{
434 if( target == nil )
435 return;
436
437 tHashUpdateEntry * element = NULL;
438 HASH_FIND_INT(hashForUpdates, &target, element);
439 if( element ) {
440 if(updateHashLocked)
441 element->entry->markedForDeletion = YES;
442 else
443 [self removeUpdateFromHash:element->entry];
444
445// // list entry
446// DL_DELETE( *element->list, element->entry );
447// free( element->entry );
448//
449// // hash entry
450// [element->target release];
451// HASH_DEL( hashForUpdates, element);
452// free(element);
453 }
454}
455
456#pragma mark CCScheduler - Common for Update selector & Custom Selectors
457
458-(void) unscheduleAllSelectors
459{
460 // Custom Selectors
461 for(tHashSelectorEntry *element=hashForSelectors; element != NULL; ) {
462 id target = element->target;
463 element=element->hh.next;
464 [self unscheduleAllSelectorsForTarget:target];
465 }
466
467 // Updates selectors
468 tListEntry *entry, *tmp;
469 DL_FOREACH_SAFE( updates0, entry, tmp ) {
470 [self unscheduleUpdateForTarget:entry->target];
471 }
472 DL_FOREACH_SAFE( updatesNeg, entry, tmp ) {
473 [self unscheduleUpdateForTarget:entry->target];
474 }
475 DL_FOREACH_SAFE( updatesPos, entry, tmp ) {
476 [self unscheduleUpdateForTarget:entry->target];
477 }
478
479}
480
481-(void) unscheduleAllSelectorsForTarget:(id)target
482{
483 // explicit nil handling
484 if( target == nil )
485 return;
486
487 // Custom Selectors
488 tHashSelectorEntry *element = NULL;
489 HASH_FIND_INT(hashForSelectors, &target, element);
490
491 if( element ) {
492 if( ccArrayContainsObject(element->timers, element->currentTimer) && !element->currentTimerSalvaged ) {
493 [element->currentTimer retain];
494 element->currentTimerSalvaged = YES;
495 }
496 ccArrayRemoveAllObjects(element->timers);
497 if( currentTarget == element )
498 currentTargetSalvaged = YES;
499 else
500 [self removeHashElement:element];
501 }
502
503 // Update Selector
504 [self unscheduleUpdateForTarget:target];
505}
506
507-(void) resumeTarget:(id)target
508{
509 NSAssert( target != nil, @"target must be non nil" );
510
511 // Custom Selectors
512 tHashSelectorEntry *element = NULL;
513 HASH_FIND_INT(hashForSelectors, &target, element);
514 if( element )
515 element->paused = NO;
516
517 // Update selector
518 tHashUpdateEntry * elementUpdate = NULL;
519 HASH_FIND_INT(hashForUpdates, &target, elementUpdate);
520 if( elementUpdate ) {
521 NSAssert( elementUpdate->entry != NULL, @"resumeTarget: unknown error");
522 elementUpdate->entry->paused = NO;
523 }
524}
525
526-(void) pauseTarget:(id)target
527{
528 NSAssert( target != nil, @"target must be non nil" );
529
530 // Custom selectors
531 tHashSelectorEntry *element = NULL;
532 HASH_FIND_INT(hashForSelectors, &target, element);
533 if( element )
534 element->paused = YES;
535
536 // Update selector
537 tHashUpdateEntry * elementUpdate = NULL;
538 HASH_FIND_INT(hashForUpdates, &target, elementUpdate);
539 if( elementUpdate ) {
540 NSAssert( elementUpdate->entry != NULL, @"pauseTarget: unknown error");
541 elementUpdate->entry->paused = YES;
542 }
543
544}
545
546-(BOOL) isTargetPaused:(id)target
547{
548 NSAssert( target != nil, @"target must be non nil" );
549
550 // Custom selectors
551 tHashSelectorEntry *element = NULL;
552 HASH_FIND_INT(hashForSelectors, &target, element);
553 if( element )
554 {
555 return element->paused;
556 }
557 return NO; // should never get here
558
559}
560
561#pragma mark CCScheduler - Main Loop
562
563-(void) tick: (ccTime) dt
564{
565 updateHashLocked = YES;
566
567 if( timeScale_ != 1.0f )
568 dt *= timeScale_;
569
570 // Iterate all over the Updates selectors
571 tListEntry *entry, *tmp;
572
573 // updates with priority < 0
574 DL_FOREACH_SAFE( updatesNeg, entry, tmp ) {
575 if( ! entry->paused && !entry->markedForDeletion )
576 entry->impMethod( entry->target, updateSelector, dt );
577 }
578
579 // updates with priority == 0
580 DL_FOREACH_SAFE( updates0, entry, tmp ) {
581 if( ! entry->paused && !entry->markedForDeletion )
582 {
583 entry->impMethod( entry->target, updateSelector, dt );
584 }
585 }
586
587 // updates with priority > 0
588 DL_FOREACH_SAFE( updatesPos, entry, tmp ) {
589 if( ! entry->paused && !entry->markedForDeletion )
590 entry->impMethod( entry->target, updateSelector, dt );
591 }
592
593 // Iterate all over the custome selectors
594 for(tHashSelectorEntry *elt=hashForSelectors; elt != NULL; ) {
595
596 currentTarget = elt;
597 currentTargetSalvaged = NO;
598
599 if( ! currentTarget->paused ) {
600
601 // The 'timers' ccArray may change while inside this loop.
602 for( elt->timerIndex = 0; elt->timerIndex < elt->timers->num; elt->timerIndex++) {
603 elt->currentTimer = elt->timers->arr[elt->timerIndex];
604 elt->currentTimerSalvaged = NO;
605
606 impMethod( elt->currentTimer, updateSelector, dt);
607
608 if( elt->currentTimerSalvaged ) {
609 // The currentTimer told the remove itself. To prevent the timer from
610 // accidentally deallocating itself before finishing its step, we retained
611 // it. Now that step is done, it's safe to release it.
612 [elt->currentTimer release];
613 }
614
615 elt->currentTimer = nil;
616 }
617 }
618
619 // elt, at this moment, is still valid
620 // so it is safe to ask this here (issue #490)
621 elt = elt->hh.next;
622
623 // only delete currentTarget if no actions were scheduled during the cycle (issue #481)
624 if( currentTargetSalvaged && currentTarget->timers->num == 0 )
625 [self removeHashElement:currentTarget];
626 }
627
628 // delete all updates that are morked for deletion
629 // updates with priority < 0
630 DL_FOREACH_SAFE( updatesNeg, entry, tmp ) {
631 if(entry->markedForDeletion )
632 {
633 [self removeUpdateFromHash:entry];
634 }
635 }
636
637 // updates with priority == 0
638 DL_FOREACH_SAFE( updates0, entry, tmp ) {
639 if(entry->markedForDeletion )
640 {
641 [self removeUpdateFromHash:entry];
642 }
643 }
644
645 // updates with priority > 0
646 DL_FOREACH_SAFE( updatesPos, entry, tmp ) {
647 if(entry->markedForDeletion )
648 {
649 [self removeUpdateFromHash:entry];
650 }
651 }
652
653 updateHashLocked = NO;
654 currentTarget = nil;
655}
656@end
657