cocos2d-x 源碼剖析(8)

寫到第7節的時候,突然覺得cocos2d-x還沒有我想的那麼大啊,或許在50節以內就要了結了。這節繼續看看CCNode這個節點,主要部分是action。雖然CCNode有不少的action相關的函數,起作用的實際上是Actionmanager。這節雖說是從CCNode開始,但是真正的內容在ActionManager中:

void CCNode::setActionManager(CCActionManager* actionManager)
{
    if( actionManager != m_pActionManager ) {
        this->stopAllActions();
        CC_SAFE_RETAIN(actionManager);
        CC_SAFE_RELEASE(m_pActionManager);
        m_pActionManager = actionManager;
    }
}
 
CCActionManager* CCNode::getActionManager()
{
    return m_pActionManager;
}
 
CCAction * CCNode::runAction(CCAction* action)
{
    CCAssert( action != NULL, "Argument must be non-nil");
    m_pActionManager->addAction(action, this, !m_bRunning);
    return action;
}
 
void CCNode::stopAllActions()
{
    m_pActionManager->removeAllActionsFromTarget(this);
}
 
void CCNode::stopAction(CCAction* action)
{
    m_pActionManager->removeAction(action);
}
 
void CCNode::stopActionByTag(int tag)
{
    CCAssert( tag != kCCActionTagInvalid, "Invalid tag");
    m_pActionManager->removeActionByTag(tag, this);
}
 
CCAction * CCNode::getActionByTag(int tag)
{
    CCAssert( tag != kCCActionTagInvalid, "Invalid tag");
    return m_pActionManager->getActionByTag(tag, this);
}
 
unsigned int CCNode::numberOfRunningActions()
{
    return m_pActionManager->numberOfRunningActionsInTarget(this);
}


這種代理方式簡化了CCNode本身的邏輯,很值得學習。在設計代碼的時候,往往不自覺的有這種意識,但是如果能清晰的認識到這樣做的優點的話,就能設計出更好代碼了。

我們深入到ActionManager的內部,就會發現它其實被當成是一個單件,意外的是他沒有采用之前的那種單件模式。而且原則上沒有保證它必須是一個單件不可。在CCDirector的init函數中,它被new了出來。在CCNode函數中,他被賦值給了m_pActionManager,由於保存了一份本地引用,所以調用了retain一次,在析構的時候release了一次。

  CCDirector*director=CCDirector::sharedDirector();
  m_pActionManager=director->getActionManager();
  m_pActionManager->retain();


一份本地引用不會增加多少邏輯,但是能減少一次函數調用,對於頻繁使用的對象來說是值得的。而且這個ActionManager的管理方式十分有意思。在CCdirector中它被new出來之後就立馬添加到了m_pScheduler中:

    // scheduler
  m_pScheduler = new CCScheduler();
  // action manager
  m_pActionManager = new CCActionManager();
  m_pScheduler->scheduleUpdateForTarget(
    m_pActionManager, kCCPrioritySystem, false);

這會導致每一幀它的update都會被調用到:

  // main loop
voidCCActionManager::update(floatdt)
{
    for(tHashElement*elt=m_pTargets;elt!=NULL;)
    {
        m_pCurrentTarget=elt;
        m_bCurrentTargetSalvaged=false;
 
        if(!m_pCurrentTarget->paused)
        {
            // The 'actions' CCMutableArray may change while inside
            // this loop.
            for(m_pCurrentTarget->actionIndex=0;
                m_pCurrentTarget->actionIndex<
                  m_pCurrentTarget->actions->num;
                m_pCurrentTarget->actionIndex++)
            {
                m_pCurrentTarget->currentAction=
                  (CCAction*)m_pCurrentTarget->actions->
                    arr[m_pCurrentTarget->actionIndex];
                if(m_pCurrentTarget->currentAction==NULL)
                {
                    continue;
                }
 
                m_pCurrentTarget->currentActionSalvaged=false;
 
                m_pCurrentTarget->currentAction->step(dt);
 
                if(m_pCurrentTarget->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.
                    m_pCurrentTarget->currentAction->release();
                }elseif(m_pCurrentTarget->currentAction->isDone())
                {
                    m_pCurrentTarget->currentAction->stop();
 
                    CCAction*pAction=
                      m_pCurrentTarget->currentAction;
                    // Make currentAction nil to prevent removeAction 
                    // from salvaging it.
                    m_pCurrentTarget->currentAction=NULL;
                    removeAction(pAction);
                }
 
                m_pCurrentTarget->currentAction=NULL;
            }
        }
 
        // elt, at this moment, is still valid
        // so it is safe to ask this here (issue #490)
        elt=(tHashElement*)(elt->hh.next);
 
        // only delete currentTarget if no actions were scheduled 
        // during the cycle (issue #481)
        if(m_bCurrentTargetSalvaged&&m_pCurrentTarget->
            actions->num==0)
        {
            deleteHashElement(m_pCurrentTarget);
        }
    }
 
    // issue #635
    m_pCurrentTarget=NULL;
}


所有被管理的action都保存在m_pTargets中,他的聲明如下:

struct _hashElement    *m_pTargets;

typedefstruct_hashElement
{
    struct_ccArray            *actions;
    CCObject                    *target;
    unsignedint                actionIndex;
    CCAction                    *currentAction;
    bool                        currentActionSalvaged;
    bool                        paused;
    UT_hash_handle                hh;
}tHashElement;

這是一個相對無腦的命名。可以看見ActionManager是以target爲主導,來管理action的。關於縮進我就不再說什麼了。這個結構很有意思,它其實是一個哈希鏈表。其內部細節我就不多做介紹了,注意這個結構的操作方式即可。

首先開始遍歷這個鏈表,判斷target是否暫停,也就是下面這兩個函數所做的影響:

void CCActionManager::pauseTarget(CCObject *pTarget)
{
    tHashElement *pElement = NULL;
    HASH_FIND_INT(m_pTargets, &pTarget, pElement);
    if (pElement)
    {
        pElement->paused = true;
    }
}
 
void CCActionManager::resumeTarget(CCObject *pTarget)
{
    tHashElement *pElement = NULL;
    HASH_FIND_INT(m_pTargets, &pTarget, pElement);
    if (pElement)
    {
        pElement->paused = false;
    }
}

在每一個target當中,遍歷它的action,並且調用它的step函數。我們注意到,在次之前有一句代碼比較可疑:

m_pCurrentTarget->currentActionSalvaged=false;

我們稍微想一想這句代碼是做什麼用的。注意進行step之後立馬就進行了判斷。那麼能對這個值做出改變的就只有step了。而且搜索整個源碼,能在外部改變這個值的只有removeAction接口,舉其中一個作爲例子:

void CCActionManager::removeActionAtIndex(unsigned int uIndex, 
  tHashElement *pElement)
{
    CCAction *pAction = (CCAction*)pElement->actions->arr[uIndex];
 
    if (pAction == pElement->currentAction && 
          (! pElement->currentActionSalvaged))
    {
        pElement->currentAction->retain();
        pElement->currentActionSalvaged = true;
    }
 
    ccArrayRemoveObjectAtIndex(pElement->actions, uIndex, true);
 
    // update actionIndex in case we are in tick.
    // looping over the actions
    if (pElement->actionIndex >= uIndex)
    {
        pElement->actionIndex--;
    }
 
    if (pElement->actions->num == 0)
    {
        if (m_pCurrentTarget == pElement)
        {
            m_bCurrentTargetSalvaged = true;
        }
        else
        {
            deleteHashElement(pElement);
        }
    }
}


這個函數雖然不是外部接口,卻是核心實現。注意如果是當前action在step中被移去自己的話,那麼就會先retain一下,然後做好並將它移去,因爲已經retain了一次,在從容器中刪除的時候不會析構。同理,如果刪除這個action會導致當前的節點被刪除,那麼也將這個節點標記起來。如果不是當前的節點,那就立馬刪了。注意這個操作是發生在鏈表遍歷之中的,看看遍歷的實現就知道,移除其他節點是安全的。

由於step會導致2個意外邏輯,接下來的代碼就好理解了。如果當前的action被標記了,就release一次。因外被標記那時就已經從容器裏移去了,這裏要做的就是讓他析構而已。如果這個action完成了。那麼就用removeAction移去它。從上面的例子可以看到,它執行的是:

ccArrayRemoveObjectAtIndex(pElement->actions,uIndex,true);

之所以能在一個循環裏面刪除數據,是因爲隨後又判斷了一下,將索引減了1.

// update actionIndex in case we are in tick. looping over the actions
if(pElement->actionIndex>=uIndex)
{
    pElement->actionIndex--;
}

還一個意外邏輯也就不難理解了,如果標記了當前的節點,並且這時候action確實爲0那就刪除這個節點。之所以要再判斷一次數目,是因爲step還可能添加新的action進來。

這份代碼的實現與之其他代碼相比要差很多,這恐怕也是導致Action經常出bug的根源。對於ActionManager的其他函數,我想在瞭解了update和remove之後,其它便不難掌握了。最後還要強調一點,雖然CCNode開出了setActionManager的接口,你如果像能正常的使用的話,還是要做不少工作的。


發佈了2 篇原創文章 · 獲贊 3 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章