Cocos2d-x 3.x 学习笔记(三):Scheduler Timer 调度与定时
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 调度与定时的更多相关文章
- Oracle学习笔记三 SQL命令
SQL简介 SQL 支持下列类别的命令: 1.数据定义语言(DDL) 2.数据操纵语言(DML) 3.事务控制语言(TCL) 4.数据控制语言(DCL)
- [Firefly引擎][学习笔记三][已完结]所需模块封装
原地址:http://www.9miao.com/question-15-54671.html 学习笔记一传送门学习笔记二传送门 学习笔记三导读: 笔记三主要就是各个模块的封装了,这里贴 ...
- JSP学习笔记(三):简单的Tomcat Web服务器
注意:每次对Tomcat配置文件进行修改后,必须重启Tomcat 在E盘的DATA文件夹中创建TomcatDemo文件夹,并将Tomcat安装路径下的webapps/ROOT中的WEB-INF文件夹复 ...
- java之jvm学习笔记三(Class文件检验器)
java之jvm学习笔记三(Class文件检验器) 前面的学习我们知道了class文件被类装载器所装载,但是在装载class文件之前或之后,class文件实际上还需要被校验,这就是今天的学习主题,cl ...
- VSTO学习笔记(三) 开发Office 2010 64位COM加载项
原文:VSTO学习笔记(三) 开发Office 2010 64位COM加载项 一.加载项简介 Office提供了多种用于扩展Office应用程序功能的模式,常见的有: 1.Office 自动化程序(A ...
- Java IO学习笔记三
Java IO学习笔记三 在整个IO包中,实际上就是分为字节流和字符流,但是除了这两个流之外,还存在了一组字节流-字符流的转换类. OutputStreamWriter:是Writer的子类,将输出的 ...
- NumPy学习笔记 三 股票价格
NumPy学习笔记 三 股票价格 <NumPy学习笔记>系列将记录学习NumPy过程中的动手笔记,前期的参考书是<Python数据分析基础教程 NumPy学习指南>第二版.&l ...
- Learning ROS for Robotics Programming Second Edition学习笔记(三) 补充 hector_slam
中文译著已经出版,详情请参考:http://blog.csdn.net/ZhangRelay/article/category/6506865 Learning ROS for Robotics Pr ...
- Learning ROS for Robotics Programming Second Edition学习笔记(三) indigo rplidar rviz slam
中文译著已经出版,详情请参考:http://blog.csdn.net/ZhangRelay/article/category/6506865 Learning ROS for Robotics Pr ...
随机推荐
- VS2008发布项目“发布失败”,没有提示错误
VS2008发布项目时发布失败,但是没有提示任何的错误. 解决方法: 组合键”Ctrl+Alt+O”; 根据这个我知道了,是因为我更改了文件名,发布时找不到导致的,然后在资源管理器中找到那一项,删除或 ...
- Android零基础入门第89节:Fragment回退栈及弹出方法
在上一期分享的文章末尾留了一个课后作业,有去思考如何解决吗?如果已经会了那么恭喜你,如果还不会也没关系,本期一起来学习. 一.回退栈 在前面两期的示例中,当我们完成一些操作后,如果想要回到操作之前的状 ...
- 演练:创建和使用静态库 (C++)
我们将创建的下一个库类型是静态库 (LIB). 使用静态库是重用代码的一种绝佳方式. 您不必在自己创建的每个程序中重新实现同一例程,而只需对这些例程编写一次,然后从需要该功能的应用程序引用它们即可. ...
- <第三方>TGRefreshO按照QQ的刷新方式下拉刷新
一 .使用方法: 刷新机制,类似QQ一样的刷新机制,弹簧.橡皮筋下拉刷新控件,类似QQ下拉刷新效果,同时支持其他样式: 首先写上这一句(必须的) #import <TGRefresh ...
- 核心思想:评价早期SaaS创业公司时,投资人在关注什么?(是否有机会发展成一个平台,长期的护城河)
编者按: 当聊到早期项目时,人们经常会问投资人一个问题:“在评价早期 SaaS 创业公司时,投资人会关注什么——指标还是其他方面?” Nakul Mandan 作为 Lightspeed 风投机构的合 ...
- scrapy爬虫框架研究!
最近由于项目需要,开始研究scrapy爬虫,走过不少弯路,准备写个记录,记下踩过的各种坑.
- 大数据基础之Kafka(1)简介、安装及使用
kafka2.0 http://kafka.apache.org 一 简介 Kafka® is used for building real-time data pipelines and strea ...
- cookie,session,用户认证组件
一. 绘画跟踪技术 在客户端与服务端的一次会务中,多次的请求与相应,HTTP协议是无状态协议,也就是说每个请求都是独立的!无法记录前一次请求的状态.会话跟踪技术即能够实现多次回话间信息共享的作用,HT ...
- Angular4.0从入门到实战打造在线竞拍网站学习笔记之一--组件
Angular4.0基础知识之组件 Angular4.0基础知识之路由 Angular4.0依赖注入 Angular4.0数据绑定&管道 最近搞到手了一部Angular4的视频教程,这几天正好 ...
- Hive学习之路(二)—— Linux环境下Hive的安装部署
一.安装Hive 1.1 下载并解压 下载所需版本的Hive,这里我下载版本为cdh5.15.2.下载地址:http://archive.cloudera.com/cdh5/cdh/5/ # 下载后进 ...