scheduler 这个类, 负责了引擎的自定义更新, 及定时更新相关的操作, 看看下面的代码,很熟悉吧。
schedule(schedule_selector(HelloWorld::update), 10);
它是如何工作的呢, 咱还记得前面mainLoop->drawScene() 的调用 吗?里面就有调用Scheduler::update(float dt)
我们用一个例子代入一下, 就拿上面的代码来说, 分2个步:
1 注册更新调用
2 引擎调用用户的更新调用函数。
1 注册更新
schedule(schedule_selector(HelloWorld::update), 10);
流程:
1 申明一个tHashTimerEntry *element
2 查询当前是否有注册过这个target的更新
3 没有则添加一个element更新元素, 有则不添加了,直接使用这个为当前的更新元素。
4 判断当前的更新元素中是否有指定的Timer, 没有则直接添加, 有则不加了, 直接返回。
5 把selector, target 封装为timer
6 把timer 加入到当前element的timers中.
7 完成整个的注册流程。
代码:
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, ""); } 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); } TimerTargetSelector *timer = new (std::nothrow) TimerTargetSelector(); timer->initWithSelector(this, selector, target, interval, repeat, delay); ccArrayAppendObject(element->timers, timer); timer->release(); }
总结一下:
1》Scheudler 内部有一个hashTable, _hashForTimers( UT_hash是一个开源的hashTable数据结构,具体的使用网上可以查到。 ), 存放在着所有的注册。
2》这个hashTable的Key = target, Value = 是一个tHashTimerEntry类型的数据结构,tHashTimerEntry这个结构存储着一个timers 数组,
3》这个timers数组封装并保存了用户传入的target, 及select方法。
见如下两个关键的数据结构, 还有一个UT_hash_handle 这里不展开了,这是个开源的hashtable 详细看一下下面的参考:
typedef struct _hashSelectorEntry { ccArray *timers; void *target; int timerIndex; Timer *currentTimer; bool currentTimerSalvaged; bool paused; UT_hash_handle hh; } tHashTimerEntry; timers 存放的是Timer 类型的对象,TimerTargetSelector 是Timer的子类,主要存放了selector, 及target class CC_DLL Timer : public Ref { protected: Timer(); public: /** get interval in seconds */ inline float getInterval() const { return _interval; }; /** set interval in seconds */ inline void setInterval(float interval) { _interval = interval; }; void setupTimerWithInterval(float seconds, unsigned int repeat, float delay); virtual void trigger() = 0; virtual void cancel() = 0; /** triggers the timer */ void update(float dt); protected: Scheduler* _scheduler; // weak ref float _elapsed; bool _runForever; bool _useDelay; unsigned int _timesExecuted; unsigned int _repeat; //0 = once, 1 is 2 x executed float _delay; float _interval; }; class CC_DLL TimerTargetSelector : public Timer { public: TimerTargetSelector(); bool initWithSelector(Scheduler* scheduler, SEL_SCHEDULE selector, Ref* target, float seconds, unsigned int repeat, float delay); inline SEL_SCHEDULE getSelector() const { return _selector; }; virtual void trigger() override; virtual void cancel() override; protected: Ref* _target; SEL_SCHEDULE _selector; };
2 引擎调用用户的更新调用函数。
其实调用还是在mainLoop里调用的, 具体位置在Director::drawScene() 里,之前的章节也介绍过这个位置 。
主要是调用了。
Scheduler::update(dt) // main loop void Scheduler::update(float dt) { _updateHashLocked = true; if (_timeScale != 1.0f) { dt *= _timeScale; } .. // 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)) { elt->currentTimer = (Timer*)(elt->timers->arr[elt->timerIndex]); elt->currentTimerSalvaged = false; 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; } } // 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); } }
具体操作:
1> 遍历当前自身内部hashTable, _hashForTimers.
2> 得到内部存储的每个tHashTimerEntity, 这个类型内部存储着array 类型的timers, 就是这个timers,存储着我们注册更新的调用。
3> 遍历这个每个timer的update(),
Timer这个类封装了我们在做自定义调用时传来的参数.
Timer的update() 会判断当前时间差, 是否应该进行调用我们注册的函数。 如果需要则触发调用
void TimerTargetSelector::trigger() { if (_target && _selector) { (_target->*_selector)(_elapsed); } } void Timer::update(float dt) { .. .. if (_runForever && !_useDelay) {//standard timer usage _elapsed += dt; if (_elapsed >= _interval) { trigger(); _elapsed = 0; } } }
参考:
uthash 哈希Table .
https://github.com/troydhanson/uthash
另外,uthash的英文使用文档介绍可从下面网址获得:
http://troydhanson.github.io/uthash/userguide.html#_add_item