C++でもコルーチンでメニュー選択


[luaのコルーチンでメニュー選択](http://qiita.com/hiz_/items/69867a55b5be5ef5d14a)を書いた後、メニュー選択なんてそう頻繁に書き換えるもんじゃなかろうにわざわざluaで書いてC++と連携させるの面倒くさいなーC++でコルーチン書けないかなーと思ってコルーチンライブラリ探してました。

C++のコルーチン実装は一番有名なのはBoost:coroutineなんですかね。
でもBoostはコンパイルが遅くなるのでなるべく使いたくない。

それでもっと軽いの探してみたら、[Boost.Asioのコルーチンマクロ](http://faithandbrave.hateblo.jp/entry/20101007/1286432629)というのを見つけました。
なんとヘッダファイル２つのみ。インクルードしている外部ファイルも無し。
単にソースフォルダに放り込んでコンパイルすればOK。

タイトルの通りマクロで実装しているらしく、中身はよく分からないけどswitch文とgoto文で頑張ってコルーチンの機能を再現してるみたいです。
注意点はいくつかあるけど、最低限の機能はあるようで、コルーチンっぽいのを使って楽したいという用途には十分です。

###使い方
[HTTP Server 4](http://www.boost.org/doc/libs/1_42_0/doc/html/boost_asio/examples.html#boost_asio.examples.http_server_4)のboost_asio/example/http/server4/coroutine.hppとboost_asio/example/http/server4/yield.hppのソースをコピーしてローカルに同名のファイルを作成・ペーストすればOKです。

基本的には、coroutineクラスを継承するクラスを作り、適当なメソッドにreenter(this) {〜〜〜}ブロックを記述し、その中にコルーチンにしたい処理を書く感じです。処理をメインルーチンに戻すときはyieldを呼べばOK。yield returnを使えば戻り値も返せます。

詳しい使い方は、さっきの[Boost.Asioのコルーチンマクロ](http://faithandbrave.hateblo.jp/entry/20101007/1286432629)と、制作者様のブログの[A potted guide to stackless coroutines](http://blog.think-async.com/2010/03/potted-guide-to-stackless-coroutines.html)を参照してください。



###注意点
1. コルーチンから呼び出した関数でyieldすることはできません。
1. switchを使っている関係上、変数のスコープが保証されません。使用する変数は全て（インデックスも）コルーチン用クラスのフィールドに持っておくのが安全です。

 
### サンプル
[luaのコルーチンでメニュー選択](http://qiita.com/hiz_/items/69867a55b5be5ef5d14a)と内容はほぼ同じです。
C++を使っていることでやや冗長なコードになっていますが、処理が直線的に書けるというメリットは変わらず。
これが2.5KB足らずのライブラリで実現できるのはお得です。

![スクリーンショット 2014-09-02 22.51.07.png](https://qiita-image-store.s3.amazonaws.com/0/35069/7b087e27-7279-25fb-54ce-86d31ccc62e1.png "スクリーンショット 2014-09-02 22.51.07.png")

~~~cpp
#include "ofApp.h"
#include "yield.hpp"

class Menu;
Menu   *menu;
bool    up, down, button;

class Menu : coroutine {
public:
    Menu(vector<string> items) {
        this->items            = items;
        this->menuIdx          = 0;
        this->y                = cursorY(menuIdx);
        this->selectedMenuItem = "";
    }
    int cursorY(int idx) {
        return 10 + idx * 16;
    }
    
    int operator()(int cursorKey, bool button) {
		// コルーチン
        reenter(this) while(true) {
            // キー入力
            while(true) {
                if(button) {
                    selectedMenuItem = items[menuIdx];
                    i = 0;
                    while(true) {
                        i++;
                        if(i>60)break;
                        yield;
                    }
                    selectedMenuItem = "";
                } else if(cursorKey == 2) {
                    menuIdx = ofWrap(menuIdx-1, 0, items.size());
                    break;
                } else if(cursorKey == 8) {
                    menuIdx = ofWrap(menuIdx+1, 0, items.size());
                    break;
                }
                yield;
            }
            // カーソル移動
            do {
                y = y + (cursorY(menuIdx) - y) * 0.2;
                yield;
            } while(abs(y - cursorY(menuIdx)) >= 2);
            y = cursorY(menuIdx);
        }
    }
    int getY() {return y;}
    int getMenuIdx() {return menuIdx;}
    int getItemNum() { return items.size();}
    string getItem(int i) { return items[i];}
    string getSelectedMenuItem() {return selectedMenuItem;}
private:
    int menuIdx;
    float y;
    vector<string> items;
    string  selectedMenuItem;
    int i;
};




//--------------------------------------------------------------
void ofApp::setup(){
    ofBackground(ofColor(0));
    ofSetFrameRate(60);
    
    // メニュー初期化
    vector<string> menuItems;
    menuItems.push_back("Start");
    menuItems.push_back("Continue");
    menuItems.push_back("Shutdown");
    menu = new Menu(menuItems);
}

//--------------------------------------------------------------
void ofApp::update(){
    // キー入力取得
    int cursorKey = 5;
    if(down) cursorKey = 8;
    if(up)   cursorKey = 2;
    
    // コルーチン再開
    (*menu)(cursorKey, button);
    
}

//--------------------------------------------------------------
void ofApp::draw(){
    // メニュー描画
    ofSetColor(122, 255, 255, 50);
    ofRect(20, menu->getY(), 200, 16);
    ofSetColor(255,255,255,255);
    for(int i=0;i<menu->getItemNum();i++) {
        ofDrawBitmapString(menu->getItem(i), 20, 22 + i * 16);
    }
    ofPushMatrix();
    ofTranslate(20, 240);
    ofDrawBitmapString(menu->getSelectedMenuItem(), 0,0);
    ofPopMatrix();
}

//--------------------------------------------------------------
void ofApp::keyPressed(int key){
    if(key == OF_KEY_DOWN)down   = true;
    if(key == OF_KEY_UP  )up     = true;
    if(key == 'z'        )button = true;
}

//--------------------------------------------------------------
void ofApp::keyReleased(int key){
    if(key == OF_KEY_DOWN)down   = false;
    if(key == OF_KEY_UP  )up     = false;
    if(key == 'z'        )button = false;
}

//--------------------------------------------------------------
void ofApp::mouseMoved(int x, int y ){

}

//--------------------------------------------------------------
void ofApp::mouseDragged(int x, int y, int button){

}

//--------------------------------------------------------------
void ofApp::mousePressed(int x, int y, int button){

}

//--------------------------------------------------------------
void ofApp::mouseReleased(int x, int y, int button){

}

//--------------------------------------------------------------
void ofApp::windowResized(int w, int h){

}

//--------------------------------------------------------------
void ofApp::gotMessage(ofMessage msg){

}

//--------------------------------------------------------------
void ofApp::dragEvent(ofDragInfo dragInfo){ 

}

~~~

画面描画には[OpenFrameworks](http://openframeworks.jp/)使ってます。