zoukankan      html  css  js  c++  java
  • Cocos2d-x 源代码分析 : Scheduler(定时器) 源代码分析

    源代码版本号 3.1r,转载请注明


            我也最终不out了,開始看3.x的源代码了。此时此刻的心情仅仅能是wtf!

    !!!!!!!

    !。只是也最终告别CC时代了。


            cocos2d-x 源代码分析文件夹

            http://blog.csdn.net/u011225840/article/details/31743129


    1.继承结构

               没错。是两张图。(你没有老眼昏花。

    。我脑子也没有秀逗。。)Ref就是原来的CCObject。而Timer类是与Scheduler类密切相关的类,所以须要把他们放在一起说。Timer和Scheduler的关系就像Data和DataManager的关系。

    2.源代码分析

    2.1 Timer

    2.1.1 Timer中的数据

    Timer类定义了一个行为运行的间隔,运行的次数等。能够理解为定时器的数据类,而详细的定时器的行为,定义在子类中。Timer中的数据例如以下:
                    
    //_elapsed 上一次运行后到如今的时间
    	//timesExecuted 运行的次数
    	//interval 运行间隔
    	//useDelay 是否使用延迟运行
        float _elapsed;
        bool _runForever;
        bool _useDelay;
        unsigned int _timesExecuted;
        unsigned int _repeat; //0 = once, 1 is 2 x executed
        float _delay;
        float _interval;

    2.1.2 Update函数

    void Timer::update(float dt)
    {
    
    	//update方法使用的是模板设计模式,将trigger与cancel的实现交给子类。
    
    
    
    	
        if (_elapsed == -1)
        {
            _elapsed = 0;
            _timesExecuted = 0;
        }
    	//四种情况
    	/*
    		1.永久运行而且不使用延迟:基本使用方法。计算elapsed大于interval后运行一次,永不cancel。
    		2.永久运行而且使用延迟:当elapsed大于延迟时间后,运行一次后,进入情况1.
    		3.不永久运行而且不使用延迟:情况1结束后,会推断运行次数是否大于反复次数,大于后则cancel。
    		4.不永久运行而且使用延迟:情况2结束后,进入情况3.
    	*/
        else
        {
            if (_runForever && !_useDelay)
            {//standard timer usage
                _elapsed += dt;
                if (_elapsed >= _interval)
                {
                    trigger();
    
                    _elapsed = 0;
                }
            }    
            else
            {//advanced usage
                _elapsed += dt;
                if (_useDelay)
                {
                    if( _elapsed >= _delay )
                    {
                        trigger();
                        
                        _elapsed = _elapsed - _delay;
                        _timesExecuted += 1;
                        _useDelay = false;
                    }
                }
                else
                {
                    if (_elapsed >= _interval)
                    {
                        trigger();
                        
                        _elapsed = 0;
                        _timesExecuted += 1;
    
                    }
                }
    
                if (!_runForever && _timesExecuted > _repeat)
                {    //unschedule timer
                    cancel();
                }
            }
        }
    }
              正如我凝视中所说,update使用了模板方法的设计模式思想。将trigger与cancel调用的过程写死,可是不同的子类实现trigger和cancel的方式不同。
               另外须要注意的是,Schedule使用时delay的需求。当有delay与没有delay我在源代码中已经分析的非常清楚了。


    2.2 TimerTargetSelector   && TimerTargetCallback

               前者是针对类(继承自Ref)中的method进行定时,而后者是针对function(普通函数)。
               前者绑定的类型是SEL_SCHEDULE(你问我这是什么?)typedef void (Ref::*SEL_SCHEDULE)(float);一个指向Ref类型的method指针,而且该method必须满足參数是float,返回值是void。后者绑定的类型是ccSchedulerFunc---------typedef std::function<void(float)> ccSchedulerFunc;这是虾米?这是c++11的新特性,事实上就是一个函数指针。

               从他们实现的trigger方法中能够更好的看清这一切。

    void TimerTargetSelector::trigger()
    {
        if (_target && _selector)
        {
            (_target->*_selector)(_elapsed);
        }
    }
    
    void TimerTargetCallback::trigger()
    {
        if (_callback)
        {
            _callback(_elapsed);
        }
    }


    最后说一下,TargetCallback中含有一个key。而前者没有。这在以下的源代码分析中会看到。(事实上原理非常easy,SEL_SCHEDULE能够当成key。ccSchedulerFunc不能,由于前者有唯一的标识,假设你不懂这点。欢迎去复习下c++的指向类中方法的函数指针)
        Ref* _target;
        SEL_SCHEDULE _selector;
     ------  ------------------------
        void* _target;
        ccSchedulerFunc _callback;
        std::string _key;



    2.3 Scheduler

    2.3.1 Schedule && UnSchedule

    Schedule有四种重载方法。当中各有两种针对不同的Timer子类,可是都大同小异。在此之前,不得不说一个用的许多的数据结构tHashTimerEntry
    typedef struct _hashSelectorEntry
    {
        ccArray             *timers;
        void                *target;
        int                 timerIndex;
        Timer               *currentTimer;
        bool                currentTimerSalvaged;
        bool                paused;
        UT_hash_handle      hh;
    } tHashTimerEntry;

         这用到了开源库uthash。关于该hast的详细使用方法。请自行谷歌。UT_hash_handle能让我们依据key值找到对应的数据。

    在这个结构里,target是key值,其它都是数据(除了hh哦)。

    timers存放着该target相关的全部timer。currentTimerSalvaged的作用是假设你想停止unschedule正在运行的timer时。会将其从timers移除。并retain,防止被自己主动回收机制回收。然后将此标识为true。以下来看下第一种TimerCallback的Schedule。


    void Scheduler::schedule(const ccSchedulerFunc& callback, void *target, float interval, unsigned int repeat, float delay, bool paused, const std::string& key)
    {
        CCASSERT(target, "Argument target must be non-nullptr");
        CCASSERT(!key.empty(), "key should not be empty!");
    	//先在hash中查找该target(key值)是否已经有数据
        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, "");
        }
    
    	//第一次创建target的数据。需要将timers初始化
        if (element->timers == nullptr)
        {
            element->timers = ccArrayNew(10);
        }
        else 
        {
    		//在timers中查找timer,看在该target下的全部timer绑定的key值是否存在,假设存在,设置新的interval后返回。
    		//这里必需要解释下,target是hash表的key值。用来查找timers等数据。
    		//而TimerCallback类型的timer本身含有一个key值(std::string类型),用来标识该唯一timer
            for (int i = 0; i < element->timers->num; ++i)
            {
                TimerTargetCallback *timer = static_cast<TimerTargetCallback*>(element->timers->arr[i]);
    
                if (key == timer->getKey())
                {
                    CCLOG("CCScheduler#scheduleSelector. Selector already scheduled. Updating interval from: %.4f to %.4f", timer->getInterval(), interval);
                    timer->setInterval(interval);
                    return;
                }        
            }
            ccArrayEnsureExtraCapacity(element->timers, 1);
        }
    	//假设TimerCallback原本不存在在timers中。就加入新的
        TimerTargetCallback *timer = new TimerTargetCallback();
        timer->initWithCallback(this, callback, target, key, interval, repeat, delay);
        ccArrayAppendObject(element->timers, timer);
        timer->release();
    }

           TimerTargetSelector的Schedule不须要本身在通过key值进行存取。其它部分都与上面同样,只有在查找是否存在Timer时,直接使用了selector。

     if (selector == timer->getSelector())
                {
                    CCLOG("CCScheduler#scheduleSelector. Selector already scheduled. Updating interval from: %.4f to %.4f", timer->getInterval(), interval);
                    timer->setInterval(interval);
                    return;
                }

            继续看下TimerTargetSelector的unschedule。
    void Scheduler::unschedule(SEL_SCHEDULE selector, Ref *target)
    {
        // explicity handle nil arguments when removing an object
        if (target == nullptr || selector == nullptr)
        {
            return;
        }
        
        //CCASSERT(target);
        //CCASSERT(selector);
        
        tHashTimerEntry *element = nullptr;
        HASH_FIND_PTR(_hashForTimers, &target, element);
        //假设该target存在数据,就进行删除操作。

    if (element) { //遍历寻找 for (int i = 0; i < element->timers->num; ++i) { TimerTargetSelector *timer = static_cast<TimerTargetSelector*>(element->timers->arr[i]); //假设正在运行的Timer是须要被unschedule的timer,将其移除而且标识当前正在运行的Timer须要被移除状态为true。 if (selector == timer->getSelector()) { if (timer == element->currentTimer && (! element->currentTimerSalvaged)) { element->currentTimer->retain(); element->currentTimerSalvaged = true; } ccArrayRemoveObjectAtIndex(element->timers, i, true); // update timerIndex in case we are in tick:, looping over the actions if (element->timerIndex >= i) { element->timerIndex--; } //当前timers中不再含有timer。可是假设正在运行的target是该target,则将正在运行的target将被清除标识为true //否则,能够直接将其从hash中移除 if (element->timers->num == 0) { if (_currentTarget == element) { _currentTargetSalvaged = true; } else { removeHashElement(element); } } return; } } } }


             同理反观TimerTargetCallback,查找时须要用到std::string。这里不再赘述。


    2.3.2 Scheduler的两种定时模式

    Scheduler同意有两种定时模式:
            1.带有interval(间隔)的定时模式。哪怕interval是0.(普通函数)
            2.不带有interval的定时模式,即在每一帧更新之后都会调用到,会将一个类的update函数放入定时器。

    (此外。模式2还引入了优先级的概念)

            从实现的源码来看,假设你有一个须要每帧更新都须要调用的function or method,请一定将该部分放入类中的update函数后使用模式2来定时。由于每一个模式2绑定了一个hash表能高速存取到,提高性能。上面一小节介绍的是怎样加入和删除模式1的定时。以下看一下模式2.

      template <class T>
        void scheduleUpdate(T *target, int priority, bool paused)
        {
            this->schedulePerFrame([target](float dt){
                target->update(dt);
            }, target, priority, paused);
        }

    别问我从哪里来。我tm来自c++11,假设不懂该写法。请自行谷歌c++11 lambda表达式。

            详细開始分析SchedulePerFrame,在此之前。要先介绍两个数据结构。

    // A list double-linked list used for "updates with priority"
    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;
    
    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;


         tListEntry,是一个双向链表,target是key。markedForDeletion来告诉scheduler是否须要删除他。tHashUpdateEntry是一个哈希表。通过target能够高速查找到对应的tListEntry。

    能够注意到。HashEntry中有个List,来表示该entry属于哪个list。在scheduler中,一共同拥有三个updateList,依据优先级分为negativeList,0List,positiveList,值越小越先运行。


         数据结构介绍完成,能够開始介绍函数了。
    void Scheduler::schedulePerFrame(const ccSchedulerFunc& callback, void *target, int priority, bool paused)
    {
    	//先检查hash中是否存在该target,假设存在,则将其deleteion的标识 置为false后返回。(可能某个操作将其置为true。而且
    	//scheduler还没来得及删除,所以这里仅仅须要再改为false就可以)
        tHashUpdateEntry *hashElement = nullptr;
        HASH_FIND_PTR(_hashForUpdates, &target, hashElement);
        if (hashElement)
        {
    #if COCOS2D_DEBUG >= 1
            CCASSERT(hashElement->entry->markedForDeletion,"");
    #endif
            // TODO: check if priority has changed!
    
            hashElement->entry->markedForDeletion = false;
            return;
        }
    
        // most of the updates are going to be 0, that's way there
        // is an special list for updates with priority 0
    	//英文凝视解释了为啥有一个0List。

    if (priority == 0) { appendIn(&_updates0List, callback, target, paused); } else if (priority < 0) { priorityIn(&_updatesNegList, callback, target, priority, paused); } else { // priority > 0 priorityIn(&_updatesPosList, callback, target, priority, paused); } }


    void Scheduler::appendIn(_listEntry **list, const ccSchedulerFunc& callback, void *target, bool paused)
    {
    	//为该target新建一个listEntry
        tListEntry *listElement = new tListEntry();
    
        listElement->callback = callback;
        listElement->target = target;
        listElement->paused = paused;
        listElement->markedForDeletion = false;
    
        DL_APPEND(*list, listElement);
    
        // update hash entry for quicker access
    	//而且为该target建立一个高速存取的target
        tHashUpdateEntry *hashElement = (tHashUpdateEntry *)calloc(sizeof(*hashElement), 1);
        hashElement->target = target;
        hashElement->list = list;
        hashElement->entry = listElement;
        HASH_ADD_PTR(_hashForUpdates, target, hashElement);
    }

    void Scheduler::priorityIn(tListEntry **list, const ccSchedulerFunc& callback, void *target, int priority, bool paused)
    {
    	//同理,为target建立一个entry
        tListEntry *listElement = new tListEntry();
    
        listElement->callback = callback;
        listElement->target = target;
        listElement->priority = priority;
        listElement->paused = paused;
        listElement->next = listElement->prev = nullptr;
        listElement->markedForDeletion = false;
    
        // empty list ?

    if (! *list) { DL_APPEND(*list, listElement); } else { bool added = false; //依据优先级。将element放在一个合适的位置,标准的有序链表插入操作,不多解释。

    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; } } // Not added? priority has the higher value. Append it. if (! added) { DL_APPEND(*list, listElement); } } // update hash entry for quick access tHashUpdateEntry *hashElement = (tHashUpdateEntry *)calloc(sizeof(*hashElement), 1); hashElement->target = target; hashElement->list = list; hashElement->entry = listElement; HASH_ADD_PTR(_hashForUpdates, target, hashElement); }



    ok。到这里,我们已经明确update的定时是怎样加入进来的。scheduler用了以下的成员来管理这些entry。
      
     //
        // "updates with priority" stuff
        //
        struct _listEntry *_updatesNegList;        // list of priority < 0
        struct _listEntry *_updates0List;            // list priority == 0
        struct _listEntry *_updatesPosList;        // list priority > 0
        struct _hashUpdateEntry *_hashForUpdates; // hash used to fetch quickly the list entries for pause,delete,etc


             以下,继续分析源代码。看一下是怎样移除这些update的定时的。

          
    void Scheduler::unscheduleUpdate(void *target)
    {
    	
        if (target == nullptr)
        {
            return;
        }
    
        tHashUpdateEntry *element = nullptr;
        HASH_FIND_PTR(_hashForUpdates, &target, element);
        if (element)
        {
            if (_updateHashLocked)
            {
                element->entry->markedForDeletion = true;
            }
            else
            {
                this->removeUpdateFromHash(element->entry);
            }
        }
    }

             代码简单介绍易懂,唯一须要注意的地方是当updateHashLocked为true时,表示当前情况下不同意更改该hash表,仅仅能先将其deletion标记为true。(在运行update的时候会将这类定时删除)这样在运行update时,即使其在hash表中,也不会运行(由于deletion为true)。标识updateHashLocked,将在scheduler的update函数開始时置为true,然后在结尾置为false,其它时候不会被更改。

    update函数会在后面介绍,以下,继续看unschedule的其它方法。


            
    void Scheduler::unscheduleAllForTarget(void *target)
    {
        // explicit nullptr handling
        if (target == nullptr)
        {
            return;
        }
    
        // Custom Selectors
        tHashTimerEntry *element = nullptr;
        HASH_FIND_PTR(_hashForTimers, &target, element);
    
        if (element)
        {
            if (ccArrayContainsObject(element->timers, element->currentTimer)
                && (! element->currentTimerSalvaged))
            {
                element->currentTimer->retain();
                element->currentTimerSalvaged = true;
            }
            ccArrayRemoveAllObjects(element->timers);
    
            if (_currentTarget == element)
            {
                _currentTargetSalvaged = true;
            }
            else
            {
                removeHashElement(element);
            }
        }
    
        // update selector
        unscheduleUpdate(target);
    }
             该方法会移除target相关的全部定时。包含update类型的,包含Custom Selector类型的,和其它的一样,须要注意该标志位。


             最后提一下unscheduleAllWithMinPriority。他会将custom 类型的定时所有移除,并将priority大于残烛的update类型定时移除。

    2.3.3 定时器的更新update

    void Scheduler::update(float dt)
    {
        _updateHashLocked = true;
    
    	//timeScale是什么意思呢,正常的速度是1.0,假设你想二倍速放就设置成2.0,假设你想慢慢放。就设置成0.5.
        if (_timeScale != 1.0f)
        {
            dt *= _timeScale;
        }
    
        //
        // Selector callbacks
        //
    
        // Iterate over all the Updates' selectors
        tListEntry *entry, *tmp;
    
    	//首先处理update类型的定时,你能够发现想调用它的callback,必须满足markedForDeletion为false,从而证明我上面的说法。
        // updates with priority < 0
        DL_FOREACH_SAFE(_updatesNegList, entry, tmp)
        {
            if ((! entry->paused) && (! entry->markedForDeletion))
            {
                entry->callback(dt);
            }
        }
    
        // updates with priority == 0
        DL_FOREACH_SAFE(_updates0List, entry, tmp)
        {
            if ((! entry->paused) && (! entry->markedForDeletion))
            {
                entry->callback(dt);
            }
        }
    
        // updates with priority > 0
        DL_FOREACH_SAFE(_updatesPosList, entry, tmp)
        {
            if ((! entry->paused) && (! entry->markedForDeletion))
            {
                entry->callback(dt);
            }
        }
    
    	//处理custom类型的定时
        // Iterate over all the custom selectors
        for (tHashTimerEntry *elt = _hashForTimers; elt != nullptr; )
        {
            _currentTarget = elt;
            _currentTargetSalvaged = false;
    		//没有被暂停。则能够处理
            if (! _currentTarget->paused)
            {
                // The 'timers' array may change while inside this loop
    			//循环内是当前target下的全部Timer
                for (elt->timerIndex = 0; elt->timerIndex < elt->timers->num; ++(elt->timerIndex))
                {
                    elt->currentTimer = (Timer*)(elt->timers->arr[elt->timerIndex]);
                    elt->currentTimerSalvaged = false;
    
                    elt->currentTimer->update(dt);
    				//假设currentTimer的update本身内部。在一定条件下unSchedule了本身,则会改变currentTimerSalvaged的标识信息。
    				//所以要再次进行推断,这就是循环上面英文凝视所述之意
                    if (elt->currentTimerSalvaged)
                    {
                        // The currentTimer told the remove itself. To prevent the timer from
                        // accidentally deallocating itself before finishing its step, we retained
                        // it. Now that step is done, it's safe to release it.
                        elt->currentTimer->release();
                    }
    
                    elt->currentTimer = nullptr;
                }
            }
    
            // elt, at this moment, is still valid
            // so it is safe to ask this here (issue #490)
            elt = (tHashTimerEntry *)elt->hh.next;
    
            // only delete currentTarget if no actions were scheduled during the cycle (issue #481)
    		//即使在大循环開始时_currentTargetSalvaged被设置为false。如今的值也可能由于上面该target的各种定时函数调用导致其为true
            if (_currentTargetSalvaged && _currentTarget->timers->num == 0)
            {
                removeHashElement(_currentTarget);
            }
        }
    
    	//这些update类型的定时要被删除咯~~
        // delete all updates that are marked for deletion
        // updates with priority < 0
        DL_FOREACH_SAFE(_updatesNegList, entry, tmp)
        {
            if (entry->markedForDeletion)
            {
                this->removeUpdateFromHash(entry);
            }
        }
    
        // updates with priority == 0
        DL_FOREACH_SAFE(_updates0List, entry, tmp)
        {
            if (entry->markedForDeletion)
            {
                this->removeUpdateFromHash(entry);
            }
        }
    
        // updates with priority > 0
        DL_FOREACH_SAFE(_updatesPosList, entry, tmp)
        {
            if (entry->markedForDeletion)
            {
                this->removeUpdateFromHash(entry);
            }
        }
    
        _updateHashLocked = false;
        _currentTarget = nullptr;
    
    }

            到了最重要的函数了。当你把定时都放入了这些list后,定时器是怎样按时调用的呢。答案就在update函数中。

           update函数,最须要注意的点是什么?是在循环内部运行每一个target的customer定时函数时候。须要注意非常可能改变绑定在该Target下的Customer Timer的状态。

    所以在每次循环之后,都会推断这些状态位,假设被改变,须要做什么操作。

    在代码凝视中,我已经说明。

    2.3.4 状态查询与暂停恢复

            bool isScheduled(const std::string& key, void *target);   &&  bool isScheduled(SEL_SCHEDULE selector, Ref *target);
            能够查询customer类型的定时是否被scheduled。

            void pauseTarget(void *target);   &&   void resumeTarget(void *target);
            恢复和暂定target相关的全部定时。

    就是更改状态而已。。


       2.3.5 3.x的新特性

           自从3.x開始。进入了c++11的时代。与此同一时候。正式引入了多线程编程。本人对多线程了解不多。仅仅能简单点出此函数,详细的使用方法。烦请各位看官谷歌或者微微一笑吧~

         /** calls a function on the cocos2d thread. Useful when you need to call a cocos2d function from another thread.
         This function is thread safe.
         @since v3.0
         */
        void performFunctionInCocosThread( const std::function<void()> &function);


    3.小结

          1.Scheduler与Timer的关系相当DataManager与Data的关系。
          2.Scheduler的两种定时模式,一种是customer selector模式,一种是update 模式。

          3.hash表用来存取相应的timer。

          4.Scheduler的update函数调用了全部Timer的update。

  • 相关阅读:
    sql语句如何将多个空格字符替换成一个空格字符
    在 ServiceModel 客户端配置部分中,找不到引用协定“myservice.Service1Soap”的默认终结点元素。这可能是因为未找到应用程序的配置文件,或者是因为客户端元素中找不到与此协定匹配的终结点元素。
    sql语句查看表结构
    VxWorks 6.9 内核编程指导之读书笔记 -- VxWorks Small-Footprint Configuration
    C#学习笔记之线程
    C#学习笔记之线程
    C#学习笔记之线程
    C#学习笔记之线程
    C#学习笔记之线程安全
    WCF学习笔记 -- 如何用C#开发一个WebService
  • 原文地址:https://www.cnblogs.com/jhcelue/p/7359219.html
Copyright © 2011-2022 走看看