在Scheduler类之前声明了四个结构体,我们看一眼
struct _listEntry;
struct _hashSelectorEntry;
struct _hashUpdateEntry;
#if CC_ENABLE_SCRIPT_BINDING
class SchedulerScriptHandlerEntry;
#endif
后面分析Scheduler时会碰到这几个数据类型,这几个结构体的定义很简单,后面碰到难点我们在详细说。
类定义
class CC_DLL Scheduler : public Ref
{}
不用多说了,这样的定义我们已经碰到好多了, Scheduler也是 Ref的了类。
老方法,先看成员变量。了解Scheduler的数据结构。
float _timeScale; // 速度控制,值为1.0f为正常速度 小于1 慢放,大于1 快放。
//
// "updates with priority" stuff
//
struct _listEntry *_updatesNegList; // list of priority < 0 三种优先级的list具体作用这里看不出来,下面在源码中去分析
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
// Used for "selectors with interval"
struct _hashSelectorEntry *_hashForTimers;
struct _hashSelectorEntry *_currentTarget;
bool _currentTargetSalvaged;
// If true unschedule will not remove anything from a hash. Elements will only be marked for deletion.
bool _updateHashLocked;
#if CC_ENABLE_SCRIPT_BINDING
Vector<SchedulerScriptHandlerEntry*> _scriptHandlerEntries;
#endif
// Used for "perform Function"
std::vector<std::function<void()>> _functionsToPerform;
std::mutex _performMutex;
看了这些成员变量,大多是一些链表,数组,具体干什么的也猜不太出来,没关系,我们从方法入手,看看都干了些什么。
构造函数 与 析构函数
Scheduler::Scheduler(void)
: _timeScale(1.0f)
, _updatesNegList(nullptr)
, _updates0List(nullptr)
, _updatesPosList(nullptr)
, _hashForUpdates(nullptr)
, _hashForTimers(nullptr)
, _currentTarget(nullptr)
, _currentTargetSalvaged(false)
, _updateHashLocked(false)
#if CC_ENABLE_SCRIPT_BINDING
, _scriptHandlerEntries(20)
#endif
{
// I don't expect to have more than 30 functions to all per frame
_functionsToPerform.reserve(30);
}
Scheduler::~Scheduler(void)
{
unscheduleAll();
}
构造函数与析构函数都很简单,注意构造函数里面有一行注释,不希望在一帧里面有超过30个回调函数。我们在编写自己的程序的时候也要注意这一点。
析构函数中调用 了 unscheduleAll 这个函数我们先不跟进看。后面再分析,这里要记住unscheduleAll是一个清理方法。
getTimeScale 与 setTimeScale 是读写_timeScale的方法,控制定时器速率的。
下面我们看 Scheduler::schedule 的几个重载方法。
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;
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);
timer->release();
}
先看 schedule 方法的几个参数 很像 TimerTargetSelector 类的init方法的几个参数。
下面看一下schedule的函数过程:
先调用了 HASH_FIND_PTR(_hashForTimers, &target, element); 有兴趣的同学可以跟一下 HASH_FIND_PTR这个宏,这行代码的含义是在 _hashForTimers 这个数组中找与&target相等的元素,用element来返回。
而_hashForTimers不是一个数组,但它是一个线性结构的,它是一个链表。
下面的if判断是判断element的值,看看是不是已经在_hashForTimers链表里面,如果不在那么分配内存创建了一个新的结点并且设置了pause状态。
再下面的if判断的含义是,检查当前这个_target的定时器列表状态,如果为空那么给element->timers分配了定时器空间
如果这个_target的定时器列表不为空,那么检查列表里是否已经存在了 selector 的回调,如果存在那么更新它的间隔时间,并退出函数。
1
|
ccArrayEnsureExtraCapacity(element->timers, 1);
|
这行代码是给 ccArray分配内存,确定能再容纳一个timer。
函数的最后四行代码,就是创建了一个新的 TimerTargetSelector 对象,并且对其赋值 还加到了 定时器列表里。
这里注意一下,调用了 timer->release() 减少了一次引用,会不会造成timer被释放呢?当然不会了,大家看一下ccArrayAppendObject方法里面已经对 timer进行了一次retain操作所以 调用了一次release后保证 timer的引用计数为1。
看过这个方法,我们清楚了几点:
-
tHashTimerEntry 这个结构体是用来记录一个Ref 对象的所有加载的定时器
-
_hashForTimers 是用来记录所有的 tHashTimerEntry 的链表头指针。
下面一个 schedule函数的重载版本与第一个基本是一样的
void Scheduler::schedule(const ccSchedulerFunc& callback, void *target, float interval, bool paused, const std::string& key)
{
this->schedule(callback, target, interval, kRepeatForever, 0.0f, paused, key);
}
唯一 的区别是这个版本的 repeat参数为 kRepeatForever 永远执行。
下面看第三个 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);
// 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)
{
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();
}
这个版本与第一个版本过程基本一样,只不过这里使用的_target不是Ref类型而是void*类型,可以自定义类型的定时器。所以用到了TimerTargetCallback这个定时器结构。
同样将所有 void*对象存到了 _hashForTimers
还有一个版本的 schedule 重载,它是第三个版本的扩展,扩展了重复次数为永远。
这里小结一下 schedule方法:
1. Ref类型与非Ref类型对象的定时器处理基本一样,都是加到了调度控制器的_hashForTimers链表里面,
2. 调用schedule方法会将指定的对象与回调函数做为参数加到schedule的 定时器列表里面。加入的过程会做一个检测是否重复添加的操作。
下面我们看一下几个 unschedule 方法。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);
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);
// update timerIndex in case we are in tick:, looping over the actions
if (element->timerIndex >= i)
{
element->timerIndex--;
}
if (element->timers->num == 0)
{
if (_currentTarget == element)
{
_currentTargetSalvaged = true;
}
else
{
removeHashElement(element);
}
}
return;
}
}
}
}
我们按函数过程看,怎么来卸载定时器的。
-
参数为一个回调函数指针和一个Ref 对象指针。
-
在 对象定时器列表_hashForTimers里找是否有 target 对象
-
在找到了target对象的条件下,对target装载的timers进行逐一遍历
-
遍历过程 比较当前遍历到的定时器的 selector是等于传入的 selctor
-
将找到的定时器从element->timers里删除。重新设置timers列表里的 计时器的个数。
-
最后_currentTarget 与 element的比较值来决定是否从_hashForTimers 将其删除。
这些代码过程还是很好理解的,不过有一个问题还没看明白,就是用到了_currentTarget 与 _currentTargetSalvaged 这两个变量,它们的作用是什么呢?下面我们带着这个问题来找答案。
再看另一个unschedule重载版本,基本都是大同小异,都是执行了这几个步骤,只是查找的参数从 selector变成了 std::string &key 对象从 Ref类型变成了void*类型。
现在我们看一下update方法。当看到update方法时就知道 这个方法是在每一帧中调用的,也是引擎驱动的灵魂。
update方法的详细分析:
void Scheduler::update(float dt)
{
_updateHashLocked = true;// 这里加了一个状态锁,应该是线程同步的作用。
if (_timeScale != 1.0f)
{
dt *= _timeScale;// 时间速率调整,根据设置的_timeScale 进行了乘法运算。
}
//
// Selector callbacks
//
// 定义了两个链表遍历的指针。
tListEntry *entry, *tmp;
// 处理优先级小于0的定时器,这些定时器存在了_updatesNegList链表里面,具体怎么存进来的,目前我们还不知道,这里放出一个疑问2
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);
}
}
// 遍历_hashForTimers里自定义的计时器对象列表
for (tHashTimerEntry *elt = _hashForTimers; elt != nullptr; )
{
_currentTarget = elt;// 这里通过遍历动态设置了当前_currentTarget对象。
_currentTargetSalvaged = false;// 当前目标定时器没有被处理过标记。
if (! _currentTarget->paused)
{
// 遍历每一个对象的定时器列表
for (elt->timerIndex = 0; elt->timerIndex < elt->timers->num; ++(elt->timerIndex))
{
elt->currentTimer = (Timer*)(elt->timers->arr[elt->timerIndex]);// 这里更新了对象的currentTimer
elt->currentTimerSalvaged = false;
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.
// currentTimerSalvaged的作用是标记当前这个定时器是否已经失效,在设置失效的时候我们对定时器增加过一次引用记数,这里调用release来减少那次引用记数,这样释放很安全,这里用到了这个小技巧,延迟释放,这样后面的程序不会出现非法引用定时器指针而出现错误
elt->currentTimer->release();
}
// currentTimer指针使用完了,设置成空指针
elt->currentTimer = nullptr;
}
}
// elt, at this moment, is still valid
// so it is safe to ask this here (issue #490)
// 因为下面有可能要清除这个对象currentTarget为了循环进行下去,这里先在currentTarget对象还存活的状态下找到链表的下一个指针。
elt = (tHashTimerEntry *)elt->hh.next;
// only delete currentTarget if no actions were scheduled during the cycle (issue #481)
// 如果_currentTartetSalvaged 为 true 且这个对象里面的定时器列表为空那么这个对象就没有计时任务了我们要把它从__hashForTimers列表里面删除。
if (_currentTargetSalvaged && _currentTarget->timers->num == 0)
{
removeHashElement(_currentTarget);
}
}
// 下面这三个循环也是清理工作
// 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->remove