LoginSignup
14
14

More than 5 years have passed since last update.

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

Last updated at Posted at 2014-09-02

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

14
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
14