Cocos2dx-lua之CCScheduler源码分析

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
如果游戏设置每秒钟60帧动画,则采用这种回调1秒钟会调用60次回调函数。(lua通过此函数设置帧数 cc.Director:getInstance():setAnimationInterval(1/60))。C++中的函数scheduleUpdate(T *target, int priority, bool paused)函数传入调用的对象,以及优先度priority,是否 停止paused,如果true则需要手动调用resumeTarget(void *target);传入调用对象才有效果。优先级priority可以是负数,越小优先度越高,会被先调用。代码如下:
    /** 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 after delay seconds. But it will only affect first schedule. After first schedule, the
    delay time is determined by interval.
    定时器可以自定义延迟多少时间执行,这个参数只对首次执行定时器时候生效。
  • @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中方法scheduleUpdate()默认优先级为0的每帧调用的。在CCSchedule.h中看见scheduleUpdate实质上是每帧对用调用者提供的update方法,看看CCNode的update方法中,如果_updateScriptHandler不为0,则每帧调用会通过LuaEngine发送一个kScheduleEvent的事件。那么_updateScriptHandler哪里来?继续看下面方法
	//******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如何使用?
project/src/cocos/framework/extends/NodeEx.lua文件中定义了3个方法可以使用。
  • 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)
  • @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
注意参数paused不要填写true,因为没有方法提供给你重新启动定时器。
  • 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会更快,内存消耗更小些。

猜你喜欢

转载自blog.csdn.net/Suarez1987/article/details/108790806