coco2dx-定时器(Scheduler)原理一 -- Timer

总结:

所有CCNode中使用到定时器ScheduleXXX的接口全部是调用了CCDirector中的Scheduler实例的接口,场景的所有定时器都统一由此管理

定时器分两种:一种是每帧执行的,交给Schedule处理,一种是定时执行的,交给了Timer,Schedule除了可以注册Ref实体的定时器,还可以注册其他自定义类的定时器。

Schedule更新的时候,首先更新Schedule列表的,再更新Timer列表的,最后判断更新ScriptHandler列表的


这章我们就来剖析Cocos2d-x的调度器 Scheduler 类的源码,从源码中去了解它的实现与应用方法。

直入正题,我们打开CCScheduler.h文件看下里面都藏了些什么。

打开了CCScheduler.h 文件,还好,这个文件没有ccnode.h那么大有上午行,不然真的吐血了, 仅仅不到500行代码。这个文件里面一共有五个类的定义,老规矩,从加载的头文件开始阅读。

#include <functional>
#include <mutex>
#include <set>
 
#include "CCRef.h"
#include "CCVector.h"
#include "uthash.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 */
    inline void setInterval(float interval) { _interval = interval; };
     
    void setupTimerWithInterval(float seconds, unsigned int repeat, float delay);
     
    virtual void trigger() = 0;
    virtual 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类定义能了解到的信息如下:

  1. Timer类也是Ref类的子类,采用了cocos2d-x统一的内存管理机制。

  2. 这里一个抽象类。必须被继承来使用。

  3. Timer主要的函数就是update,这个我们重点分析。


初步了解之后,我们按照老方法,先看看Timer类都有哪些成员变量,了解一下它的数据结构。

第一个变量为

1
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;             
// 时间间隔。


总结一下:

  1. 通过分析Timer类的成员变量,我们可以知道这是一个用来描述一个计时器的类;

  2. 每隔 _interval 来触发一次;

  3. 可以设置定时器触发时的延迟 _useDelay和延迟时间 _delay;

  4. 可以设置定时器触发的次数_repeat 也可以设置定时器永远执行 _runforever。


Timer类的方法

getInterval 与 setInterval不用多说了,就是_interval的 读写方法。


下面看一下 setupTimerWithInterval方法。

void Timer::setupTimerWithInterval(float seconds, unsigned int repeat, float delay)
{
    _elapsed = -1;
    _interval = seconds;
    _delay = delay;
    _useDelay = (_delay > 0.0f) ? true : false;
    _repeat = repeat;
    _runForever = (_repeat == kRepeatForever) ? true : 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 的定义(cocos2.x只有Timer)

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, float seconds, unsigned int repeat, float delay);
     
    inline SEL_SCHEDULE getSelector() const { return _selector; };
     
    virtual void trigger() override;
    virtual void cancel() override;
     
protected:
    Ref* _target;
    SEL_SCHEDULE _selector;
};


这个类也很简单。


我们先看一下成员变量 一共两个成员变量

Ref* _target;

这里关联了一个 Ref对象,应该是执行定时器的对象。

SEL_SCHEDULE _selector;

SEL_SCHEDULE  这里出现了一个新的类型,我们跟进一下,这个类型是在Ref类下面定义的,我们看一下。
class Node;
 
typedef void (Ref::*SEL_CallFunc)();
typedef void (Ref::*SEL_CallFuncN)(Node*);
typedef void (Ref::*SEL_CallFuncND)(Node*, 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, SEL_SCHEDULE selector, Ref* target, float seconds, unsigned int repeat, 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 Timer
{
public:
    TimerTargetCallback();
     
    /** Initializes a timer with a target, a lambda and an interval in seconds, repeat in number of times to repeat, delay in seconds. */
    bool initWithCallback(Scheduler* scheduler, const ccSchedulerFunc& callback, void *target, const std::string& key, float seconds, unsigned int repeat, float delay);
     
    /**
     * @js NA
     * @lua NA
     */
    inline const ccSchedulerFunc& getCallback() const { return _callback; };
    inline const std::string& getKey() const { return _key; };
     
    virtual void trigger() override;
    virtual void cancel() override;
     
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, _target);
}


这两个方法实现也很简单,

在trigger中就是调用了callback方法并且把_elapsed作为参数 传递。

cancel与上面的cancel实现一样,后面我们会重点分析 unschedule 方法。

下面一个Timer类的了类是TimerScriptHandler 与脚本调用 有关,这里大家自行看一下代码,结构与上面的两个类大同小异。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值