Usually,an application is separated into two parts: foreground and backgroud,where foreground is charge of drawing UI and doing UI flow controlling,backgroud is responsible for processing data logica. The foreground is named main UI thread and the backgroud layer is usually a worker thread.
But in cocos2dx,all the operations are done in the UI thread via CCDisplayLinkDirector::mainLoop in CCDirector.cpp. To avoid blocking UI thread,we can use the two methods to achieve it:
> Using timer mechanism,i.e.,CCScheduler.
With this way,the operation will be execute until the timeout is triggered,but it is still run in UI thread. So when a blocking call is invoked,the UI thread is still be blocked.
> Using worker thread to do the blocking operation,such as reading data from network etc.
When there is no data comes,the worker thread will be blocked,and it will spend no cpu times. Since it will not never blocking UI thread,it is the prefered method.
Cocos2dx doesn't provide an implementation for multithreading,this article provides a way to achieve multithreading in cocos2dx. It will need to change several source files in cocos2dx framework.
The goal of this implementation is that:
> Using pthread which is supported in most of platforms,such as linux,windows,even ios,android etc. (cocos2dx/httpclient is using pthread)
> Easy for any CCNode (also including its subclasses) who wants to handle the messages to register/unregister the handler.
> Provides a message queue for each worker thread for communicating with other threads
The main class design is as below:
class EXTCCHandler {
public:
// Max len of the name is 32 char
EXTCCHandler(const char *pName);
virtual ~EXTCCHandler();
// it will suspend current thread if there is no message in queue
static bool hasMessage(const EXTCCHandler * const handler);
// it will proccess messages in queue and
// return till all the messages in queue are processed
static void processMessage(const EXTCCHandler * const handler);
// register a handler to current hander thread
// when a message comes,
// "processMessage" will dispatch msg to each registered handler
void registerHandler(const CCObject *pTarget,const EXTCCHandleMessage &handler);
// unregister a handler
void unregisterHandler(
const CCObject *pTarget,
const EXTCCHandleMessage &handler);
// post a message to current handler thread,it's async call
void postMessage(const EXTCCMessage &msg);
};
class EXTCCHandlerThread : public EXTCCHandler {
~EXTCCHandlerThread();
// create a handler thread,Max len of the thread name is 32 char
static EXTCCHandlerThread *create(const char *pThreadName);
protected:
EXTCCHandlerThread(const char *pThreadName);
private:
static void *loop(void *pArgs);
Some changes in Cocos2dx framework:
> make folder of "HandlerThread" under "cocos2d-x-2.x.x/cocos2dx/platform"
> copy the source code to "cocos2d-x-2.x.x/cocos2dx/platform/HandlerThread"
> find CCDirector.cpp and CCDirector.h (which locate in cocos2dx/) andadd static variable in CCDirector.cpp (below "static CCDisplayLinkDirector *s_SharedDirector = NULL;")
static EXTCCHandler *s_MainLoopHandler = NULL;
> add static function in class CCDirector
CCDirector.h:add below class declaration before
NS_CC_BEGIN
:
class EXTCCHandler;
CCDirector.h: (add below statement blow "static CCDirector* sharedDirector(void);"
static EXTCCHandler *mainLoopHandler(void);
CCDirector.cpp: (add below implementation below static method "CCDirector* CCDirector::sharedDirector(void)"
EXTCCHandler *CCDirector::mainLoopHandler(void) {
if (s_MainLoopHandler == NULL) {
s_MainLoopHandler = new EXTCCHandler("MainUILoop");
}
return s_MainLoopHandler;
}
CCDirector.cpp: add below statements into CCDisplayLinkDirector::mainLoop function
void CCDisplayLinkDirector::mainLoop(void)
{
if (m_bPurgeDirecotorInNextLoop)
{
m_bPurgeDirecotorInNextLoop = false;
purgeDirector();
}
else if (! m_bInvalid)
drawScene();
// release the objects
CCPoolManager::sharedPoolManager()->pop();
/// { added for supporting handler thread ///
if (s_MainLoopHandler) {
EXTCCHandler::processMessage(s_MainLoopHandler);
/// added for supporting handler thread } ///
}
> add header file includes in "cocos2d-x-2.x.x/cocos2dx/include/cocos2d.h"
// Handler thread
#include "platform/HandlerThread/EXTCCHandlerThread.h"
In Xcode,we MUST run "install-templates-xcode.sh" again to update the templates in Xcode:
in console,run below command:
sudo ./install-templates-xcode.sh -f
NOTE: because we didn't udpate the templates project file in cocos2dx templates for Xcode,so on every time we create Xcode cocos2dx project,we MUST copy the folder of "HandlerThread" into the created project folder (xxx is the created project name): "xxx//libs/cocos2dx/platform/",and add these files in XCode project explorer manually.
In Android,we MUST update Android.mk in "cocos2d-x-2.x.x/cocos2dx",add below source files in LOCAL_SRC_FILES:
\
platform/HandlerThread/EXTCCHandler.cpp \
platform/HandlerThread/EXTCCHandlerThread.cpp
Only XCode and Android platform I've tried,if on other platforms,you must do similar changes by yourself.
How to use:
0. In UI thread,we can register a handler to process message in UI thread.
bool HelloWorldScene::handleMessageInUIThread(const EXTCCMessage &msg) {
// implementations
// return true if this handler has processed this msg,otherwise,false is returned
switch (msg.msgId) {
case 2:
break;
default:
return false;
}
return true;
}
// register this Handler to UI Threader
CCDirector::mainLoopHandler()->registerHandler(this,
(EXTCCHandleMessage)& HelloWorldScene:: handleMessageInUIThread);
(EXTCCHandleMessage)& HelloWorldScene:: handleMessageInUIThread);
1. Creating worker thread:
EXTCCHandlerThread *g_workThread = EXTCCHandlerThread::create("WorkThreadName");
2. Register a message handle to this created handler thread (function of handleMessage will be run in g_workThread):
bool
handleMessageInWorkerThread(const EXTCCMessage &msg) {
case 1:
g_workThread->registerHandler(this,
(EXTCCHandleMessage)& handleMessageInWorkerThread);
(EXTCCHandleMessage)& handleMessageInWorkerThread);
3. When we get a handlerThread handler,we can post message in UI thread or other worker thread to it for processing:
EXTCCMessage msg;
msg.msgId = 1;
msg.msgData1 = NULL;
// "msg" will be processed by "handleMessageInWorkerThread" running in g_workThread
// "msg" will be processed by "handleMessageInWorkerThread" running in g_workThread
g_workThread->postMessage(msg);
4. In a worker thread,we can send message to UI thread for updating UI:
msg.msgId = 2;
// "msg" will be processed by "handleMessageInUIThread" in UI thread
CCDirector::mainLoopHandler()->postMessage(msg);
5. If the object doesn't need to proccess message anymore,you MUST unregister the handler to avoid memory leak:
CCDirector::mainLoopHandler()->unregisterHandler(this,
g_workThread->unregisterHandler(this,0)">handleMessageInWorkerThread);
g_workThread->unregisterHandler(this,0)">handleMessageInWorkerThread);
Notice in using multithreads in cocos2dx:
> In worker thread,it CAN NOT update UI element at any time. The corrected way is posting a message which contains the updating data to UI thread for doing updates. (refer to:
http://www.cocos2d-x.org/projects/cocos2d-x/wiki/How_to_use_pthread#How-to-use-pthread)
> This handler thread doesn't contains Autorelease mechanism provided by Cocos2dx,so
it's user's responsibility to do release on msgData in EXTCCMessage.
> There is no limits on HanderThread's message queue. It will grow double length (the default length is 16) if there is no slot for new coming message
> Because Android didn't provide a cancel call for pthread (pthread_cancel,pthread_testcancel),so in this implementation,no EXTCCHandlerThread::cancel is provided. If you want this API,pls google it,there is some ways to achieve it.
> If a message didn't be processed by any handler,a log will be output:
###Message [msgId] is NOT processed by thread [ThreadName]!!!
Source code:
Right click below picture to save it to your PC,rename suffix name to rar,and open it with winRar:
Source code:
Right click below picture to save it to your PC,rename suffix name to rar,and open it with winRar: