Cocos2dx-lua的CCScheduler源码分析
本文通过cocos2dx的CCScheduler源码分析,介绍了CCScheduler是什么,以及在cocos2d-lua如何使用CCScheduler。
文章目录
前言
CCScheduler是什么?CCScheduler是可以理解成一个定时器,周期性执行一次任务(回调函数)。Cocos2d官方不建议游戏逻辑中使用系统的Timer。如果使用系统的定时器,可能出现游戏逻辑混乱,达不到预期效果。可以理成CCScheduler缺乏时间精度,系统定时器比较精确计时。提示:以下是本篇文章正文内容,下面案例可供参考
一、CCScheduler的分类
CCScheduler一共有两种类型的回调函数callbacks (selectors),一种是每帧调用(update selector)类型,这种回调函数用户可以自定义执行回调函数的优先级(priority);另外一种就是用户自定义回调函数(custom selector),可以每帧调用用户自定义的回调函数,或者自定义的一个时间周期(interval)调用。
1.每帧调用(update selector)
- -scheduleUpdate
/** 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);
}
- - unscheduleUpdate
取消定时器,传入调用者的对象指针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);
- - resumeTarget
重新开始定时器,传入调用者的对象指针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);
- - pauseTarget
手动停止定时器,传入调用者的对象指针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);
2.自定义定时器(custom selector)
自定义的定时器可以在每个‘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<>的唯一标记。
- 周期性的“无限”次数调用回调函数,代码如下
- 2)schedule(const ccSchedulerFunc& callback, void target, float interval, bool paused, const std::string& key)*
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<>的唯一标记。
- 可以延迟执行,可以停止,可以选择执行次数,周期内执行回调函数。跟方法1的区别在于回调函数使用的是函数指针,因此,参数不需要传入key作为唯一标识。代码如下
- 3)schedule(SEL_SCHEDULE selector, Ref target, float interval, unsigned int repeat, float delay, bool paused)*
void schedule(SEL_SCHEDULE selector, Ref *target, float interval, unsigned int repeat, float delay, bool paused);
- 周期性的“无限”次数调用回调函数,跟方法2的区别在于回调函数使用的是函数指针,因此,参数不需要传入key作为唯一标识。代码如下
- 4)schedule(SEL_SCHEDULE selector, Ref target, float interval, 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<void(float)> 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;
二、Lua中如何使用?
1.每帧调用(update selector)的使用
先看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);
}
- - scheduleUpdate()
//******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;
}
- - scheduleUpdateWithPriorityLua(int nHandler, int priority)
CCNode另外一个方法scheduleUpdateWithPriorityLua(int nHandler, int priority);看名字就知道是给Lua调用的,传入回调函数以及可以自定义定时器的优先级。此方法调用之前会调用unscheduleUpdate(),之前已经有的定时器会被删除。因此Lua使用时候一个cc.Node的对象只能拥有一个每帧调用的定时器回调方法。上面说到想LuaEngine发送一个名字叫做kScheduleEvent的事件,我们查看CCLuaEngine.cpp的handleScheduler发现,这个方法实际就是想lua虚拟栈压入dt这个参数,然后调用Lua的回调函数传入dt参数。
- Lua如何使用?
- onUpdate(callback)
- scheduleUpdate(callback)
- self:scheduleUpdateWithPriorityLua(callback,priority)
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
...
2.自定义定时器(custom selector)的使用
先看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;
}
}
}
- scheduleScriptFunc(unsigned int handler, float interval, bool paused)
注意参数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
- Lua如何使用?
测试代码如下:
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会更快,内存消耗更小些。