cocos2d-x 源码剖析(10)

上一节讲CCActionInterval,这节讲CCActionInstant。同样为了方便,我们用CCPlace来讲解。作为了解过CCActionInterval的过来人来说,CCActionInstant意外的简单。还记得这两种Aciton之间的区别吗?前者需要一个duration,而后者调用一次就介绍了,自然也不用什么持续时间了。翻看源码,可以发现CCActionInstant和CCActionInterval是一套逻辑,毕竟他们都是CCActionInstant的子类。那么可以说所有的CCActionInstant都是这套逻辑了。下面是CCPlace的全部实现:

CCPlace* CCPlace::create(const CCPoint& pos)
{
    CCPlace *pRet = new CCPlace();
 
    if (pRet && pRet->initWithPosition(pos)) {
        pRet->autorelease();
        return pRet;
    }
 
    CC_SAFE_DELETE(pRet);
    return NULL;
}
 
bool CCPlace::initWithPosition(const CCPoint& pos) {
    m_tPosition = pos;
    return true;
}
 
CCObject * CCPlace::copyWithZone(CCZone *pZone) {
    CCZone *pNewZone = NULL;
    CCPlace *pRet = NULL;
 
    if (pZone && pZone->m_pCopyObject) {
        pRet = (CCPlace*) (pZone->m_pCopyObject);
    } else {
        pRet = new CCPlace();
        pZone = pNewZone = new CCZone(pRet);
    }
 
    CCActionInstant::copyWithZone(pZone);
    pRet->initWithPosition(m_tPosition);
    CC_SAFE_DELETE(pNewZone);
    return pRet;
}
 
void CCPlace::update(float time) {
    CC_UNUSED_PARAM(time);
    m_pTarget->setPosition(m_tPosition);
}


而在CCAction之中isDone被默认实现为:

bool CCAction::isDone()
{
    return true;
}

所以它的逻辑就是调用一次就删,就这么简单。这节有点短,我们再抓几个例子充数吧。比如说CCCallFunc。我之所以抓它来讲,是因为这个action很危险。而且他也有特殊的地方,比如说它的init函数:

bool CCCallFunc::initWithTarget(CCObject* pSelectorTarget) {
    if (pSelectorTarget) 
    {
        pSelectorTarget->retain();
    }
 
    if (m_pSelectorTarget) 
    {
        m_pSelectorTarget->release();
    }
 
    m_pSelectorTarget = pSelectorTarget;
    return true;
}

看吧,他把Target retain了一次。init函数还避免掉了重复调用可能出现的bug。这个小特殊没有什么大不了,其危险之处在于采用函数指针来实现回调。

CCObject*  m_pSelectorTarget;
 
intm_nScriptHandler;
 
union
{
  SEL_CallFunc    m_pCallFunc;
  SEL_CallFuncN    m_pCallFuncN;
  SEL_CallFuncND    m_pCallFuncND;
  SEL_CallFuncO  m_pCallFuncO;
};

C++被设计为尽早在编译期发现错误,如果你使用函数指针,编译器不太容易发现你的误用。而且一旦误用,就会发生未定义的行为,运行不一定马上崩溃,这种bug是最要命的。好在最新的代码中已经使用function来实现回调。这种实现一定要引以为戒。

还有一个特殊的CCActionInstant action是CCRemoveSelf。这种Action是很方便的,但是我对它的安全性保持怀疑。它的作用是调用一次target的release,而且在init的时候没有将target retain,这有点小hack了。当然整个action的设计都不咋滴。我在前面忘记讲removeFromParentAndCleanup这个函数了,乘此机会来补一下。

void CCNode::removeFromParentAndCleanup(bool cleanup)
{
    if (m_pParent != NULL)
    {
        m_pParent->removeChild(this,cleanup);
    } 
}

void CCNode::removeChild(CCNode* child,bool cleanup)
{
    // explicit nil handling
    if (m_pChildren==NULL)
    {
        return;
    }
 
    if(m_pChildren->containsObject(child))
    {
        this->detachChild(child,cleanup);
    }
}

void CCNode::detachChild(CCNode *child, bool doCleanup)
{
    // IMPORTANT:
    //  -1st do onExit
    //  -2nd cleanup
    if (m_bRunning)
    {
        child->onExitTransitionDidStart();
        child->onExit();
    }
 
    // If you don't do cleanup, the child's actions will
    // not get removed and the
    // its scheduledSelectors_ dict will not get released!
    if (doCleanup)
    {
        child->cleanup();
    }
 
    // set parent nil at the end
    child->setParent(NULL);
 
    m_pChildren->removeObject(child);
}


这个函数本身没什么,如果参数为true则调用cleanup函数,而且默认是为true的。

void CCNode::cleanup()
{
    // actions
    this->stopAllActions();
    this->unscheduleAllSelectors();
 
    if(m_eScriptType!=kScriptTypeNone)
    {
        CCScriptEngineManager::sharedManager()->
          getScriptEngine()->executeNodeEvent(this,kCCNodeOnCleanup);
    }
 
    // timers
    arrayMakeObjectsPerformSelector(m_pChildren,cleanup,CCNode*);
}

从Parent中移去大体上是这个流程,先调用自己的onExitTransitionDidStart,导致所有的子Node的

onExitTransitionDidStart都调用一遍,调用onExit,导致所有子Node的onExit都调用一遍,调用自己cleanup,导致所有的子Node的cleanup都调用一遍。最后从父容器中移去,也就是引用计数减1.如果引用计数到0,导致自己析构函数调用。这时他会删除自己Children容器,导致所有的子Node的引用计数都减1.

我们知道能对action有影响的是只有cleanup函数,如果在step中调用到了cleanup这个函数,那么就相当于在action中调用了removeAllActionsFromTarget这个函数。前面讲了一个removeActionAtIndex。说这个函数在step中调用是安全的。而且分析了这个函数是最终一个action的step的情况,也是安全的。我们现在分析一下这个函数,算作温习:

void CCActionManager::removeAllActionsFromTarget(CCObject* pTarget)
{
  // explicit null handling
  if(pTarget==NULL)
  {
      return;
  }
 
  tHashElement* pElement=NULL;
  HASH_FIND_INT(m_pTargets,&pTarget,pElement);
  if(pElement)
  {
    if(ccArrayContainsObject(pElement->actions,
      pElement->currentAction)&&(!pElement->currentActionSalvaged))
    {
      pElement->currentAction->retain();
      pElement->currentActionSalvaged=true;
    }
 
    ccArrayRemoveAllObjects(pElement->actions);
    if(m_pCurrentTarget==pElement)
    {
      m_bCurrentTargetSalvaged=true;
    }
    else
    {
      deleteHashElement(pElement);
    }
  }
  else
  {
//  CCLOG("cocos2d: removeAllActionsFromTarget: Target not found");
  }
}


之前说过在step中删除其他节点是安全的,那么有问题的只可能是ccArrayRemoveAllObjects(pElement->actions);这里的情况有2个,如果这个action是最后一个action。循环不再,不会有问题。如果这个action不是最后一个action。之前那个函数不会有问题,是因为它无耻的手动回减了一次index。这里调用了一下ccArrayRemoveAllObjects(pElement->actions),将actions的num减为0,那么在下一次循环判断为false,不再循环也就没什么问题了。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值