Cocos2d-x 3.2:定时器的使用和原理探究(2)

Cocos2d-x 3.2:定时器的使用和原理探究(2)

本文转载至深入了解Cocos2d-x 3.x:定时器的使用和原理探究(2)

 

上一篇说到定时器的使用方法,这篇主要分析它的实现原理。

 

1.哈希链表

Cocos2d-x封装了一个结构体,叫做UT_hash_handle,只要在自定义的结构体中声明这个结构体变量,就实现了哈希链表,并且能使用一系列的哈希链表专用的宏。这个结构体的具体实现如下:

1
2
3
4
5
6
7
8
9
10
typedef  struct  UT_hash_handle {
    struct  UT_hash_table *tbl;
    void  *prev;                        /* prev element in app order      */
    void  *next;                        /* next element in app order      */
    struct  UT_hash_handle *hh_prev;    /* previous hh in bucket order    */
    struct  UT_hash_handle *hh_next;    /* next hh in bucket order        */
    void  *key;                         /* ptr to enclosing struct's key  */
    unsigned keylen;                   /* enclosing struct's key len     */
    unsigned hashv;                    /* result of hash-fcn(key)        */
} UT_hash_handle;

这个结构体主要实现的是一个双向链表,具体实现哈希验证的还要看UT_hash_table 结构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
typedef  struct  UT_hash_table {
    UT_hash_bucket *buckets;
    unsigned num_buckets, log2_num_buckets;
    unsigned num_items;
    struct  UT_hash_handle *tail;  /* tail hh in app order, for fast append    */
    ptrdiff_t  hho;  /* hash handle offset (byte pos of hash handle in element */
 
    /* in an ideal situation (all buckets used equally), no bucket would have
     * more than ceil(#items/#buckets) items. that's the ideal chain length. */
    unsigned ideal_chain_maxlen;
 
    /* nonideal_items is the number of items in the hash whose chain position
     * exceeds the ideal chain maxlen. these items pay the penalty for an uneven
     * hash distribution; reaching them in a chain traversal takes >ideal steps */
    unsigned nonideal_items;
 
    /* ineffective expands occur when a bucket doubling was performed, but 
     * afterward, more than half the items in the hash had nonideal chain
     * positions. If this happens on two consecutive expansions we inhibit any
     * further expansion, as it's not helping; this happens when the hash
     * function isn't a good fit for the key domain. When expansion is inhibited
     * the hash will still work, albeit no longer in constant time. */
    unsigned ineff_expands, noexpand;
 
    uint32_t signature;  /* used only to find hash tables in external analysis */
#ifdef HASH_BLOOM
    uint32_t bloom_sig;  /* used only to test bloom exists in external analysis */
    uint8_t *bloom_bv;
    char  bloom_nbits;
#endif
 
} UT_hash_table;

然后看看与哈希链表相关的宏定义,使用这些宏能很方便的插入链表,删除链表,查找链表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
  * 查找元素
  * head:哈希链表的头指针
  * findptr:要查找的元素指针
  * out:查找结果
  */
HASH_FIND_PTR(head,findptr,out) 
/**
  * 添加元素
  * head:哈希链表的头指针
  * ptrfield:要添加的元素指针
  * add:要添加的哈希链表元素
  */
HASH_ADD_PTR(head,ptrfield,add) 
/**
  * 替换元素
  * head:哈希链表的头指针
  * ptrfield:要替换的元素指针
  * add:要替换的哈希链表元素
  */
HASH_REPLACE_PTR(head,ptrfield,add)
/**
  * 删除
  * head:哈希链表的头指针
  * delptr:要删除的元素指针
  */
HASH_DEL(head,delptr)

以上是引擎中实现的哈希链表的相关知识,接下来再看看与定时器相关的哈希链表。定时器的实现中,将一个定时器存储在哈希链表中,那么在scheduler是如何实现以后哈希链表的结构体的呢?如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 不同优先级的update定时器的双向链表
typedef  struct  _listEntry
{
     struct  _listEntry   *prev, *next;
     ccSchedulerFunc     callback;
     void                 *target;
     int                  priority;
     bool                 paused;
     bool                 markedForDeletion;  // selector will no longer be called and entry will be removed at end of the next tick
} tListEntry;
//内置的update定时器
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                 currentTimerSalvaged;
     bool                 paused;
     UT_hash_handle      hh;
} tHashTimerEntry;

以上就是相关的哈希链表的知识,接下来从定义定时器的函数Node::schedule中一步一步的分析定时器是如何加入到哈希链表中的。

 

2.如何定义自定义定时器

首先,上一篇文章中说到了很多个自定义定时器的函数,但是最终会调用的函数只有两个,分别是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 定义一个自定义的定时器
* selector:回调函数
* interval:重复间隔时间,重复执行间隔的时间,如果传入0,则表示每帧调用
* repeat:重复运行次数,如果传入CC_REPEAT_FOREVER则表示无限循环
* delay:延时秒数,延迟delay秒开始执行第一次回调
*/
void  schedule(SEL_SCHEDULE selector,  float  interval, unsigned  int  repeat,  float  delay);
 
/**
* 使用lambda函数定义一个自定义定时器
* callback:lambda函数
* interval:重复间隔时间,重复执行间隔的时间,如果传入0,则表示每帧调用
* repeat:重复运行次数,如果传入CC_REPEAT_FOREVER则表示无限循环
* delay:延时秒数,延迟delay秒开始执行第一次回调
* key:lambda函数的Key,用于取消定时器
* @lua NA
*/
void  schedule( const  std::function< void ( float )>& callback,  float  interval, unsigned  int  repeat,  float  delay,  const  std::string &key);

本文从传统的定义定时器的方法入手,也就是第一个方法。接下来看看这个方法的实现:

1
2
3
4
5
6
7
void  Node::schedule(SEL_SCHEDULE selector,  float  interval, unsigned  int  repeat,  float  delay)
{
     CCASSERT( selector,  "Argument must be non-nil" );
     CCASSERT( interval >=0,  "Argument must be positive" );
 
     _scheduler->schedule(selector,  this , interval , repeat, delay, !_running);
}

看到其实还是调用_scheduler的schedule方法,那么_scheduler又是个什么鬼?

1
Scheduler *_scheduler;   ///< scheduler used to schedule timers and updates

查看定义可以知道是一个Scheduler 的指针,但是这个指针从哪里来?在构造函数中有真相

1
2
3
4
5
6
7
Node::Node( void )
{
     // set default scheduler and actionManager
     _director = Director::getInstance();
     _scheduler = _director->getScheduler();
     _scheduler->retain();
}

是从导演类中引用的。这一块暂时我们不管,接下来深入到_scheduler->schedule函数中分析,如下是函数的具体实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
void  Scheduler::schedule(SEL_SCHEDULE selector, Ref *target,  float  interval, unsigned  int  repeat,  float  delay,  bool  paused)
{
     CCASSERT(target,  "Argument target must be non-nullptr" );
     
     //定义并且查找链表元素
     tHashTimerEntry *element = nullptr;
     HASH_FIND_PTR(_hashForTimers, &target, element);
     
     //没找到
     if  (! element)
     {
         //创建一个链表元素
         element = (tHashTimerEntry *) calloc ( sizeof (*element), 1);
         element->target = target;
         
         //添加到哈希链表中
         HASH_ADD_PTR(_hashForTimers, target, element);
         
         // Is this the 1st element ? Then set the pause level to all the selectors of this target
         element->paused = paused;
     }
     else
     {
         CCASSERT(element->paused == paused,  "" );
     }
     
     //检查这个元素的定时器数组,如果数组为空 则new 10个数组出来备用
     if  (element->timers == nullptr)
     {
         element->timers = ccArrayNew(10);
     }
     else
     {
         //循环查找定时器数组,看看是不是曾经定义过相同的定时器,如果定义过,则只需要修改定时器的间隔时间
         for  ( int  i = 0; i < element->timers->num; ++i)
         {
             TimerTargetSelector *timer =  dynamic_cast <TimerTargetSelector*>(element->timers->arr[i]);
             
             if  (timer && selector == timer->getSelector())
             {
                 CCLOG( "CCScheduler#scheduleSelector. Selector already scheduled. Updating interval from: %.4f to %.4f" , timer->getInterval(), interval);
                 timer->setInterval(interval);
                 return ;
             }
         }
         //扩展1个定时器数组
         ccArrayEnsureExtraCapacity(element->timers, 1);
     }
     
     //创建一个定时器,并且将定时器加入到当前链表指针的定时器数组中
     TimerTargetSelector *timer =  new  (std:: nothrow ) TimerTargetSelector();
     timer->initWithSelector( this , selector, target, interval, repeat, delay);
     ccArrayAppendObject(element->timers, timer);
     timer->release();
}

这一段代码具体分析了如何将自定义定时器加入到链表中,并且在链表中的存储结构是怎么样的,接下来看看内置的Update定时器。

 

3.如何定义Update定时器

Update定时器的开启方法有两个,分别是:

1
2
3
4
5
6
7
8
9
/**
  * 开启自带的update方法,这个方法会每帧执行一次,默认优先级为0,并且在所有自定义方法执行之前执行
*/
void  scheduleUpdate( void );
 
/**
  * 开启自带的update方法,这个方法会每帧执行一次,设定的优先级越小,越优先执行
*/
void  scheduleUpdateWithPriority( int  priority);

第一个方法实际上是直接调用第二个方法,并且把优先级设置为0,我们直接看第二个方法就可以了。

1
2
3
4
void  Node::scheduleUpdateWithPriority( int  priority)
{
     _scheduler->scheduleUpdate( this , priority, !_running);
}

具体调用还是要进入到_scheduler->scheduleUpdate。

1
2
3
4
5
6
7
8
9
10
11
12
13
/** Schedules the 'update' selector for a given target with a given priority.
      The 'update' selector will be called every frame.
      The lower the priority, the earlier it is called.
      @since v3.0
      @lua NA
*/
template  < class  T>
void  scheduleUpdate(T *target,  int  priority,  bool  paused)
{
     this ->schedulePerFrame([target]( float  dt){
          target->update(dt);
      }, target, priority, paused);
}

可以看到这里主要还是调用了一个schedulePerFrame函数,并且传入了一个lambda函数。这个函数实际上调用的是target->update,接下来走进schedulePerFrame看看它的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
void  Scheduler::schedulePerFrame( const  ccSchedulerFunc& callback,  void  *target,  int  priority,  bool  paused)
{
     //定义并且查找链表元素
     tHashUpdateEntry *hashElement = nullptr;
     HASH_FIND_PTR(_hashForUpdates, &target, hashElement);
     
     //如果找到,就直接改优先级
     if  (hashElement)
     {
         // 检查优先级是否改变
         if  ((*hashElement->list)->priority != priority)
         {
             //检查是否被锁定
             if  (_updateHashLocked)
             {
                 CCLOG( "warning: you CANNOT change update priority in scheduled function" );
                 hashElement->entry->markedForDeletion =  false ;
                 hashElement->entry->paused = paused;
                 return ;
             }
             else
             {
                 // 在这里先停止到update,后面会加回来 
                 unscheduleUpdate(target);
             }
         }
         else
         {
             hashElement->entry->markedForDeletion =  false ;
             hashElement->entry->paused = paused;
             return ;
         }
     }
 
     // 优先级为0,加入到_updates0List链表中,并且加入到_hashForUpdates表中
     if  (priority == 0)
     {
         appendIn(&_updates0List, callback, target, paused);
     }
     // 优先级小于0,加入到_updatesNegList链表中,并且加入到_hashForUpdates表中
     else  if  (priority < 0)
     {
         priorityIn(&_updatesNegList, callback, target, priority, paused);
     }
     // 优先级大于0,加入到_updatesPosList链表中,并且加入到_hashForUpdates表中
     else
     {
         // priority > 0
         priorityIn(&_updatesPosList, callback, target, priority, paused);
     }
}

在这里看上去逻辑还是很清晰的,有两个函数要重点分析一下,分别是

1
2
void  Scheduler::appendIn(_listEntry **list,  const  ccSchedulerFunc& callback,  void  *target,  bool  paused)
void  Scheduler::priorityIn(tListEntry **list,  const  ccSchedulerFunc& callback,  void  *target,  int  priority,  bool  paused)

第一个用于添加默认优先级,第二个函数用于添加指定优先级的。首先看添加默认优先级的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void  Scheduler::appendIn(_listEntry **list,  const  ccSchedulerFunc& callback,  void  *target,  bool  paused)
{
     //创建一个链表元素
     tListEntry *listElement =  new  tListEntry();
 
     listElement->callback = callback;
     listElement->target = target;
     listElement->paused = paused;
     listElement->priority = 0;
     listElement->markedForDeletion =  false ;
     
     //添加到双向链表中
     DL_APPEND(*list, listElement);
 
     //创建一个哈希链表元素
     tHashUpdateEntry *hashElement = (tHashUpdateEntry *) calloc ( sizeof (*hashElement), 1);
     hashElement->target = target;
     hashElement->list = list;
     hashElement->entry = listElement;
     //添加到哈希链表中
     HASH_ADD_PTR(_hashForUpdates, target, hashElement);
}

接下来看另一个函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
void  Scheduler::priorityIn(tListEntry **list,  const  ccSchedulerFunc& callback,  void  *target,  int  priority,  bool  paused)
{
     //同上一个函数
     tListEntry *listElement =  new  tListEntry();
 
     listElement->callback = callback;
     listElement->target = target;
     listElement->priority = priority;
     listElement->paused = paused;
     listElement->next = listElement->prev = nullptr;
     listElement->markedForDeletion =  false ;
 
     //如果链表为空
     if  (! *list)
     {
         DL_APPEND(*list, listElement);
     }
     else
     {
         bool  added =  false ;
         //保证链表有序
         for  (tListEntry *element = *list; element; element = element->next)
         {
             // 如果优先级小于当前元素的优先级,就在这个元素前面插入
             if  (priority < element->priority)
             {
                 if  (element == *list)
                 {
                     DL_PREPEND(*list, listElement);
                 }
                 else
                 {
                     listElement->next = element;
                     listElement->prev = element->prev;
 
                     element->prev->next = listElement;
                     element->prev = listElement;
                 }
 
                 added =  true ;
                 break ;
             }
         }
 
         //如果新加入的优先级最低,则加入到链表的最后
         if  (! added)
         {
             DL_APPEND(*list, listElement);
         }
     }
 
     //同上一个函数
     tHashUpdateEntry *hashElement = (tHashUpdateEntry *) calloc ( sizeof (*hashElement), 1);
     hashElement->target = target;
     hashElement->list = list;
     hashElement->entry = listElement;
     HASH_ADD_PTR(_hashForUpdates, target, hashElement);
}

本文简单的分析了哈希链表以及定时器的存储和添加,下一篇文章将分析定时器是如何运转起来的。

转载于:https://www.cnblogs.com/dudu580231/p/4560348.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值