关于一个游戏而言,守时器是有必要的,而它一般作为一个游戏根本公共组件,而守时器在游戏逻辑中运用是十分显着的(比方吃药回血,每几秒回血多少),而关于游戏逻辑而言需求开发一个高功率高精度(毫秒等级)的守时器。
一:剖析Ace库守时器完成方法
1.Ace种守时器完成有4种,这儿不详细介绍完成细节,首要介绍完成数据结构,功能。
详细的4种守时器都是从ACE_Timer_Queue_T承继,每种守时器用不同的数据结构来完成详细Timer的算法。
1)ACE_Timer_Heap守时器,依据触发时刻树立一个优先级行列(一个最小堆数据结构)来保护一切的守时器,价值便是删去和刺进进程为O(logn),价值比较高。
2)ACE_Timer_List守时器,依据触发时刻树立一个有序的双向链表,价值便是刺进守时器价值较高。
3)ACE_Timer_Hash守时器,选用开链的Hash方法每一个桶为一个单链表,在查看一切桶超时的时分会遍历链表一切的元素。为了进步功率这儿所用的Hash桶应该满足 大,而关于守时器一般是频频的超时呼应守时器,现已刺进和删去,呼应会选用迭代的方法。所以功率并不是那么高效。
4)ACE_Timer_Wheel守时器,选用的一种时刻轮的方法,详细完成就好象一个轮子上面有许多插槽,每一个插槽下面包含一个有序双向链表,在Ace中把轮子叫做Wheel,插槽叫做Spoke,每一个守时器被Hash到Spoke中,而Spoke也能够了解为timer的分辨率,而Spoke的核算公式为 🙁 触发时刻 >> 分辨率的位数)&(spoke巨细-1).然后在依据触发时刻把守时器刺进到每一个Spoke的有序双向链表中, 与Ace_timer_Hash的完成相似,仅仅这儿用户能够指定Spoke巨细。这儿价值便是刺进的时分或许最坏为O(n).
咱们公司现在CTimer便是选用Ace的ACE_Timer_Wheel原理规划的。
这儿有一个图更能直观的描绘这种思维:
完成方法为Vector,list组合。
二: 本文介绍一种选用linux中止处理的守时器规划方法
此守时器的查找,删去,刺进都是O(1)
1) 介绍规划原理
守时器是依据时刻的中止函数,便是依据触发时刻来超时呼应。所以只需咱们规划一个依据时刻的Hash算法。只需咱们能咱们把一个函数触发时刻悉数Hash到此Hash算法的桶中,就完成了查找,刺进,删去O(1)的操作了,其实不然依据时刻的hash算法如同挺杂乱,并且桶的数量太大,内存耗费太多,所以把一个时刻直接Hash价值太大。是否有一种其他的方法呢,linux中止处理选用一种相似水表核算水量的方法,方法便是日子中的水表,第一个指针转一圈后一个转一格,假定每一个圈都是10个刻度,第一个圈能表明10,那么第二圈没一个刻度表明第一个圈的1圈,就能表明10*10, 二个圈总共就能表明10*10 + 10。 以此类推,5个圈就能表明10^5+10^4+10^3+10^2+10…
一个32bits的整数,假如准确到1毫秒,则2^32位能够表明49.3天,而一般服务器应该不会直接运转49.3天,这儿咱们选用5个轮子(即圈), 轮子巨细分别为256,64,64,64,64 ,轮子顺次类推表明范围在0~256, 256~256*64, 256*64~256*64^2, 256*64^2~256*64^3, 256*64^3~256*64^4, 假定这儿精度为n毫秒,第一个轮子表明n*256秒时刻内触发函数,第二个轮子的第二个插孔则表明n*256*2时刻范围内的,
2)一些界说:
A. 轮子,这儿选用的轮子与上面介绍的Ace轮子大约相同,一个循环列队,每一个插槽你们有一个双向链表,留意这儿链表不需求排序,所以在刺进的是O(1)的操作。轮子为5个。
3) 操作:
A. Hash算法:这儿Hash算法依据他的多少时刻后触发,直接Hash得到轮子以及插槽,然后刺进到某个插槽双向的链表中。
B.守时器触发: 守时器触发只会触发第一个轮子超时的一切守时器,由于后边4个轮子守时器表明都在前1轮子触发完了才会触发,所以这儿让后边4个轮子保护表明行将产生的守时。这儿会依据当第一个轮子转第几圈后,第二个轮子会把第几插槽的一切守时器悉数刺进到第一个轮子中,顺次类推,第二个轮子转一个第三个轮子某个插槽又会刺进到第二个轮子中。。。
4)留意的当地:
A.将一个守时器刺进到它应该所在的守时器轮子插槽中。
B.守时器的搬迁,也行将一个守时器从它本来所在的轮子插槽搬迁到另一个轮子插槽中。
C.超时呼应履行当时现已到期的守时器。
三:编码完成
1) 常量界说
/**//// define m
#define lnum 5
#define sbits 6
#define ebits 8
#define sbitsize ( 1 << sbits )
#define ebitsize ( 1 << ebits )
#define sMask ( sbitsize- 1)
#define eMask ( ebitsize -1)
2) 数据结构
1/**//// 守时器指针结点
2struct ListNode
3{
4 ListNode *next,*prev;
5};
6
7/**////
8/// 守时器类型
9///
10enum eTimerType
11{
12 eTimer1 = 10,
13 eTimer2 ,
14 eTimer3
15};
16
17/**////
18/// 守时器结点,tlist表明结点的指针,expires循环周期时刻
19/// etime 触发周期时刻,pFun触发函数.
20///
21struct timernode
22{
23 ListNode tlist;
24 ulong expires;
25 ulong etime;
26 void *pFun;
27 eTimerType eType;
28};
3) 轮子类
1/**////
2/// 一个轮子,一个循环行列
3///
4///
5class CLinkList
6{
7
8public:
9
10 CLinkList(void);
11
12 CLinkList( int size );
13
14 ~CLinkList(void);
15
16 /**////
17 /// 初始化
18 ///
19 void init();
20
21 /**////
22 /// 让指针指向自己
23 ///
24 void init_list_self( int index);
25
26 /**////
27 /// 把news刺进到prev,next之间
28 ///
29 void insert_listnode(ListNode *news , ListNode* prev , ListNode* next);
30
31 /**////
32 /// 刺进到链表头
33 ///
34 void insert_head( ListNode* news , ListNode* head );
35
36 /**////
37 /// 刺进到链表尾
38 ///
39 void insert_tail( ListNode* news , ListNode* head );
40
41 /**////
42 /// 删去节点
43 ///
44 void list_del( ListNode* list);
45
46 /**////
47 /// 得到改轮子转到第几个插槽
48 ///
49 int GetIndex( ) const { return m_index ;}
50
51 /**////
52 /// 更新轮子的插槽
53 ///
54 void SetIndex(int idx) { m_index = idx ;}
55
56 /**////
57 /// 得到轮子插槽的指针
58 ///
59 ListNode* GetNode(int index) const;
60
61private:
62 /**////
63 /// 轮子的插槽数组
64 ///
65 ListNode *m_List;
66
67 /**////
68 /// 轮子当时转到的索引
69 ///
70 int m_index;
71
72 /**////
73 /// 轮子巨细
74 ///
75 int m_Size;
76
77};4)守时器办理类
守时器办理类
1/**////
2/// 守时器办理类,办理守时器的五个轮子
3///
4class CTimer
5{
6public:
7 /**////
8 /// 结构函数如下
9 ///
10 CTimer(void);
11
12 CTimer( int second);
13
14 ~CTimer(void);
15
16public:
17 /**////
18 /// 初始化守时器办理类
19 ///
20 void Init(int Second = 0);
21
22 /**////
23 /// 添加一个守时器
24 ///
25 void add_timer(timernode *times );
26
27 /**////
28 /// 检测守时器是否存在
29 ///
30 /// @return 假如存在回来true,否则为false
31 ///
32 bool check_timer(timernode* times);
33
34 /**////
35 /// 删去守时器
36 ///
37 /// @return 假如删去成功回来true,否则为false
38 ///
39 bool delete_timer(CLinkList* list, timernode *times);
40
41 /**////
42 /// 从头初始化一个守时器
43 ///
44 void init_timer(timernode* timers);
45
46 /**////
47 /// 守时器的搬迁,也行将一个守时器从它本来所在的守时器向量搬迁到另一个守时器向量中。
48 ///
49 void cascade_timer(CLinkList* timers);
50
51 /**////
52 /// 履行当时现已到期的守时器,一切小于jeffies的守时器
53 ///
54 void Expires( ulong jeffies);
55
56 /**////
57 /// 从头初始化一个守时器
58 ///
59 void Cancel(timernode* timers);
60
61 /**////
62 /// 从头核算一个守时器
63 ///
64 void Mod_timer(timernode* timers);
65
66private:
67 /**//// 5个轮子
68 CLinkList* m_tv1;
69 CLinkList* m_tv2;
70 CLinkList* m_tv3;
71 CLinkList* m_tv4;
72 CLinkList* m_tv5;
73 CLinkList** g_vecs;
74
75 /**//// 守时器大局tick
76 ulong m_jeffies;
77 /**//// 前次运转时刻
78 ulong m_Lasttime;
79 /**//// 准确到毫秒
80 ulong m_mSecond;
81};
82
四: 测验
经过本文的介绍能够了解次守时器的的查找,删去,刺进都是O(1)的杂乱度。
/**//// 游戏事情基类
class CGameEvent
{
public:
virtual long TimeOut( eTimerType type) = 0;
};
测验比如:
1long Sum1= 0 ;
2long Sum2= 0 ;
3long Sum3= 0 ;
4long Sum = 0;
5
6class CTimertest : public CGameEvent
7{
8public:
9 long TimeOut( eTimerType type)
10 {
11 switch ( type)
12 {
13 case eTimer1:
14 std::cout <<"Sum1 = "<< Sum1 ++ << std::endl;
15 break;
16 case eTimer2:
17 std::cout <<"Sum2 = "<< Sum2 ++ << std::endl;
18 break;
19 case eTimer3:
20 std::cout <<"Sum3 = "<< Sum3 ++ << std::endl;
21 break;
22 default:
23 std::cout <<"Sum = "<< Sum ++ << std::endl;
24 break;
25 }
26 return 0;
27 }
28};
29
30int _tmain(int argc, _TCHAR* argv[])
31{
32 CTimer mytimer( 40 );
33
34 long n;
35 std::cin >> n;
36 CTimerTest test;
37 for ( int i = 0 ; i < n ; i++ )
38 {
39 timernode* tim = new timernode ;
40 tim->expires = 0;
41 tim->etime = GetCurrSystemTime() + (rand() % 1000 ) * 6;
42 tim->pFun =&test;
43 tim->eType =(eTimerType)( i%3 + 10 );
44
45 mytimer.add_timer( tim );
46 }
47
48 for ( ;; )
49 {
50 if ( (Sum1 + Sum2 + Sum3) == n )
51 break;
52
53 /**//// 运转一切的守时器
54 mytimer.Expires( GetCurrSystemTime() );
55 }
56
57 std::cout << " sum1 = " << Sum1
58 << " sum2 = " << Sum2
59 << " sum3 = " << Sum3
60 << " sum = " << Sum ;
61 return 0;
62}