定时器的设计

项目开发日志(六)

定时器在游戏项目中使用非常普遍,特别是在游戏服务端项目中,定时器检查函数会在每一帧调用.而保证游戏性能的关键就是在每一帧尽可能的运行少的代码,我们知道系统运行每一行代码其实是有时间的.所以每帧必然调用的代码量越少越好,在定时器的设计上 就要求我们尽可能的追求性能的极致.

一般的定时器设计原理

假设把整个游戏注册的所有定时器都放到一个vector容器中,一般的做法可能是每帧搜索一下这个vector,然后拿出来对比时间,时间到了则触发.这种在处理定时事件少的情况下是没有问题的,但是如果定时事件非常多的话,则需要改进下算法了.

改进后的定时器设计

加入时间桶的概念,优化每帧搜索区间

  • 检查频率的设定

定时器的检查应该是靠时间驱动,这就要求我们设定一个时间间隔,用来设置两次检查的时间.一般的游戏是60p,60帧,两帧之间的时间间隔应该是:

1s/60 = 1000ms/60 = 16

也就是说大概16毫秒,所以我们根据游戏帧频把检查频率设置为16ms.让程序每隔16ms检查一次定时器.

#define CHECK_FREQUENCY         16  //精确到16ms
  • 时间刻度的设定

时间刻度,概念可能比较模糊,例如我们需要每隔5s运行函数A,每隔10s运行函数B,那么5s 10s这些间隔时间应该怎么保存呢?如果保存到一个数组里面,每次检查都逐个搜索一遍 然后 检查下时间,这样在时间事件非常多的时候会让这个循环次数非常多,这样每帧运行的代码量会呈几何级增长,显然这并不是理想的处理方式.所以我们引入时间刻度,把时间按照时间刻度进行等分,把不同间隔的时间事件放到不同刻度的对象里面.然后根据时间的进行每帧检查邻近的刻度.这样大大减少了检查事件运行的代码量,从而缩短了误差时间.这个时间刻度必须比检查频率大.我们设定为60ms,(为什么是64?其实可以更大 只要比16大就可以,不过根据经验而言64更好.也就是说64是一个根据以前的编程经验所设定的值 )

#define TIME_GRID           64
  • 时间桶的设定

有了检查频率和时间刻度,我们需要引入时间桶的概念,把全部定时事件抽象成一个一个的时间点,然后把时间点统一放到这个时间桶里.我们设定一个时间跨度12分钟的时间桶,(为什么是12分钟,这个时间长度也是一个经验值,你可以根据需要设定成10分钟 20分钟).

12分钟在桶里的长度是: 12min = 720s = 7200000 (ms)

12分钟把时间桶分割为: 7200000/64 = 11250 (段)

我们把时间桶设计成vector,用一个时间刻度存储一个值,把不同的时间事件存入vector的相应位置中:

例如5s的时间 5s/64ms = 78,所以把注册间隔为5s的时间事件放置在vector列表容器的78的位置.

  • 定时检查算法

设置好时间桶之后主要是应用于定时检查中,根据上文的设定 我们每隔16ms 就会检查一下注册的定时器,如果时间到了则调出来触发事件.

我们会设定一个开始时间A;每次运行检查算法的时候获取当前时间B;初始检查时间C;

然后根据如下算法计算出B时间里应该检查的时间桶区段:

检查起点 : BeginItor = [(C - A)/时间刻度]%时间桶长度
检查结束 : EndItor = [(B - A)/时间刻度]%时间桶长度

每一个检查频率里面 我们只需要检查[BeginItor,EndItor]区间里的事件就可以了.而不用检查整个定时器容器.这样做大大缩减了每帧的代码量.

  • 代码实现

github

《定时器的设计》有一个想法

发表评论

邮箱地址不会被公开。 必填项已用*标注