原文地址: http://blog.csdn.net/u011225840/article/details/32141349
1.继承结构
没错,是两张图。(你没有老眼昏花。。我脑子也没有秀逗。。)Ref就是原来的CCObject,而Timer类是与Scheduler类密切相关的类,所以需要把他们放在一起说。Timer和Scheduler的关系就像Data和DataManager的关系。
2.源码分析
2.1 Timer
2.1.1 Timer中的数据
Timer类定义了一个行为执行的间隔,执行的次数等,可以理解为定时器的数据类,而具体的定时器的行为,定义在子类中。Timer中的数据如下:
-
-
-
-
- float _elapsed;
- bool _runForever;
- bool _useDelay;
- unsigned int _timesExecuted;
- unsigned int _repeat;
- float _delay;
- float _interval;
2.1.2 Update函数
- void Timer::update(float dt)
- {
-
-
-
-
-
-
- if (_elapsed == -1)
- {
- _elapsed = 0;
- _timesExecuted = 0;
- }
-
-
-
-
-
-
-
- else
- {
- if (_runForever && !_useDelay)
- {
- _elapsed += dt;
- if (_elapsed >= _interval)
- {
- trigger();
-
- _elapsed = 0;
- }
- }
- else
- {
- _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)
- {
- 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!");
-
- 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);
-
-
- element->paused = paused;
- }
- else
- {
- CCASSERT(element->paused == paused, "");
- }
-
-
- if (element->timers == nullptr)
- {
- element->timers = ccArrayNew(10);
- }
- else
- {
-
-
-
- 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);
- }
-
- 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)
- {
-
- if (target == nullptr || selector == nullptr)
- {
- return;
- }
-
-
-
-
- tHashTimerEntry *element = nullptr;
- HASH_FIND_PTR(_hashForTimers, &target, element);
-
- if (element)
- {
-
- for (int i = 0; i < element->timers->num; ++i)
- {
- TimerTargetSelector *timer = static_cast<TimerTargetSelector*>(element->timers->arr[i]);
-
- if (selector == timer->getSelector())
- {
- if (timer == element->currentTimer && (! element->currentTimerSalvaged))
- {
- element->currentTimer->retain();
- element->currentTimerSalvaged = true;
- }
-
- ccArrayRemoveObjectAtIndex(element->timers, i, true);
-
-
- if (element->timerIndex >= i)
- {
- element->timerIndex--;
- }
-
-
-
- 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,在此之前,要先介绍两个数据结构。
-
- typedef struct _listEntry
- {
- struct _listEntry *prev, *next;
- ccSchedulerFunc callback;
- void *target;
- int priority;
- bool paused;
- bool markedForDeletion;
- } tListEntry;
-
- typedef struct _hashUpdateEntry
- {
- tListEntry **list;
- tListEntry *entry;
- 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)
- {
-
-
- tHashUpdateEntry *hashElement = nullptr;
- HASH_FIND_PTR(_hashForUpdates, &target, hashElement);
- if (hashElement)
- {
- #if COCOS2D_DEBUG >= 1
- CCASSERT(hashElement->entry->markedForDeletion,"");
- #endif
-
-
- hashElement->entry->markedForDeletion = false;
- return;
- }
-
-
-
-
- if (priority == 0)
- {
- appendIn(&_updates0List, callback, target, paused);
- }
- else if (priority < 0)
- {
- priorityIn(&_updatesNegList, callback, target, priority, paused);
- }
- else
- {
-
- priorityIn(&_updatesPosList, callback, target, priority, paused);
- }
- }
- void Scheduler::appendIn(_listEntry **list, const ccSchedulerFunc& callback, void *target, bool paused)
- {
-
- tListEntry *listElement = new tListEntry();
-
- listElement->callback = callback;
- listElement->target = target;
- listElement->paused = paused;
- listElement->markedForDeletion = false;
-
- DL_APPEND(*list, listElement);
-
-
-
- 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)
- {
-
- tListEntry *listElement = new tListEntry();
-
- listElement->callback = callback;
- listElement->target = target;
- listElement->priority = priority;
- listElement->paused = paused;
- listElement->next = listElement->prev = nullptr;
- listElement->markedForDeletion = false;
-
-
- if (! *list)
- {
- DL_APPEND(*list, listElement);
- }
- else
- {
- bool added = false;
-
- 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;
- }
- }
-
-
- if (! added)
- {
- DL_APPEND(*list, listElement);
- }
- }
-
-
- 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。
-
-
-
- struct _listEntry *_updatesNegList;
- struct _listEntry *_updates0List;
- struct _listEntry *_updatesPosList;
- struct _hashUpdateEntry *_hashForUpdates;
下面,继续分析源码,看一下是如何移除这些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)
- {
-
- if (target == nullptr)
- {
- return;
- }
-
-
- 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);
- }
- }
-
-
- unscheduleUpdate(target);
- }
该方法会移除target相关的所有定时,包括update类型的,包括Custom Selector类型的,和其他的一样,需要注意该标志位。
最后提一下unscheduleAllWithMinPriority,他会将custom 类型的定时全部移除,并将priority大于残烛的update类型定时移除。
2.3.3 定时器的更新update
- void Scheduler::update(float dt)
- {
- _updateHashLocked = true;
-
-
- if (_timeScale != 1.0f)
- {
- dt *= _timeScale;
- }
-
-
-
-
-
-
- tListEntry *entry, *tmp;
-
-
-
- DL_FOREACH_SAFE(_updatesNegList, entry, tmp)
- {
- if ((! entry->paused) && (! entry->markedForDeletion))
- {
- entry->callback(dt);
- }
- }
-
-
- DL_FOREACH_SAFE(_updates0List, entry, tmp)
- {
- if ((! entry->paused) && (! entry->markedForDeletion))
- {
- entry->callback(dt);
- }
- }
-
-
- DL_FOREACH_SAFE(_updatesPosList, entry, tmp)
- {
- if ((! entry->paused) && (! entry->markedForDeletion))
- {
- entry->callback(dt);
- }
- }
-
-
-
- for (tHashTimerEntry *elt = _hashForTimers; elt != nullptr; )
- {
- _currentTarget = elt;
- _currentTargetSalvaged = false;
-
- if (! _currentTarget->paused)
- {
-
-
- 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);
-
-
- if (elt->currentTimerSalvaged)
- {
-
-
-
- elt->currentTimer->release();
- }
-
- elt->currentTimer = nullptr;
- }
- }
-
-
-
- elt = (tHashTimerEntry *)elt->hh.next;
-
-
-
- if (_currentTargetSalvaged && _currentTarget->timers->num == 0)
- {
- removeHashElement(_currentTarget);
- }
- }
-
-
-
-
- DL_FOREACH_SAFE(_updatesNegList, entry, tmp)
- {
- if (entry->markedForDeletion)
- {
- this->removeUpdateFromHash(entry);
- }
- }
-
-
- DL_FOREACH_SAFE(_updates0List, entry, tmp)
- {
- if (entry->markedForDeletion)
- {
- this->removeUpdateFromHash(entry);
- }
- }
-
-
- 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。