Skip to content

cocos2d x 3.3 011 调度器

cheyiliu edited this page Dec 29, 2014 · 16 revisions

代码

  • Scheduler类
  • Timer TimerTargetSelector TimerTargetCallback的包装
  • Node类针对Scheduler作了相关的包装函数

API

  • 开启schedule
  • 取消unschedule
  • 暂停pause
  • 恢复resume
  • 其他

schedule参数类型

  • ccSchedulerFunc: CCScheduler.h:typedef std::function<void(float)> ccSchedulerFunc;
  • SEL_SCHEDULE: CCRef.h:typedef void (Ref::*SEL_SCHEDULE)(float);

相关源码

  • timer调度相关(非update调度)
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表中查找target
    HASH_FIND_PTR(_hashForTimers, &target, element);
    
    if (! element)
    {
//没找到则创建
        element = (tHashTimerEntry *)calloc(sizeof(*element), 1);
        element->target = target;
        
//加入hash表
        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, "");
    }
    
//若timers数组空则new
    if (element->timers == nullptr)
    {
        element->timers = ccArrayNew(10);
    }
//非空
    else
    {
        for (int i = 0; i < element->timers->num; ++i)
        {
            TimerTargetSelector *timer = static_cast<TimerTargetSelector*>(element->timers->arr[i]);
            
//若原来存在则重新设置间隔并返回
            if (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);
    }
    
//新建timer并加入target对应的timers数组
    TimerTargetSelector *timer = new (std::nothrow) TimerTargetSelector();
    timer->initWithSelector(this, selector, target, interval, repeat, delay);
    ccArrayAppendObject(element->timers, timer);
    timer->release();
}
  • update调度相关

void Scheduler::schedulePerFrame(const ccSchedulerFunc& callback, void *target, int priority, bool paused)
{
    tHashUpdateEntry *hashElement = nullptr;
//在hash表中查找,_hashForUpdates是一个辅助结构
    HASH_FIND_PTR(_hashForUpdates, &target, hashElement);
    if (hashElement)
    {
//运来存在找到了
        // 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
//针对3种优先级分别插入到双向链表
    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)
{
//传入的list是_updates0List
//新建一节点并设置
    tListEntry *listElement = new tListEntry();

    listElement->callback = callback;
    listElement->target = target;
    listElement->paused = paused;
    listElement->priority = 0;
    listElement->markedForDeletion = false;

//插入到双向链表
    DL_APPEND(*list, listElement);

//同时更新辅助数据结构, hash表
    // update hash entry for quicker access
    tHashUpdateEntry *hashElement = (tHashUpdateEntry *)calloc(sizeof(*hashElement), 1);
    hashElement->target = target;
    hashElement->list = list;
    hashElement->entry = listElement;
    HASH_ADD_PTR(_hashForUpdates, target, hashElement);
}

  • 动力源 Scheduler::update

// main loop 每次循环都会调用_scheduler->update(_deltaTime);

void Scheduler::update(float dt)
{
//设置标记,正在更新
    _updateHashLocked = true;

//是'快进''快退'效果?
    if (_timeScale != 1.0f)
    {
//是则重新计算dt
        dt *= _timeScale;
    }

    //
    // Selector callbacks
    //

    // Iterate over all the Updates' selectors
    tListEntry *entry, *tmp;

//update调度相关触发
//分优先级遍历双向链表
    // updates with priority < 0
    DL_FOREACH_SAFE(_updatesNegList, entry, tmp)
    {
        if ((! entry->paused) && (! entry->markedForDeletion))
        {
//!!!关键点update相关
//do callback
            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);
        }
    }

//timer相关调度触发
//遍历hash表
    // 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
            for (elt->timerIndex = 0; elt->timerIndex < elt->timers->num; ++(elt->timerIndex))
            {
//遍历timers数组
                elt->currentTimer = (Timer*)(elt->timers->arr[elt->timerIndex]);
                elt->currentTimerSalvaged = false;

//!!!关键点, 调用到timer的update
                elt->currentTimer->update(dt);

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

//继续hash表的下一项
        // 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();
        }
        
    }
}
  • Timer::update

void Timer::update(float dt)
{
    if (_elapsed == -1)
    {
//刚开始的赋值
        _elapsed = 0;
        _timesExecuted = 0;
    }
    else
    {
        if (_runForever && !_useDelay)
        {//standard timer usage
            _elapsed += dt;
            if (_elapsed >= _interval)
            {
//普通用法,一直循环且不delay
//当逝去的时间>=间隔时, 触发
                trigger();

                _elapsed = 0;
            }
        }    
        else
        {//advanced usage
            _elapsed += dt;
            if (_useDelay)
            {
                if( _elapsed >= _delay )
                {
//高级用法,use delay,且逝去的时间>=delay了
//触发
                    trigger();
                    
                    _elapsed = _elapsed - _delay;
                    _timesExecuted += 1;
                    _useDelay = false;
                }
            }
            else
            {
                if (_elapsed >= _interval)
                {
//没有use delay,且逝去的时间>=_interval了
//触发
                    trigger();
                    
                    _elapsed = 0;
                    _timesExecuted += 1;

                }
            }

            if (!_runForever && _timesExecuted > _repeat)
            {    //unschedule timer
//不是一直循环的timer且执行次数大于repeat
//取消,调用的_scheduler->unschedule
                cancel();
            }
        }
    }
}


void TimerTargetSelector::trigger()
{
    if (_target && _selector)
    {
//回调函数
        (_target->*_selector)(_elapsed);
    }
}

小结

  • Scheduler负责维护一堆类成员函数指针和对应的定时器属性,结合timer机制,实现定时callback
  • 3.3中,Node这个基类的Scheduler成员是来自导演的,_scheduler = director->getScheduler(); 可以理解为是全局共享的。
  • 导演的mainLoop每次循环都会调用Scheduler的update方法来触发相关update回调;或导演的mainLoop每次循环都会调用Scheduler的update方法,然后调用到timer的update并最终到trigger or cancel来触发相关回调。
  • 定时事件分优先级,体现在Scheduler::update方法中的调用顺序。
  • 基本数据结构
    • 针对update(每1帧调用1次)由3(_updatesNegList _updates0List _updatesPosList)个双向链表来维护(Scheduler::update调用时,遍历这三个链表来区分优先级),同时提供了辅助_hashForUpdates来实现快速查找,删除等。相关构建函数(removeUpdateFromHash appendIn )
    • 针对非update的定时器由hash表维护,表头struct _hashSelectorEntry *_hashForTimers; hash表的每一项有一个ccArray timers,用来存放callback和timer相关属性。
    • 上面的hash结构由uthash实现。
    • 上面的双向链表结构由utlist实现。

扩展阅读

Clone this wiki locally