Javaでもコルーチン使いたいですね。
特にゲームで欲しくなるのですが、キャラの挙動を手続き的に記述したい場合に便利です。
例えば60フレーム移動して、その後10フレーム毎に玉を撃つのを6回繰り返して…といった処理を逐次処理的に組むなら以下のようになります。
while (true) {
for ( int i=0; i<60; i++ ) {
move();
}
for ( int i=0; i<60; i++ ) {
if ( i% 10 ==0 ) shot();
}
}
しかしながらゲームの場合、複数のキャラを同時に動かさないといけないので1フーレムに一回処理メソッドが呼ばれるような形にせざるをえなくなります。
こうなるとフラグの管理などが面倒ですね。
private count_;
void action() {
if ( count_ % 120<60 ) {
move();
}else{
if ( count_% 10 ==0 ) shot();
}
count_++;
}
スレッドを使えばいいのですが、それはそれで面倒ですし。
そこでコルーチンを使うと最初の例を以下のように書けます。
while (true) {
for ( int i=0; i<60; i++ ) {
move();
yield();
}
for ( int i=0; i<60; i++ ) {
if ( i% 10 ==0 ) shot();
yield();
}
}
yield();が呼ばれるたびに一旦処理がはなれ呼び出し元にもどります。
再度呼ばれる際は、yield();の次の行から処理が継続されます。
便利!
とはいえJavaにはコルーチンの標準の実装がないので作ってみました。
実装は結局スレッドと同期を使ってます。
コルーチンとは:コルーチン
#ソース
Corouchan.java
/**
* pseudo Coroutine for Java "Corouchan"
* コルーチンもどき クラス
* 派生してaction()をオーバーライドして使ってね。
* @author F/T
*
* The MIT License (MIT)
*
* Copyright (c) 2011 F/T
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
package com.dokokano.shotchange.game.common;
import android.util.Log; // for Log class ※Android以外で使用する場合は除外してください
public class Corouchan implements Runnable{
Object lock_ = new Object();
Object returnLock_ = new Object();
Thread thread_;
boolean exit_ = false;
boolean exitDone_ = false;
// スレッドスタート
public void start() {
Log.d("corouchan", "start()");
thread_ = new Thread(this);
thread_.start();
// スレッドがスタートするまで待つ (returnLock_がnotifyされるのを待つ)
synchronized (returnLock_) {
try {
returnLock_.wait(1000);
} catch (InterruptedException e) {
// run() 直後のreturnLock_.notifyAll();が早過ぎる場合はデッドロックになるかも。タイムオーバーで回避
Log.d("corouchan", "InterruptedException@start");
e.printStackTrace();
}
}
}
// スレッド終了 (終了要求を出す。即座には停止しない)
// ※これ呼んでスレッド終了しないとアプリが終わらないよ!
public void end() {
Log.d("corouchan", "end()");
exit_ = true;
synchronized (lock_) {
lock_.notifyAll();
}
}
// 終了要求がきているか?
public boolean isExit() {
return exit_;
}
// 終了を完了したか?
public boolean isExitDone() {
return exitDone_;
}
@Override
public void run() {
Log.d("corouchan", "run()");
// 開始処理
// 呼び出し元のwaitを解除
synchronized (returnLock_) {
returnLock_.notifyAll();
}
// next()が呼ばれるまでwait
synchronized (lock_) {
try {
lock_.wait();
} catch (InterruptedException e) {
Log.d("corouchan", "InterruptedException@run");
e.printStackTrace();
}
}
// コルーチン本体(ユーザー作成部)を呼ぶ
action();
// 終了処理
exitDone_ = true;
synchronized (returnLock_) {
returnLock_.notifyAll();
}
}
/**
* ユーザーアクション
* 派生クラスでここにコルーチンで実行したい処理を記述
* yiedl() を呼ぶと呼び出し元に戻る
*/
public void action() {
Log.d("corouchan", "POS1"); // なんらかの処理
if ( yield() ) return; // yield()の戻り値がtrueの場合は中断要求なので、終了する。
Log.d("corouchan", "POS2");
if ( yield() ) return;
Log.d("corouchan", "POS3");
if ( yield() ) return;
Log.d("corouchan", "POS4");
if ( yield() ) return;
Log.d("corouchan", "POS5");
}
/**
* コルーチンの処理を一時停止して、呼び出し元に戻る
* @return 終了要求 trueの場合はコルーチンの処理を中断してください。
*/
public boolean yield() {
Log.d("corouchan", "yield()");
synchronized (returnLock_) {
returnLock_.notifyAll();
}
try {
synchronized (lock_) {
lock_.wait(1000); // ※呼び出し元にもどった直後next()を呼ばれると、next()内のnotify()の後に、wait()が呼ばれてデッドロックになるかもしれない
}
} catch (InterruptedException e) {
Log.d("corouchan", "InterruptedException@yield");
e.printStackTrace();
}
Log.d("corouchan", "yield() - exit");
return exit_;
}
/*
* コルーチンの次の処理を実行
* @return ture:成功 false:失敗(すでにコルーチンは終了しているなど)
*/
public boolean next() {
Log.d("corouchan", "next()");
if ( exit_ || exitDone_ ) return false; // すでに終了している
try {
synchronized (returnLock_) {
synchronized (lock_) {
lock_.notifyAll();
}
// コルーチン内で yield()が呼ばれるまでwait
returnLock_.wait();
}
Log.d("corouchan", "next() - exit true");
return true;
} catch (InterruptedException e) {
Log.d("corouchan", "InterruptedException@next");
e.printStackTrace();
}
Log.d("corouchan", "next() - exit false");
return false;
}
}
#呼び出し例
Corouchan c = new Corouchan(); // インスタンス作成
c.start(); // 処理開始(1回めのaction()呼び出しが発生)
for ( int i=0; i<10; i++ ) {
c.next(); // next()を呼ぶとコルーチンに制御が戻る
// i==4 の段階で「pos5」が出力され、以降はなにも起きない
}
c.end(); // 終了
#既知のバグ
next() メソッドを短い間隔で呼ぶとたま~にデッドロックします。
だめじゃん!
ゲームで使うように60fpsくらいの間隔で呼ぶなら平気っぽいですけど…