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を参照してください。
注意点
- コルーチンから呼び出した関数でyieldすることはできません。
- switchを使っている関係上、変数のスコープが保証されません。使用する変数は全て(インデックスも)コルーチン用クラスのフィールドに持っておくのが安全です。
サンプル
luaのコルーチンでメニュー選択と内容はほぼ同じです。
C++を使っていることでやや冗長なコードになっていますが、処理が直線的に書けるというメリットは変わらず。
これが2.5KB足らずのライブラリで実現できるのはお得です。
# 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使ってます。