mirror of
https://github.com/smallmain/cocos-enhance-kit.git
synced 2024-12-26 11:48:29 +00:00
524 lines
14 KiB
C++
524 lines
14 KiB
C++
/****************************************************************************
|
|
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
|