上一章,我们分析Node类的源码,在Node类里面耦合了一个 Scheduler 类的对象,这章我们就来剖析Cocos2d-x的调度器 Scheduler 类的源码,从源码中去了解它的实现与应用方法。
直入正题,我们打开CCScheduler.h文件看下里面都藏了些什么。
打开了CCScheduler.h 文件,还好,这个文件没有ccnode.h那么大有上午行,不然真的吐血了,仅仅不到500行代码。这个文件里面一共有五个类的定义,老规矩,从加载的头文件开始阅读。
#include <functional>
#include <mutex>
#include <set>
#include "CCRef.h"
#include CCVector.huthash.h"
NS_CC_BEGIN
/**
* @addtogroup global
* @{
*/
class Scheduler;
typedef std::function<void(float)> ccSchedulerFunc;
代码很简单,看到加载了ref类,可以推断Scheduler 可能也继承了ref类,对象统一由Cocos2d-x内存管理器来管理。
这点代码值得注意的就是下面 定义了一个函数类型 ccSchedulerFunc 接收一个float参数 返回void类型。
下面我们看这个文件里定义的第一个类 Timer
class CC_DLL Timer : public Ref
{
protected:
Timer();
public:
* get interval in seconds */
inline float getInterval() const { return _interval; };
* set interval in seconds void setInterval(float interval) { _interval = interval; };
void setupTimerWithInterval(float seconds,unsigned int repeat,float delay);
virtual void trigger() = 0;
void cancel() = 0;
* triggers the timer */
void update(float dt);
protected:
Scheduler* _scheduler; // weak ref
float _elapsed;
bool _runForever;
bool _useDelay;
unsigned int _timesExecuted;
unsigned int _repeat; 0 = once,1 is 2 x executed
float _delay;
float _interval;
};
第一点看过这个Timer类定义能了解到的信息如下:
- Timer类也是Ref类的子类,采用了cocos2d-x统一的内存管理机制。
- 这里一个抽象类。必须被继承来使用。
- Timer主要的函数就是update,这个我们重点分析。
初步了解之后,我们按照老方法,先看看Timer类都有哪些成员变量,了解一下它的数据结构。
第一个变量为
Scheduler* _scheduler; weak ref
这是一个Scheduler类的对象指针,后面有一个注释说这个指针是一个 弱引用,弱引用的意思就是,在这个指针被赋值的时候并没有增加对_scheduler的引用 计数。
后面几个变量也很好理解。
float
_elapsed; 渡过的时间.
bool _runForever; 状态变量,标记是否永远的运行。
bool _useDelay; 状态变量,标记是否使用延迟
unsigned int _timesExecuted; 记录已经执行了多少次。
unsigned int _repeat;
// 定义要执行的总次数,0为1次 1为2次 ……
float _delay; 延迟的时间 单位应该是秒
float _interval;
// 时间间隔。
总结一下,通过分析Timer类的成员变量,我们可以知道这是一个用来描述一个计时器的类,
每隔 _interval 来触发一次,
可以设置定时器触发时的延迟 _useDelay和延迟时间 _delay.
可以设置定时器触发的次数_repeat 也可以设置定时器永远执行 _runforever
下面看Timer类的方法。
getInterval 与 setInterval不用多说了,就是_interval的 读写方法。
下面看一下 setupTimerWithInterval方法。
void Timer::setupTimerWithInterval(
float delay)
{
_elapsed = -
1;
_interval =
seconds;
_delay =
delay;
_useDelay = (_delay >
0.0f) ?
true :
false;
_repeat =
repeat;
_runForever = (_repeat == kRepeatForever) ?
false;
}
这也是一个设置定时器属性的方法。
参数 seconds是设置了_interval
第二个参数repeat设置了重复的次数
第三个delay设置了延迟触发的时间。
通过 这三个参数的设置还计算出了几个状态变量 根据 delay是否大于0.0f计算了_useDelay
#define kRepeatForever (UINT_MAX -1)
根据 repeat值是否是 kRepeatForever来设置了 _runforever。
注意一点 第一行代码
_elapsed= -1;
这说明这个函数 setupTimerWithInterval 是一个初始化的函数,将已经渡过的时间初始化为-1。所以在已经运行的定时器使用这个函数的时候计时器会重新开始。
下面看一下重要的方法 update
void Timer::update(
float dt)//参数dt表示距离上一次update调用的时间间隔,这也是从后面的代码中分析出来的。
{
if (_elapsed == -
1)// 如果 _elapsed值为-1表示这个定时器是第一次进入到update方法 作了初始化操作。
{
_elapsed =
0;
_timesExecuted =
0;
}
else
{
if (_runForever && !
_useDelay)
{standard timer usage
_elapsed +=
dt; //累计渡过的时间。
if (_elapsed >=
_interval)
{
trigger();
_elapsed =
0; //触发后将_elapsed清除为0,小鱼分析这里可能会有一小点的问题,因为 _elapsed值有可能大于_interval这里没有做冗余处理,所以会吞掉一些时间,比如 1秒执行一次,而10秒内可能执行的次数小于10,吞掉多少与update调用的频率有关系。
}
}
else
{advanced usage
_elapsed +=
dt;
if (_useDelay)
{
if( _elapsed >=
_delay )
{
trigger();
_elapsed = _elapsed -
_delay;//延迟执行的计算,代码写的很干净
_timesExecuted +=
1;
_useDelay =
false;延迟已经过了,清除_useDelay标记。
}
}
else
{
if (_elapsed >=
_interval)
{
trigger();
_elapsed =
0;
_timesExecuted +=
1;
}
}
if (!_runForever && _timesExecuted >
_repeat)触发的次数已经满足了_repeat的设置就取消定时器。
{ unschedule timer
cancel();
}
}
}
}
这个update 代码很简单,就是一个标准的定时器触发逻辑,没有接触过的同学可以试模仿一下。
在这个update方法里,调用了 trigger与 cancel方法,现在我们可以理解这两个抽象方法是个什么作用,
trigger是触发函数
cancel是取消定时器
具体怎么触发与怎么取消定时器,就要在Timer的子类里实现了。
Timer类源码我们分析到这里,下面看Timer类的第一个子类 TimerTargetSelector 的定义
class CC_DLL TimerTargetSelector :
public Timer
{
public:
TimerTargetSelector();
* Initializes a timer with a target,a selector and an interval in seconds,repeat in number of times to repeat,delay in seconds. bool initWithSelector(Scheduler* scheduler,SEL_SCHEDULE selector,Ref* target,255); line-height:1.5!important">float
delay);
inline SEL_SCHEDULE getSelector() return _selector; };
void trigger()
override;
void cancel()
override;
protected:
Ref*
_target;
SEL_SCHEDULE _selector;
};
这个类也很简单。
我们先看一下成员变量 一共两个成员变量
这里关联了一个 Ref对象,应该是执行定时器的对象。
SEL_SCHEDULE 这里出现了一个新的类型,我们跟进一下,这个类型是在Ref类下面定义的,我们看一下。
class
Node;
typedef void (Ref::*
SEL_CallFunc)();
typedef void (Ref::*SEL_CallFuncN)(Node*
);
typedef void (Ref::*SEL_CallFuncND)(Node*,255); line-height:1.5!important">void*
);
typedef void (Ref::*SEL_CallFuncO)(Ref*
);
typedef void (Ref::*SEL_MenuHandler)(Ref*
);
typedef void (Ref::*SEL_SCHEDULE)(
float);
#define callfunc_selector(_SELECTOR) static_cast<cocos2d::SEL_CallFunc>(&_SELECTOR)
#define callfuncN_selector(_SELECTOR) static_cast<cocos2d::SEL_CallFuncN>(&_SELECTOR)
#define callfuncND_selector(_SELECTOR) static_cast<cocos2d::SEL_CallFuncND>(&_SELECTOR)
#define callfuncO_selector(_SELECTOR) static_cast<cocos2d::SEL_CallFuncO>(&_SELECTOR)
#define menu_selector(_SELECTOR) static_cast<cocos2d::SEL_MenuHandler>(&_SELECTOR)
#define schedule_selector(_SELECTOR) static_cast<cocos2d::SEL_SCHEDULE>(&_SELECTOR)
可以看到 SEL_SCHEDULE是一个关联Ref类的函数指针定义
_selector 是一个函数,那么应该就是定时器触发的回调函数。
TimerTargetSelector 也就是一个目标定时器,指定一个Ref对象的定时器
下面我们来看TimerTargetSelector 的几个主要的函数。
bool TimerTargetSelector::initWithSelector(Scheduler* scheduler,255); line-height:1.5!important">float
delay)
{
_scheduler =
scheduler;
_target =
target;
_selector =
selector;
setupTimerWithInterval(seconds,repeat,delay);
return true;
}
这个数不用多说,就是一个TimerTargetSelector的初始化方法。后面三个参数是用来初始化基类Timer的。
第一个参数 scheduler 因为我们还没分析到 Scheduler类现在还不能明确它的用处,这里我们先标红记下。
getSelector方法不用多说,就是 _selector的 读取方法,注意这个类没有setSelector因为初始化 _selector要在 initWithSelector方法里进行。
接下来就是两个重载方法 trigger 和 cancel
下面看看实现过程
void
TimerTargetSelector::trigger()
{
if (_target &&
_selector)
{
(_target->*
_selector)(_elapsed);
}
}
void TimerTargetSelector::cancel()
{
_scheduler->
unschedule(_selector,_target);
}
实现过程非常简单。
在trigger函数中,实际上就是调用 了初始化传进来的回调方法。 _selector 这个回调函数接收一个参数就是度过的时间_elapsed
cancel方法中调用 了 _scheduler的 unschedule方法,这个方法怎么实现的,后面我们分析到Scheduler类的时候再细看。
小结:
TimerTargetSelector 这个类,是一个针对Ref 对象的定时器,调用的主体是这个Ref 对象。采用了回调函数来执行定时器的触发过程。
下面我们继续进行 阅读 TimerTargetCallback 类的源码
class CC_DLL TimerTargetCallback :
public:
TimerTargetCallback();
bool initWithCallback(Scheduler* scheduler,255); line-height:1.5!important">const ccSchedulerFunc& callback,255); line-height:1.5!important">void *target,255); line-height:1.5!important">const std::
string& key,0); line-height:1.5!important">*
* @js NA
* @lua NA
const ccSchedulerFunc& getCallback()
return _callback; };
inline string& getKey()
return _key; };
protected:
void*
_target;
ccSchedulerFunc _callback;
std::string _key;
};
这个类也是 Timer 类的子类,与TimerTargetSelector类的结构类似
先看成员变量,
_target 一个void类型指针,应该是记录一个对象的
ccSchedulerFunc 最上在定义的一个回调函数
还有一个_key 应该是一个定时器的别名。
initWithCallback 这个函数就是一些set操作来根据参数对其成员变量赋值,不用多说。
getCallback 是 _callback的读取方法。
getkey是_key值的读取方法。
下面我们重点看一下 trigger与 cancel的实现。
void TimerTargetCallback::trigger()
{
if (_callback)
{
_callback(_elapsed);
}
}
void TimerTargetCallback::cancel()
{
_scheduler->unschedule(_key,179); text-decoration:underline; border:none!important">
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;