2011/06/27

Processing でマルチスレッド

最近ちょっと重たい記事が続いたので、今日は軽めにいきたいと思います。

Processing 等で大量のリソースを読み込む際、それらの処理をコンストラクタや初期化用メソッドに記述すると、フレームレートが激落ちくんになってしまいます。

例えば Cover Flow なんかも、読み込んだ画像から鏡面反射用のテクスチャを構築するため、多くの画像を表示させる場合は初期化の際のオーバヘッドが高くなってしまいます。

そこで今日は、ロード処理をバックグラウンドで動かし、表向きには『Now Loading...』的なアニメーションをぬるぬる表示させるためのちょっとした Tips をお話ししましょう。
ロード中は、進捗バーが表示されるよ



タイトルで思いっきりネタバレしていますが、ロード用の処理はすべて別スレッドに移す、ただそれだけです。

Processing の公式 API リファレンスをざっと見ても、スレッドに関する記述は見当たらなかったのですが、Processing でもマルチスレッドは一応使えるのです。
boolean isLoading;  // スレッド終了判定フラグ
float   percent;    // 進捗率
PImage  dummy;      // ダミー画像

void setup() {
  size(400, 300);
  isLoading = true;
  Thread t = new Thread(new LoadThread());
  t.start();
}

synchronized void draw() {
  background(255);
  
  if(isLoading) {
    /* ロード中 */
    
    // 進捗バー表示
    int barWidth  = width - 20;
    int barHeight = 10;
    noFill();
    stroke(0, 100);
    rect((width - barWidth) / 2, (height - barHeight) / 2,
         barWidth, barHeight);
    
    noStroke();
    fill(255, 0, 0, 100);
    rect((width - barWidth) / 2, (height - barHeight) / 2,
         percent * (width - 20) / 100, barHeight);
  } else {
    /* ロード後 */
    
    image(dummy, 0, 0);  // 画像表示
  }
}

// バックグラウンド処理はこちら側に書く
class LoadThread implements Runnable {
  public synchronized void run() {
    // 時間かせぎのために、同じ画像を100回読み込む
    long max = 100;
    for(long i = 0; i < max; i++) {
      dummy = loadImage("tekitou.bmp");
      percent = (float)i*100 / max;  // 進捗率更新
    }
    isLoading = false;  // 読み込んだらフラグ更新
  }
}

上記のソースから明らかですが、読み込みの終了判定に、なんとグローバル変数のフラグ isLoading を使っています。

実はこれ、けっこう邪道な方法で、複数のスレッドが isLoading に対して同時にアクセスし、なおかつ値を書き換えた場合、予期せぬ結果をもたらす事があるため、一般的なマルチスレッドプログラミングでは禁じ手とされています。

Java にはスレッド間の同期を取るための様々な機構が用意されており、通常のアプリケーションではむしろそちらを用いるのが無難です。

今回、敢えてグローバルなフラグを用いたかというと、Java に標準搭載されているスレッド同期の機構は、基本的に Processing と相性が悪い(とぼくは思っている)からです。

スレッドの同期というのは、いわゆる『待ち合わせ』ですので、これを使うとロードが終わるまでメインスレッドの処理が止まってしまうのです(つまりアニメーションが走らなくなってしまいます)。

Processing の draw メソッドは、ロード時のフラストレーションを解消するために、別スレッドの状態に拘わらず恒常的に実行し続けなければならないはずですから、これでは本末転倒です。

複数のスレッドが共有資源を同時にいじる』という事態さえ発生しなければ、グローバル変数をスレッド間で共有しても問題はないはずです(たぶん)。

ですので、まず Thread オブジェクトは1つしか作らず、グローバル変数(フラグや PImage オブジェクト等)は単一スレッドのみから書き換えるという約束をします。

これで、ソースの綺麗さはさておき、まともに動くバックグラウンドローディングが完成しました。



で。

これだけだと正直あまり使い物にならんのですが、例によって複数の状態遷移を伴うゲームとかを作るときに真価を発揮するんじゃないかと思っています。

たとえば、『次状態のリソースのロード専用ステート』を作り、ロードが終わり次第、次状態へリンクするような仕様にしてしまえば、『Now Loading...』アニメーションをゲームのステートマシンに組み込む事ができます。

問題はステート間のリソースの引き継ぎですが、この辺で設計したステートマシンはコンストラクタの仕様を拘束する要素が一切入っていないので、ロードしたリソースとそのファイル名との対で HashMap にでもぶち込んで次状態に引き渡せばよいかな、と思います。

しかしマルチスレッド難しい……。

0 件のコメント:

コメントを投稿

ひとことどうぞφ(・ω・,,)