寫到第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的接口,你如果像能正常的使用的話,還是要做不少工作的。