Cocos2Dx之调度器-欧阳左至

Cocos2Dx之调度器-欧阳左至,第1张

概述有的时候我们还需要使用其他的时间触发机制,比如一个重复性动作2秒之后再执行,并且重复间隔为3秒。怎么实现呢? 通过前面的分析,我们知道每个帧间隔时间到期后,都会调用CCDirector的mainLoop来绘制场景和释放内存回收池。这个时间点是Cocos2Dx绘制场景的时间点。帧的间隔时间是确定的,它是Cocos2Dx的最小时间单位。我们可以通过累加已经过去的时间来实现一个定时器,并在指定的时间到期

有的时候我们还需要使用其他的时间触发机制,比如一个重复性动作2秒之后再执行,并且重复间隔为3秒。怎么实现呢?

通过前面的分析,我们知道每个帧间隔时间到期后,都会调用CCDirector的mainLoop来绘制场景和释放内存回收池。这个时间点是Cocos2Dx绘制场景的时间点。帧的间隔时间是确定的,它是Cocos2Dx的最小时间单位。我们可以通过累加已经过去的时间来实现一个定时器,并在指定的时间到期以后执行请求的回调函数。Cocos2Dx的调度器实现也就是根据这一原理。

CCDirector有一个调度器成员:CCScheduler。CC_PROPERTY(CCScheduler*,m_pScheduler,Scheduler)。在绘制场景的函数CCDirector::drawScene中,会调用调度器的update函数。但并不只是CCDirector拥有调度器成员,CCNode也同样拥有一个调度器成员。CCNode的调度器成员实际上是在CCNode的构造函数中,通过CCDirector的getScheduler函数获取的CCDirector调度器的一个引用。CCNode还对它获取的调度器执行了retain *** 作。理论上,就算CCDirector析构了,CCNode还能访问调度器,但不存在这样的使用场景。

CCNode和CCDirector共享了同一个CCScheduler。我们自己还是可以定义自己的调度器,继承自CCScheduler即可,然后通过CCDirector::setScheduler和CCNode::setScheduler进行设置。这样,我们就能使用自己的调度器了。虽然我们可以修改默认的调度器,但并没有必要这样做。不然你要保证CCDirector和CCNode使用的是同一个调度器。因为调度器的运转是依赖于CCDirector的。

现在,我们有两种方式来使用注册一个调度器:

一是使用CCNode的子类,比如CCSprite、CCScene、cclayer。

schedule(schedule_selector(Schedulerautoremove::autoremove),0.5f);

二是使用CCDirector。

CCDirector::sharedDirector()->getScheduler()->scheduleSelector(schedule_selector(Schedulerautoremove::autoremove),this,0.5f,kCCRepeatForever,0.0f,false);

它们的作用是一样的。只是调用的接口不一样,并且前者显然比后者要简单一些。我们可以理解CCNode的调度器是访问CCDirector调度器的一个封装而已。

CCNode的schedule函数最多有四个参数:

SEL_SCHEDulE selector 一个继承自CCNode子类的成员函数
float interval 调度间隔,以秒为单位。如果设置为0,意味着每一个帧间隔到期都调用一次,并且这种情况建议使用scheduleUpdate。
unsigned int repeat 调度次数。调度器总共调度次数会是repeat+1。kCCRepeatForever表示无限次。
float delay 第一次调度需要等待的时间。

需要注意的是scheduleUpdate用于每帧都需要执行的动作。对于这类动作,我们不需要注册使用调度器,直接重载CCNode的update即可。CCScheduler对于schedule和update是分开管理的,分别定义了两个哈希表来存储:m_pHashForUpdates和m_pHashForTimers。后面再回头来看。

无论是CCNode的schedule,还是CCDirector的调度器接口,最后都会使用CCScheduler::scheduleSelector。CCScheduler使用CCTimer来封装管理调度器的细节,比如调度器使用的成员函数、Target对象等。CCTimer最重要的 *** 作是update。CCTimer::update负责累计时间,在调度器指定的时间到期之后调用Target上的调度成员函数。

?
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 voID CCTimer::update( float dt) { if (m_bRunForever&&!m_bUseDelay) { //standardtimerusage m_fElapsed+=dt; (m_fElapsed>=m_fInterval) { (m_pTarget&&m_pfnSelector) { (m_pTarget->*m_pfnSelector)(m_fElapsed); } m_fElapsed=0; } } else //advancedusage m_fElapsed+=dt; (m_bUseDelay) { (m_fElapsed>=m_fDelay) { (m_pTarget&&m_pfnSelector) { (m_pTarget->*m_pfnSelector)(m_fElapsed); } m_fElapsed=m_fElapsed-m_fDelay; m_uTimesExecuted+=1; m_bUseDelay= false ; } } else { (m_fElapsed>=m_fInterval) { (m_pTarget&&m_pfnSelector) { (m_pTarget->*m_pfnSelector)(m_fElapsed); } m_fElapsed=0; m_uTimesExecuted+=1; } } (!m_bRunForever&&m_uTimesExecuted>m_uRepeat) //unscheduletimer CCDirector::sharedDirector()->getScheduler()->unscheduleSelector(m_pfnSelector,m_pTarget); } } }

CCTimer::update存在两个分支,前面一个分支处理调度器一直执行并且不需要Delay的情况,后者处理其它的情况。m_fElapsed是已经过去的时间,如果m_fElapsed比指定的间隔m_fInterval大,意味着时间过期了,然后调用Target上的Selector。如果最后发现,我们指定的执行次数已经达到了,就会自动删除这个调度器。

? 37
CCScheduler::scheduleSelector(SEL_SCHEDulEpfnSelector,CCObject*pTarget, @H_549_404@fInterval,unsigned int repeat,monospace!important; border:0px!important; bottom:auto!important; float:none!important; height:auto!important; left:auto!important; line-height:1.1em!important; outline:0px!important; overflow:visible!important; position:static!important; right:auto!important; top:auto!important; vertical-align:baseline!important; wIDth:auto!important; Font-size:1em!important; min-height:auto!important; background:none!important">delay,monospace!important; color:grey!important; border:0px!important; bottom:auto!important; float:none!important; height:auto!important; left:auto!important; line-height:1.1em!important; outline:0px!important; overflow:visible!important; position:static!important; right:auto!important; top:auto!important; vertical-align:baseline!important; wIDth:auto!important; Font-weight:bold!important; Font-size:1em!important; min-height:auto!important; background:none!important">bool bPaused) tHashTimerEntry*pElement=NulL; HASH_FIND_INT(m_pHashForTimers,&pTarget,pElement); (!pElement) { pElement=(tHashTimerEntry*) calloc ( sizeof (*pElement),1); pElement->target=pTarget; (pTarget) { pTarget->retain(); } HASH_ADD_INT(m_pHashForTimers,target,pElement); pElement->paused=bPaused; } (pElement->timers==NulL) { pElement->timers=ccArrayNew(10); } else for (unsigned @H_549_404@i=0;i<pElement->timers->num;++i) CCTimer*timer=(CCTimer*)pElement->timers->arr[i]; (pfnSelector==timer->getSelector()) { timer->setInterval(fInterval); return ccArrayEnsureExtraCapacity(pElement->timers,1); } CCTimer*pTimer= new CCTimer(); pTimer->initWithTarget(pTarget,pfnSelector,fInterval,repeat,delay); ccArrayAppendobject(pElement->timers,pTimer); pTimer->release(); CCScheduler存储调度器的数据结构是一个哈希表。使用的是开源的UTHash。据说很好用,只用include一个头文件即可。UTHash需要我们自己定义一个带Key的数据结构。schedule调度器使用的数据结构:

10
typedef struct _hashSelectorEntry ccArray*timers; CCObject*target; //hashkey(retained) unsigned @H_549_404@timerIndex; CCTimer*currentTimer; @H_549_404@currentTimerSalvaged; paused; UT_hash_handlehh; }tHashTimerEntry;

可以看出,使用Target对象的指针作为哈希表的键。也就是说,同一个CCNode对象调用schedule注册的所有调度器都放在一个桶中。前面说过,一个调度器会对应一个CCTimer,这些CCTimer存放在tHashTimerEntry的timers成员当中。

CCScheduler::scheduleSelector首先根据传入的Target地址来寻找是不是已经有一个该对象所对应的哈希桶。如果没有的话,先创建一个。然后检查tHashTimerEntry的timers是否为空,如果为空,先创建一个存放CCTimer的数组,大小为10。否则,timers数组不为空,需要检查是不是已经有一个相同的的调度器了。如果已经存在相同的调度器,只需要将它的调度间隔时间赋值为新设置的调度时间,其他参数不变。如果timers数组中,还没有这样的调度器,就创建一个CCTimer对象封装一下我们设置的调度器,然后添加到timers数组的最后。我们后面会发现,插入的顺序就是调度执行的顺序。

现在我们已经将一个调度器添加到CCScheduler里面。什么时候来触发调度器呢?我们知道,每个帧间隔时间到期后会调用CCdisplaylinkDirector::mainLoop,它又会调用CCDirector::drawScene。CCDirector::drawScene内部会检查当前游戏是否处于暂停状态(CCDirector::pause),如果处于游戏处于暂停状态,调度器会停止计时,也不会触发任何调度器,包括每一帧调用的update调度器。如果游戏处于正常运行过程当中,CCScheduler::update会被执行,他是负责调度器的主循环。

48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 CCScheduler::update( @H_549_404@m_bUpdateHashLocked= true ; (m_fTimeScale!=1.0f) { dt*=m_fTimeScale; } tListEntry*pEntry,*pTmp; DL_FOREACH_SAFE(m_pUpdatesNegList,pEntry,pTmp) ((!pEntry->paused)&&(!pEntry->markedForDeletion)) { pEntry->target->update(dt); DL_FOREACH_SAFE(m_pUpdates0List,pTmp) ((!pEntry->paused)&&(!pEntry->markedForDeletion)) pEntry->target->update(dt); } } DL_FOREACH_SAFE(m_pUpdatesPosList,pTmp) { ((!pEntry->paused)&&(!pEntry->markedForDeletion)) pEntry->target->update(dt); } (tHashTimerEntry*elt=m_pHashForTimers;elt!=NulL;) { m_pCurrentTarget=elt; m_bCurrentTargetSalvaged= ; (!m_pCurrentTarget->paused) { (elt->timerIndex=0;elt->timerIndex<elt->timers->num;++(elt->timerIndex)) { elt->currentTimer=(CCTimer*)(elt->timers->arr[elt->timerIndex]); elt->currentTimerSalvaged= ; elt->currentTimer->update(dt); (elt->currentTimerSalvaged) { elt->currentTimer->release(); } elt->currentTimer=NulL; } elt=(tHashTimerEntry*)elt->hh.next; (m_bCurrentTargetSalvaged&&m_pCurrentTarget->timers->num==0) { removeHashElement(m_pCurrentTarget); } } { (pEntry->markedForDeletion) { this ->removeUpdateFromHash(pEntry); } } { (pEntry->markedForDeletion) { ->removeUpdateFromHash(pEntry); } } { (pEntry->markedForDeletion) { ->removeUpdateFromHash(pEntry); } } ; m_pCurrentTarget=NulL; CCScheduler::update根据优先级,先去处理update调度器。优先级是数值越小,优先级越高。处理逻辑是先处理优先级为负数的,然后处理优先级为0的,最后处理优先级为正数的。调度完毕,删除update调度器的顺序也是按这样的顺序。update调度器使用的数据结构,比schedule调度器要多一个双向链表。update调度器使用了一个HASH表,和三个双向链表。HASH表还是以Target的对象指针作为HASH的键。双向链表维护的是前面3个优先级。对于大于0和小于0的链表,优先级内部还是有序的。

7 _hashUpdateEntry
tListEntry**List; //WhichListdoesitbelongto? tListEntry*entry; //entryintheList //hashkey(retained) UT_hash_handlehh; }tHashUpdateEntry;

CCScheduler的scheduleUpdateForTarget通过priorityIn来完成update调度器的添加。

update调度器的执行就是调用Target的update函数。schedule调度器的执行:循环遍历HASH表,然后委托给了CCTimer的update函数。CCTimer::update我们前面已经看过,它累计时间,并作调度器指定的时候到期过后,调用注册的SEL_SCHEDulE selector函数。

CCScheduler::update内部用到了m_fTimeScale,它可以帮忙加快或减慢时间流逝的速度。

总结

以上是内存溢出为你收集整理的Cocos2Dx之调度器-欧阳左至全部内容,希望文章能够帮你解决Cocos2Dx之调度器-欧阳左至所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1076331.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-26
下一篇 2022-05-26

发表评论

登录后才能评论

评论列表(0条)

保存