時間のかかる処理はユーザーにストレスを与えます。 できれば上手く工夫をして、重さを感じなくさせるよう工夫することが必要です。 今回は、そのような時に利用できる「マルチスレッド処理」についてcocos2dxで実装をしていきたいと思います。
マルチスレッドとは
プログラムの処理は通常「メインスレッド」で行われます。 コードの書かれた順番に処理を行いますので、例えば、凄く重い処理があれば、その処理が終わらないと次の処理へと移れません。 この部分で、オブジェクトの動きが止まったり、音楽が遅れたりする原因となります。
そこで用意されているのが「メインスレッド」とは別の「スレッド」で処理を行う方法です。 これが「マルチスレッド処理」と言われています。
Xcodeでのスレッドの確認方法
プロジェクトをビルドしている時に、[CPU]を調べると どの「スレッド」を利用しているかわかります。
通常の「メインスレッド」のみ。
「マルチスレッド」利用時。(Thread7がマルチスレッド)
マルチスレッドの注意点
マルチスレッドを使えば、非同期で処理が行えるので、画面の表示に時間がかかることは無くなります。 しかし、処理自体が軽くなることではないのでCPUに負担をかけます。 アプリが落ちたりする原因にもなるので、注意下さい。
マルチスレッドを実装する
今回は「cocos2dxのレシピ本」を参考にしています。 実践で利用する際には、以下の2点を考えて下さい。
- メインスレッドで利用する変数
- マルチスレッドでどのような処理を行うか
まずは、マルチスレッドの処理です。 今回は[POSIXスレッド]を利用しています。 [c]
include <semaphore.h>
include <unistd.h>
if CC_TARGET_PLATFORM == CC_PLATFORM_IOS
define MY_USE_NAMED_SEMAPHORE 1
else
define MY_USE_NAMED_SEMAPHORE 0
endif
if MY_USE_NAMED_SEMAPHORE
define MY_SEMAPHORE "cocosThreadSample.sem"
else
static sem_t s_semaphore;
endif
static sem_t* s_pSemaphore = NULL; static pthread_mutex_t s_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_t s_thread;
struct threadData{
//スレッド作業データ格納配列(__String型)
cocos2d::Vector<cocos2d::__String*> vec;
};
static threadData *s_threadData;
//マルチスレッド処理 void thread_function(void arg){
threadData *td = (threadData*)arg;
for (int i = 0; i < 20; i++) {
cocos2d::__String *str = new cocos2d::__String();
str->initWithFormat("%d",i);
//0.5秒に一度データを投入
pthread_mutex_lock(&s_mutex);
td->vec.pushBack(str);
pthread_mutex_unlock(&s_mutex);
usleep(500000); // wait 0.5sec
}
sem_post(s_pSemaphore);
return NULL;
}
[/c]
以下が「メインスレッド」で利用する際の処理の一例です。 「マルチスレッド」で格納した数字をラベルで順番に表示をさせています。 [c] void MultiThreadLayer::startMultiThread(){
if MY_USE_NAMED_SEMAPHORE
s_pSemaphore = sem_open(MY_SEMAPHORE, O_CREAT,0644,0);
if (s_pSemaphore == SEM_FAILED) {
return;
}
else
if (sem_init(&s_semaphore, 0, 0) < 0) {
return;
}
s_pSemaphore = &s_semaphore;
endif
//スレッド用の作業データを作成
s_threadData = new threadData();
//マルチスレッド起動
pthread_mutex_init(&s_mutex, NULL);
pthread_create(&s_thread, NULL,thread_function, (void*)s_threadData);
pthread_detach(s_thread);
//スレッド表示ルート開始
//this->schedule(schedule_selector(MultiThreadLayer::update_therad_progress));
}
/*
メインスレッドのUpdate(ルート)
*/
void MultiThreadLayer::update_therad_progress(float delta){
std::string str = "";
//スレッドからデータを取得
pthread_mutex_lock(&s_mutex);
Vector<__String*> _ver = s_threadData->vec;
for (int i =0; i < _ver.size(); i++) {
//マルチスレッドで格納した__Stringを利用
__String *ccstr = (__String*)_ver.at(i);
str += ccstr->getCString();
}
pthread_mutex_unlock(&s_mutex);
//ラベル表示
auto _label = (Label*)this->getChildByTag(1);
const std::string _current = _label->getString();
if (strcmp(_current.c_str(), str.c_str()) != 0) {
CCLOG("Update : %s",_current.c_str());
_label->setString(str);
}
//マルチスレッド終了時の処理
int ret = sem_trywait(s_pSemaphore);
if (ret == 0) {
//Updateを止める
this->unschedule(schedule_selector(MultiThreadLayer::update_therad_progress));
s_threadData->vec.clear(); // removeAllObject
delete s_threadData;
s_threadData = NULL;
if MY_NAMED_SEMAPHORE
sem_close(s_pSemaphore);
sem_unlink(MY_SEMAPHORE);
else
sem_destroy(s_pSemaphore);
endif
}
} [/c]
最後に
cocos2dx3.xを利用しているのですが、iOS/Android共にマルチスレッドが実装できました。 ただ、実践でどのように利用するかが見えてきません。 よくあるのが「大量の画像を配置する」「大きいサイズの画像を配置する」など画像と連動して利用するのがいいのかもしれませんね。