前言
- 文章属于旧有文章搬运, 之前在csdn上面
- 2019.11.6修改
Timer是定时器, 用途:
- 延时执行事件
- 周期性执行事件
参考官方文档Using Timers
Timer可以...
Timer能...
Timer...
...
编不下去了, 自己百度谷歌吧(手动狗头)
TimerManager
![246791a4b382264dcc836e8639a7a995.png](https://i-blog.csdnimg.cn/blog_migrate/4198e6d328735903ab6979eb1b8e22dc.png)
Timer的管理类是FTimerManager, 是一个全局的Timer管理类.
![19387bc2caed85c4ee73126e73521f76.png](https://i-blog.csdnimg.cn/blog_migrate/791a7f8bd41e11e22af9aca394c4277d.png)
通常通过UWorld::GetTimerManager()获得
可以看到实际实现是先判断GameInstance是否存在, 存在返回UGameInstance的TimerManger, 否则返回UWorld自己的TimerManger
![f7b9d6f2b599ca622d8e5c7d5f434875.png](https://i-blog.csdnimg.cn/blog_migrate/4d0adf799d5023d889bafe212d2aa6a5.jpeg)
![1e68e6a7639fb5898a6e704b57baec80.png](https://i-blog.csdnimg.cn/blog_migrate/74c93c288f2d14239e17a487bb8cbeef.jpeg)
接着, 如上图, TimerManger在UWorld和UGameInstance的构造函数中创建, 调用构造函数, 不试图赋值UGameInstance
![de72bf13cc38319721f25ed3bcabb213.png](https://i-blog.csdnimg.cn/blog_migrate/078c0b4adb556ff48dd61f596e091a81.png)
![0cdfc1bc3b347e819d9ef0fd3c4e78a7.png](https://i-blog.csdnimg.cn/blog_migrate/df6e02d495b5560fa90dbf47e84ce9a5.png)
在TimerManger的构造函数中, 主要还是给InternalTime设0, 该值是TimerManger内部的一个独立时钟, 在TimerManger的每次Tick调用时计时.
![703f58f63af695daeea8e00207ca530b.png](https://i-blog.csdnimg.cn/blog_migrate/6e7681f4b0bd6e7a7f4a92209f69b2cd.jpeg)
在UWorld::Tick中调用FTimerManager的Tick函数实际执行Timer里面的操作
![4305a554d0c31216e35bd47d241e901d.png](https://i-blog.csdnimg.cn/blog_migrate/efc35f648a3dd13d7265a96a518a82e8.jpeg)
![d1c976320aac8bdcfb1c7b451d64cfe7.png](https://i-blog.csdnimg.cn/blog_migrate/928833995df2a69a53cb382551aa0b7e.jpeg)
TimerManger的销毁是在UWorld::FinishDestroy和UGameInstance::FinishDestroy中
delete然后置nullptr, 简单粗暴.
Timer的执行
![4797774f55b47ae6965d0b4cb8286044.png](https://i-blog.csdnimg.cn/blog_migrate/2a5fffaeed33e12dacd68d6bc96a0f8c.jpeg)
从堆栈中也可以FTimerManger::Tick被UWorld::Tick调用
![fdf6a947b48bb8e328f1757328d71a6d.png](https://i-blog.csdnimg.cn/blog_migrate/2db485297e87e1e3733bd92d27c4a787.jpeg)
如图, 这个函数体主要做了如下内容 :
- 先调用FTimerManager::HasBeenTickedThisFrame, 并维护LastTickedFrame避免一帧内被多次执行.
![3e3e07dcfb6653aa56e18b297243fcb7.png](https://i-blog.csdnimg.cn/blog_migrate/64aee2afe2a50c3537de4ed310d03f65.png)
![427463864597681e1f6ef1c8cdd3c47f.png](https://i-blog.csdnimg.cn/blog_migrate/e4174c410b54a83cdfad5f48c2dad220.png)
- 然后InternalTime计时, 增加DeltaTime偏移量
- 接着对ActiveTimerHeap和PendingTimerSet进行一定逻辑处理
![f53e7d43d2b7587ea0bc584ee17c6d2d.png](https://i-blog.csdnimg.cn/blog_migrate/ea106521df6c787a06ed4729352644e2.png)
ActiveTimerHeap是一个当前活跃的, 待处理的FTimerHandle数组
![99eec07c27dad629c62ad9d7530d7076.png](https://i-blog.csdnimg.cn/blog_migrate/d31aabda9771d937f4f6ce9d91cb94b8.jpeg)
看while循环体主体逻辑:
不断取堆顶的FTimerHandle, 并得到对应的FTimerData.
如果该FTimerData的状态是可被移除, 那么移除, 继续
不断执行, 直到InternalTimer(内部的独立时钟)大于FTimerData的ExpireTime, 即定时器到时间了, 那么执行一定逻辑, 否则, 没有过时间, 就直接break跳出循环
因为ActiveTimerHeap是按照过期时间排序的
![fe36440e19a7890ceab44e10e6dd8e75.png](https://i-blog.csdnimg.cn/blog_migrate/0e8572c32a7519920964df6cd6ab461a.jpeg)
接着看, 如果定时器到时间了, 会将ActiveTimerHeap的堆顶元素移动置CurrentlyExecutingTimer(当前正在执行的Timer), 并更新FTimerData的Status为ETimerStatus::Executing
注意, 这里的CurrentlyExecutingTimer和上文中的TopHandler是同一个值, 所以改Top(FTImerData)修改的是同一Timer的信息
然后, 重点来了
划重点了!
划重点了!
划重点了!
上图圈起来的代码, CallCount(执行次数), 为了避免在两帧之间时间过长而导致丢掉一些定时器的间隔执行, 计算, 两帧之间实际需要执行多少次.
设一个Timer, 每0.1秒执行一次, Tick总共过了5秒, 有
- 定时器必定执行50次, 执行次数恒定
- 如果比较卡顿, 每0.5秒执行1帧, 即定时器可能也可以在同一帧执行多次
- 定时器是假定时, 即期望0.1s, 0.2s, 0.3s, 0.4s, 0.5s执行, 实际可能是0.5s, 0.5s, 0.5s, 0.5s, 0.5s执行5次
![54e9ce89ec18594bc333b83e1a2ef901.png](https://i-blog.csdnimg.cn/blog_migrate/40a0ada1110c12e5e2b8035c6dca173d.png)
![61a3d4833bc4bf97b4b840f088c00e46.png](https://i-blog.csdnimg.cn/blog_migrate/6020040c417b537586a6c95b115cc1f3.png)
而Timer的执行, 实际上也就是绑定的代理执行. 此处略
// 可以参考代理相关的文章自己啃, 难度不大
![117997da75cf6dfa4ec3462d86eca784.png](https://i-blog.csdnimg.cn/blog_migrate/bfc29ea7df5f2d8e5744d9da18ad274a.png)
在执行后有个更新指针的操作, 为了防止执行后无效, 这是为什么呢?
考验你C++功底的时候到了, 自行理解, 此处忽略(手动狗头)
![d1e5b36639d8d27d45cd393a3a3eecb4.png](https://i-blog.csdnimg.cn/blog_migrate/6485ebfc88c78717684529aadaa81fc1.jpeg)
在Timer执行完毕之后, 做清理.
如果Timer不循环, 执行一次, 那么完成使命了, 清理掉吧!
如果Timer代理无效了, 那么无法完成使命, 也清理掉吧!
否则, 嗯, 设置下次时间, 打回去, 接着干活(手动狗头)
// 所以循环Timer啊, Timer对应的代理实际对象啊, 多数是不需要人工维护移除的
// 即多数Timer绑定好, 不清除, 也没啥影响, 他会自动移除无效的Timer
![ad1ed8484e6fe43e93e332e4215249d3.png](https://i-blog.csdnimg.cn/blog_migrate/aaa3c79b74e84eb17b413efefe74ec8d.jpeg)
最后再看PendingTimerSet的处理, 这是在当前Tick帧添加的定时器集合, 将里面的定时器添加到ActiveTimerHeap里面
即很明显, Timer最少也会延迟一帧调用
Timer的使用
![de31862459ab3e40f487010371e4c80e.png](https://i-blog.csdnimg.cn/blog_migrate/7e530657eb331bf631a4bff98b05cb2e.jpeg)
参照上面若干函数, 最后都通过FTimerManger::InternalSetTimer函数SetTimerForNextTick相关基本同, 略
![6dcc01a573b412b9763c1a49e05ac96c.png](https://i-blog.csdnimg.cn/blog_migrate/a8d606223c69a76dc789f33e8a878d6f.jpeg)
FTimerManger::InternalSetTimer中在设置Timer时, 会查找一下TimerHandle是否有效, 有效会清除.
![47ecccc2b7ae1fc36a6d044787f4711c.png](https://i-blog.csdnimg.cn/blog_migrate/177142a796d488037ca846e6ebf0f721.jpeg)
接着就是创建一个FTimerData信息, 并维护, 具体略了
![c962b8d0bf894d0581225296330eabf2.png](https://i-blog.csdnimg.cn/blog_migrate/e1bd651509f78de963eab921e4813050.jpeg)
![98450e5f84f7a598b6fef59e7c7e2b10.png](https://i-blog.csdnimg.cn/blog_migrate/a2fb771b901891c28541e52fc1e48c47.png)
最后贴一张使用图, 一些基础知识就懒得提了.
// 基础知识 : TimerHandler清除, 获得剩余时间, 暂停和取消暂停, 是否存在等函数
// 看看头文件, 配合使用即可.
以及提一下这两个Timer的代理:
- DECLARE_DELEGATE(FTimerDelegate)
- DECLARE_DYNAMIC_DELEGATE(FTimerDynamicDelegate)
自行参考代理相关文章, 配合使用, 能玩出花(笑)
结语
- 骗赞, 骗评论了.