‎1. 概述

Cocos2d-x 的 Scheduler 离不开 Timer。Timer 类是定时器,用来规定一个回调函数应该在何时被触发。Timer 封装了已运行时间、重复次数、已执行次数、延迟秒数、时间间隔、要触发的回调函数等等,都是与一个回调函数触发相关的成员。

Scheduler 是调度器,用来对 Timer 进行调度,Timer 只是定义了回调函数的触发条件、触发次数等,真正的触发动作由 Scheduler 执行。

2. Timer 和 TimerTargetSelector、TimerTargetCallback

Timer 的成员:

class CC_DLL Timer : public Ref
{void setupTimerWithInterval(float seconds, unsigned int repeat, float delay); //
void setAborted() { _aborted = true; }
bool isAborted() const { return _aborted; }
bool isExhausted() const; virtual void trigger(float dt) = ;
virtual void cancel() = ; void update(float dt); Scheduler* _scheduler; // weak ref
float _elapsed; // 已运行时间
bool _runForever; // 是否永远运行
bool _useDelay; // 是否使用延迟
unsigned int _timesExecuted; // 已执行次数
unsigned int _repeat; // 规定执行次数, 0 = once
float _delay; // 延迟
float _interval; // 时间间隔
bool _aborted; // fff
};

Timer 有两个子类 TimerTargetSelector、TimerTargetCallback。两个子类的成员有所不同:

// TimerTargetSelector
Ref* _target;
SEL_SCHEDULE _selector;
// TimerTargetCallback
void* _target;
ccSchedulerFunc _callback;
std::string _key;

这两个类回调函数的函数类型不同和 target 类型不同,对应了两个 Scheduler::schedule(...) 方法的回调函数指针参数的不同。两个schedule(...)方法定义如下:

void schedule(SEL_SCHEDULE selector, Ref *target, float interval, unsigned int repeat, float delay, bool paused);
void schedule(const ccSchedulerFunc& callback, void *target, float interval, bool paused, const std::string& key);

TimerTargetCallback 对应的 schedule 方法参数的函数指针为 ccSchedulerFunc& callback,同时参数中包含 key,key 是 Timer 的标志,有唯一性 。ccSchedulerFunc的定义如下:

typedef std::function<void(float)> ccSchedulerFunc;

这是一个函数类型,函数满足返回值为空,参数为1个 float。

TimerTargetSelector 对应的 schedule 方法参数的函数指针为 SEL_SCHEDULE selector,不包含 key。为什么这里就不含 key 了呢?

看 SEL_SCHEDULE 的定义:

typedef void (Ref::*SEL_SCHEDULE)(float);

SEL_SCHEDULE 是函数指针,函数是 Ref 对象的成员函数,满足返回值为空,参数为1个float。

不含 key 的原因是这里定义的是指向类成员函数的指针。指向类成员函数的指针与普通函数指针的区别是,前者不仅要匹配函数的参数类型个数和返回值类型,还要匹配所属的类的对象。也就是说,selector 起到了 key 的作用,通过 selector 和 target 能找到某个类的对象对应的 Timer,而 callback 和 target 不行,所以 TimerTargetCallback 要加上 Key。

Timer 的 update(float dt) 方法是 Timer 计算时间和执行次数,判断是否触发回调函数和是否销毁 Timer所执行的函数。在符合触发条件时调用子类的 trigger(_delay) 方法,trigger(_delay)在两个子类中,调用了回调函数 *_selector 或者 _callback。当符合 isExhausted() 条件,即 Timer 不永远执行且已重复次数大于等于规定的重复次数数时,调用子类的 cancel() 方法,即调用子类对应的 unschedule(...) 方法。

3. Scheduler 调度器内 Timer 的定义与销毁

3.1 Scheduler 2种调度方式

默认调度方式:每帧调度,每帧更新,是不带间隔的调度,间隔是 interval 变量,使用 scheduleUpdate()

用户自定义调度方式:带间隔调度,不每帧更新,用户通过间隔决定更新时机,使用 schedule(...)

3.2 Scheduler 3个结构体成员

// 每帧调度使用
typedef struct _listEntry
{
struct _listEntry *prev, *next;
ccSchedulerFunc callback;
void *target;
int priority;
bool paused;
bool markedForDeletion;
} 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; // 自定义调度使用,带间隔
typedef struct _hashSelectorEntry
{
ccArray *timers;
void *target;
int timerIndex;
Timer *currentTimer;
bool paused;
UT_hash_handle hh;
} tHashTimerEntry;

tHashTimerEntry 包含了 UT_hash_handle 类型变量。在结构体中使用 UT_hash_handle 类型,就实现了哈希链表。通过哈希链表连接,每个 Entry 以 void * 类型的 target 作为“key”,Entry 作为“value”。在下面的 schedule(...) 方法中可以知道,对于Entry 的查找正是通过“key”,即指针 target 来进行的。通过“key”(target)快速找到对应的 Entry。Entry 中包括了 ccArray * 类型的 timers,每个 target 的众多 Timer 按 ccArray 数据结构排列,timers 是指向这个存储 Timer 的数据结构的指针。

剩下两个结构体在3.6节介绍。

3.3 Scheduler 的 schedule(...) 成员方法

按间隔调度使用Scheduler 的 schedule(...) 方法,该方法可以对 Entry 和 Entry 内部的定时器 Timer 进行定义、修改。

该方法看似有很多重载,实际只根据 Timer 两个子类,分为两种,在第2节也有介绍:

// 针对 TimerTargetSelector
void schedule(SEL_SCHEDULE selector, Ref *target, float interval, unsigned int repeat, float delay, bool paused);
// 针对 TimerTargetCallback
void schedule(const ccSchedulerFunc& callback, void *target, float interval, bool paused, const std::string& key);

下面是 TimeTargetSelector 的 schedule(...) 方法内执行的大致过程:

第1节有介绍,因为 TimeTargetSelector 用 selector 对 Timer 有唯一性,所以在判断 target 的每一个 Timer 是否为要找的 Timer 时,用 selector == timer->getSelector() 进行判断。而在 TimerTargetCallback 对应的 schedule(...) 方法中,这步判断改为

key == timer->getKey(),因为此情况下 key 对 Timer有唯一性,故用 key 进行判断。

3.4 Scheduler 的 unschedule(...) 成员方法

unschedule(...) 根据 Timer 两个子类,分为两种:

void unschedule(const std::string &key, void *target);
void unschedule(SEL_SCHEDULE selector, Ref *target);

下面是 TimerTargetSelector 的 unschedule(...)方法执行大致过程:

Timer 有两个子类,用 key 或 selector 判断 Timer 不再赘述。

3.5 Scheduler 的 schedulePerFrame(...) 方法

每帧调度用到的是 schedulePerFrame(...) 方法:

void schedulePerFrame(const ccSchedulerFunc& callback, void *target, int priority, bool paused);

对该方法的调用过程如下,从上向下进行:

// ABCScene::init()
this->scheduleUpdate(); // Node::scheduleUpdate()
scheduleUpdateWithPriority(); // Node::scheduleUpdateWithPriority(int priority)
_scheduler->scheduleUpdate(this, priority, !_running); // Scheduler
template <class T>
void scheduleUpdate(T *target, int priority, bool paused)
{
this->schedulePerFrame([target](float dt){
target->update(dt);
}, target, priority, paused);
}

3.2节提到,每帧调度用到了两个结构体变量:

// 每帧调度使用
typedef struct _listEntry
{
struct _listEntry *prev, *next;
ccSchedulerFunc callback;
void *target;
int priority;
bool paused;
bool markedForDeletion;
} 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 以双向链表方式相连,markedForDeletion 标记告诉 Schedule 是否删除该 Entry,同时存储了回调函数、优先级等。

每帧调度中,一个 target 绑定一个回调函数,一对一的关系,因为是每帧调度,不需考虑调度的间隔、次数等,所以 target 和回调函数直接绑定在一个结构体变量,也可以理解成一个 target 绑定了一个 Timer,Timer 中只定义了回调函数。

而按间隔调度一个 target 绑定多个回调函数,一对多的关系,因为每个回调函数调度的时机不同,所以用到 Timer 进行区分,众多 Timer 组合在一起成为 timers,和一个 target 绑定在一起。

tHashUpdateEntry 通过哈希链表连接,可以快速地通过“key”(target),找到指向 tListEntry 的指针 entry,list 变量用以区分该哈希链表中的 Entry 优先级与0的大小关系,list 分为3类。

schedulePerFrame(...) 方法执行大致过程如下:

4. Scheduler 执行调度

终于写到 update(float dt)方法了。

该方法被调用到的顺序如下:

Application::run()
Director::mainLoop()
Director::drawScene()
calculateDeltaTime();
_scheduler->update(_deltaTime);

update(...) 方法执行的大致过程:

一些 bool 变量的大致作用:

_updateHashLocked 在 Scheduler 构造函数中置 false。在 Scheduler 的 upadte 方法开始置 true,方法结束置 false,该变量。在 Scheduler::removeUpdateFromHash(struct _listEntry *entry) 方法中,当该变量为 false 时,可以删除一个每帧调度类型的Entry。

if (!_updateHashLocked)
CC_SAFE_DELETE(element->entry);
else
{
element->entry->markedForDeletion = true;
_updateDeleteVector.push_back(element->entry);
}

如果该变量为 true,则调度器正在 update,此时不能直接删除 Entry,需要把 Entry 加入到 _updateDeleteVector 中,在 update 方法结束前,即所有更新结束后进行删除。

_currentTargetSalvaged 在构造函数中置 false。在 Scheduler 的 upadte 方法中,把当前遍历到的 Entry 作为正在执行的 Entry 后,_currentTargetSalvaged 置 false。在 unscheduleAllForTarget(void *target) 和unschedule(const std::string &key, void *target)方法中,如果选择的 target (Entry)正在执行,则该变量置 true,不在执行则成功删除 Entry。其余操作在 Action相关文件中,暂未学习,在学习后对此处补充。

‎Cocos2d-x 3.x 学习笔记(三):Scheduler Timer 调度与定时的更多相关文章

  1. Oracle学习笔记三 SQL命令

    SQL简介 SQL 支持下列类别的命令: 1.数据定义语言(DDL) 2.数据操纵语言(DML) 3.事务控制语言(TCL) 4.数据控制语言(DCL)  

  2. [Firefly引擎][学习笔记三][已完结]所需模块封装

    原地址:http://www.9miao.com/question-15-54671.html 学习笔记一传送门学习笔记二传送门 学习笔记三导读:        笔记三主要就是各个模块的封装了,这里贴 ...

  3. JSP学习笔记(三):简单的Tomcat Web服务器

    注意:每次对Tomcat配置文件进行修改后,必须重启Tomcat 在E盘的DATA文件夹中创建TomcatDemo文件夹,并将Tomcat安装路径下的webapps/ROOT中的WEB-INF文件夹复 ...

  4. java之jvm学习笔记三(Class文件检验器)

    java之jvm学习笔记三(Class文件检验器) 前面的学习我们知道了class文件被类装载器所装载,但是在装载class文件之前或之后,class文件实际上还需要被校验,这就是今天的学习主题,cl ...

  5. VSTO学习笔记(三) 开发Office 2010 64位COM加载项

    原文:VSTO学习笔记(三) 开发Office 2010 64位COM加载项 一.加载项简介 Office提供了多种用于扩展Office应用程序功能的模式,常见的有: 1.Office 自动化程序(A ...

  6. Java IO学习笔记三

    Java IO学习笔记三 在整个IO包中,实际上就是分为字节流和字符流,但是除了这两个流之外,还存在了一组字节流-字符流的转换类. OutputStreamWriter:是Writer的子类,将输出的 ...

  7. NumPy学习笔记 三 股票价格

    NumPy学习笔记 三 股票价格 <NumPy学习笔记>系列将记录学习NumPy过程中的动手笔记,前期的参考书是<Python数据分析基础教程 NumPy学习指南>第二版.&l ...

  8. Learning ROS for Robotics Programming Second Edition学习笔记(三) 补充 hector_slam

    中文译著已经出版,详情请参考:http://blog.csdn.net/ZhangRelay/article/category/6506865 Learning ROS for Robotics Pr ...

  9. Learning ROS for Robotics Programming Second Edition学习笔记(三) indigo rplidar rviz slam

    中文译著已经出版,详情请参考:http://blog.csdn.net/ZhangRelay/article/category/6506865 Learning ROS for Robotics Pr ...

随机推荐

  1. foreach获取索引值

    List<" }; foreach (string item in items) { int index = items.IndexOf(item); Console.WriteLin ...

  2. Ubuntu 搭建 GitLab 笔记

    简介 GitLab 社区版可以提供许多与 GitHub 相同的功能,且部署在属于自己的机器上,我们会因为网络及其他一些问题而不便使用 GitHub ,这时部署一个 GitLab 是最好的选择. 下载 ...

  3. 通过SSIS的“查找”组件进行不同数据源之间数据的合并操作

    原文:通过SSIS的"查找"组件进行不同数据源之间数据的合并操作 为了协助开发还原生产环境中的某些bug,需要将将生产环境的某些特定表数据导入到测试环境做测试,之前一直都是暴力地t ...

  4. 16.09 working note

    这个月最主要任务是linux shell script学习. 其次是继续spring源码学习. 其余时间C.C++和Java学习. 01 9月第一天,9点多才到家.做道简单的oj题练习下.因为简单,所 ...

  5. 基于ASP.NET的新闻管理系统(三)代码展示

    5.1.1栏目部分 增加栏目(addLanMu.aspx): <html xmlns="http://www.w3.org/1999/xhtml"> <head  ...

  6. Hexo+NexT(三):Next主题配置详解

    阅读本篇之前,假定读者已经有了Node.js的基础,如需要补充Node.js知识的,请自行百度. Hexo是在Node.js框架下的一个项目,利用Node.js提供的强大功能,完成从Markdown到 ...

  7. Hexo+NexT(五):Hexo第三方插件提供功能及配置

    本篇文章介绍NexT中通过第三方实现的功能,有的需要通过额外的插件,有的需要通过第三方提供的功能.这些功能丰富了网站内容,弥补了原生静态网站的不足. Hexo博客专题索引页 增加百度统计分析功能 访问 ...

  8. Azkaban学习之路(二)—— Azkaban 3.x 编译及部署

    一.Azkaban 源码编译 1.1 下载并解压 Azkaban 在3.0版本之后就不提供对应的安装包,需要自己下载源码进行编译. 下载所需版本的源码,Azkaban的源码托管在GitHub上,地址为 ...

  9. Codeforces Round #564 (Div. 2)A

    A. Nauuo and Votes 题目链接:http://codeforces.com/contest/1173/problem/A 题目 Nauuo is a girl who loves wr ...

  10. 一个commit引发的思考

    这几天我翻了翻golang的提交记录,发现了一条很有意思的提交:bc593ea,这个提交看似简单,但是引人深思. commit讲了什么 commit的标题是"sync: document i ...