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 ...
随机推荐
- SQL Server 2008收缩日志文件--dbcc shrinkfile参数说明
原文:SQL Server 2008收缩日志文件--dbcc shrinkfile参数说明 DBCC SHRINKFILE 收缩相关数据库的指定数据文件或日志文件大小. 语法 DBCC SHRINKF ...
- webmethod基本认知
六种控件统称flow step insert/invoke 插入services,类似调用函数 BRANCH 分支结构 参数名在switch定义 子参数以label确定 注意:确保label唯一,否则 ...
- ML:梯度下降(Gradient Descent)
现在我们有了假设函数和评价假设准确性的方法,现在我们需要确定假设函数中的参数了,这就是梯度下降(gradient descent)的用武之地. 梯度下降算法 不断重复以下步骤,直到收敛(repeat ...
- 配置QtCreator+CDB远程调试环境(用到了符号表) good
相关环境信息:开发机Win7 x64.远程机器WinXP.调试器是CDB.Qt版本5.2.1 一.部署远程机器环境 我这里用的是虚拟机(Windows XP),根据你要调试的程序选择安装不同架构的Wi ...
- SIP:用Riverbank的SIP创建C++库的Python模块(把自己的C++库包装成Python模块)
我们发现PyQt做的Python版的PyQt是如此好用,如果想把自己的C++库包装成Python模块该如何实现呢? 这里介绍下用SIP包装C++库时值得参考的功能实现: 需要Python模块中实现C+ ...
- c++类运算符重载遇到的函数形参问题
class A { public: A(int arg1, int arg2); ~A(); A &operator = ( A &other); A operator + ( A & ...
- ansible(三)
一.setup模块(收集信息 ) 1.ansible中的setup模块可以收集到的信息 ansible web -m setup ansible_all_ipv4_addresses # ipv4的所 ...
- SYN4104型 数字网同步时钟
SYN4104型 数字网同步时钟 产品概述 SYN4104型数字网同步时钟是由西安同步电子科技有限公司精心设计.自行研发生产的一款高精度锁相时钟频率源,接收GPS信号,使恒温晶振输出频率同步于GPS卫 ...
- python正则表达式模块
正则表达式是对字符串的最简约的规则的表述.python也有专门的正则表达式模块re. 正则表达式函数 释义 re.match() 从头开始匹配,匹配失败返回None,匹配成功可通过group(0)返回 ...
- React躬行记(4)——生命周期
组件的生命周期(Life Cycle)包含三个阶段:挂载(Mounting).更新(Updating)和卸载(Unmounting),在每个阶段都会有相应的回调方法(也叫钩子)可供选择,从而能更好的控 ...