/* * cocos2d for iPhone: http://www.cocos2d-iphone.org * * Copyright (c) 2009 Valentin Milea * * Copyright (c) 2008-2010 Ricardo Quesada * Copyright (c) 2011 Zynga Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ #import "CCActionManager.h" #import "CCScheduler.h" #import "ccMacros.h" // // singleton stuff // static CCActionManager *sharedManager_ = nil; @interface CCActionManager (Private) -(void) removeActionAtIndex:(NSUInteger)index hashElement:(tHashElement*)element; -(void) deleteHashElement:(tHashElement*)element; -(void) actionAllocWithHashElement:(tHashElement*)element; @end @implementation CCActionManager #pragma mark ActionManager - init + (CCActionManager *)sharedManager { if (!sharedManager_) sharedManager_ = [[self alloc] init]; return sharedManager_; } +(id)alloc { NSAssert(sharedManager_ == nil, @"Attempted to allocate a second instance of a singleton."); return [super alloc]; } +(void)purgeSharedManager { [[CCScheduler sharedScheduler] unscheduleUpdateForTarget:self]; [sharedManager_ release]; sharedManager_ = nil; } -(id) init { if ((self=[super init]) ) { [[CCScheduler sharedScheduler] scheduleUpdateForTarget:self priority:0 paused:NO]; targets = NULL; } return self; } - (void) dealloc { CCLOGINFO( @"cocos2d: deallocing %@", self); [self removeAllActions]; sharedManager_ = nil; [super dealloc]; } #pragma mark ActionManager - Private -(void) deleteHashElement:(tHashElement*)element { ccArrayFree(element->actions); HASH_DEL(targets, element); // CCLOG(@"cocos2d: ---- buckets: %d/%d - %@", targets->entries, targets->size, element->target); [element->target release]; free(element); } -(void) actionAllocWithHashElement:(tHashElement*)element { // 4 actions per Node by default if( element->actions == nil ) element->actions = ccArrayNew(4); else if( element->actions->num == element->actions->max ) ccArrayDoubleCapacity(element->actions); } -(void) removeActionAtIndex:(NSUInteger)index hashElement:(tHashElement*)element { id action = element->actions->arr[index]; if( action == element->currentAction && !element->currentActionSalvaged ) { [element->currentAction retain]; element->currentActionSalvaged = YES; } ccArrayRemoveObjectAtIndex(element->actions, index); // update actionIndex in case we are in tick:, looping over the actions if( element->actionIndex >= index ) element->actionIndex--; if( element->actions->num == 0 ) { if( currentTarget == element ) currentTargetSalvaged = YES; else [self deleteHashElement: element]; } } #pragma mark ActionManager - Pause / Resume -(void) pauseTarget:(id)target { tHashElement *element = NULL; HASH_FIND_INT(targets, &target, element); if( element ) element->paused = YES; // else // CCLOG(@"cocos2d: pauseAllActions: Target not found"); } -(void) resumeTarget:(id)target { tHashElement *element = NULL; HASH_FIND_INT(targets, &target, element); if( element ) element->paused = NO; // else // CCLOG(@"cocos2d: resumeAllActions: Target not found"); } #pragma mark ActionManager - run -(void) addAction:(CCAction*)action target:(id)target paused:(BOOL)paused { NSAssert( action != nil, @"Argument action must be non-nil"); NSAssert( target != nil, @"Argument target must be non-nil"); tHashElement *element = NULL; HASH_FIND_INT(targets, &target, element); if( ! element ) { element = calloc( sizeof( *element ), 1 ); element->paused = paused; element->target = [target retain]; HASH_ADD_INT(targets, target, element); // CCLOG(@"cocos2d: ---- buckets: %d/%d - %@", targets->entries, targets->size, element->target); } [self actionAllocWithHashElement:element]; NSAssert( !ccArrayContainsObject(element->actions, action), @"runAction: Action already running"); ccArrayAppendObject(element->actions, action); [action startWithTarget:target]; } #pragma mark ActionManager - remove -(void) removeAllActions { for(tHashElement *element=targets; element != NULL; ) { id target = element->target; element = element->hh.next; [self removeAllActionsFromTarget:target]; } } -(void) removeAllActionsFromTarget:(id)target { // explicit nil handling if( target == nil ) return; tHashElement *element = NULL; HASH_FIND_INT(targets, &target, element); if( element ) { if( ccArrayContainsObject(element->actions, element->currentAction) && !element->currentActionSalvaged ) { [element->currentAction retain]; element->currentActionSalvaged = YES; } ccArrayRemoveAllObjects(element->actions); if( currentTarget == element ) currentTargetSalvaged = YES; else [self deleteHashElement:element]; } // else { // CCLOG(@"cocos2d: removeAllActionsFromTarget: Target not found"); // } } -(void) removeAction: (CCAction*) action { // explicit nil handling if (action == nil) return; tHashElement *element = NULL; id target = [action originalTarget]; HASH_FIND_INT(targets, &target, element ); if( element ) { NSUInteger i = ccArrayGetIndexOfObject(element->actions, action); if( i != NSNotFound ) [self removeActionAtIndex:i hashElement:element]; } // else { // CCLOG(@"cocos2d: removeAction: Target not found"); // } } -(void) removeActionByTag:(NSInteger)aTag target:(id)target { NSAssert( aTag != kCCActionTagInvalid, @"Invalid tag"); NSAssert( target != nil, @"Target should be ! nil"); tHashElement *element = NULL; HASH_FIND_INT(targets, &target, element); if( element ) { NSUInteger limit = element->actions->num; for( NSUInteger i = 0; i < limit; i++) { CCAction *a = element->actions->arr[i]; if( a.tag == aTag && [a originalTarget]==target) { [self removeActionAtIndex:i hashElement:element]; break; } } } } #pragma mark ActionManager - get -(CCAction*) getActionByTag:(NSInteger)aTag target:(id)target { NSAssert( aTag != kCCActionTagInvalid, @"Invalid tag"); tHashElement *element = NULL; HASH_FIND_INT(targets, &target, element); if( element ) { if( element->actions != nil ) { NSUInteger limit = element->actions->num; for( NSUInteger i = 0; i < limit; i++) { CCAction *a = element->actions->arr[i]; if( a.tag == aTag ) return a; } } // CCLOG(@"cocos2d: getActionByTag: Action not found"); } // else { // CCLOG(@"cocos2d: getActionByTag: Target not found"); // } return nil; } -(NSUInteger) numberOfRunningActionsInTarget:(id) target { tHashElement *element = NULL; HASH_FIND_INT(targets, &target, element); if( element ) return element->actions ? element->actions->num : 0; // CCLOG(@"cocos2d: numberOfRunningActionsInTarget: Target not found"); return 0; } #pragma mark ActionManager - main loop -(void) update: (ccTime) dt { for(tHashElement *elt = targets; elt != NULL; ) { currentTarget = elt; currentTargetSalvaged = NO; if( ! currentTarget->paused ) { // The 'actions' ccArray may change while inside this loop. for( currentTarget->actionIndex = 0; currentTarget->actionIndex < currentTarget->actions->num; currentTarget->actionIndex++) { currentTarget->currentAction = currentTarget->actions->arr[currentTarget->actionIndex]; currentTarget->currentActionSalvaged = NO; [currentTarget->currentAction step: dt]; if( currentTarget->currentActionSalvaged ) { // The currentAction told the node to remove it. To prevent the action from // accidentally deallocating itself before finishing its step, we retained // it. Now that step is done, it's safe to release it. [currentTarget->currentAction release]; } else if( [currentTarget->currentAction isDone] ) { [currentTarget->currentAction stop]; CCAction *a = currentTarget->currentAction; // Make currentAction nil to prevent removeAction from salvaging it. currentTarget->currentAction = nil; [self removeAction:a]; } currentTarget->currentAction = nil; } } // elt, at this moment, is still valid // so it is safe to ask this here (issue #490) elt = elt->hh.next; // only delete currentTarget if no actions were scheduled during the cycle (issue #481) if( currentTargetSalvaged && currentTarget->actions->num == 0 ) [self deleteHashElement:currentTarget]; } // issue #635 currentTarget = nil; } @end