1. 程式人生 > >cocos2d-x的定時器分析

cocos2d-x的定時器分析

在cocos2d-x遊戲的主迴圈中,只用了三行語句便完成了整個引擎的排程系統,十分優雅。

void CCDirector::drawScene(void)
{
    ...
    //處理定時器的呼叫
    if (! m_bPaused)
    {
        m_pScheduler->update(m_fDeltaTime);
    }
    ...
}
在導演類中定義了一個全域性的定時器,有導演單獨控制,也正是這個定時器,維護著整個引擎的排程。
bool CCDirector::init(void)
{
	...
    // scheduler
    m_pScheduler = new CCScheduler();
    ...
}
然後再通過主迴圈每幀的去排程它,從而完成排程。

在此之前得先知道外界是如何註冊定時器供引擎排程的。在之前的CCNode文章中就已經提及到CCNode包括呼叫和停止定時器的功能,也就是說真正的入口還是在節點類,由節點去註冊,然後由導演去完成每幀的排程。

其中,CCNode握有導演類的全域性定時器的指標:

CCNode::CCNode(void)...
{
    ...
    m_pScheduler = director->getScheduler();
    m_pScheduler->retain();
    ...
}
然後再通過這個全域性定時器完成相應的註冊和停止等工作,也就是說,CCNode只是對真正的定時器類進行了包裝,這樣便能隱藏定時器的細節。
//設定一個新的定時器
void CCNode::setScheduler(CCScheduler* scheduler)
{
    if( scheduler != m_pScheduler ) {//如果是新的定時器
        this->unscheduleAllSelectors();//停止舊定時器的所有排程
        CC_SAFE_RETAIN(scheduler);//新定時器的引用計數加1
        CC_SAFE_RELEASE(m_pScheduler);//釋放掉舊定時器的資源
        m_pScheduler = scheduler;//設定新定時器
    }
}
//獲取定時器
CCScheduler* CCNode::getScheduler()
{
	//放回當前的定時器
    return m_pScheduler;
}
//註冊update型別的定時器
void CCNode::scheduleUpdate()
{
    scheduleUpdateWithPriority(0);//呼叫優先順序為0的定時器
}
//註冊帶有優先順序的update型別的定時器
void CCNode::scheduleUpdateWithPriority(int priority)
{
    m_pScheduler->scheduleUpdateForTarget(this, priority, !m_bRunning);//呼叫指定優先順序的定時器
}
//lua指令碼相關,略過
void CCNode::scheduleUpdateWithPriorityLua(int nHandler, int priority)
{
    unscheduleUpdate();
    m_nUpdateScriptHandler = nHandler;
    m_pScheduler->scheduleUpdateForTarget(this, priority, !m_bRunning);
}
//停止當前節點的update定時器排程
void CCNode::unscheduleUpdate()
{
    m_pScheduler->unscheduleUpdateForTarget(this);//停止當前節點的update定時器
    if (m_nUpdateScriptHandler)//指令碼相關,略過
    {
        CCScriptEngineManager::sharedManager()->getScriptEngine()->removeScriptHandler(m_nUpdateScriptHandler);
        m_nUpdateScriptHandler = 0;
    }
}
//註冊自定義定時器,每幀呼叫一次
void CCNode::schedule(SEL_SCHEDULE selector)
{
    this->schedule(selector, 0.0f, kCCRepeatForever, 0.0f);//無限次呼叫指定定時器
}
//註冊自定義定時器,每隔interval秒呼叫一次
void CCNode::schedule(SEL_SCHEDULE selector, float interval)
{
    this->schedule(selector, interval, kCCRepeatForever, 0.0f);//無限次的每隔interval時間呼叫指定定時器
}
//註冊自定義定時器,每隔interval秒呼叫一次,重複排程repeat次,並設定延遲時間delay
void CCNode::schedule(SEL_SCHEDULE selector, float interval, unsigned int repeat, float delay)
{
	//檢查引數合法化
    CCAssert( selector, "Argument must be non-nil");
    CCAssert( interval >=0, "Argument must be positive");

    m_pScheduler->scheduleSelector(selector, this, interval , repeat, delay, !m_bRunning);//執行排程
}
//延遲delay呼叫一次
void CCNode::scheduleOnce(SEL_SCHEDULE selector, float delay)
{
    this->schedule(selector, 0.0f, 0, delay);//delay時間後呼叫一次指定定時器
}
//根據選擇器停止定時器
void CCNode::unschedule(SEL_SCHEDULE selector)
{
    // explicit nil handling
    if (selector == 0)//過濾空引用
        return;

    m_pScheduler->unscheduleSelector(selector, this);//停止指定的定時器
}
//停止該節點的所有定時器
void CCNode::unscheduleAllSelectors()
{
    m_pScheduler->unscheduleAllForTarget(this);//停止該節點的所有定時器
}
//恢復該節點的所有定時器和動作
void CCNode::resumeSchedulerAndActions()
{
    m_pScheduler->resumeTarget(this);//該節點的定時器重新工作
    m_pActionManager->resumeTarget(this);//該節點的動作重新被執行
}
//暫停該節點的所有定時器和動作
void CCNode::pauseSchedulerAndActions()
{
    m_pScheduler->pauseTarget(this);//暫停該節點的定時器
    m_pActionManager->pauseTarget(this);//暫停該節點的動作
}

//更新函式,一般重寫它
void CCNode::update(float fDelta)//一般重寫它,在主迴圈中會自動去呼叫它
{
    if (m_nUpdateScriptHandler)
    {
        CCScriptEngineManager::sharedManager()->getScriptEngine()->executeSchedule(m_nUpdateScriptHandler, fDelta, this);
    }
    
    if (m_pComponentContainer && !m_pComponentContainer->isEmpty())
    {
        m_pComponentContainer->visit(fDelta);
    }
}
可見經過封裝後的定時器在節點中使用十分簡潔清晰明瞭。

接下分析真正的定時器類:CCSchedule。

在cocos2d-x中有兩種定時器:

1)update定時器:每幀都會被觸發,使用scheduleUpdate方法來啟動。

2)schedule定時器:根據選擇器來自定義的定時器,可以設定觸發時間間隔,延遲,重複次數等,使用schedule的相關方法來啟動。

其相關的屬性和方法如下:

class CC_DLL CCScheduler : public CCObject
{
public:
	//構造
    CCScheduler();
    /**
     *  @js NA
     *  @lua NA
     */
    //析構
    ~CCScheduler(void);
    //返回時間刻度
    inline float getTimeScale(void) { return m_fTimeScale; }
    /** Modifies the time of all scheduled callbacks.
    You can use this property to create a 'slow motion' or 'fast forward' effect.
    Default is 1.0. To create a 'slow motion' effect, use values below 1.0.
    To create a 'fast forward' effect, use values higher than 1.0.
    @since v0.8
    @warning It will affect EVERY scheduled selector / action.
    */
    //設定時間刻度,預設為1.0,設定它,可以改變定時器的排程速度
    inline void setTimeScale(float fTimeScale) { m_fTimeScale = fTimeScale; }

    /** 'update' the scheduler.
     *  You should NEVER call this method, unless you know what you are doing.
     *  @js NA
     *  @lua NA
     */
    //update定時器的排程函式
    void update(float dt);

    /** The scheduled method will be called every 'interval' seconds.
     If paused is YES, then it won't be called until it is resumed.
     If 'interval' is 0, it will be called every frame, but if so, it's recommended to use 'scheduleUpdateForTarget:' instead.
     If the selector is already scheduled, then only the interval parameter will be updated without re-scheduling it again.
     repeat let the action be repeated repeat + 1 times, use kCCRepeatForever to let the action run continuously
     delay is the amount of time the action will wait before it'll start

     @since v0.99.3, repeat and delay added in v1.1
     @js  NA
     @lua NA
     */
    //自定義定時器的排程函式
    void scheduleSelector(SEL_SCHEDULE pfnSelector, CCObject *pTarget, float fInterval, unsigned int repeat, float delay, bool bPaused);

    /** calls scheduleSelector with kCCRepeatForever and a 0 delay 
     *  @js NA
     *  @lua NA
     */
    //自定義定時器的排程函式(repeat = kCCRepeatForever,delay = 0)
    void scheduleSelector(SEL_SCHEDULE pfnSelector, CCObject *pTarget, float fInterval, bool bPaused);
    /** Schedules the 'update' selector for a given target with a given priority.
     The 'update' selector will be called every frame.
     The lower the priority, the earlier it is called.
     @since v0.99.3
     @lua NA
     */
    //帶有優先順序定時器的排程函式
    void scheduleUpdateForTarget(CCObject *pTarget, int nPriority, bool bPaused);

    /** Unschedule a selector for a given target.
     If you want to unschedule the "update", use unscheudleUpdateForTarget.
     @since v0.99.3
     @lua NA
     */
    //根據定時選擇器移除定時器的回撥
    void unscheduleSelector(SEL_SCHEDULE pfnSelector, CCObject *pTarget);

    /** Unschedules the update selector for a given target
     @since v0.99.3
     @lua NA
     */
    //根據目標移除定時器的回撥
    void unscheduleUpdateForTarget(const CCObject *pTarget);

    /** Unschedules all selectors for a given target.
     This also includes the "update" selector.
     @since v0.99.3
     @js  unscheduleCallbackForTarget
     @lua NA
     */
    //移除所有帶有目標的定時器的回撥
    void unscheduleAllForTarget(CCObject *pTarget);

    /** Unschedules all selectors from all targets.
     You should NEVER call this method, unless you know what you are doing.

     @since v0.99.3
     @js unscheduleAllCallbacks
     @lua NA
     */
    //移除所有定時器的回撥
    void unscheduleAll(void);
    
    /** Unschedules all selectors from all targets with a minimum priority.
      You should only call this with kCCPriorityNonSystemMin or higher.
      @since v2.0.0
      @js unscheduleAllCallbacksWithMinPriority
      @lua NA
      */
    //移除所有小於指定優先順序的定時器的回撥
    void unscheduleAllWithMinPriority(int nMinPriority);

    /** The scheduled script callback will be called every 'interval' seconds.
     If paused is YES, then it won't be called until it is resumed.
     If 'interval' is 0, it will be called every frame.
     return schedule script entry ID, used for unscheduleScriptFunc().
     @js NA
     */
    //指令碼相關,略過
    unsigned int scheduleScriptFunc(unsigned int nHandler, float fInterval, bool bPaused);
    
    /** Unschedule a script entry. 
     *  @js NA
     */
    //指令碼相關,略過
    void unscheduleScriptEntry(unsigned int uScheduleScriptEntryID);

    /** Pauses the target.
     All scheduled selectors/update for a given target won't be 'ticked' until the target is resumed.
     If the target is not present, nothing happens.
     @since v0.99.3
     @lua NA
     */
    //暫停目標定時器
    void pauseTarget(CCObject *pTarget);

    /** Resumes the target.
     The 'target' will be unpaused, so all schedule selectors/update will be 'ticked' again.
     If the target is not present, nothing happens.
     @since v0.99.3
     @lua NA
     */
    //恢復目標定時器
    void resumeTarget(CCObject *pTarget);

    /** Returns whether or not the target is paused
     @since v1.0.0
     @lua NA
     */
    //目標定時器是否被暫停
    bool isTargetPaused(CCObject *pTarget);

    /** Pause all selectors from all targets.
     You should NEVER call this method, unless you know what you are doing.
     @since v2.0.0
     @lua NA
     */
    //暫停所有目標定時器
    CCSet* pauseAllTargets();

    /** Pause all selectors from all targets with a minimum priority.
     You should only call this with kCCPriorityNonSystemMin or higher.
     @since v2.0.0
     @lua NA
     */
    //暫停所有小於指定優先順序的目標定時器
    CCSet* pauseAllTargetsWithMinPriority(int nMinPriority);

    /** Resume selectors on a set of targets.
     This can be useful for undoing a call to pauseAllSelectors.
     @since v2.0.0
     @lua NA
     */
    //恢復set集合中指定的目標定時器
    void resumeTargets(CCSet* targetsToResume);

private:
    void removeHashElement(struct _hashSelectorEntry *pElement);//從普通定時器散列表移除一個普通定時器
    void removeUpdateFromHash(struct _listEntry *entry);//從update定時器散列表中移除一個update定時器

    // update specific

    void priorityIn(struct _listEntry **ppList, CCObject *pTarget, int nPriority, bool bPaused);//按指定的優先順序往連結串列合適的位置增加一個定時器
    void appendIn(struct _listEntry **ppList, CCObject *pTarget, bool bPaused);//往連結串列追加一個定時器

protected:
    float m_fTimeScale;//時間刻度

    //
    // "updates with priority" stuff
    //
    struct _listEntry *m_pUpdatesNegList;        //優先順序小於0的排程器連結串列
    struct _listEntry *m_pUpdates0List;          //優先順序等於0的排程器連結串列
    struct _listEntry *m_pUpdatesPosList;        //優先順序大於0的排程器連結串列
    struct _hashUpdateEntry *m_pHashForUpdates;  //記錄全部update定時器的散列表

    // Used for "selectors with interval"
    struct _hashSelectorEntry *m_pHashForTimers; //記錄全部計時器的散列表
    struct _hashSelectorEntry *m_pCurrentTarget; //當前的目標選擇器散列表
    bool m_bCurrentTargetSalvaged;//回收標記
    bool m_bUpdateHashLocked;//是否鎖定定時器。如果為true,則在呼叫unschedule時不會直接在散列表中刪除該定時器,而是該定時器僅被標記為已刪除在散列表中
    CCArray* m_pScriptHandlerEntries;//指令碼相關
};


一、update定時器:

update定時器在一個節點中只能註冊一個,呼叫次序主要依據三個儲存了update定時器的連結串列的優先順序來執行。

    struct _listEntry *m_pUpdatesNegList;        //優先順序小於0的排程器連結串列
    struct _listEntry *m_pUpdates0List;          //優先順序等於0的排程器連結串列
    struct _listEntry *m_pUpdatesPosList;        //優先順序大於0的排程器連結串列
typedef struct _listEntry//定時器連結串列結構
{
    struct _listEntry   *prev, *next;	//連結串列前驅節點,連結串列後繼節點
    CCObject            *target;        //目標
    int                 priority;	//優先順序
    bool                paused;		//是否暫停
    bool                markedForDeletion; //是否被標記為刪除
} tListEntry;
而為了方便檢索這些節點的update定時器,又定義了一個hash散列表來儲存這些定時器(本身並不執行排程任務)。
struct _hashUpdateEntry *m_pHashForUpdates;  //記錄全部update定時器的散列表
typedef struct _hashUpdateEntry//update定時器散列表結構
{
    tListEntry          **list; //連結串列容器
    tListEntry          *entry; //連結串列元素,即update定時器的連結串列結構(value)
    CCObject            *target;//目標(key)
    UT_hash_handle      hh;//散列表操作工具
} tHashUpdateEntry;
其中以節點類為key,定時器連結串列結構為value。

文章的開頭提過,cocos2d-x遊戲的定時器的排程是在主迴圈中用一個全域性定時器來完成排程的,其執行的方法就是CCSchedule::update,它包括了update定時器和自定義定時器的排程:

//主迴圈呼叫
void CCScheduler::update(float dt)
{
    m_bUpdateHashLocked = true;//鎖定定時器,這樣檢索定時器時保證迭代不會被破壞

    if (m_fTimeScale != 1.0f)//如果時間刻度不等於預設的1.0
    {
        dt *= m_fTimeScale;//調整定時器排程時間
    }

    // Iterate over all the Updates' selectors
    tListEntry *pEntry, *pTmp;//用於迭代連結串列

    // updates with priority < 0
    //迭代優先順序小於0的定時器
    DL_FOREACH_SAFE(m_pUpdatesNegList, pEntry, pTmp)
    {
        if ((! pEntry->paused) && (! pEntry->markedForDeletion))//如果該定時器沒有被暫停和標記已刪除
        {
            pEntry->target->update(dt);//觸發定時器排程
        }
    }

    // updates with priority == 0
    //迭代優先順序等於0的定時器
    DL_FOREACH_SAFE(m_pUpdates0List, pEntry, pTmp)
    {
        if ((! pEntry->paused) && (! pEntry->markedForDeletion))//如果該定時器沒有被暫停和標記已刪除
        {
            pEntry->target->update(dt);//觸發定時器排程
        }
    }

    // updates with priority > 0
    //迭代優先順序大於0的定時器
    DL_FOREACH_SAFE(m_pUpdatesPosList, pEntry, pTmp)
    {
        if ((! pEntry->paused) && (! pEntry->markedForDeletion))//如果該定時器沒有被暫停和標記已刪除
        {
            pEntry->target->update(dt);//觸發定時器排程
        }
    }

    ...


    // delete all updates that are marked for deletion
    // updates with priority < 0
    //移除已被標記為刪除且優先順序小於0的定時器
    DL_FOREACH_SAFE(m_pUpdatesNegList, pEntry, pTmp)
    {
        if (pEntry->markedForDeletion)
        {
            this->removeUpdateFromHash(pEntry);
        }
    }

    // updates with priority == 0
    //移除已被標記為刪除且優先順序等於0的定時器
    DL_FOREACH_SAFE(m_pUpdates0List, pEntry, pTmp)
    {
        if (pEntry->markedForDeletion)
        {
            this->removeUpdateFromHash(pEntry);
        }
    }

    // updates with priority > 0
    //移除已被標記為刪除且優先順序大於0的定時器
    DL_FOREACH_SAFE(m_pUpdatesPosList, pEntry, pTmp)
    {
        if (pEntry->markedForDeletion)
        {
            this->removeUpdateFromHash(pEntry);
        }
    }

    m_bUpdateHashLocked = false;

    m_pCurrentTarget = NULL;
}
這裡省略了在中間的自定義定時器的執行。

可以看到真個update定時器的呼叫過程是(通過重寫update,並以多型的形式呼叫,多型真是一個BT的東西):

1)排程優先順序高的的定時器(優先順序小於0,一般是動作類)

2)排程優先順序中等的的定時器(優先順序等於0,一般是預設註冊的定時器)

3)排程優先順序低的的定時器(優先順序大於0,一般是自定義優先順序的定時器)

4)在三個儲存了update定時器的連結串列中分別檢查那些已經被標記為已刪除的定時器,並從散列表中刪除它,這樣這個定時器便被停止了

實現細節:

1)註冊update定時器(CCNode在呼叫scheduleUpdate時最終會呼叫到這個方法)

void CCScheduler::scheduleUpdateForTarget(CCObject *pTarget, int nPriority, bool bPaused)
{

    tHashUpdateEntry *pHashElement = NULL;
    HASH_FIND_INT(m_pHashForUpdates, &pTarget, pHashElement);//通過pTarget索引查詢雜湊表中的相應元素,找到則將其賦給pHashElement
    if (pHashElement)//找到元素
    {
#if COCOS2D_DEBUG >= 1
        CCAssert(pHashElement->entry->markedForDeletion,"");
#endif
        // TODO: check if priority has changed!

        pHashElement->entry->markedForDeletion = false;//將該元素的刪除標記標記為false
        return;
    }
    //沒有找到,根據優先順序往對應的連結串列插入一個新的元素

    // most of the updates are going to be 0, that's way there
    // is an special list for updates with priority 0
    if (nPriority == 0)
    {
        appendIn(&m_pUpdates0List, pTarget, bPaused);
    }
    else if (nPriority < 0)
    {
        priorityIn(&m_pUpdatesNegList, pTarget, nPriority, bPaused);
    }
    else
    {
        // priority > 0
        priorityIn(&m_pUpdatesPosList, pTarget, nPriority, bPaused);
    }
}
void CCScheduler::appendIn(_listEntry **ppList, CCObject *pTarget, bool bPaused)
{
    tListEntry *pListElement = (tListEntry *)malloc(sizeof(*pListElement));//申請一塊記憶體空間

    pListElement->target = pTarget;//設定目標
    pListElement->paused = bPaused;//設定暫停
    pListElement->markedForDeletion = false;//設定刪除標記

    DL_APPEND(*ppList, pListElement);//往連結串列追加一個元素

    // update hash entry for quicker access
    //向散列表中增加一個節點,用於快速訪問
    tHashUpdateEntry *pHashElement = (tHashUpdateEntry *)calloc(sizeof(*pHashElement), 1);
    pHashElement->target = pTarget;
    pTarget->retain();
    pHashElement->list = ppList;
    pHashElement->entry = pListElement;
    HASH_ADD_INT(m_pHashForUpdates, target, pHashElement);
}
void CCScheduler::priorityIn(tListEntry **ppList, CCObject *pTarget, int nPriority, bool bPaused)
{
    tListEntry *pListElement = (tListEntry *)malloc(sizeof(*pListElement));//申請一塊記憶體空間

    pListElement->target = pTarget;//設定目標
    pListElement->priority = nPriority;//設定優先順序
    pListElement->paused = bPaused;//設定暫停
    pListElement->next = pListElement->prev = NULL;//設定前驅和後繼節點
    pListElement->markedForDeletion = false;//設定刪除標記

    // empty list ?
    if (! *ppList)//連結串列容器不為空
    {
        DL_APPEND(*ppList, pListElement);//往連結串列中增加一個節點
    }
    else
    {
        bool bAdded = false;//記錄節點是否插在連結串列的中間或頭部

        for (tListEntry *pElement = *ppList; pElement; pElement = pElement->next)//遍歷連結串列
        {
            if (nPriority < pElement->priority)//找到一個優先順序比次節點小的節點
            {
                if (pElement == *ppList)//如果是第一個元素
                {
                    DL_PREPEND(*ppList, pListElement);//將此節點放到連結串列的頭部
                }
                else
                {
                    pListElement->next = pElement;//將新增加的節點的後繼節點指向當前被遍歷的節點
                    pListElement->prev = pElement->prev;//將新增加的節點的後繼節點指向當前被遍歷的節點的前驅節點

                    pElement->prev->next = pListElement;//當前被遍歷的節點的前驅節點的後繼節點指向新增加的節點
                    pElement->prev = pListElement;//將當前被遍歷的節點的前驅節點指向新增加的節點
                }

                bAdded = true;//該節點已在中間或頭部插入
                break;
            }
        }

        // Not added? priority has the higher value. Append it.
        if (! bAdded)//如果之前的操作中節點沒有被增加,則將此節點追加到連結串列的尾部(即優先順序最低)
        {
            DL_APPEND(*ppList, pListElement);
        }
    }

    // update hash entry for quick access
    //向散列表中增加一個節點,用於快速訪問
    tHashUpdateEntry *pHashElement = (tHashUpdateEntry *)calloc(sizeof(*pHashElement), 1);
    pHashElement->target = pTarget;
    pTarget->retain();
    pHashElement->list = ppList;
    pHashElement->entry = pListElement;
    HASH_ADD_INT(m_pHashForUpdates, target, pHashElement);
}
2)停止當前節點的update定時器排程(CCNode在呼叫unscheduleUpdate時最終會呼叫到這個方法)
void CCScheduler::unscheduleUpdateForTarget(const CCObject *pTarget)
{
    if (pTarget == NULL)//目標為空,返回
    {
        return;
    }

    tHashUpdateEntry *pElement = NULL;
    HASH_FIND_INT(m_pHashForUpdates, &pTarget, pElement);//通過pTarget索引查詢雜湊表中的相應元素,找到則將其賦給pElement
    if (pElement)//找到元素
    {
        if (m_bUpdateHashLocked)//是否鎖定定時器
        {
            pElement->entry->markedForDeletion = true;//標記刪除狀態
        }
        else
        {
            this->removeUpdateFromHash(pElement->entry);//直接從散列表中移除
        }
    }
}
void CCScheduler::removeUpdateFromHash(struct _listEntry *entry)
{
    tHashUpdateEntry *element = NULL;

    HASH_FIND_INT(m_pHashForUpdates, &entry->target, element);//通過entry->target索引查詢雜湊表中的相應元素,找到則將其賦給element
    if (element)//找到元素
    {
        // list entry
    	//從連結串列刪除它的元素並釋放資源
        DL_DELETE(*element->list, element->entry);
        free(element->entry);

        // hash entry
        //從散列表刪除它並釋放資源
        CCObject* pTarget = element->target;
        HASH_DEL(m_pHashForUpdates, element);
        free(element);

        // target#release should be the last one to prevent
        // a possible double-free. eg: If the [target dealloc] might want to remove it itself from there
        //目標的引用計數減1(在往散列表增加節點時retain了一次,這裡被刪除,要手動release一次)
        pTarget->release();
    }
}
3)刪除某一個節點update定時器
void CCScheduler::unscheduleUpdateForTarget(const CCObject *pTarget)
{
    if (pTarget == NULL)//目標為空,返回
    {
        return;
    }

    tHashUpdateEntry *pElement = NULL;
    HASH_FIND_INT(m_pHashForUpdates, &pTarget, pElement);//通過pTarget索引查詢雜湊表中的相應元素,找到則將其賦給pElement
    if (pElement)//找到元素
    {
        if (m_bUpdateHashLocked)//是否鎖定定時器
        {
            pElement->entry->markedForDeletion = true;//標記刪除狀態
        }
        else
        {
            this->removeUpdateFromHash(pElement->entry);//直接從散列表中移除
        }
    }
}

update定時器相對簡單,因為它的排程時間是固定的,且每個節點只能有一個,引擎遍歷它時用散列表,排程它時根據優先順序遍歷三個儲存update定時器的連結串列來執行update定時器的呼叫,確實很簡單。


二、自定義定時器:

這個定時器就相對複雜得多,因為他會涉及到計時器的管理。維護的它的資料結構是兩個散列表和一個輔助屬性:

    struct _hashSelectorEntry *m_pHashForTimers; //記錄全部計時器的散列表
    struct _hashSelectorEntry *m_pCurrentTarget; //當前的目標選擇器散列表
    bool m_bCurrentTargetSalvaged;//回收標記
typedef struct _hashSelectorEntry//自定義定時器散列表結構
{
    ccArray             *timers;//計時器容器(value)
    CCObject            *target; //目標(key)
    unsigned int        timerIndex;//計數器索引
    CCTimer             *currentTimer;//當前的計時器
    bool                currentTimerSalvaged;//回收標記
    bool                paused;//是否暫停
    UT_hash_handle      hh;//散列表操作工具
} tHashTimerEntry;
這個散列表的key是依然是一個排程節點類,value是一個儲存了多個計時器的陣列。

在分析實現之前得懂得這個計時器的作用。

由於一個節點允許有多個自定義定時器,那麼一個節點就要維護這個一對多的關係,所以在上面的上面的散列表用了一個數組才儲存它,並用一個指標標明當前所引用的計時器是哪個(在陣列中,以選擇器來區分同一節點下的不同自定義定時器),而且自定義定時器還允許使用者自定義什麼時候去呼叫它(延遲呼叫)、呼叫的時間間隔、重複呼叫次數,所以這個計時器必要儲存這些資訊。

計時器的標頭檔案:

class CC_DLL CCTimer : public CCObject
{
public:
    /**
     *  @js  ctor
     *  @lua NA
     */
	//構造
    CCTimer(void);
    
    /** get interval in seconds */
    //返回時間間隔(單位:秒)
    float getInterval(void) const;
    /** set interval in seconds */
    //設定時間間隔(單位:秒)
    void setInterval(float fInterval);
    /**
     *  @lua NA
     */
    //獲取定時器
    SEL_SCHEDULE getSelector() const;
    
    /** Initializes a timer with a target and a selector. 
     *  @lua NA
     */
    //初始化目標定時器的計時器
    bool initWithTarget(CCObject *pTarget, SEL_SCHEDULE pfnSelector);
    
    /** Initializes a timer with a target, a selector and an interval in seconds, repeat in number of times to repeat, delay in seconds. 
     *  @lua NA
     */
    //初始化目標定時器的計時器
    bool initWithTarget(CCObject *pTarget, SEL_SCHEDULE pfnSelector, float fSeconds, unsigned int nRepeat, float fDelay);
    
    /** Initializes a timer with a script callback function and an interval in seconds. */
    //指令碼相關,略過
    bool initWithScriptHandler(int nHandler, float fSeconds);
    
    /** triggers the timer */
    //觸發計數器排程
    void update(float dt);
    
public:
    /** Allocates a timer with a target and a selector. 
     *  @lua NA
     */
    static CCTimer* timerWithTarget(CCObject *pTarget, SEL_SCHEDULE pfnSelector);
    
    /** Allocates a timer with a target, a selector and an interval in seconds. 
     *  @lua NA
     */
    static CCTimer* timerWithTarget(CCObject *pTarget, SEL_SCHEDULE pfnSelector, float fSeconds);
    
    /** Allocates a timer with a script callback function and an interval in seconds. */
    static CCTimer* timerWithScriptHandler(int nHandler, float fSeconds);
    /**
     *  @lua NA
     */
    inline int getScriptHandler() { return m_nScriptHandler; };

protected:
    CCObject *m_pTarget;//目標
    float m_fElapsed;//記錄過去的時間
    bool m_bRunForever;//是否是永久的呼叫
    bool m_bUseDelay;//是否是延遲呼叫
    unsigned int m_uTimesExecuted;//呼叫次數
    unsigned int m_uRepeat; //重複呼叫的次數,0為1次,1為2次,以此類推
    float m_fDelay;//延遲時間
    float m_fInterval;//時間間隔
    SEL_SCHEDULE m_pfnSelector;//選擇器(目標函式)
    
    int m_nScriptHandler;//指令碼相關,略過
};
計時器的實現:
CCTimer::CCTimer()
: m_pTarget(NULL)
, m_fElapsed(-1)
, m_bRunForever(false)
, m_bUseDelay(false)
, m_uTimesExecuted(0)
, m_uRepeat(0)
, m_fDelay(0.0f)
, m_fInterval(0.0f)
, m_pfnSelector(NULL)
, m_nScriptHandler(0)
{
}

CCTimer* CCTimer::timerWithTarget(CCObject *pTarget, SEL_SCHEDULE pfnSelector)
{
    CCTimer *pTimer = new CCTimer();//建立一個新的計時器

    pTimer->initWithTarget(pTarget, pfnSelector, 0.0f, kCCRepeatForever, 0.0f);//初始化計時器
    pTimer->autorelease();//放入記憶體回收池

    return pTimer;
}

CCTimer* CCTimer::timerWithTarget(CCObject *pTarget, SEL_SCHEDULE pfnSelector, float fSeconds)
{
    CCTimer *pTimer = new CCTimer();//建立一個新的計時器

    pTimer->initWithTarget(pTarget, pfnSelector, fSeconds, kCCRepeatForever, 0.0f);//初始化計時器
    pTimer->autorelease();//放入記憶體回收池

    return pTimer;
}

CCTimer* CCTimer::timerWithScriptHandler(int nHandler, float fSeconds)//指令碼相關,略過
{
    CCTimer *pTimer = new CCTimer();

    pTimer->initWithScriptHandler(nHandler, fSeconds);
    pTimer->autorelease();

    return pTimer;
}

bool CCTimer::initWithScriptHandler(int nHandler, float fSeconds)//指令碼相關,略過
{
    m_nScriptHandler = nHandler;
    m_fElapsed = -1;
    m_fInterval = fSeconds;

    return true;
}

bool CCTimer::initWithTarget(CCObject *pTarget, SEL_SCHEDULE pfnSelector)
{
    return initWithTarget(pTarget, pfnSelector, 0, kCCRepeatForever, 0.0f);//初始化計時器
}

bool CCTimer::initWithTarget(CCObject *pTarget, SEL_SCHEDULE pfnSelector, float fSeconds, unsigned int nRepeat, float fDelay)
{
	//初始化工作
    m_pTarget = pTarget;//設定目標
    m_pfnSelector = pfnSelector;//設定選擇器(目標函式)
    m_fElapsed = -1;//設定記錄過去時間
    m_fInterval = fSeconds;//設定時間間隔
    m_fDelay = fDelay;//設定延遲時間
    m_bUseDelay = (fDelay > 0.0f) ? true : false;//如果延遲時間大於0,則認為是延遲呼叫
    m_uRepeat = nRepeat;//設定重複呼叫次數
    m_bRunForever = (nRepeat == kCCRepeatForever) ? true : false;//設定是否永遠重複執行
    return true;
}

void CCTimer::update(float dt)
{
    if (m_fElapsed == -1)//如果過去的時間被重置-1
    {
        m_fElapsed = 0;//重新記錄過去的用時時間,所以將m_fElapsed置0,開始計時
        m_uTimesExecuted = 0;//呼叫次數
    }
    else
    {
        if (m_bRunForever && !m_bUseDelay)//如果是永遠的執行且非延遲
        {//標準計時器的呼叫
            m_fElapsed += dt;//過去的時間加1
            if (m_fElapsed >= m_fInterval)//過去的時間大於設定時間間隔(如一個定時器每隔2秒呼叫一次,那麼被記錄的過去時間大於2的話,則認為可以觸發該定時器的呼叫)
            {
                if (m_pTarget && m_pfnSelector)
                {
                    (m_pTarget->*m_pfnSelector)(m_fElapsed);//觸發定時器的呼叫(m_pfnSelector是個函式指標,m_pTarget是目標物件,m_fElapsed是時間引數)
                }

                if (m_nScriptHandler)//指令碼相關,略過
                {
                    CCScriptEngineManager::sharedManager()->getScriptEngine()->executeSchedule(m_nScriptHandler, m_fElapsed);
                }
                m_fElapsed = 0;//重新記錄過去的用時時間,所以將m_fElapsed置0,重新開始計時
            }
        }    
        else
        {//update和CCDelay計時器的呼叫
            m_fElapsed += dt;//過去時間加上幀差
            if (m_bUseDelay)//如果是延遲呼叫
            {
                if( m_fElapsed >= m_fDelay )//當已記錄的過去的時間大於延遲時間
                {
                    if (m_pTarget && m_pfnSelector)
                    {
                        (m_pTarget->*m_pfnSelector)(m_fElapsed);//觸發定時器的呼叫(m_pfnSelector是個函式指標,m_pTarget是目標物件,m_fElapsed是時間引數)
                    }

                    if (m_nScriptHandler)//指令碼相關,略過
                    {
                        CCScriptEngineManager::sharedManager()->getScriptEngine()->executeSchedule(m_nScriptHandler, m_fElapsed);
                    }

                    m_fElapsed = m_fElapsed - m_fDelay;//重置過去時間
                    m_uTimesExecuted += 1;//呼叫次數加1
                    m_bUseDelay = false;//標記延遲呼叫結束
                }
            }
            else
            {
                if (m_fElapsed >= m_fInterval)//過去的時間大於設定時間間隔(如一個定時器每隔2秒呼叫一次,那麼被記錄的過去時間大於2的話,則認為可以觸發該定時器的呼叫)
                {
                    if (m_pTarget && m_pfnSelector)
                    {
                        (m_pTarget->*m_pfnSelector)(m_fElapsed);//觸發定時器的呼叫(m_pfnSelector是個函式指標,m_pTarget是目標物件,m_fElapsed是時間引數)
                    }

                    if (m_nScriptHandler)//指令碼相關,略過
                    {
                        CCScriptEngineManager::sharedManager()->getScriptEngine()->executeSchedule(m_nScriptHandler, m_fElapsed);
                    }

                    m_fElapsed = 0;//重新記錄過去的用時時間,所以將m_fElapsed置0,重新開始計時
                    m_uTimesExecuted += 1;//呼叫次數加1

                }
            }

            if (!m_bRunForever && m_uTimesExecuted > m_uRepeat)//如果是不是永久的重複呼叫,且呼叫次數大於重複呼叫次數的,則停止呼叫
            {    //unschedule timer
                CCDirector::sharedDirector()->getScheduler()->unscheduleSelector(m_pfnSelector, m_pTarget);
            }
        }
    }
}

float CCTimer::getInterval() const
{
	//返回時間間隔
    return m_fInterval;
}

void CCTimer::setInterval(float fInterval)
{
	//設定時間間隔
    m_fInterval = fInterval;
}

SEL_SCHEDULE CCTimer::getSelector() const
{
	//返回選擇器指標
    return m_pfnSelector;
}

接下來看主迴圈是如何處理自定義定時器:

//主迴圈呼叫
void CCScheduler::update(float dt)
{
    m_bUpdateHashLocked = true;//鎖定定時器,這樣檢索定時器時保證迭代不會被破壞

    if (m_fTimeScale != 1.0f)//如果時間刻度不等於預設的1.0
    {
        dt *= m_fTimeScale;//調整定時器排程時間
    }

    ...

    // Iterate over all the custom selectors
    //迭代自定義定時器
    for (tHashTimerEntry *elt = m_pHashForTimers; elt != NULL; )
    {
        m_pCurrentTarget = elt;
        m_bCurrentTargetSalvaged = false;

        if (! m_pCurrentTarget->paused)
        {
            // The 'timers' array may change while inside this loop
            for (elt->timerIndex = 0; elt->timerIndex < elt->timers->num; ++(elt->timerIndex))
            {
                elt->currentTimer = (CCTimer*)(elt->timers->arr[elt->timerIndex]);
                elt->currentTimerSalvaged = false;

                elt->currentTimer->update(dt);
                //如果在update執行了unscheduleXXX時刪除該計時器,也就是自己在排程時候通知自己刪除自己(計時器陣列會改變,儘量避免這樣做)
                //(注意:這時的計時器陣列的大小會發生改變,所以要小心處理timerIndex,另外這裡之所以要release是因為這種情況計時器陣列在移除計時器時只是從陣列中移除掉而已,計時器本身還是存在的,所以要release一下,
                //也就是說它在unscheduleXXX時已經執行了retian,之所以要在unscheuleXXX中retian它是因為是在update中通知自己刪除自己,為了保證在計時器在排程時不會出錯,要將自己retain一下,然後標記
                //為回收狀態,執行完排程(update)後再回收它)
                if (elt->currentTimerSalvaged)//如果當前的計時器被標記為回收狀態,則刪除它
                {
                    // The currentTimer told the remove itself. To prevent the timer from
                    // accidentally deallocating itself before finishing its step, we retained
                    // it. Now that step is done, it's safe to release it.
                    elt->currentTimer->release();
                }

                elt->currentTimer = NULL;
            }
        }

        // elt, at this moment, is still valid
        // so it is safe to ask this here (issue #490)
        elt = (tHashTimerEntry *)elt->hh.next;//下一個計時器

        // only delete currentTarget if no actions were scheduled during the cycle (issue #481)
        if (m_bCurrentTargetSalvaged && m_pCurrentTarget->timers->num == 0)//如果該節點沒有計時器且被標記為可回收時,從散列表中移除它
        {
            removeHashElement(m_pCurrentTarget);
        }
    }

    ...

    m_bUpdateHashLocked = false;

    m_pCurrentTarget = NULL;
}
實現細節:

1)註冊自定義定時器(CCNode在呼叫schedule或scheduleOnce時最終會呼叫到這個方法)

void CCScheduler::scheduleSelector(SEL_SCHEDULE pfnSelector, CCObject *pTarget, float fInterval, unsigned int repeat, float delay, bool bPaused)
{
    CCAssert(pfnSelector, "Argument selector must be non-NULL");
    CCAssert(pTarget, "Argument target must be non-NULL");

    tHashTimerEntry *pElement = NULL;
    //通過pTarget索引查詢雜湊表中的相應元素,找到則將其賦給pElement
    HASH_FIND_INT(m_pHashForTimers, &pTarget, pElement);

    if (! pElement)
    {
    	//散列表沒找到,則申請一塊推空間
        pElement = (tHashTimerEntry *)calloc(sizeof(*pElement), 1);
        pElement->target = pTarget;//設定目標
        if (pTarget)
        {
            pTarget->retain();//將目標的引用計數加1,防止野指標或空懸指標的呼叫
        }
        HASH_ADD_INT(m_pHashForTimers, target, pElement);//將元素放入散列表

        // Is this the 1st element ? Then set the pause level to all the selectors of this target
        pElement->paused = bPaused;//設定暫停
    }
    else
    {
    	//找到則斷言是否被暫停
        CCAssert(pElement->paused == bPaused, "");
    }

    if (pElement->timers == NULL)//如果計時器為空
    {
        pElement->timers = ccArrayNew(10);//建立預設大小為10的計時器容器
    }
    else 
    {//如果已有計時器容器
        for (unsigned int i = 0; i < pElement->timers->num; ++i)//遍歷計時器容器(確保一個節點的某個選擇器只能有一個)
        {
            CCTimer *timer = (CCTimer*)pElement->timers->arr[i];

            if (pfnSelector == timer->getSelector())//如果在目標節點中存在該選擇器,則返回
            {
                CCLOG("CCScheduler#scheduleSelector. Selector already scheduled. Updating interval from: %.4f to %.4f", timer->getInterval(), fInterval);
                timer->setInterval(fInterval);//設定計時器時間間隔
                return;
            }        
        }
        ccArrayEnsureExtraCapacity(pElement->timers, 1);//確保定時器有足夠的記憶體空間,不夠則擴充套件兩倍
    }

    CCTimer *pTimer = new CCTimer();//建立一個新的計時器
    pTimer->initWithTarget(pTarget, pfnSelector, fInterval, repeat, delay);//初始化計時器
    ccArrayAppendObject(pElement->timers, pTimer);//將新建立的計時器加入到計時器容器中
    pTimer->release();//還原pTimer的引用計數為1
}

2)停止當前節點的自定義定時器排程(CCNode在呼叫unschedule時最終會呼叫到這個方法)

void CCScheduler::unscheduleSelector(SEL_SCHEDULE pfnSelector, CCObject *pTarget)
{
    // explicity handle nil arguments when removing an object
    if (pTarget == 0 || pfnSelector == 0)//引數檢查
    {
        return;
    }

    //CCAssert(pTarget);
    //CCAssert(pfnSelector);

    tHashTimerEntry *pElement = NULL;
    HASH_FIND_INT(m_pHashForTimers, &pTarget, pElement);//通過pTarget索引查詢雜湊表中的相應元素,找到則將其賦給pElement

    if (pElement)//如果找到
    {
        for (unsigned int i = 0; i < pElement->timers->num; ++i)//遍歷計時器容器
        {
            CCTimer *pTimer = (CCTimer*)(pElement->timers->arr[i]);

            if (pfnSelector == pTimer->getSelector())//如果找到對應的要移除的選擇器
            {
                if (pTimer == pElement->currentTimer && (! pElement->currentTimerSalvaged))//當前的排程計時器與即將要執行移除的計時器相等(在排程時通知自己刪除自己)且回收標記為false時
                {
                    pElement->currentTimer->retain();//在主迴圈中處理它(防止計時器在執行排程時呼叫了unscheduleXXX方法來刪除自己而出現的野指標錯誤)
                    pElement->currentTimerSalvaged = true;//可回收
                }
                //注意:從容器中移除時,計時器本身有可能還是存在的,如果當前的排程計時器與即將要執行移除的計時器相等的話計時器是不會被刪除掉的,僅僅只是從容器中移除掉而已,當執行完排程後主迴圈會自動刪除它
                ccArrayRemoveObjectAtIndex(pElement->timers, i, true);//從計時器容器中移除該計時器
                

                // update timerIndex in case we are in tick:, looping over the actions
                if (pElement->timerIndex >= i)//保證主迴圈遍歷時不會出錯
                {
                    pElement->timerIndex--;
                }

                if (pElement->timers->num == 0)//如果該節點的定時器的計時器容器的數量等於0
                {
                    if (m_pCurrentTarget == pElement)//如果正在執行的節點與次遍歷的節點相等(也是排程時通知自己刪除自己)
                    {
                        m_bCurrentTargetSalvaged = true;//標記為當前的目標節點為可回收(主迴圈會處理它)
                    }
                    else
                    {
                        removeHashElement(pElement);//從散列表中移除該定時器
                    }
                }

                return;
            }
        }
    }
}
void CCScheduler::removeHashElement(_hashSelectorEntry *pElement)
{

	cocos2d::CCObject *target = pElement->target;

    ccArrayFree(pElement->timers);//釋放計時器的資源
    HASH_DEL(m_pHashForTimers, pElement);//從散列表中刪除該元素
    free(pElement);//釋放該定時器元素的資源

    // make sure the target is released after we have removed the hash element
    // otherwise we access invalid memory when the release call deletes the target
    // and the target calls removeAllSelectors() during its destructor
    target->release();//目標的引用計數減1(在建立一個計時器時已經節點已經被retain了一下,所以在移除時這裡也要手動的release一下)

}

三、其他細節的實現

1)暫停恢復相關:

void CCScheduler::resumeTarget(CCObject *pTarget)
{
    CCAssert(pTarget != NULL, "");

    //處理自定義選擇器
    tHashTimerEntry *pElement = NULL;
    HASH_FIND_INT(m_pHashForTimers, &pTarget, pElement);//通過pTarget索引查詢雜湊表中的相應元素,找到則將其賦給pElement
    if (pElement)//找到元素
    {
        pElement->paused = false;//將暫停標誌置為false
    }

    //處理update選擇器
    tHashUpdateEntry *pElementUpdate = NULL;
    HASH_FIND_INT(m_pHashForUpdates, &pTarget, pElementUpdate);//通過pTarget索引查詢雜湊表中的相應元素,找到則將其賦給pElementUpdate
    if (pElementUpdate)//找到元素
    {
        CCAssert(pElementUpdate->entry != NULL, "");
        pElementUpdate->entry->paused = false;//將暫停標誌置為false
    }
}

void CCScheduler::pauseTarget(CCObject *pTarget)
{
    CCAssert(pTarget != NULL, "");

    //處理自定義選擇器
    tHashTimerEntry *pElement = NULL;
    HASH_FIND_INT(m_pHashForTimers, &pTarget, pElement);//通過pTarget索引查詢雜湊表中的相應元素,找到則將其賦給pElement
    if (pElement)//找到元素
    {
        pElement->paused = true;//將暫停標誌置為true
    }

    //處理update選擇器
    tHashUpdateEntry *pElementUpdate = NULL;
    HASH_FIND_INT(m_pHashForUpdates, &pTarget, pElementUpdate);//通過pTarget索引查詢雜湊表中的相應元素,找到則將其賦給pElementUpdate
    if (pElementUpdate)//找到元素
    {
        CCAssert(pElementUpdate->entry != NULL, "");
        pElementUpdate->entry->paused = true;//將暫停標誌置為true
    }
}

bool CCScheduler::isTargetPaused(CCObject *pTarget)
{
    CCAssert( pTarget != NULL, "target must be non nil" );

    //處理自定義選擇器
    tHashTimerEntry *pElement = NULL;
    HASH_FIND_INT(m_pHashForTimers, &pTarget, pElement);
    if( pElement )//找到元素,返回暫停標誌位
    {
        return pElement->paused;
    }
    
    // We should check update selectors if target does not have custom selectors
    //處理update選擇器
	tHashUpdateEntry *elementUpdate = NULL;
	HASH_FIND_INT(m_pHashForUpdates, &pTarget, elementUpdate);
	if ( elementUpdate )//找到元素,返回暫停標誌位
    {
		return elementUpdate->entry->paused;
    }
    //買有找到,返回false
    return false;  // should never get here
}

CCSet* CCScheduler::pauseAllTargets()
{
    return pauseAllTargetsWithMinPriority(kCCPrioritySystem);
}

CCSet* CCScheduler::pauseAllTargetsWithMinPriority(int nMinPriority)
{
	//定義一個用於收集小於指定優先順序的定時器容器
    CCSet* idsWithSelectors = new CCSet();// setWithCapacity:50];
    idsWithSelectors->autorelease();

    //處理自定義選擇器
    for(tHashTimerEntry *element = m_pHashForTimers; element != NULL;
        element = (tHashTimerEntry*)element->hh.next)//自定義選擇器沒有優先順序,遍歷它
    {
        element->paused = true;//設定暫停標誌位為true
        idsWithSelectors->addObject(element->target);//加到容器中
    }

    //處理update選擇器,根據指定優先順序,分別在對應的連結串列做相應處理
    tListEntry *entry, *tmp;
    if(nMinPriority < 0) 
    {
        DL_FOREACH_SAFE( m_pUpdatesNegList, entry, tmp ) 
        {
            if(entry->priority >= nMinPriority) 
            {
                entry->paused = true;
                idsWithSelectors->addObject(entry->target);
            }
        }
    }

    if(nMinPriority <= 0) 
    {
        DL_FOREACH_SAFE( m_pUpdates0List, entry, tmp )
        {
            entry->paused = true;
            idsWithSelectors->addObject(entry->target);
        }
    }

    DL_FOREACH_SAFE( m_pUpdatesPosList, entry, tmp ) 
    {
        if(entry->priority >= nMinPriority) 
        {
            entry->paused = true;
            idsWithSelectors->addObject(entry->target);
        }
    }
    //返回小於指定優先順序的定時器容器
    return idsWithSelectors;
}

void CCScheduler::resumeTargets(CCSet* pTargetsToResume)
{
    CCSetIterator iter;
    for (iter = pTargetsToResume->begin(); iter != pTargetsToResume->end(); ++iter)//恢復容器中指定的定時器
    {
        resumeTarget(*iter);
    }
}

2)停止一個節點的所有定時器
void CCScheduler::unscheduleAllForTarget(CCObject *pTarget)
{
    // explicit NULL handling
    if (pTarget == NULL)
    {
        return;
    }

    //處理自定義定時器
    tHashTimerEntry *pElement = NULL;
    HASH_FIND_INT(m_pHashForTimers, &pTarget, pElement);//通過pTarget索引查詢雜湊表中的相應元素,找到則將其賦給pElement

    if (pElement)//找到元素
    {
        if (ccArrayContainsObject(pElement->timers, pElement->currentTimer)
            && (! pElement->currentTimerSalvaged))//排程時自己通知刪除自己
        {
            pElement->currentTimer->retain();//主迴圈處理它
            pElement->currentTimerSalvaged = true;//標記回收狀態
        }
        ccArrayRemoveAllObjects(pElement->timers);//移除該節點的所有計時器

        if (m_pCurrentTarget == pElement)//如果正在執行的節點與次遍歷的節點相等
        {
            m_bCurrentTargetSalvaged = true;//標記為當前的目標節點為可回收(主迴圈會處理它)
        }
        else
        {
            removeHashElement(pElement);//從散列表在移除該元素
        }
    }

    // update selector
    //移除該節點的定時器呼叫
    unscheduleUpdateForTarget(pTarget);
}

3)停止遊戲中所有的定時器
void CCScheduler::unscheduleAll(void)
{
    unscheduleAllWithMinPriority(kCCPrioritySystem);
}

void CCScheduler::unscheduleAllWithMinPriority(int nMinPriority)
{
    // Custom Selectors
    tHashTimerEntry *pElement = NULL;
    tHashTimerEntry *pNextElement = NULL;
    //計時器停止呼叫(自定義定時器沒有優先順序,直接停止排程)
    for (pElement = m_pHashForTimers; pElement != NULL;)
    {
        // pElement may be removed in unscheduleAllSelectorsForTarget
        pNextElement = (tHashTimerEntry *)pElement->hh.next;
        unscheduleAllForTarget(pElement->target);

        pElement = pNextElement;
    }

    // Updates selectors
    //根據優先順序在相應的連結串列中移除定時器
    tListEntry *pEntry, *pTmp;
    if(nMinPriority < 0) 
    {
        DL_FOREACH_SAFE(m_pUpdatesNegList, pEntry, pTmp)
        {
            if(pEntry->priority >= nMinPriority) 
            {
                unscheduleUpdateForTarget(pEntry->target);
            }
        }
    }

    if(nMinPriority <= 0) 
    {
        DL_FOREACH_SAFE(m_pUpdates0List, pEntry, pTmp)
        {
            unscheduleUpdateForTarget(pEntry->target);
        }
    }

    DL_FOREACH_SAFE(m_pUpdatesPosList, pEntry, pTmp)
    {
        if(pEntry->priority >= nMinPriority) 
        {
            unscheduleUpdateForTarget(pEntry->target);
        }
    }

    if (m_pScriptHandlerEntries)
    {
        m_pScriptHandlerEntries->removeAllObjects();
    }
}


以上便是cocos2d-x定時器實現所有細節,熟悉這些細節後便可以分析cocos2d-x的動作類了。