setitimer 创建两个定时器_UE4 Timer(定时器)相关源码分析

本文介绍了UE4中的Timer管理类FTimerManager,包括其在UWorld和UGameInstance中的创建与销毁,以及Timer的执行逻辑。详细阐述了如何在Tick中处理活跃和待处理的定时器,确保定时器的准确执行,同时讨论了多定时器的调度策略,防止因帧率波动导致的执行次数丢失问题。
摘要由CSDN通过智能技术生成

前言

  • 文章属于旧有文章搬运, 之前在csdn上面
  • 2019.11.6修改

Timer是定时器, 用途:

  • 延时执行事件
  • 周期性执行事件
参考官方文档Using Timers

Timer可以...

Timer能...

Timer...

...

编不下去了, 自己百度谷歌吧(手动狗头)


TimerManager

246791a4b382264dcc836e8639a7a995.png

Timer的管理类是FTimerManager, 是一个全局的Timer管理类.

19387bc2caed85c4ee73126e73521f76.png

通常通过UWorld::GetTimerManager()获得
可以看到实际实现是先判断GameInstance是否存在, 存在返回UGameInstanceTimerManger, 否则返回UWorld自己的TimerManger

f7b9d6f2b599ca622d8e5c7d5f434875.png

1e68e6a7639fb5898a6e704b57baec80.png

接着, 如上图, TimerMangerUWorldUGameInstance的构造函数中创建, 调用构造函数, 不试图赋值UGameInstance

de72bf13cc38319721f25ed3bcabb213.png

0cdfc1bc3b347e819d9ef0fd3c4e78a7.png

TimerManger的构造函数中, 主要还是给InternalTime设0, 该值是TimerManger内部的一个独立时钟, 在TimerManger的每次Tick调用时计时.

703f58f63af695daeea8e00207ca530b.png

UWorld::Tick中调用FTimerManagerTick函数实际执行Timer里面的操作

4305a554d0c31216e35bd47d241e901d.png

d1c976320aac8bdcfb1c7b451d64cfe7.png

TimerManger的销毁是在UWorld::FinishDestroy和UGameInstance::FinishDestroy中

delete然后置nullptr, 简单粗暴.


Timer的执行

4797774f55b47ae6965d0b4cb8286044.png

从堆栈中也可以FTimerManger::TickUWorld::Tick调用

fdf6a947b48bb8e328f1757328d71a6d.png

如图, 这个函数体主要做了如下内容 :

  • 先调用FTimerManager::HasBeenTickedThisFrame, 并维护LastTickedFrame避免一帧内被多次执行.

3e3e07dcfb6653aa56e18b297243fcb7.png

427463864597681e1f6ef1c8cdd3c47f.png
  • 然后InternalTime计时, 增加DeltaTime偏移量
  • 接着对ActiveTimerHeapPendingTimerSet进行一定逻辑处理

f53e7d43d2b7587ea0bc584ee17c6d2d.png

ActiveTimerHeap是一个当前活跃的, 待处理的FTimerHandle数组

99eec07c27dad629c62ad9d7530d7076.png

while循环体主体逻辑:

不断取堆顶的FTimerHandle, 并得到对应的FTimerData.

如果该FTimerData的状态是可被移除, 那么移除, 继续
不断执行, 直到InternalTimer(内部的独立时钟)大于FTimerDataExpireTime, 即定时器到时间了, 那么执行一定逻辑, 否则, 没有过时间, 就直接break跳出循环

因为ActiveTimerHeap是按照过期时间排序的

fe36440e19a7890ceab44e10e6dd8e75.png

接着看, 如果定时器到时间了, 会将ActiveTimerHeap的堆顶元素移动置CurrentlyExecutingTimer(当前正在执行的Timer), 并更新FTimerDataStatusETimerStatus::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

61a3d4833bc4bf97b4b840f088c00e46.png

而Timer的执行, 实际上也就是绑定的代理执行. 此处略

// 可以参考代理相关的文章自己啃, 难度不大

117997da75cf6dfa4ec3462d86eca784.png

在执行后有个更新指针的操作, 为了防止执行后无效, 这是为什么呢?

考验你C++功底的时候到了, 自行理解, 此处忽略(手动狗头)

d1e5b36639d8d27d45cd393a3a3eecb4.png

Timer执行完毕之后, 做清理.

如果Timer不循环, 执行一次, 那么完成使命了, 清理掉吧!

如果Timer代理无效了, 那么无法完成使命, 也清理掉吧!

否则, 嗯, 设置下次时间, 打回去, 接着干活(手动狗头)

// 所以循环Timer啊, Timer对应的代理实际对象啊, 多数是不需要人工维护移除的
// 即多数Timer绑定好, 不清除, 也没啥影响, 他会自动移除无效的Timer

ad1ed8484e6fe43e93e332e4215249d3.png

最后再看PendingTimerSet的处理, 这是在当前Tick帧添加的定时器集合, 将里面的定时器添加到ActiveTimerHeap里面

即很明显, Timer最少也会延迟一帧调用


Timer的使用

de31862459ab3e40f487010371e4c80e.png

参照上面若干函数, 最后都通过FTimerManger::InternalSetTimer函数SetTimerForNextTick相关基本同, 略

6dcc01a573b412b9763c1a49e05ac96c.png

FTimerManger::InternalSetTimer中在设置Timer时, 会查找一下TimerHandle是否有效, 有效会清除.

47ecccc2b7ae1fc36a6d044787f4711c.png

接着就是创建一个FTimerData信息, 并维护, 具体略了


c962b8d0bf894d0581225296330eabf2.png

98450e5f84f7a598b6fef59e7c7e2b10.png

最后贴一张使用图, 一些基础知识就懒得提了.

// 基础知识 : TimerHandler清除, 获得剩余时间, 暂停和取消暂停, 是否存在等函数
// 看看头文件, 配合使用即可. 

以及提一下这两个Timer的代理:

  • DECLARE_DELEGATE(FTimerDelegate)
  • DECLARE_DYNAMIC_DELEGATE(FTimerDynamicDelegate)

自行参考代理相关文章, 配合使用, 能玩出花(笑)


结语

  • 骗赞, 骗评论了.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要启动多个定时器,可以使用 `setitimer` 函数来实现。`setitimer` 函数可以设置一个或多个定时器,用于在指定的时间间隔内触发信号。 下面是一个示例代码,演示如何启动多个定时器: ```c #include <stdio.h> #include <stdlib.h> #include <sys/time.h> #include <signal.h> void timer_handler(int signum) { printf("Timer expired!\n"); } int main() { struct itimerval timer1, timer2; // 设置第一个定时器 timer1.it_value.tv_sec = 1; // 第一次触发的时间间隔为1秒 timer1.it_value.tv_usec = 0; timer1.it_interval.tv_sec = 2; // 之后每次触发的时间间隔为2秒 timer1.it_interval.tv_usec = 0; setitimer(ITIMER_REAL, &timer1, NULL); // 使用实时时间 // 设置第二个定时器 timer2.it_value.tv_sec = 5; // 第一次触发的时间间隔为5秒 timer2.it_value.tv_usec = 0; timer2.it_interval.tv_sec = 5; // 之后每次触发的时间间隔为5秒 timer2.it_interval.tv_usec = 0; setitimer(ITIMER_REAL, &timer2, NULL); // 注册信号处理函数 signal(SIGALRM, timer_handler); // 等待定时器触发 while (1) { // 进行其他操作 } return 0; } ``` 在上述示例中,我们使用了两个不同的 `itimerval` 结构体变量 `timer1` 和 `timer2` 来分别设置两个定时器。然后使用 `setitimer` 函数将这两个定时器设置为实时计时器,并指定了对应的时间间隔。之后,我们注册了信号处理函数 `timer_handler` 来处理定时器触发的信号 `SIGALRM`。 最后,在主函数中使用一个无限循环来等待定时器的触发,可以在循环中进行其他操作。 希望这个示例能帮助到你!如有任何问题,请随时提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值