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 ...
随机推荐
- SQLSERVER使用密码加密备份文件以防止未经授权还原数据库
原文:SQLSERVER使用密码加密备份文件以防止未经授权还原数据库 SQLSERVER使用密码加密备份文件以防止未经授权还原数据库 在备份数据库的时候,用户可以为媒体集.备份集或两者指定密码 在ba ...
- Vm安装
说明:都是默认安装,并不需要繁琐设置,所以没有文字说明
- painter半透明的 底层窗口全透明背景
- Cloudera Impala需求
Cloudera Impala需求 为了达到预期的效果,Impala依赖于软件.硬件的可用性,以及下面章节描述的配置. 继续阅读: 支持的操作系统 支持的Hadoop发布 Hive Metastore ...
- Django学习笔记(20)——BBS+Blog项目开发(4)Django如何使用Bootstrap
本文学习如何通过Django使用Bootstrap.其实在之前好几个Django项目中已经尝试使用过了Bootstrap,而且都留有学习记录,我已经大概有了一个大的框架,那么本文就从头再走一遍流程,其 ...
- MySQL之SQL优化详解(二)
目录 MySQL之SQL优化详解(二) 1. SQL的执行顺序 1.1 手写顺序 1.2 机读顺序 2. 七种join 3. 索引 3.1 索引初探 3.2 索引分类 3.3 建与不建 4. 性能分析 ...
- 短视频技术详解:Android端的短视频开发技术
在 <如何快速实现移动端短视频功能?>中,我们主要介绍了当前短视频的大热趋势以及开发一个短视频应用所涉及到的功能和业务.在本篇文章中,我们主要谈一谈短视频在Android端上的具体实现技术 ...
- JavaScript 操作 DOM 总结
基本概念 DOM 是 JavaScript 操作网页的接口,全称为"文档对象模型"(Document Object Model).它的作用是将网页转为一个 JavaScript 对 ...
- Scala 学习之路(七)—— 常用集合类型之 Map & Tuple
一.映射(Map) 1.1 构造Map // 初始化一个空map val scores01 = new HashMap[String, Int] // 从指定的值初始化Map(方式一) val sco ...
- 《实战Java高并发程序设计》读书笔记
文章目录 第二章 Java并行程序基础 2.1 线程的基本操作 2.1.1 线程中断 2.1.2 等待(wait)和通知(notify) 2.1.3 等待线程结束(join)和谦让(yield) 2. ...