| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523 |
- /****************************************************************************
- Copyright (c) 2008-2010 Ricardo Quesada
- Copyright (c) 2010-2012 cocos2d-x.org
- Copyright (c) 2011 Zynga Inc.
- Copyright (c) 2013-2016 Chukong Technologies Inc.
- Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
- http://www.cocos2d-x.org
- Permission is hereby granted, free of charge, to any person obtaining a copy
- of this software and associated documentation files (the "Software"), to deal
- in the Software without restriction, including without limitation the rights
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- copies of the Software, and to permit persons to whom the Software is
- furnished to do so, subject to the following conditions:
- The above copyright notice and this permission notice shall be included in
- all copies or substantial portions of the Software.
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- THE SOFTWARE.
- ****************************************************************************/
- #include "base/CCScheduler.h"
- #include "base/ccMacros.h"
- #include "base/utlist.h"
- #include "base/ccCArray.h"
- #define CC_REPEAT_FOREVER (UINT_MAX -1)
- NS_CC_BEGIN
- // data structures
- // 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;
- // Hash Element used for "selectors with interval"
- typedef struct _hashSelectorEntry
- {
- ccArray *timers;
- void *target;
- int timerIndex;
- Timer *currentTimer;
- bool currentTimerSalvaged;
- bool paused;
- UT_hash_handle hh;
- } tHashTimerEntry;
- // implementation Timer
- Timer::Timer()
- {
- }
- void Timer::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;
- }
- void Timer::update(float dt)
- {
- if (_elapsed == -1)
- {
- _elapsed = 0;
- _timesExecuted = 0;
- return;
- }
- // accumulate elapsed time
- _elapsed += dt;
- // deal with delay
- if (_useDelay)
- {
- if (_elapsed < _delay)
- {
- return;
- }
- trigger(_delay);
- _elapsed = _elapsed - _delay;
- _timesExecuted += 1;
- _useDelay = false;
- // after delay, the rest time should compare with interval
- if (!_runForever && _timesExecuted > _repeat)
- { //unschedule timer
- cancel();
- return;
- }
- }
- // if _interval == 0, should trigger once every frame
- float interval = (_interval > 0) ? _interval : _elapsed;
- while (_elapsed >= interval)
- {
- trigger(interval);
- _elapsed -= interval;
- _timesExecuted += 1;
- if (!_runForever && _timesExecuted > _repeat)
- {
- cancel();
- break;
- }
- if (_elapsed <= 0.f)
- {
- break;
- }
-
- if (_scheduler->isCurrentTargetSalvaged())
- {
- break;
- }
- }
- }
- // TimerTargetCallback
- TimerTargetCallback::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(float dt)
- {
- if (_callback)
- {
- _callback(dt);
- }
- }
- void TimerTargetCallback::cancel()
- {
- _scheduler->unschedule(_key, _target);
- }
- // implementation of Scheduler
- Scheduler::Scheduler()
- {
- // I don't expect to have more than 30 functions to all per frame
- _functionsToPerform.reserve(30);
- }
- Scheduler::~Scheduler(void)
- {
- unscheduleAll();
- }
- void Scheduler::removeHashElement(_hashSelectorEntry *element)
- {
- ccArrayFree(element->timers);
- HASH_DEL(_hashForTimers, element);
- free(element);
- }
- 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);
- }
- 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!");
- 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, "element's paused should be paused!");
- }
- if (element->timers == nullptr)
- {
- element->timers = ccArrayNew(10);
- }
- else
- {
- for (int i = 0; i < element->timers->num; ++i)
- {
- TimerTargetCallback *timer = dynamic_cast<TimerTargetCallback*>(element->timers->arr[i]);
- if (timer && 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);
- }
- TimerTargetCallback *timer = new (std::nothrow) TimerTargetCallback();
- timer->initWithCallback(this, callback, target, key, interval, repeat, delay);
- ccArrayAppendObject(element->timers, timer);
- timer->release();
- }
- void Scheduler::unschedule(const std::string &key, void *target)
- {
- // explicit handle nil arguments when removing an object
- if (target == nullptr || key.empty())
- {
- return;
- }
- //CCASSERT(target);
- //CCASSERT(selector);
- tHashTimerEntry *element = nullptr;
- HASH_FIND_PTR(_hashForTimers, &target, element);
- if (element)
- {
- for (int i = 0; i < element->timers->num; ++i)
- {
- TimerTargetCallback *timer = dynamic_cast<TimerTargetCallback*>(element->timers->arr[i]);
- if (timer && key == timer->getKey())
- {
- 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--;
- }
- if (element->timers->num == 0)
- {
- if (_currentTarget == element)
- {
- _currentTargetSalvaged = true;
- }
- else
- {
- removeHashElement(element);
- }
- }
- return;
- }
- }
- }
- }
- bool Scheduler::isScheduled(const std::string& key, void *target)
- {
- CCASSERT(!key.empty(), "Argument key must not be empty");
- CCASSERT(target, "Argument target must be non-nullptr");
- tHashTimerEntry *element = nullptr;
- HASH_FIND_PTR(_hashForTimers, &target, element);
- if (!element)
- {
- return false;
- }
- if (element->timers == nullptr)
- {
- return false;
- }
- else
- {
- for (int i = 0; i < element->timers->num; ++i)
- {
- TimerTargetCallback *timer = dynamic_cast<TimerTargetCallback*>(element->timers->arr[i]);
- if (timer && key == timer->getKey())
- {
- return true;
- }
- }
- return false;
- }
- return false; // should never get here
- }
- void Scheduler::unscheduleAll()
- {
- for (tHashTimerEntry *element = _hashForTimers, *nextElement = nullptr; element != nullptr;)
- {
- // element may be removed in unscheduleAllSelectorsForTarget
- nextElement = (tHashTimerEntry *)element->hh.next;
- unscheduleAllForTarget(element->target);
-
- element = nextElement;
- }
- }
- 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);
- }
- }
- }
- void Scheduler::resumeTarget(void *target)
- {
- CCASSERT(target != nullptr, "target can't be nullptr!");
- // custom selectors
- tHashTimerEntry *element = nullptr;
- HASH_FIND_PTR(_hashForTimers, &target, element);
- if (element)
- {
- element->paused = false;
- }
- }
- void Scheduler::pauseTarget(void *target)
- {
- CCASSERT(target != nullptr, "target can't be nullptr!");
- // custom selectors
- tHashTimerEntry *element = nullptr;
- HASH_FIND_PTR(_hashForTimers, &target, element);
- if (element)
- {
- element->paused = true;
- }
- }
- bool Scheduler::isTargetPaused(void *target)
- {
- CCASSERT( target != nullptr, "target must be non nil" );
- // Custom selectors
- tHashTimerEntry *element = nullptr;
- HASH_FIND_PTR(_hashForTimers, &target, element);
- if( element )
- {
- return element->paused;
- }
- return false; // should never get here
- }
- std::set<void*> Scheduler::pauseAllTargets()
- {
- std::set<void*> idsWithSelectors;
-
- // Custom Selectors
- for(tHashTimerEntry *element = _hashForTimers; element != nullptr;
- element = (tHashTimerEntry*)element->hh.next)
- {
- element->paused = true;
- idsWithSelectors.insert(element->target);
- }
-
- return idsWithSelectors;
- }
- void Scheduler::resumeTargets(const std::set<void*>& targetsToResume)
- {
- for(const auto &obj : targetsToResume) {
- this->resumeTarget(obj);
- }
- }
- void Scheduler::performFunctionInCocosThread(const std::function<void ()> &function)
- {
- _performMutex.lock();
- _functionsToPerform.push_back(function);
- _performMutex.unlock();
- }
- void Scheduler::removeAllFunctionsToBePerformedInCocosThread()
- {
- std::unique_lock<std::mutex> lock(_performMutex);
- _functionsToPerform.clear();
- }
- // main loop
- void Scheduler::update(float dt)
- {
- _updateHashLocked = true;
- // 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);
- }
- }
- _updateHashLocked = false;
- _currentTarget = nullptr;
- //
- // 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();
- }
- }
- }
- NS_CC_END
|