本文通过cocos2dx的CCScheduler源码分析,介绍了CCScheduler是什么,以及在cocos2d-lua如何使用CCScheduler。
提示:以下是本篇文章正文内容,下面案例可供参考
CCScheduler一共有两种类型的回调函数callbacks (selectors),一种是每帧调用(update selector)类型,这种回调函数用户可以自定义执行回调函数的优先级(priority);另外一种就是用户自定义回调函数(custom selector),可以每帧调用用户自定义的回调函数,或者自定义的一个时间周期(interval)调用。
/** Schedules the 'update' selector for a given target with a given priority.
The 'update' selector will be called every frame.
The lower the priority, the earlier it is called.
@since v3.0
@lua NA
*/
template <class T>
void scheduleUpdate(T *target, int priority, bool paused)
{
this->schedulePerFrame([target](float dt){
target->update(dt);
}, target, priority, paused);
}
取消定时器,传入调用者的对象指针void *target
/** Unschedules the update selector for a given target
@param target The target to be unscheduled.
@since v0.99.3
*/
void unscheduleUpdate(void *target);
重新开始定时器,传入调用者的对象指针void *target。
如果调用scheduleUpdate时候传入参数bool paused是true,那么必须调用此方法,才能重新开始定时器。或者手动停止之后可以调用此方法重新开启定时器
/** Resumes the target.
The 'target' will be unpaused, so all schedule selectors/update will be 'ticked' again.
If the target is not present, nothing happens.
@param target The target to be resumed.
@since v0.99.3
*/
void resumeTarget(void *target);
手动停止定时器,传入调用者的对象指针void *target。
/** Pauses the target.
All scheduled selectors/update for a given target won't be 'ticked' until the target is resumed.
If the target is not present, nothing happens.
@param target The target to be paused.
@since v0.99.3
*/
void pauseTarget(void *target);
自定义的定时器可以在每个‘interval’周期内调用一次传入的回调函数。我们先认识一下参数。代码如下:
可以延迟执行,可以停止,可以选择执行次数,周期内执行回调函数。
1)schedule(const ccSchedulerFunc& callback, void target, float,interval, unsigned int repeat, float delay, bool paused, const std::string& key)*
// schedule
typedef std::function<void(float)> ccSchedulerFunc;
void schedule(const ccSchedulerFunc& callback, void *target, float interval, unsigned int repeat, float delay, bool paused, const std::string& key);
- @param callback The callback function.
const ccSchedulerFunc& callback 这是std::function定义的一个函数,支持C++11的新特性Lambda表达式。- @param target The target of the callback function.
调用者的对象- @param interval The interval to schedule the callback. If the value is 0, then the callback will be scheduled every frame.
定时器的周期,如果设置为0则是每帧调用。Cocos2dx官方推荐使用上述方法scheduleUpdate代替。- @param repeat repeat+1 times to schedule the callback.
定时器可以自定义回调函数执行次数,实际执行次数是repeat+1次。CC_REPEAT_FOREVER使用这个宏可以“无限”重复调用此方法。- @param delay Schedule call back after
delay
seconds. If the value is not 0, the first schedule will happen afterdelay
seconds. But it will only affect first schedule. After first schedule, the
delay time is determined byinterval
.
定时器可以自定义延迟多少时间执行,这个参数只对首次执行定时器时候生效。- @param paused Whether or not to pause the schedule.
定时器可以自定义是否停止,如果是true,定时器不会生效 ,除非手动条用resumeTarget。- @param key The key to identify the callback function, because there is not way to identify a std::function<>
key作为std::function<>的唯一标记。
void schedule(const ccSchedulerFunc& callback, void *target, float interval, bool paused, const std::string& key);
- @param callback The callback function.
const ccSchedulerFunc& callback 这是std::function定义的一个函数,支持C++11的新特性Lambda表达式。- @param target The target of the callback function.
调用者的对象- @param interval The interval to schedule the callback. If the value is 0, then the callback will be scheduled every frame.
定时器的周期,如果设置为0则是每帧调用。Cocos2dx官方推荐使用上述方法scheduleUpdate代替。- @param paused Whether or not to pause the schedule.
定时器可以自定义是否停止,如果是true,定时器不会生效 ,除非手动条用resumeTarget。- @param key The key to identify the callback function, because there is not way to identify a std::function<>.
key作为std::function<>的唯一标记。
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);
CCschedule中的一些数据的注释
// 双向链表tListEntry的结构体,
// A list double-linked list used for "updates with priority"
typedef struct _listEntry
{
struct _listEntry *prev, *next; //(保存前后一个tListEntry的结构体)
ccSchedulerFunc callback; // 回调函数(typedef std::function ccSchedulerFunc;)
void *target; //调用schedule的对象
int priority; // 优先级(<0最优先 ==0其次 >0最后)越小越优先The lower the priority, the earlier it is called
bool paused; // 是否停止定时器
bool markedForDeletion; // 标记是否等待删除selector will no longer be called and entry will be removed at end of the nexttick
} tListEntry;
// "updates with priority" stuff (Update selectors)
- struct _listEntry *_updatesNegList; // list of priority < 0 最优先的链表
- struct _listEntry *_updates0List; // list priority == 0 其次
- struct _listEntry *_updatesPosList; // list priority > 0 最后
// 每帧调用的快速查找的hash table
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 used to fetch quickly the list entries for pause,delete,etc
// 哈希
struct _hashUpdateEntry *_hashForUpdates;
// 系统级别的优先级值是最小整数INT_MIN -(2^31) = -2147483648
// Priority level reserved for system services.
const int Scheduler::PRIORITY_SYSTEM = INT_MIN;
// 用户最低优先级是系统级别+1 即-2147483647.
// Minimum priority level for user scheduling.
const int Scheduler::PRIORITY_NON_SYSTEM_MIN = PRIORITY_SYSTEM + 1;
// hash used to fetch quickly the list entries for pause,delete,etc
- struct _hashUpdateEntry *_hashForUpdates;
- struct _hashSelectorEntry *_hashForTimers;
- struct _hashSelectorEntry *_currentTarget;
// 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;
//lua传入的定时器Vector,可以定义多个自定义定时器
Vector<SchedulerScriptHandlerEntry*> _scriptHandlerEntries;
先看CCNode的C++源码
//******CCNode.cpp******
void Node::scheduleUpdate()
{
scheduleUpdateWithPriority(0);
}
void Node::scheduleUpdateWithPriority(int priority)
{
_scheduler->scheduleUpdate(this, priority, !_running);
}
// override me
void Node::update(float fDelta)
{
#if CC_ENABLE_SCRIPT_BINDING
if (0 != _updateScriptHandler)
{
//only lua use
SchedulerScriptData data(_updateScriptHandler,fDelta);
ScriptEvent event(kScheduleEvent,&data);
ScriptEngineManager::getInstance()->getScriptEngine()->sendEvent(&event);
}
#endif
if (_componentContainer && !_componentContainer->isEmpty())
{
_componentContainer->visit(fDelta);
}
}
//******CCSchedule.h******
/** Schedules the 'update' selector for a given target with a given priority.
The 'update' selector will be called every frame.
The lower the priority, the earlier it is called.
@since v3.0
@lua NA
*/
template <class T>
void scheduleUpdate(T *target, int priority, bool paused)
{
this->schedulePerFrame([target](float dt){
target->update(dt);
}, target, priority, paused);
}
//******CCNode.cpp******
void Node::scheduleUpdateWithPriorityLua(int nHandler, int priority)
{
unscheduleUpdate();
#if CC_ENABLE_SCRIPT_BINDING
_updateScriptHandler = nHandler;
#endif
_scheduler->scheduleUpdate(this, priority, !_running);
}
void Node::unscheduleUpdate()
{
_scheduler->unscheduleUpdate(this);
#if CC_ENABLE_SCRIPT_BINDING
if (_updateScriptHandler)
{
ScriptEngineManager::getInstance()->getScriptEngine()->removeScriptHandler(_updateScriptHandler);
_updateScriptHandler = 0;
}
#endif
}
//******CCLuaEngine.cpp******
int LuaEngine::handleScheduler(void* data)
{
if (NULL == data)
return 0;
SchedulerScriptData* schedulerInfo = static_cast<SchedulerScriptData*>(data);
_stack->pushFloat(schedulerInfo->elapse);
int ret = _stack->executeFunctionByHandler(schedulerInfo->handler, 1);
_stack->clean();
return ret;
}
CCNode另外一个方法scheduleUpdateWithPriorityLua(int nHandler, int priority);看名字就知道是给Lua调用的,传入回调函数以及可以自定义定时器的优先级。此方法调用之前会调用unscheduleUpdate(),之前已经有的定时器会被删除。因此Lua使用时候一个cc.Node的对象只能拥有一个每帧调用的定时器回调方法。上面说到想LuaEngine发送一个名字叫做kScheduleEvent的事件,我们查看CCLuaEngine.cpp的handleScheduler发现,这个方法实际就是想lua虚拟栈压入dt这个参数,然后调用Lua的回调函数传入dt参数。
function Node:onUpdate(callback)
self:scheduleUpdateWithPriorityLua(callback, 0)
return self
end
Node.scheduleUpdate = Node.onUpdate
NodeEx.lua文件可以看出onUpdate以及scheduleUpdate都是调用self:scheduleUpdateWithPriorityLua方法且默认优先级是0的。看如下测试代码:
-- 测试代码
local MyMainTest = class("MyMainTest",function ()
return cc.Node:create()
end)
function MyMainTest:ctor()
self:onUpdate(handler(self,self.updateOne))
self:scheduleUpdate(handler(self,self.updateTwo))
self:scheduleUpdateWithPriorityLua(handler(self,self.updateThree),-1)
end
function MyMainTest:onEnter()
MyMainTest.super.onEnter(self)
end
function MyMainTest:updateOne(dt)
print("===>updateOne",dt)
end
function MyMainTest:updateTwo(dt)
print("===>updateTwo",dt)
end
function MyMainTest:updateThree(dt)
print("====>updateThree",dt)
end
输出结果如下,不难看出,重复定义了3个每帧调用的定时器,结果只有最后一个生效。
[LUA-print] ====>updateThree 0.016666667535901
[LUA-print] ====>updateThree 0.025760000571609
[LUA-print] ====>updateThree 0.025115000084043
[LUA-print] ====>updateThree 0.025940999388695
[LUA-print] ====>updateThree 0.025885999202728
[LUA-print] ====>updateThree 0.02535199932754
...
先看c++代码
#if CC_ENABLE_SCRIPT_BINDING
unsigned int Scheduler::scheduleScriptFunc(unsigned int handler, float interval, bool paused)
{
SchedulerScriptHandlerEntry* entry = SchedulerScriptHandlerEntry::create(handler, interval, paused);
_scriptHandlerEntries.pushBack(entry);
return entry->getEntryId();
}
void Scheduler::unscheduleScriptEntry(unsigned int scheduleScriptEntryID)
{
for (ssize_t i = _scriptHandlerEntries.size() - 1; i >= 0; i--)
{
SchedulerScriptHandlerEntry* entry = _scriptHandlerEntries.at(i);
if (entry->getEntryId() == (int)scheduleScriptEntryID)
{
entry->markedForDeletion();
break;
}
}
}
注意参数paused不要填写true,因为没有方法提供给你重新启动定时器。
- @param handler The Lua callback function id.
lua注册的回调函数handler id- @param interval The interval to schedule the callback. If the value is 0, then the callback will be scheduled every frame.
定时器的周期,如果设置为0则是每帧调用。Cocos2dx官方推荐使用上述方法scheduleUpdate代替。- @param paused Whether or not to pause the schedule.
定时器可以自定义是否停止,如果是true,定时器不会生效 ,除非手动条用resumeTarget。- @param key The key to identify the callback function, because there is not way to identify a
local MyMainTest = class("MyMainTest",function ()
return cc.Node:create()
end)
function MyMainTest:ctor()
self:enableNodeEvents()
end
function MyMainTest:onEnter()
self:onUpdate(handler(self,self.updateOne))
self:scheduleUpdate(handler(self,self.updateTwo))
self:scheduleUpdateWithPriorityLua(handler(self,self.updateThree),-1)
self.entryId_1 = cc.Director:getInstance():getScheduler():scheduleScriptFunc(handler(self,self.updateFour),0.1,false)
self.entryId_2 = cc.Director:getInstance():getScheduler():scheduleScriptFunc(handler(self,self.updateFive),0.1,false)
end
function MyMainTest:updateOne(dt)
print("===>updateOne",dt)
end
function MyMainTest:updateTwo(dt)
print("===>updateTwo",dt)
end
function MyMainTest:updateThree(dt)
print("====>updateThree",dt)
end
function MyMainTest:updateFour(dt)
print("====>updateFour",dt,self.entryId_1)
end
function MyMainTest:updateFive(dt)
print("====>updateFive",dt,self.entryId_2)
end
function MyMainTest:onExit()
cc.Director:getInstance():getScheduler():unscheduleScriptEntry(self.entryId_1)
cc.Director:getInstance():getScheduler():unscheduleScriptEntry(self.entryId_2)
end
不难看出,通过scheduleScriptFunc自定义的定时器可以定义多个同时存在,而每帧调用(update selector)则只能仅存1个。 输出结果如下:
[LUA-print] ====>updateThree 0.15378099679947
[LUA-print] ====>updateThree 0.026101000607014
[LUA-print] ====>updateThree 0.025371000170708
[LUA-print] ====>updateThree 0.024353999644518
[LUA-print] ====>updateThree 0.025475025177002
[LUA-print] ====>updateFive 0.10000000149012 4
[LUA-print] ====>updateFour 0.10000000149012 3
...
以上就是本人对CCScheduler的理解,如果有错误请指出。官方推荐使用update selector方式,避免更多使用custom selectors,因为update selector会更快,内存消耗更小些。