14
Help us understand the problem. What are the problem?

More than 5 years have passed since last update.

posted at

updated at

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

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

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

それでもっと軽いの探してみたら、Boost.Asioのコルーチンマクロというのを見つけました。
なんとヘッダファイル2つのみ。インクルードしている外部ファイルも無し。
単にソースフォルダに放り込んでコンパイルすればOK。

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

使い方

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のコルーチンマクロと、制作者様のブログのA potted guide to stackless coroutinesを参照してください。

注意点

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

サンプル

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

スクリーンショット 2014-09-02 22.51.07.png

#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使ってます。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
14
Help us understand the problem. What are the problem?