Cocos2d-x 源代碼分析 : Scheduler(定時器) 源代碼分析
源代碼版本號 3.1r,轉載請註明
我也最終不out了,開始看3.x的源代碼了。此時此刻的心情僅僅能是wtf!
!!!!!!!
!。只是也最終告別CC時代了。
cocos2d-x 源代碼分析文件夾
http://blog.csdn.net/u011225840/article/details/31743129
1.繼承結構
沒錯。是兩張圖。(你沒有老眼昏花。
。我腦子也沒有秀逗。。)Ref就是原來的CCObject。而Timer類是與Scheduler類密切相關的類,所以須要把他們放在一起說。Timer和Scheduler的關系就像Data和DataManager的關系。
2.源代碼分析
2.1 Timer
2.1.1 Timer中的數據
Timer類定義了一個行為運行的間隔,運行的次數等。能夠理解為定時器的數據類,而詳細的定時器的行為,定義在子類中。Timer中的數據例如以下://_elapsed 上一次運行後到如今的時間 //timesExecuted 運行的次數 //interval 運行間隔 //useDelay 是否使用延遲運行 float _elapsed; bool _runForever; bool _useDelay; unsigned int _timesExecuted; unsigned int _repeat; //0 = once, 1 is 2 x executed float _delay; float _interval;
2.1.2 Update函數
void Timer::update(float dt) { //update方法使用的是模板設計模式,將trigger與cancel的實現交給子類。 if (_elapsed == -1) { _elapsed = 0; _timesExecuted = 0; } //四種情況 /* 1.永久運行而且不使用延遲:基本使用方法。計算elapsed大於interval後運行一次,永不cancel。 2.永久運行而且使用延遲:當elapsed大於延遲時間後,運行一次後,進入情況1. 3.不永久運行而且不使用延遲:情況1結束後,會推斷運行次數是否大於反復次數,大於後則cancel。 4.不永久運行而且使用延遲:情況2結束後,進入情況3. */ else { if (_runForever && !_useDelay) {//standard timer usage _elapsed += dt; if (_elapsed >= _interval) { trigger(); _elapsed = 0; } } else {//advanced usage _elapsed += dt; if (_useDelay) { if( _elapsed >= _delay ) { trigger(); _elapsed = _elapsed - _delay; _timesExecuted += 1; _useDelay = false; } } else { if (_elapsed >= _interval) { trigger(); _elapsed = 0; _timesExecuted += 1; } } if (!_runForever && _timesExecuted > _repeat) { //unschedule timer cancel(); } } } }
2.2 TimerTargetSelector && TimerTargetCallback
前者是針對類(繼承自Ref)中的method進行定時,而後者是針對function(普通函數)。 前者綁定的類型是SEL_SCHEDULE(你問我這是什麽?)typedef void (Ref::*SEL_SCHEDULE)(float);一個指向Ref類型的method指針,而且該method必須滿足參數是float,返回值是void。後者綁定的類型是ccSchedulerFunc---------typedef std::function<void(float)> ccSchedulerFunc;這是蝦米?這是c++11的新特性,事實上就是一個函數指針。
從他們實現的trigger方法中能夠更好的看清這一切。
void TimerTargetSelector::trigger() { if (_target && _selector) { (_target->*_selector)(_elapsed); } } void TimerTargetCallback::trigger() { if (_callback) { _callback(_elapsed); } }
最後說一下,TargetCallback中含有一個key。而前者沒有。這在以下的源代碼分析中會看到。(事實上原理非常easy,SEL_SCHEDULE能夠當成key。ccSchedulerFunc不能,由於前者有唯一的標識,假設你不懂這點。歡迎去復習下c++的指向類中方法的函數指針)
Ref* _target; SEL_SCHEDULE _selector; ------ ------------------------ void* _target; ccSchedulerFunc _callback; std::string _key;
2.3 Scheduler
2.3.1 Schedule && UnSchedule
Schedule有四種重載方法。當中各有兩種針對不同的Timer子類,可是都大同小異。在此之前,不得不說一個用的許多的數據結構tHashTimerEntry的typedef struct _hashSelectorEntry { ccArray *timers; void *target; int timerIndex; Timer *currentTimer; bool currentTimerSalvaged; bool paused; UT_hash_handle hh; } tHashTimerEntry;
這用到了開源庫uthash。關於該hast的詳細使用方法。請自行谷歌。UT_hash_handle能讓我們依據key值找到對應的數據。
在這個結構裏,target是key值,其它都是數據(除了hh哦)。
timers存放著該target相關的全部timer。currentTimerSalvaged的作用是假設你想停止unschedule正在運行的timer時。會將其從timers移除。並retain,防止被自己主動回收機制回收。然後將此標識為true。以下來看下第一種TimerCallback的Schedule。
void Scheduler::schedule(const ccSchedulerFunc& callback, void *target, float interval, unsigned int repeat, float delay, bool paused, const std::string& key) { CCASSERT(target, "Argument target must be non-nullptr"); CCASSERT(!key.empty(), "key should not be empty!"); //先在hash中查找該target(key值)是否已經有數據 tHashTimerEntry *element = nullptr; HASH_FIND_PTR(_hashForTimers, &target, element); //沒有就創建一個。而且將其加入 if (! element) { element = (tHashTimerEntry *)calloc(sizeof(*element), 1); element->target = target; HASH_ADD_PTR(_hashForTimers, target, element); // Is this the 1st element ? Then set the pause level to all the selectors of this target element->paused = paused; } else { CCASSERT(element->paused == paused, ""); } //第一次創建target的數據。需要將timers初始化 if (element->timers == nullptr) { element->timers = ccArrayNew(10); } else { //在timers中查找timer,看在該target下的全部timer綁定的key值是否存在,假設存在,設置新的interval後返回。 //這裏必需要解釋下,target是hash表的key值。用來查找timers等數據。 //而TimerCallback類型的timer本身含有一個key值(std::string類型),用來標識該唯一timer for (int i = 0; i < element->timers->num; ++i) { TimerTargetCallback *timer = static_cast<TimerTargetCallback*>(element->timers->arr[i]); if (key == timer->getKey()) { CCLOG("CCScheduler#scheduleSelector. Selector already scheduled. Updating interval from: %.4f to %.4f", timer->getInterval(), interval); timer->setInterval(interval); return; } } ccArrayEnsureExtraCapacity(element->timers, 1); } //假設TimerCallback原本不存在在timers中。就加入新的 TimerTargetCallback *timer = new TimerTargetCallback(); timer->initWithCallback(this, callback, target, key, interval, repeat, delay); ccArrayAppendObject(element->timers, timer); timer->release(); }
TimerTargetSelector的Schedule不須要本身在通過key值進行存取。其它部分都與上面同樣,只有在查找是否存在Timer時,直接使用了selector。
if (selector == timer->getSelector()) { CCLOG("CCScheduler#scheduleSelector. Selector already scheduled. Updating interval from: %.4f to %.4f", timer->getInterval(), interval); timer->setInterval(interval); return; }
繼續看下TimerTargetSelector的unschedule。
void Scheduler::unschedule(SEL_SCHEDULE selector, Ref *target) { // explicity handle nil arguments when removing an object if (target == nullptr || selector == nullptr) { return; } //CCASSERT(target); //CCASSERT(selector); tHashTimerEntry *element = nullptr; HASH_FIND_PTR(_hashForTimers, &target, element); //假設該target存在數據,就進行刪除操作。if (element) { //遍歷尋找 for (int i = 0; i < element->timers->num; ++i) { TimerTargetSelector *timer = static_cast<TimerTargetSelector*>(element->timers->arr[i]); //假設正在運行的Timer是須要被unschedule的timer,將其移除而且標識當前正在運行的Timer須要被移除狀態為true。 if (selector == timer->getSelector()) { if (timer == element->currentTimer && (! element->currentTimerSalvaged)) { element->currentTimer->retain(); element->currentTimerSalvaged = true; } ccArrayRemoveObjectAtIndex(element->timers, i, true); // update timerIndex in case we are in tick:, looping over the actions if (element->timerIndex >= i) { element->timerIndex--; } //當前timers中不再含有timer。可是假設正在運行的target是該target,則將正在運行的target將被清除標識為true //否則,能夠直接將其從hash中移除 if (element->timers->num == 0) { if (_currentTarget == element) { _currentTargetSalvaged = true; } else { removeHashElement(element); } } return; } } } }
同理反觀TimerTargetCallback,查找時須要用到std::string。這裏不再贅述。
2.3.2 Scheduler的兩種定時模式
Scheduler同意有兩種定時模式: 1.帶有interval(間隔)的定時模式。哪怕interval是0.(普通函數) 2.不帶有interval的定時模式,即在每一幀更新之後都會調用到,會將一個類的update函數放入定時器。(此外。模式2還引入了優先級的概念)
從實現的源碼來看,假設你有一個須要每幀更新都須要調用的function or method,請一定將該部分放入類中的update函數後使用模式2來定時。由於每一個模式2綁定了一個hash表能高速存取到,提高性能。上面一小節介紹的是怎樣加入和刪除模式1的定時。以下看一下模式2.
template <class T> void scheduleUpdate(T *target, int priority, bool paused) { this->schedulePerFrame([target](float dt){ target->update(dt); }, target, priority, paused); }
別問我從哪裏來。我tm來自c++11,假設不懂該寫法。請自行谷歌c++11 lambda表達式。
詳細開始分析SchedulePerFrame,在此之前。要先介紹兩個數據結構。
// A list double-linked list used for "updates with priority" typedef struct _listEntry { struct _listEntry *prev, *next; ccSchedulerFunc callback; void *target; int priority; bool paused; bool markedForDeletion; // selector will no longer be called and entry will be removed at end of the next tick } tListEntry; typedef struct _hashUpdateEntry { tListEntry **list; // Which list does it belong to ?tListEntry *entry; // entry in the list void *target; ccSchedulerFunc callback; UT_hash_handle hh; } tHashUpdateEntry;
tListEntry,是一個雙向鏈表,target是key。markedForDeletion來告訴scheduler是否須要刪除他。tHashUpdateEntry是一個哈希表。通過target能夠高速查找到對應的tListEntry。
能夠註意到。HashEntry中有個List,來表示該entry屬於哪個list。在scheduler中,一共同擁有三個updateList,依據優先級分為negativeList,0List,positiveList,值越小越先運行。
數據結構介紹完成,能夠開始介紹函數了。
void Scheduler::schedulePerFrame(const ccSchedulerFunc& callback, void *target, int priority, bool paused) { //先檢查hash中是否存在該target,假設存在,則將其deleteion的標識 置為false後返回。(可能某個操作將其置為true。而且 //scheduler還沒來得及刪除,所以這裏僅僅須要再改為false就可以) tHashUpdateEntry *hashElement = nullptr; HASH_FIND_PTR(_hashForUpdates, &target, hashElement); if (hashElement) { #if COCOS2D_DEBUG >= 1 CCASSERT(hashElement->entry->markedForDeletion,""); #endif // TODO: check if priority has changed! hashElement->entry->markedForDeletion = false; return; } // most of the updates are going to be 0, that's way there // is an special list for updates with priority 0 //英文凝視解釋了為啥有一個0List。if (priority == 0) { appendIn(&_updates0List, callback, target, paused); } else if (priority < 0) { priorityIn(&_updatesNegList, callback, target, priority, paused); } else { // priority > 0 priorityIn(&_updatesPosList, callback, target, priority, paused); } }
void Scheduler::appendIn(_listEntry **list, const ccSchedulerFunc& callback, void *target, bool paused) { //為該target新建一個listEntry tListEntry *listElement = new tListEntry(); listElement->callback = callback; listElement->target = target; listElement->paused = paused; listElement->markedForDeletion = false; DL_APPEND(*list, listElement); // update hash entry for quicker access //而且為該target建立一個高速存取的target tHashUpdateEntry *hashElement = (tHashUpdateEntry *)calloc(sizeof(*hashElement), 1); hashElement->target = target; hashElement->list = list; hashElement->entry = listElement; HASH_ADD_PTR(_hashForUpdates, target, hashElement); }
void Scheduler::priorityIn(tListEntry **list, const ccSchedulerFunc& callback, void *target, int priority, bool paused) { //同理,為target建立一個entry tListEntry *listElement = new tListEntry(); listElement->callback = callback; listElement->target = target; listElement->priority = priority; listElement->paused = paused; listElement->next = listElement->prev = nullptr; listElement->markedForDeletion = false; // empty list ?if (! *list) { DL_APPEND(*list, listElement); } else { bool added = false; //依據優先級。將element放在一個合適的位置,標準的有序鏈表插入操作,不多解釋。
for (tListEntry *element = *list; element; element = element->next) { if (priority < element->priority) { if (element == *list) { DL_PREPEND(*list, listElement); } else { listElement->next = element; listElement->prev = element->prev; element->prev->next = listElement; element->prev = listElement; } added = true; break; } } // Not added? priority has the higher value. Append it. if (! added) { DL_APPEND(*list, listElement); } } // update hash entry for quick access tHashUpdateEntry *hashElement = (tHashUpdateEntry *)calloc(sizeof(*hashElement), 1); hashElement->target = target; hashElement->list = list; hashElement->entry = listElement; HASH_ADD_PTR(_hashForUpdates, target, hashElement); }
ok。到這裏,我們已經明確update的定時是怎樣加入進來的。scheduler用了以下的成員來管理這些entry。
// // "updates with priority" stuff // struct _listEntry *_updatesNegList; // list of priority < 0 struct _listEntry *_updates0List; // list priority == 0 struct _listEntry *_updatesPosList; // list priority > 0 struct _hashUpdateEntry *_hashForUpdates; // hash used to fetch quickly the list entries for pause,delete,etc
以下,繼續分析源代碼。看一下是怎樣移除這些update的定時的。
void Scheduler::unscheduleUpdate(void *target) { if (target == nullptr) { return; } tHashUpdateEntry *element = nullptr; HASH_FIND_PTR(_hashForUpdates, &target, element); if (element) { if (_updateHashLocked) { element->entry->markedForDeletion = true; } else { this->removeUpdateFromHash(element->entry); } } }
代碼簡單介紹易懂,唯一須要註意的地方是當updateHashLocked為true時,表示當前情況下不同意更改該hash表,僅僅能先將其deletion標記為true。(在運行update的時候會將這類定時刪除)這樣在運行update時,即使其在hash表中,也不會運行(由於deletion為true)。標識updateHashLocked,將在scheduler的update函數開始時置為true,然後在結尾置為false,其它時候不會被更改。
update函數會在後面介紹,以下,繼續看unschedule的其它方法。
void Scheduler::unscheduleAllForTarget(void *target) { // explicit nullptr handling if (target == nullptr) { return; } // Custom Selectors tHashTimerEntry *element = nullptr; HASH_FIND_PTR(_hashForTimers, &target, element); if (element) { if (ccArrayContainsObject(element->timers, element->currentTimer) && (! element->currentTimerSalvaged)) { element->currentTimer->retain(); element->currentTimerSalvaged = true; } ccArrayRemoveAllObjects(element->timers); if (_currentTarget == element) { _currentTargetSalvaged = true; } else { removeHashElement(element); } } // update selector unscheduleUpdate(target); }該方法會移除target相關的全部定時。包含update類型的,包含Custom Selector類型的,和其它的一樣,須要註意該標誌位。
最後提一下unscheduleAllWithMinPriority。他會將custom 類型的定時所有移除,並將priority大於殘燭的update類型定時移除。
2.3.3 定時器的更新update
void Scheduler::update(float dt) { _updateHashLocked = true; //timeScale是什麽意思呢,正常的速度是1.0,假設你想二倍速放就設置成2.0,假設你想慢慢放。就設置成0.5. if (_timeScale != 1.0f) { dt *= _timeScale; } // // Selector callbacks // // Iterate over all the Updates' selectors tListEntry *entry, *tmp; //首先處理update類型的定時,你能夠發現想調用它的callback,必須滿足markedForDeletion為false,從而證明我上面的說法。 // updates with priority < 0 DL_FOREACH_SAFE(_updatesNegList, entry, tmp) { if ((! entry->paused) && (! entry->markedForDeletion)) { entry->callback(dt); } } // updates with priority == 0 DL_FOREACH_SAFE(_updates0List, entry, tmp) { if ((! entry->paused) && (! entry->markedForDeletion)) { entry->callback(dt); } } // updates with priority > 0 DL_FOREACH_SAFE(_updatesPosList, entry, tmp) { if ((! entry->paused) && (! entry->markedForDeletion)) { entry->callback(dt); } } //處理custom類型的定時 // Iterate over all the custom selectors for (tHashTimerEntry *elt = _hashForTimers; elt != nullptr; ) { _currentTarget = elt; _currentTargetSalvaged = false; //沒有被暫停。則能夠處理 if (! _currentTarget->paused) { // The 'timers' array may change while inside this loop //循環內是當前target下的全部Timer for (elt->timerIndex = 0; elt->timerIndex < elt->timers->num; ++(elt->timerIndex)) { elt->currentTimer = (Timer*)(elt->timers->arr[elt->timerIndex]); elt->currentTimerSalvaged = false; elt->currentTimer->update(dt); //假設currentTimer的update本身內部。在一定條件下unSchedule了本身,則會改變currentTimerSalvaged的標識信息。 //所以要再次進行推斷,這就是循環上面英文凝視所述之意 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 = nullptr; } } // 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) //即使在大循環開始時_currentTargetSalvaged被設置為false。如今的值也可能由於上面該target的各種定時函數調用導致其為true if (_currentTargetSalvaged && _currentTarget->timers->num == 0) { removeHashElement(_currentTarget); } } //這些update類型的定時要被刪除咯~~ // delete all updates that are marked for deletion // updates with priority < 0 DL_FOREACH_SAFE(_updatesNegList, entry, tmp) { if (entry->markedForDeletion) { this->removeUpdateFromHash(entry); } } // updates with priority == 0 DL_FOREACH_SAFE(_updates0List, entry, tmp) { if (entry->markedForDeletion) { this->removeUpdateFromHash(entry); } } // updates with priority > 0 DL_FOREACH_SAFE(_updatesPosList, entry, tmp) { if (entry->markedForDeletion) { this->removeUpdateFromHash(entry); } } _updateHashLocked = false; _currentTarget = nullptr; }
到了最重要的函數了。當你把定時都放入了這些list後,定時器是怎樣按時調用的呢。答案就在update函數中。
update函數,最須要註意的點是什麽?是在循環內部運行每一個target的customer定時函數時候。須要註意非常可能改變綁定在該Target下的Customer Timer的狀態。
所以在每次循環之後,都會推斷這些狀態位,假設被改變,須要做什麽操作。
在代碼凝視中,我已經說明。
2.3.4 狀態查詢與暫停恢復
bool isScheduled(const std::string& key, void *target); && bool isScheduled(SEL_SCHEDULE selector, Ref *target); 能夠查詢customer類型的定時是否被scheduled。void pauseTarget(void *target); && void resumeTarget(void *target); 恢復和暫定target相關的全部定時。
就是更改狀態而已。。
2.3.5 3.x的新特性
自從3.x開始。進入了c++11的時代。與此同一時候。正式引入了多線程編程。本人對多線程了解不多。僅僅能簡單點出此函數,詳細的使用方法。煩請各位看官谷歌或者微微一笑吧~/** calls a function on the cocos2d thread. Useful when you need to call a cocos2d function from another thread.
This function is thread safe.
@since v3.0
*/
void performFunctionInCocosThread( const std::function<void()> &function);
3.小結
1.Scheduler與Timer的關系相當DataManager與Data的關系。 2.Scheduler的兩種定時模式,一種是customer selector模式,一種是update 模式。3.hash表用來存取相應的timer。
4.Scheduler的update函數調用了全部Timer的update。
Cocos2d-x 源代碼分析 : Scheduler(定時器) 源代碼分析