Scheduler类源代码不完全分析(分量足

一,Timer类
1,是Ref的子类,采用引用计数管理内存
2,主要方法update( )实现了一个定时器逻辑
3,抽象类,必须被继承使用
  virtual void trigger() = 0 ;//到时触发的方法
 virtual void cancel() = 0; //取消定时器
//两个为纯虚函数,需要在子类重载实现

protected :
   
    Scheduler* _scheduler; // weak ref 弱引用,在这个指针被赋值时并没有增加对 _scheduler的引用计数
    float _elapsed;   //度过的时间
    bool _runForever; //标记是否永久运行
    bool _useDelay;   //标记是否使用延迟
    unsigned int _timesExecuted; //纪录已经执行的次数
    unsigned int _repeat; //0 = once, 1 is 2  executed 定义执行的总次数(_repeat+1次)
    float _delay;     //延迟时间
    float _interval;  //时间间隔,隔 _interval调用一次

//初始化方法
    void setupTimerWithInterval( float seconds, unsigned int repeat, float delay);
{
_elapsed = - 1 ;  
_interval = seconds;
_delay = delay;
_useDelay = ( _delay > 0.0f ) ? true : false ;
_repeat = repeat;
_runForever = ( _repeat == CC_REPEAT_FOREVER ) ? true : false ;
//无限重复次数为一个unsignedint的最大值  #define CC_REPEAT_FOREVER (UINT_MAX - 1 )
}

4,Timer的子类 TimerTargetSelector(绑定Ref基类对象)
//初始化方法
   bool initWithSelector( Scheduler * scheduler, SEL_SCHEDULE selector, Ref * target, float seconds, unsigned int repeat, float delay);
// SEL_SCHEDULE是一个Ref函数指针定义

void TimerTargetSelector ::trigger()
{
   
if ( _target && _selector )
    {
        (
_target ->* _selector )( _elapsed );//调用初始化传进来的回调方法
    }
}

void TimerTargetSelector ::cancel()
{
   
_scheduler -> unschedule ( _selector , _target );
}

5, TimerTargetCallback(绑定任意对象)

bool TimerTargetCallback ::initWithCallback( Scheduler * scheduler, const ccSchedulerFunc & callback, void *target, const std :: string & key, float seconds, unsigned int repeat, float delay)
{
   
_scheduler = scheduler;
   
_target = target;
   
_callback = callback;
   
_key = key;
   
setupTimerWithInterval (seconds, repeat, delay);
   
return true ;
}

void TimerTargetCallback ::trigger()
{
   
if ( _callback )
    {
       
_callback ( _elapsed );
    }
}

void TimerTargetCallback ::cancel()
{
   
_scheduler -> unschedule ( _key , _target );
}

6. TimerScriptHandler与以上两个类似,有关脚本的



二,Scheduler类
巨多。。好难。。看不懂。。

首先,
Scheduler有两种调度模式:
1,按帧调度(被集成在Node类中)
 
数据结构:
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;

  提供了3个双向链表来维护(按优先级区分,Scheduler::update调用时,遍历三个链表来区分优先级)
    struct _listEntry *_updatesNegList;        // list of priority < 0
   
struct _listEntry *_updates0List;            // list priority == 0
    struct _listEntry *_updatesPosList;        // list priority > 0
 提供了一个 _hashForUpdates( _hashUpdateEntry 结构体链表头指针)来实现快速查找等操作
 
相关构造函数:
     void schedulePerFrame( const ccSchedulerFunc & callback, void *target, int priority, bool paused);
    // update specific
   void priorityIn(struct _listEntry **list, const ccSchedulerFunc& callback, void *target, int priority, bool paused);//优先级不为0的添加方法
   void appendIn(struct _listEntry **list, const ccSchedulerFunc& callback, void *target, bool paused);//优先级为0的添加方法

2,按时间间隔调度

数据结构:
    typedef struct _hashSelectorEntry
{
   
ccArray             *timers;
   
void                 *target;
   
int                 timerIndex;
   
Timer               *currentTimer;
   
bool                 currentTimerSalvaged;//清理标识
   
bool                 paused;
   
UT_hash_handle       hh;
} tHashTimerEntry;

 由一个HashTable来维护(开源第三方数据结构Uthash),所有的时间间隔调度事件都存储在这个HashTable中,这个HashTable的 Key = target(注册的对象),Value =  tHashTimerEntry结构体, _hashForTimers _hashSelectorEntry 这个结构体链表的头指针
按时间间隔调度还可以分为两种对象形式,一是Ref基类的对象,一种是任意对象
结构体中:
tHashTimerEntry这个结构体用来记录一个Ref对象所有注册的定时器
Timer类是用来绑定对象与回调方法(不同对象有不同的绑定方法)
timers是一个数组,用来存储所有的Timer对象,所以一个对 象可以注册多个调度事件。

其次,主要逻辑
程序每一次事件循环 mainLoop()中都会调用Scheduler::update()(此时的scheduler是全局共享的一个单例对象),然后在这个update中会 1,遍历三个双向链表,查找当前可以执行的回调。2,遍历hashtable中的对象(结构体),调用每个结构体中timers数组中所有Timer对象的update()方法(此时的update是Timer类中的,实现定时器功能)。

再,贴源代码解释
ps:
HASH_FIND_PTR 是uthash中的接口,作用是查找hash表中是否存在某个数据
HASH_FIND_PTR ( _hashForUpdates , &target, hashElement);
查找 _hashForUpdates 表中key为target的数据,如果存在则返回给 hashElement,没有则无操作

HASH_ADD_PTR ( _hashForTimers , target , element);//添加key = target,value = element的数据进表中
HASH_DEL ( _hashForUpdates , element);//删除数据
1.按帧调度
首先在 Node ::scheduleUpdate()中会最终调用到 Scheduler ::schedulePerFrame(注册按帧更新的方法)
void Scheduler ::schedulePerFrame( const ccSchedulerFunc & callback, void *target, int priority, bool paused)
{
    //在 _hashForUpdates中检查当前target是否有注册
    tHashUpdateEntry *hashElement = nullptr ;
    HASH_FIND_PTR(_hashForUpdates, &target, hashElement);
    if (hashElement)//存在数据,不进行插入,也就是说一个node只能加入一次帧调度列表中,也只能有一个回调过程,即Node::update(),所以如果想实现自己的方法应该重载这个update。
    {
       
// check if priority has changed  判断优先级是否变化
       
if ((*hashElement-> list )-> priority != priority)//优先级改变
        {
            if (_updateHashLocked)//正在执行update,则暂时不删除
            {
               
CCLOG ( "warning: you CANNOT change update priority in scheduled function" );
                hashElement->
entry -> markedForDeletion = false ;
                hashElement->
entry -> paused = paused;
               
return ;
            }
           
else
            {
                //不在执行update,则直接删除
            // will be added again outside if (hashElement).
               
unscheduleUpdate (target);
            }
        }
       
else
        {
            //优先级没变,标记不被删除,退出
            hashElement-> entry -> markedForDeletion = false ;
            hashElement->
entry -> paused = paused;
           
return ;
        }
    }

          //不存在数据,则按照优先级插入到对应的双向链表中
    // most of the updates are going to be 0, that's way there
   
// is an special list for updates with priority 0
   
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)
{
   
tListEntry *listElement = new tListEntry ();//建双向链表
    //赋值
    listElement->
callback = callback;
    listElement->
target = target;
    listElement->
paused = paused;
    listElement->
priority = 0 ;
    listElement->
markedForDeletion = false ;
    //添加到*list链表中
   
DL_APPEND (*list, listElement);

    // update hash entry for quicker access再将这个新的链表添加到 _hashForUpdates中
    tHashUpdateEntry *hashElement = ( tHashUpdateEntry *) calloc ( sizeof (*hashElement), 1 );
    hashElement->
target = target;
    hashElement->
list = list;
    hashElement->
entry = listElement;
   
HASH_ADD_PTR ( _hashForUpdates , target , hashElement);
}
priorityIn是按照优先级加入表中
取消注册unschedulerupdate()
2.按时间间隔调度
注册方法有两种
任意参数:
    void schedule( const ccSchedulerFunc & callback, void *target, float interval, unsigned int repeat, float delay, bool paused, const std :: string & key);
    void schedule( const ccSchedulerFunc & callback, void *target, float interval, bool paused, const std :: string & key);

Ref基类:
    void schedule( SEL_SCHEDULE selector, Ref *target, float interval, unsigned int repeat, float delay, bool paused);
    void schedule( SEL_SCHEDULE selector, Ref *target, float interval, bool paused);


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);//查找当前target有没有注册
   
   
if (! element)
    {
        //没有的话分配空间,设置target后加入到 _hashForTimers 中,并设置状态为pause
        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, ");// 设置状态为pause
    }
   
    if (element-> timers == nullptr )
    {
        //如果timers数组为空,则新分配10个空间
        element-> timers = ccArrayNew ( 10 );
    }
   
else
    {
        //不为空,则遍历timers数组
        for ( int i = 0 ; i < element-> timers -> num ; ++i)
        {
           
TimerTargetSelector *timer = dynamic_cast < TimerTargetSelector *>(element-> timers -> arr [i]);
            //查找跟当前selector相同的timer对象,更新他的时间间隔,也就是说一个selector只能注册一次
           
if (timer && selector == timer->getSelector ())
            {
               
CCLOG ( "CCScheduler#scheduleSelector. Selector already scheduled. Updating interval from: %.4f to %.4f" , timer-> getInterval (), interval);
                timer->
setInterval (interval);
               
return ;
            }
        }
        ccArrayEnsureExtraCapacity(element->timers, 1);//将timers数组空间扩大1个单位,确保能存储
    }
    //新建timer对象,并封装selector,target等参数后添加到timers数组中
    TimerTargetSelector *timer = new ( std :: nothrow ) TimerTargetSelector ();
    timer->
initWithSelector ( this , selector, target, interval, repeat, delay);
   
ccArrayAppendObject (element-> timers , timer);
    timer->
release ();//因为添加到数组的操作有retain(),所以应该release一次
}
void Scheduler ::schedule( SEL_SCHEDULE selector, Ref *target, float interval, bool paused)
{
   
this -> schedule (selector, target, interval, CC_REPEAT_FOREVER , 0.0f , paused);
}


void Scheduler ::schedule( const ccSchedulerFunc & callback, void *target, float interval, unsigned int repeat, float delay, bool paused, const std :: string & key)
{
            if (timer && key == timer->getKey ())
            {
               
CCLOG ( "CCScheduler#scheduleSelector. Selector already scheduled. Updating interval from: %.4f to %.4f" , timer-> getInterval (), interval);
                timer->
setInterval (interval);
               
return ;
                                    }
}
void Scheduler ::schedule( const ccSchedulerFunc & callback, void *target, float interval, bool paused, const std :: string & key)
{
   
this -> schedule (callback, target, interval, CC_REPEAT_FOREVER , 0.0f , paused, key);
}
Tips:任意参数的注册方法和定义中都比Ref类的多了一个Key,这是因为SEL_SCHEDULE可以当成key,ccSchedulerFunc不能,因为前者有唯一的标识(查看指向类中方法的函数指针)

取消注册的方法也很简单

3. Scheduler::update()
// main loop
void Scheduler ::update( float dt)
{
   
_updateHashLocked = true ;//状态锁

   
if ( _timeScale != 1.0f )//时间线
    {
        dt *=
_timeScale ;
    }

   
//
    // Selector callbacks
    //
//处理按帧调度
   // Iterate over all the Updates’ selectors 
    tListEntry *entry, *tmp;//定义两个指针用于链表遍历
    //依次遍历三个双向链表,对活动有效的定时器进行回调
    // 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 );
        }
    }

//处理按时间间隔调度
   
// Iterate over all the custom selectors
    for (tHashTimerEntry *elt = _hashForTimers; elt != nullptr; )
//遍历 _hashForTimers 中的对象
    {
        _currentTarget = elt;  //主循环中用来标记当前执行到哪个target对象
        _currentTargetSalvaged = false ;//标记 _currentTarget 是否需要进行清除

       
if (! _currentTarget -> paused )
        {
            // The 'timers' array may change while inside this loop
            //遍历timers数组
            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);//调用Timer::update()

               
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.
// currentTimerSalvaged的作用是标记当前这个定时器是否已经失效,在设置失效的时候我们对定时器增加过一次引用记数,这里调用release来减少那次引用记数,这样释放很安全,这里用到了这个小技巧,延迟释放,这样后面的程序不会出现非法引用定时器指针而出现错误
                    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)
       
if ( _currentTargetSalvaged && _currentTarget -> timers -> num == 0 )
        {
           
removeHashElement ( _currentTarget );
        }
    }

    // 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 ;

#if CC_ENABLE_SCRIPT_BINDING
    //
   
// Script callbacks
   
//

   
// Iterate over all the script callbacks
   
if (! _scriptHandlerEntries . empty ())
    {
       
for ( auto i = _scriptHandlerEntries . size () - 1 ; i >= 0 ; i--)
        {
           
SchedulerScriptHandlerEntry * eachEntry = _scriptHandlerEntries . at (i);
           
if (eachEntry-> isMarkedForDeletion ())
            {
               
_scriptHandlerEntries . erase (i);
            }
           
else if (!eachEntry-> isPaused ())
            {
                eachEntry->
getTimer ()-> update (dt);
            }
        }
    }
#endif
    //
   
// Functions allocated from another thread
   
//
    //多线程处理函数的定时任务
   
// Testing size is faster than locking / unlocking.
   
// And almost never there will be functions scheduled to be called.
   
if ( ! _functionsToPerform . empty () ) {
       
_performMutex . lock ();
       
// fixed #4123: Save the callback functions, they must be invoked after '_performMutex.unlock()', otherwise if new functions are added in callback, it will cause thread deadlock.
       
auto temp = _functionsToPerform ;
       
_functionsToPerform . clear ();
       
_performMutex . unlock ();
       
for ( const auto &function : temp ) {
            function();
        }
       
    }
}

你可能感兴趣的:(Scheduler类源代码不完全分析(分量足)