涉及到以下东西:


  • _listEntry (tListEntry)
1
2
3
4
5
6
7
8
9
10
/* 用于全局的定时器 */
typedef struct _listEntry
{
struct _listEntry *prev, *next; // 双向链表
ccSchedulerFunc callback; // 回调函数
void *target; // 对象
int priority; // 优先级
bool paused; // 是否暂停
bool markedForDeletion; // 当这个被标记为ture的时候,这个selector在下个周期时间就会被停止
} tListEntry;
  • _hashUpdateEntry (tHashUpdateEntry)
1
2
3
4
5
6
7
8
9
/* 全局定时器的hash */
typedef struct _hashUpdateEntry
{
tListEntry **list; // 在哪个链表中
tListEntry *entry; // 定时器
void *target; // 对象,键值
ccSchedulerFunc callback; // 回调函数
UT_hash_handle hh; // uthash内置结构体
} tHashUpdateEntry;
  • _hashSelectorEntry (tHastTimeEntry)
1
2
3
4
5
6
7
8
9
10
typedef struct _hashSelectorEntry
{
ccArray *timers; // 计时器数组
void *target; // 对象,键值
int timerIndex; // 定时器索引
Timer *currentTimer; // 当前定时器
bool currentTimerSalvaged; // 是否被回收利用
bool paused; // 是否暂停
UT_hash_handle hh; // uthash内置结构体
} tHashTimerEntry;

对于名字不统一的解释:

  • _hashSelectorEntry 给 TimerTargetSelector
  • tHashTimerEntry 给 TimerTargetCallback

由于该结构体的计时器数组timers是保存着Ref*的数组,所以可以根据需要把timers中的元素强制转换成相应类型。


  • Timer (TimerTargetSelector, TimerTargetCallback, 脚本)
  • Scheduler

路径

1
2
2d/CCScheduler.h
2d/CCScheduler.cpp

源码分析

我们需要先引用两个东西,详细可见cocos2dx 3.0 – Macros

1
2
3
4
5
6
7
/* cpp11 回调函数 */
/* 2d/CCScheduler.h */
typedef std::function<void(float)> ccSchedulerFunc;

/* 函数指针 */
/* base/CCRef.h */
typedef void (Ref::*SEL_SCHEDULE)(float);

成员变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class CC_DLL Scheduler : public Ref
{
...

protected:

...

/* 这让我想起来子弹时间 */
float _timeScale;

/*****************************************/
/* 全局定时器任务列表,按照优先级分为3类 */
/*****************************************/

struct _listEntry *_updatesNegList; // 优先级小于 0
struct _listEntry *_updates0List; // 优先级等于 0
struct _listEntry *_updatesPosList; // 优先级大于 0
struct _hashUpdateEntry *_hashForUpdates; // 用于快速查找上述三个列表的hash表

/* 自定义selector */

struct _hashSelectorEntry *_hashForTimers; // 自定义定定时器任务
struct _hashSelectorEntry *_currentTarget; // 当前遍历到的
bool _currentTargetSalvaged; // 是否要回收利用?
bool _updateHashLocked; // 如果为true则不能从hash删除任何元素,只会被标记成待删除。

#if CC_ENABLE_SCRIPT_BINDING
Vector<SchedulerScriptHandlerEntry*> _scriptHandlerEntries;
#endif

// Used for "perform Function"
/* 这里是接受从其他线程回调函数 */
/* 这里的回调函数没有参数和返回值 */
std::vector<std::function<void()>> _functionsToPerform; // 回调函数数组
std::mutex _performMutex; // 互斥锁
};

接着我们来看看主循环:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
void Scheduler::update(float dt)
{
_updateHashLocked = true;

if (_timeScale != 1.0f) {
dt *= _timeScale;
}

tListEntry *entry, *tmp;

/********************************/
/* 先对全局的定时器进行一次调用 */
/********************************/

/* 优先级小于 0 */
DL_FOREACH_SAFE(_updatesNegList, entry, tmp) {
if ((! entry->paused) && (! entry->markedForDeletion)) {
entry->callback(dt);
}
}

/* 优先级等于 0 */
DL_FOREACH_SAFE(_updates0List, entry, tmp) {
if ((! entry->paused) && (! entry->markedForDeletion)) {
entry->callback(dt);
}
}

/* 优先级大于 0 */
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) {
/* timers 数组可能在循环中改变 */
/* 遍历该对象的所有定时器 */
for (elt->timerIndex = 0; elt->timerIndex < elt->timers->num; ++(elt->timerIndex))
{
elt->currentTimer = (Timer*)(elt->timers->arr[elt->timerIndex]);
elt->currentTimerSalvaged = false;

/* 执行一次 update 操作 */
elt->currentTimer->update(dt);

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)
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;

#if CC_ENABLE_SCRIPT_BINDING
/* 略去脚本相关 */
#endif

/* 从其他线程来的 */

if( !_functionsToPerform.empty() ) {
_performMutex.lock();
auto temp = _functionsToPerform;
_functionsToPerform.clear();
_performMutex.unlock();
/* 函数调用必须在unlock之后 */
for( const auto &function : temp ) {
function();
}

}
}

我们可以把Scheduler分成3部分:

  • 系统级定时器,_updatesNegList,_updates0List,_updatesPosList
  • 自定义定时器,TimerTargetCallback,TimerTargetSelector
  • 其他线程的函数,_functionsToPerform

Selector

1
2
3
4
5
6
7
template <class T>
void scheduleUpdate(T *target, int priority, bool paused)
{

this->schedulePerFrame([target](float dt){
target->update(dt);
}, target, priority, paused);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void Scheduler::schedulePerFrame(const ccSchedulerFunc& callback, void *target, int priority, bool paused)
{
tHashUpdateEntry *hashElement = nullptr;
HASH_FIND_PTR(_hashForUpdates, &target, hashElement);

/* 若再待更新的hash表中没有找到 */
/* 那么就不需要再去更新定时任务 */
if (hashElement) {
#if COCOS2D_DEBUG >= 1
CCASSERT(hashElement->entry->markedForDeletion,"");
#endif
hashElement->entry->markedForDeletion = false;
return;
}

/* appendIn是在链表尾部添加元素 */
/* priorityIn是根据优先级插入链表中,使链表有序 */

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);
}
}

appendInpriorityIn的共同之处:

  • 将元素添加到列表之中
  • 更新_hashForUpdates这个hash表

Custom Selector

自定义定时器任务有两种类型:

  • c/c++ 函数指针 – SEL_SCHEDULE
  • std::function – ccSchedulerFunc

SEL_SCHEDULE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
void Scheduler::schedule(SEL_SCHEDULE selector, Ref *target, float interval, unsigned int repeat, float delay, bool paused)
{
CCASSERT(target, "Argument target must be non-nullptr");

tHashTimerEntry *element = nullptr;
HASH_FIND_PTR(_hashForTimers, &target, element);

/* 若没找到,新申请一个节点 */
if (! element) {
element = (tHashTimerEntry *)calloc(sizeof(*element), 1);
element->target = target;

/* 更新_hashForTimers哈希表 */
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, "");
}

if (element->timers == nullptr) {
/* 若是这个对象的第一个定时器任务,则分配定时器任务数组 */
element->timers = ccArrayNew(10);
} else {
/* 寻找是否有相同的定时器任务 */
for (int i = 0; i < element->timers->num; ++i) {
TimerTargetSelector *timer = static_cast<TimerTargetSelector*>(element->timers->arr[i]);

if (selector == timer->getSelector()) {
/* 若找到直接返回 */
CCLOG("CCScheduler#scheduleSelector. Selector already scheduled. Updating interval from: %.4f to %.4f", timer->getInterval(), interval);
timer->setInterval(interval);
return;
}
}
/* 否则,为这个定时器任务数组扩容 */
ccArrayEnsureExtraCapacity(element->timers, 1);
}

/* 将这个新的定时器任务加入定时器数组中 */
TimerTargetSelector *timer = new TimerTargetSelector();
timer->initWithSelector(this, selector, target, interval, repeat, delay);
ccArrayAppendObject(element->timers, timer);
/* 由于上一句增加了一个引用计数,所以这里调用release */
timer->release();
}

void Scheduler::schedule(SEL_SCHEDULE selector, Ref *target, float interval, bool paused)
{
this->schedule(selector, target, interval, kRepeatForever, 0.0f, paused);
}

bool Scheduler::isScheduled(SEL_SCHEDULE selector, Ref *target)
{
CCASSERT(selector, "Argument selector must be non-nullptr");
CCASSERT(target, "Argument target must be non-nullptr");

tHashTimerEntry *element = nullptr;
HASH_FIND_PTR(_hashForTimers, &target, element);

/* 先查找这个对象是否在hash表中 */
if (!element) {
return false;
}

/* 然后查找这个对象的定时器任务数组里是否有这个任务 */
if (element->timers == nullptr) {
return false;
} else {
for (int i = 0; i < element->timers->num; ++i) {
TimerTargetSelector *timer = static_cast<TimerTargetSelector*>(element->timers->arr[i]);

if (selector == timer->getSelector()) {
return true;
}
}

return false;
}

/* 按照正常流程,不会执行到这步 */
return false;
}

void Scheduler::unschedule(SEL_SCHEDULE selector, Ref *target)
{
if (target == nullptr || selector == nullptr) {
return;
}

/* 先在hash表中寻找是否有这个对象的定时器任务 */
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);

/* 更新timerIndex */
/* 保证timerIndex = i - 1 ? */
if (element->timerIndex >= i) {
element->timerIndex--;
}

/* 若这个定时器任务是最后一个定时器任务 */
/* 则从hash表中删除这个对象 */
if (element->timers->num == 0) {
/* 若当前对象和查找到的对象相同,则标记删除,等待下一个时间周期删除 */
/* 否则直接从hash表删除该对象 */
if (_currentTarget == element) {
_currentTargetSalvaged = true;
} else {
removeHashElement(element);
}
}

return;
}
}
}
}

ccSchedulerFunc

其实这个和SEL_SCHEDULE实现很类似,只是在对定时器任务的类型有所区别。由于实现相似,所以这里就不贴代码了。

Function

这个部分就这么一小段代码,我看了下,cocos引擎里面自己是没有调用这个函数的。

1
2
3
4
5
6
void Scheduler::performFunctionInCocosThread(const std::function<void ()> &function)
{
_performMutex.lock();
_functionsToPerform.push_back(function);
_performMutex.unlock();
}

总结

Scheduler的update函数是所有定时器任务,Action等等的最早的update。总结为以下三点:

  • 具有全局任务,每一帧更新 – 三个双向链表,和一个hash表。
  • 自定义任务,可以自定义时间间隔 – 一个hash表
  • 其他线程的函数 – 只能是void f()类型的函数