源碼版本 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();
}
}
}
}
正如我註釋中所說,update使用了模板方法的設計模式思想,將trigger與cancel調用的過程寫死,但是不同的子類實現trigger和cancel的方式不同。 另外需要注意的是,Schedule使用時delay的需求,當有delay與沒有delay我在源碼中已經分析的很清楚了。
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,而前者沒有。這在下面的源碼分析中會看到。(其實原理很簡單,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);
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。