注:COCOS分析的版本为3.4
COCOS的定时器是通过一个哈希表进行保存的,每一帧循环的时候都会调用定时器的update方法,并传入两帧之间的时间间隔 /*void Scheduler::update(float dt)*/,在update方法中对哈希表进行轮询调用回调函数。
在轮询调用中将定时器分为四种,其中三种是每一帧必定循环调用的方法,struct _listEntry *_updatesNegList; // list of priority < 0
struct _listEntry *_updates0List; // list priority == 0 struct _listEntry *_updatesPosList; // list priority > 0,简称为系统调用的定时器,例外一种也叫做客户模式,是通过调用管理器的update方法。
以下是对定时器源码进行的详细分析(摘自: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中的数据
- //_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
- void TimerTargetSelector::trigger()
- {
- if (_target && _selector)
- {
- (_target->*_selector)(_elapsed);
- }
- }
- void TimerTargetCallback::trigger()
- {
- if (_callback)
- {
- _callback(_elapsed);
- }
- }
- Ref* _target;
- SEL_SCHEDULE _selector;
- ------ ------------------------
- void* _target;
- ccSchedulerFunc _callback;
- std::string _key;
2.3 Scheduler
2.3.1 Schedule && UnSchedule
- typedef struct _hashSelectorEntry
- {
- ccArray *timers;
- void *target;
- int timerIndex;
- Timer *currentTimer;
- bool currentTimerSalvaged;
- bool paused;
- UT_hash_handle hh;
- } tHashTimerEntry;
- 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();
- }
- if (selector == timer->getSelector())
- {
- CCLOG("CCScheduler#scheduleSelector. Selector already scheduled. Updating interval from: %.4f to %.4f", timer->getInterval(), interval);
- timer->setInterval(interval);
- return;
- }
- 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的两种定时模式
- template <class T>
- void scheduleUpdate(T *target, int priority, bool paused)
- {
- this->schedulePerFrame([target](float dt){
- target->update(dt);
- }, target, priority, paused);
- }
- // 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);
- }
- //
- // "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
- 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);
- }
- }
- }
- 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);
- }
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;
- }
update函数,最需要注意的点是什么?是在循环内部执行每个target的customer定时函数时候,需要注意很可能改变绑定在该Target下的Customer Timer的状态。所以在每次循环之后,都会判断这些状态位,如果被改变,需要做什么操作。在代码注释中,我已经说明。
2.3.4 状态查询与暂停恢复
2.3.5 3.x的新特性
This function is thread safe.
@since v3.0
*/
void performFunctionInCocosThread( const std::function<void()> &function);
3.小结