GDCのebuild書いたよ

id:Akiva:20101219#1292793625 [火星の月に魅せられて1]
でD言語について書きかけてますが、まだ本格採用は決まってません。
とにかく色々試してみないと何も決められない訳ですが、
うちの環境では64bit版のGentoo Linuxをメインに開発してて、
どうにもD言語はまだ64bitへの正式対応をしていない訳ですよ。
というかこれが最近D言語に触れる人が減った理由かなぁ。


本家の公式コンパイラであるdmdはバージョン1でもバージョン2でも64bitのコードを吐いたりリンクできなくて、
バージョン2の仕様が決まり次第、一気に対応するんじゃないかと噂されてますが、いつになることやら。


本家コンパイラの他にも処理系が幾つか存在していて、

  • GDC : GCCを利用したフロントエンドで、開発スナップショットはD2.0に対応。
  • LDC : LLVMを利用したフロントエンドで、D1.0の対応途中。
  • .NET版D言語コンパイラ : 名前のとおり.NET上で動かすらしいけど本家ページで公式リリース無し。

うーん、ぶっちゃけどれも不完全なまま一年以上開発止まってて、使っていくには幸先不安です。
GDCが内部でオープンDMD使って完成度も高く、C言語との連携もしやすく64bitにも対応してたみたいですが、
公式ページを見ると2007年でリリース止まってて、
公式Portageでもgcc-4.1.2を最後にして、GCCのD言語対応が終了しています。


今使ってるGCCのバージョンが4.4.4で、かなり古いけど試しに4.1.2のD対応版をインストールしてみようとしたけど、
どうにもビルドに失敗する。
もしインストールに成功したとしても、4.1以前と4.2以降ではABI仕様が大幅に変わってて、
共存させるのは色々と面倒くさい。
素直にdmdバージョン2の開発が終わるのを待つしか無いのか…と思いながらも色々調べたら、
GDCの後継プロジェクトがちゃんと存在するようです。


https://bitbucket.org/goshawk/gdc/wiki/Home


今までDavid Friedman氏がSF.net上で開発したまま居なくなってしまったのを、
最後の開発スナップショットをベースにVincenzo Ampolo氏が手を加えて、
最新のGCCでも動作するように修正され、D2.0でもちゃんと動作するようになってるみたいです。
まだ公式のリリースが無くてmercurialレポジトリからGCCにパッチを当てて動かすしか無いんですが、
公式のD言語セットアップパッチがリリースされれば、大手のパッケージ管理システムでも捕捉されると思います。


とりあえず、64bitでまともに動かせる現状で唯一の選択肢である為、
いち早く動作を検証すべくebuildファイルを作ってみました。Gentooの中の人にしか役に立ちませんけど。
/usr/portage/sys-devel/gcc/gcc-4.4.4-r2.ebuildに手を加えたので、差分だけ公開します。

※ (修正と追記)
 記事の掲載初期のコードではsys-devel/gdcというパッケージ名で公開していましたが、
 同バージョン系列(4.4どうし等)のgccと共存させようとするとファイルの衝突エラーが出る為、
 そのままsys-devel/gccというパッケージ名で既存のパッケージを置き換える仕様に変更しました。
 本来は最新レポジトリを取り込んで安定版扱いはしないのが習わしですが、利便性重視でこうしました。
--- /usr/portage/sys-devel/gcc/gcc-4.4.4-r2.ebuild	2010-11-29 23:07:34.000000000 +0200
+++ /var/lib/layman/myport/sys-devel/gcc/gcc-4.4.4-r2.ebuild	2010-12-24 21:31:55.019000100 +0200
@@ -19,14 +19,14 @@
 SSP_UCLIBC_STABLE=""
 #end Hardened stuff
 
-inherit toolchain
+inherit toolchain mercurial
 
 DESCRIPTION="The GNU Compiler Collection"
 
 LICENSE="GPL-3 LGPL-3 || ( GPL-3 libgcc libstdc++ gcc-runtime-library-exception-3.1 ) FDL-1.2"
 KEYWORDS="alpha amd64 arm hppa ia64 ~mips ppc ppc64 s390 sh sparc x86 ~x86-fbsd"
 
-IUSE=""
+IUSE="d d2"
 
 RDEPEND=">=sys-libs/zlib-1.1.4
 	>=sys-devel/gcc-config-1.4
@@ -56,6 +56,7 @@
 		nls? ( sys-devel/gettext )
 	)"
 DEPEND="${RDEPEND}
+	dev-vcs/mercurial
 	test? ( >=dev-util/dejagnu-1.4.4 >=sys-devel/autogen-5.5.4 )
 	>=sys-apps/texinfo-4.8
 	>=sys-devel/bison-1.875
@@ -70,6 +71,8 @@
 fi
 
 src_unpack() {
+	use d && mercurial_fetch https://goshawk@bitbucket.org/goshawk/gdc gdc
+
 	gcc_src_unpack
 
 	use vanilla && return 0
@@ -79,6 +82,15 @@
 	[[ ${CHOST} == ${CTARGET} ]] && epatch "${FILESDIR}"/gcc-spec-env.patch
 
 	[[ ${CTARGET} == *-softfloat-* ]] && epatch "${FILESDIR}"/4.4.0/gcc-4.4.0-softfloat.patch
+
+	if use d
+	then
+		cd ${S}
+		ln -s ../d gcc/d
+		setupconf="-v1"
+		use d2 && setupconf="-v2"
+		gcc/d/setup-gcc.sh ${setupconf} || die "failed in D setup"
+	fi
 }
 
 pkg_setup() {
@@ -89,3 +101,4 @@
 		ewarn "Any bugs resulting from the use of Graphite will not be fixed."
 	fi
 }
+


僕の場合は、Overlayのディレクトリの中に更に個人的なmyportというポートを作って、
公式に取り込まれていないしOverlaysのページにも無いようなデータを入れるスクリプトを作って放りこんでます。
公式Portageと同バージョンのパッケージが追加ポートに入ってる場合は、後者が優先されるようです。
USE環境変数の中にdが入ってなければ(d2が入っていたとしても)通常のgccと全く同じで、
dとd2が入っていればDバージョン2.0、dだけが入っている場合はバージョン1.0が入ります。
推奨されるのはバージョン1.0の方です(USE="d -d2")。
現状ではD1.0とD2.0は共存できないようです。


……結局備忘録に終わった。ほとんど需要は無さそうですね。

LLVMとclangを試してみる

最近噂になってるLLVMを試してみました。
LLVM(Low Level Virtual Machine)は任意の言語を用いて多段階の最適化を試みるコンパイラ基盤だそうで、
従来の処理系ではコンパイルレベルのローカルな(主にファイルやモジュール単位の)最適化が限界なのに対して、
言語に依存しないリンカーレベルのグローバルな(最終的なアセンブリでの)最適化が行えます。
また、中間言語や仮想機械、JITコンパイラなどを用いる意味ではJavaや.NETの構想に近いですが、
実行時最適化だけでなくネイティブコード(というかこっちが現状でデフォルト)も吐ける他、
GCCとのABI(バイナリ)互換も有効らしいので、簡単に過去の遺産を有効利用できます。


GCCに頼らないBSDライセンスのコンパイラ群であるclangが、FreeBSDやLinuxをビルドできるようになったということで、
最新のGPL呪縛を逃れる為に*BSD勢は乗り換え開始してるし、
開発には多くのAppleエンジニアが関わってるみたいで、実際にAppleの主要な製品の幾つかに利用されている。
そろそろ本格的に利用できる時期なんじゃなかろうか、という意見が多いようですので、ちょっと流行に乗ってみます。

Gentooの場合は、llvm-2.8-r1が十分テストされてstable状態なのでemergeで一発導入可能。
clangや、GCCベースのllvm-gccはテスト中ですが~ARCHキーワードを受け入れれば最新版が導入可能です。
さっそくclangのリンク時最適化を試してみましょう。
ベンチマークとかはノウハウ全然分からないので他の方の記事に任せますが、
clangやllvm-gccの吐くコードの実行速度は現状では処理によりけりで、標準のgccより速かったり遅かったり、
時にはMSVCより速いコードを吐いたりする時もあるので今後に期待です。ICCには負けるけど。


以下の二つのファイルで実行ファイルを作ってみます。


[test1.c]

#include <stdio.h>

extern void (int*, int*);

int main(void)
{
  int x = 1, y = 2;
  swap(&x, &y);
  printf("x = %d, y = %d\n", x, y);
}


[test2.c]

void swap(int *x, int *y)
{
  int tmp = *x;
  *x = *y;
  *y = tmp;
}


まずは普通に標準GCCで最適化オプション付きで、順を追いながらコンパイルしていきます。

$ gcc -c -O2 test1.c
(プリプロセス、コンパイル、アセンブル。test1.oができる。)
$ gcc -c -O2 test2.c
(プリプロセス、コンパイル、アセンブル。test2.oができる。)
$ nm test1.o test2.o

test1.o:
                 U __printf_chk
0000000000000000 T main
                 U swap

test2.o:
0000000000000000 T swap
(シンボルのリストアップ。printfが内部で最適化済みの__printf_chkに差し替えられてる!)
$ gcc test1.o test2.o
(リンク、ロード。a.outができる。)
$ nm a.out | grep -v " _"
0000000000601010 W data_start
0000000000400570 T main
00000000004005c0 T swap
(シンボルのリストアップ。内部シンボルはとりあえず興味無いので表示しない。)
$ ./a.out
x = 2, y = 1
(実行可能。)


結果として、目に見える形ではtest1.c内の標準関数printf呼び出しだけが最適化されたのが分かりますが、
流石に最適化オプションを付けたところでswap関数が展開されたりはしません。
-O3を付けたところでこれは同じで、ファイル単位の最適化をした後にリンクをする従来の方式では、
これが限界だということが分かります。


さきほどのgccの処理を、clangに置き換えることも可能ですが、この場合gccと同じくローカル最適化しかできません。
グローバル最適化を行う為には、手順が増えますがLLVMの中間コードを作る必要があります。
Makefileを書く参考にでもなるかと思います。

$ clang -emit-llvm -c test1.c
(プリプロセス、ビットコードコンパイル。test1.oができる。)
$ clang -emit-llvm -c test2.c
(プリプロセス、ビットコードコンパイル。test2.oができる。)
$ llvm-nm test1.o test2.o

test1.o:
         T main
         U swap
         U printf

test2.o:
         T swap
(シンボルのリストアップ。言語コンパイル時点では最適化しないのが流儀。)
$ llvm-ld test1.o test2.o
(リンク。面白いことにa.outとa.out.bcというファイルができる。
 実はa.outはシェルスクリプトで、次のコマンドを呼び出す。)
$ lli a.out.bc
x = 2, y = 1
(ビットコードa.out.bcをJITコンパイラ付きランタイムで実行。)
$ llvm-nm a.out.bc
         T main
         U printf
(ビットコードのシンボルリストアップ。なんとswapが無くなってる!!!)
$ llc a.out.bc
(システムコンパイル。a.out.sができる。)
$ clang a.out.s
(ネイティブコードアセンブル、リンク、ロード。a.outが上書きされる。)
$ nm a.out | grep -v " _"
0000000000601010 W data_start
0000000000400540 T main
                 U printf@@GLIBC_2.2.5
(実行ファイルのシンボルリストアップ。色々内部シンボル出てくるけど非表示。)
$ ./a.out
x = 2, y = 1
(実行可能。)


実に面白い結果が得られました。
ビットコードなる中間言語コードのリンクをすることで、swap関数がインライン展開された模様です。
printf関数は独自のものに差し替えられていないようで、この辺が戦略の違いでしょうか。
LLVMを使う場合は言語ソースのコンパイル時点では最適化しないのが流儀だそうで、
シンボルを破壊せずデバッグしやすいし、より統計的に効率のいいグローバル最適化ができるって話です。


まだまだ実行ファイルのパフォーマンスに関しては圧倒的なアドバンテージは持っていないものの、
コンパイル速度やメモリ効率は既にGCCを凌駕しています。
また、使ってみると分かりますがエラーメッセージが非常に親切で分かりやすく、
今後の発展に大いに期待できそうです。


例えば、スタティックリンクするライブラリが巨大なもの(標準C++ライブラリやwxWidgets)であったとしても、
LLVMのビットコード形式でライブラリが用意されくれるようになれば、
リンカが必要シンボルだけをピックアップしてリンクしてくれるので、
リリースファイルが肥大化せずに済むんじゃないかなー。というかこれ本当に実現できそうだなー。
D言語も(あんまり活発じゃないけど)LLVMポーティングされ始めてるみたいだし、
この環境ならC++でも多少はイライラが解決するんじゃないかとも感じています。

火星の月に魅せられて1

開発言語に関する独善的な考察です。御託ばかりで面白くないと思います。
それでも時間をかけた思惑なので文章にして残しておきます。


ここまでLevanプロジェクトはC++とLua、wxWidgetsでまったりゆったり開発を目論んでましたが、
正確にはバインディングの目的などで、boostやluabindも必要になって使ってましたが、
正直、最初から分かっちゃいたけどC++のコーディングしんどいし、コンパイル時間もイライラさせます。
何よりバグの修正がしんどいし、依存性解決もめんどいし、記述も冗長になりがちだし、
ドキュメンテーションも(まだやってないし)やる気おきません。
/// ## --- /** 怠惰と不精と傲慢こそプログラマの三大美徳とは(略) **/


皆言うけど、やっぱりこんな化石級の言語は時代遅れだし、必要に駆られない限りは使いたくないなー、
とか思いながら、まだ何も決まってないけど、色々調べながら検討してみました。
有名どころで、すぐ候補にあがるのは、Objective-CやらC#やら、OCamlやら…
どれも一通り弄ったことはあるんですが。
スクリプティング環境を作るんだから、主軸開発はネイティブコードを吐ける言語じゃないと難しい。
JITコンパイラは許容範囲かな。


そうこう色々考えてるうちに、有力候補としてあがったのはD言語です。
コンパイラ開発の権威とも言える人が中心に開発してて、割と昔から注目されてるので、
自分もバージョン0代の時代に何度から何度か触ってみたり、
「まぁそのうち使うかも知れない」とか思って適当にGentooでコンパイラだけ放りこんだりしてました。
でもまぁ世間的にも、2007年にいきなりバージョン1が発表されて保守モード、
バージョン2の開発が始まったりでD言語熱が冷めたと言われるように、
言語仕様も定まりきらないイケてる言語なのか、イケてない言語なのか微妙…
って印象で、自分も最新情報を追ってませんでした。
でも他の言語にしたって、実行速度や汎用性は問題なくても、記述性というか使い捨て感というか、
そういうのに欠けるんだよなぁ… うまく説明できないけど。


プログラミング言語の嗜好性って、どうにも人を選ぶので、理想の言語論に答えが無いのは分かってるけど、
一般論では表現力や再利用性などが(スクリプト言語も含んで)生産的な言語の指針にされるようですよね。
とはいえ最終的には、成果物を使う人間にとって最も重要なのは、

  • 記述性(いかに書きやすいか)
  • 可読性(いかに読みやすいか)

の二つに尽きる気がするんですよね。それに実行速度が付いてこないと本物じゃないんだけど。
だから、結論としては手続き型で書き殴りが許されるべきで、
用途に応じてクラスの記述や関数の動的生成なんかも気軽にできるべきで、
それはもちろんスクリプト言語の領分なんだけど、
コンパイル型の言語もそれを見越した設計じゃないと生き残れないんじゃないかな。


D言語がそういった理想論を解決できるとは思わないし、
ある種の独善的で逆に柔軟性を欠いた言語仕様を気に入らない人も少なくないです。
ですがC/C++のコーディングを長年やって疲れた人が、今までのノウハウを生かしながら、
実行速度を殺さずに流行のパラダイムを気軽に試せたり、楽をするためのツールの揃ったD言語てのは、
悪くない選択肢だと思うんです。
何よりエントリーポイントを作る為に新規クラスの記述を要求する言語ってのバカげてると思うんだよなぁ…


次回以降は道連れになるスクリプト言語や、現存する処理系なんかの御託を並べようかと。

聖地の食べるラー油

プログラミング関係の話ばっかり書いても息が詰まるので、たまには全く無関係のネタでも。
和食や中華がほとんど手に入らないイスラエルに5年ほど住んでいるので、
色々工夫しながら生活しております。


ユダヤ教の食事規定の関係で、普通のスーパーには豚製品は無いし、
肉製品と乳製品との組み合わせもアウトなので、せっかく乳製品がおいしい国なのに、ピザにサラミも乗せられません。


というわけで、今回は日本でもちょっとしたブームになったそうな食べるラー油に挑戦してみました。
実は普通のラー油も以前作ったんですが、フィルタリングとか無いぶん、こっちの方が楽だったりします。
レシピはインターネットで簡単に拾えるので、こっちで簡単に手に入る材料から紹介させて頂きます。


  • サラダ油(カノーラ油とヒマワリ油のブレンド)
  • 砕いたチリ
  • アーモンドダイスかアーモンドスライス
  • オニオンチップ
  • ガーリックチップ
  • 胡麻
  • 刻みネギ
  • 調味料(醤油、砂糖、その他お好みで)


簡単に言えば、この材料を上から順に時間を置きながら焦がさないようにゆっくり熱すれば完成です。
幸いうちの寮は電気コンロで火力が出ません。嬉しくないけど。
調味料は一瞬で焦げてしまいかねないので、火を止めて十分冷めてから入れて下さい。後は経験則で。
今回はたまたま日本から持ってきたコチュジャンと顆粒状鶏ガラスープの素があったので加えました。
旨かった。アーモンドの食感が最高ですぞ。


ラーメンスープの素とかあったら簡単に旨みを付けられますが、ほぼ確実にラードや海鮮ダシが入ってると思うので、
ルームメイトのユダヤ人に食べさせられません。残念!

備忘録: Linux上のMinGWクロスコンパイル環境でBoostを導入

LevanaのWindows用バイナリをビルドする為に、MSYS/MinGWを使ってましたが、
どうにもシェルを互換動作させる為のオーバーヘッドが大きいみたいで、
make中はOSの反応が著しく低下するし、時間もかかりすぎて効率悪いし、
そもそも必要無い限りはWindows立ち上げない人間なので、もうちょっといい方法を考えねば、と思いました。
MinGWは、Linux上でも動かすことができるので、いっそのことビルドもLinuxでやっちゃいましょう。
Gentoo Linux上でcrossdevを使ってmingw32を導入したまではいいものの、Boostの導入に思いの外つまずいたので、
今後の為に備忘録とっておきます。


クロスコンパイルの為の環境は/usr/mingw32/usrがプレフィックスで、
コンパイラやリンカなどの必須バイナリ名にも、mingw32-というプレフィックスがついています。
こういった特殊なファイル配置の場合でも、autoconfの使えるwxWidgetsなどはさほど苦労せず入れられたんですが、
bjamなるBoostの導入システムがかなりの曲者です。Boostが優れたライブラリだけに惜しすぎる。


とりあえずソースの準備はebuildを使えばラクラクです。

$ sudo su
# ebuild /usr/portage/dev-libs/boost/boost-1.41.0-r3.ebuild prepare

(ビルド環境準備)

# cd /var/tmp/portage/dev-libs/boost-1.41.0-r3/work/boost_1_41_0/


bjam(うちの場合はbjam-1_41)が入ってると仮定しますが、ここでそのままbjam走らせたり、
prefixの設定をしても、適切なコンパイラ(mingw32-g++)を呼んでくれないし、
どうにも設定の方法がなさそうなので、gccにシンボリックリンクを張ってやりたいところですが、
もっと汎用性のあるgcc-configを使ってみましょう。

# gcc-config -l
 [1] mingw32-4.4.4 *
 [2] x86_64-pc-linux-gnu-4.4.4 *

むぅ。クロスコンパイル環境とネイティブ環境は共存できて、プレフィックス無しのgccを名乗らせてくれないので、
思い切ってクロスコンパイル環境をディスってネイティブ環境と思い込ませてやりましょう。
これでgcc-configはクロスコンパイル環境をネイティブ環境だと勘違いしてくれます。

# sed -i "s/CTARGET/#CTARGET/" /etc/env.d/gcc/mingw32-4.4.4
# gcc-config mingw32-4.4.4
 * Switching native-compiler to mingw32-4.4.4 ...                [ ok ]
# source /etc/profile
# gcc -v
Using built-in specs.
Target: mingw32
Configured with: /var/tmp/cross/mingw32/portage/cross-mingw32/gcc-4.4.4-r2/work/gcc-4.4.4/configure
--prefix=/usr --bindir=/usr/x86_64-pc-linux-gnu/mingw32/gcc-bin/4.4.4
--includedir=/usr/lib/gcc/mingw32/4.4.4/include --datadir=/usr/share/gcc-data/mingw32/4.4.4
--mandir=/usr/share/gcc-data/mingw32/4.4.4/man --infodir=/usr/share/gcc-data/mingw32/4.4.4/info
--with-gxx-include-dir=/usr/lib/gcc/mingw32/4.4.4/include/g++-v4 --host=x86_64-pc-linux-gnu
--target=mingw32 --build=x86_64-pc-linux-gnu --disable-altivec --disable-fixed-point --with-ppl
--with-cloog --enable-nls --without-included-gettext --with-system-zlib --disable-werror
--enable-secureplt --disable-libmudflap --disable-libssp
--with-python-dir=/share/gcc-data/mingw32/4.4.4/python --enable-checking=release
--disable-libgcj --enable-languages=c,c++,fortran --with-sysroot=/usr/mingw32 --disable-bootstrap
--with-bugurl=http://bugs.gentoo.org/ --with-pkgversion='Gentoo 4.4.4-r2 p1.3, pie-0.4.5'
Thread model: win32
gcc version 4.4.4 (Gentoo 4.4.4-r2 p1.3, pie-0.4.5)


心置きなくMinGWでビルドできるようになりましたが、Windows用バイナリを作りたいことをbjamに知らせるために、
適切なオプション指定で、ようやくビルドできるようになります。

# cd /var/tmp/portage/dev-libs/boost-1.41.0-r3/work/boost_1_41_0/
# bjam-1_41 toolset=gcc target-os=windows threadapi=win32 --prefix=/usr/mingw32/usr \
> --link=static --runtime-link=static --without-python release stage

(ビルド処理)

# bjam-1_41 toolset=gcc target-os=windows threadapi=win32 --prefix=/usr/mingw32/usr \
> --link=static --runtime-link=static --without-python release install

(インストール処理)

めでたし、めでたし?


あぁ、ちゃんとgcc-configで環境を戻しておかないと、
標準C++ライブラリに依存したプログラムが動かなくなるので注意です。

# gcc-config x86_64-pc-linux-gnu-4.4.4
 * Switching native-compiler to x86_64-pc-linux-gnu-4.4.4 ...             [ ok ]

wxWidgetsでイベントループを考える2

id:Akiva:20101208 [wxWidgetsでイベントループを考える1]の続き
今回も長いです。


前回の記事で、メインスレッドはLuaインタプリタに主導権を持たせるということ、
イベントループはLuaスクリプトとして自分で書けるようにする必要があるという結論に至りました。


wxWidgetsの仕様では一応、wxAppの派生クラスでMainLoopメソッドをオーバーライドした場合、
標準で用意されたイベントループの代わりに、自前のループ処理が呼び出されるという事になっているのですが、
その自前のループ処理の記述方法についての解説がなかなかネット上でも見つかりません。
試しに空の無限ループでMainLoopをオーバーライドした場合は、当然ながらイベント処理が行われていない為、
ウィンドウが表示された状態だとしてもハングして応答しなくなります。
日本語訳リファレンスの中では、wxApp::Yieldメソッドに関して、

Yield(処理の明け渡し)はウィンドウシステムでペンディング(未処理)メッセージを制御します。
これは例えば時間のかかる処理がテキストウィンドウに書き込むときに役に立ちます。
時々のYieldがないと、テキストウィンドウは適切に更新できません。(後略)

とあるため、試しにLinux環境で毎回Yieldだけするループを書いて、
トップウィンドウのクローズを確認したらループ終了するプログラムを書いたら、
ちゃんとウィンドウ処理をするようになり、ループ内に任意の処理を記述しても思い通りの動作をしました。


…しかし!


WindowsでMinGW環境を整えて、このコードをコンパイルしてみたものの、
何故かハングして、ウィンドウを閉じようとするとフリーズします。何で!?
要はWindowsの場合、Yieldだけでは不十分な模様。ならwxApp::PendingとwxApp::Dispatchで解決するのでは?
と思ったので、試しにこんなコードを書いてみました。

int myApp::MainLoop()
{
  while(this->GetTopWindow())
  {
    // 任意の処理
    while(this->Pending()) { this->Dispatch(); }
    this->Yield();
  }
  return 0;
}

それでも状況は変わらずハングアップ。どうにもMainLoopオーバーライド関係の資料は、
ぐぐってみても日本語でも英語でもなかなか言及されてない。恐らく公式のドキュメントは無いのでしょう。
幾つかのWikiサイトの説明でも、描画ループはアイドリング処理かクロックイベントに書きましょうってあるんですが、
繰り返し言うことになりますが、それじゃぁリアルタイム処理にもLuaの手続きスクリプトにも不都合があります。
しょうがないから標準のMainLoopがソースコードでどういう実装になってるか調べてみて、
自前のループ記述の参考にしようと思った次第です。(参考にしたのはwxWidgetsバージョン2.8.10.1-r5のソース)
その結果、分かったのは、src/common/appcmn.cpp内(OSに依存しない部分の実装)で、

int wxAppBase::MainLoop()
{
    wxEventLoopTiedPtr mainLoop(&m_mainLoop, new wxEventLoop);

    return m_mainLoop->Run();
}

とあり、どうやらイベントループ専用のクラスがあって、そいつを生成・アクティベーション(?)して、
イベントループを、アプリケーション終了まで延々と走らせているようですね。
こりゃwxEventLoop::Runの実装も確認しなきゃいけないですね。

と、いうわけでsrc/common/evtloopcmn.cppの中に該当するメソッドの実装を見つけましたが、
これが意外と長い… wxEventLoopManualというwxEventLoopの基底クラスのメソッドなんですが、
半無限ループ・例外処理・半無限ループのネスト構造になってて面倒なので、肝心な箇所だけピックアップすると、

int wxEventLoopManual::Run()
{
  // (略、ループが既に実行中じゃないかどうかの確認など)
  // 意訳:
  //   下記のProcessIdle()とDispatch()は例外を投げかねないので、ここのコードは例外安全であるべきだ。
  //   それ故、Undo動作が必要なアクションには全てローカルなオブジェクトを使わなければならない。
  wxEventLoopActivator activate(wx_static_cast(wxEventLoop *, this));
  // (略、ループカウンタの初期化とかループの説明とか。コメントも長い。)
  for ( ;; )
  {
    try
    {
      for ( ;; )
      {
        // 意訳: 独自処理したくなるかも知れないから選択肢を用意しとく。
        OnNextIteration();
        // 意訳: 他に何らかの処理が必要でない限りはアイドリング処理させてあげる。
        while ( !Pending() && (wxTheApp && wxTheApp->ProcessIdle()))
          ;
        // (略、終了フラグが立った場合の最後の処理)

        // 意訳:
        //   メッセージがあるか、実行すべきアイドリング処理がないみたいだから、
        //   Dispatch()呼んでおいて次のメッセージを待とう。
        if ( !Dispatch() )
        {
          // 意訳: WM_QUITきたよー。
          break;
        }
      }
      // 意訳: もちろん外側のループも抜けるよー。
      break;
    }
    catch (...)
    {
      // (略、例外処理)
    }
  }
  return m_exitcode;
}

長ぇけど良く分かった気がする!


これを元に、自前のループを書くのに足りなかった要素を考察すると、

  • イベント処理の待ち受け用に、ちゃんとwxEventLoopを生成、アクティベーションしておく。(初期化処理内で)
  • イベントが無い時はアイドリング処理をする。これを明確に記述すべし。

その他の例外処理とか終了処理は、ある意味おまけみたいなもんです。
結果として、ProcessIdleとYidleをセットにしたYieldメソッドを実装しなおして、
Luaへポーティングすることになりました。
ところが、毎回こんな自前のループを必要とする訳じゃなく、一般的な対話型アプリケーションの場合はむしろ、
標準のメインループのような自動処理をしてくれた方がありがたいと思うので、こいつもポーティング。


id:Akiva:20101209#1291928579 [Levanaでとりあえず窓を開く]でも触れましたが、
コールバック関数の登録などを初期化処理で行って自動イベントループを使いたい場合は、

require 'lev/all'

-- 初期化処理。オブジェクトの生成やコールバック関数の定義・登録など

-- 自動処理のイベントループ
app.autoloop()

とするのが簡単です。
app.autoloopを呼び出すと処理をLevanaライブラリの自動ループに明け渡し、Lua側は待機状態に。
トップウィンドウの解体と同時にLua側に処理が戻ります。


描画ループやゲームループといったリアルタイム処理が必要な場合は、

require 'lev/all'

-- 初期化処理。オブジェクト生成など。この場合でもコールバック関数の定義・登録も可能。

-- 自前のイベントループ
while ( condition ) -- 終了条件
do
  -- ループ毎の処理。デバイス入出力処理や描画関数など。
  -- おそらくクロックイベントの代わりに、ここでタイマー処理が必要になるでしょう。
  app.yield()
end

として、分りやすい手続き型のスクリプティングが可能になります。


以上、二回に渡って長々と説明しましたが、これがLevanaプロジェクトを使う場合の、
根幹となるフレームワークとも呼べるコンセプトです。
今後とも、インターフェイスの修正などの変更はあり得ますが、
「使い始めてみると、直感的でとっても簡単。それでも使用者には柔軟な選択の幅がある快適スクリプティング環境。」
ってのをスローガンに開発を進めていこうと思います。
wxWidgetsのイベント処理なんかも、ごく一部の人には有用な情報になるんじゃないかと思います。


プロジェクト開設しておきながら不安だったけど、既にwxPythonやwxLuaとは全然違う方向性になってきてて、
面白い事になってきたなーって思います。

SourceForge.netに移行

開発拠点をSourceForge.jpからSourceForge.netに移行しました。
日本語で開発してるものの、一応国際化を視野に入れてるもので、こっちの方がいいかなぁ、と。
まぁREADMEとかのドキュメントを日本語でしか書いてないので、
ダウンロードした外国人は困るでしょうけど。
SourceForge.jpもミラーとダウンロードポータルとして使う予定ですけど、
自動的にリリースをミラーする方法って誰か知らないでしょうか…


一応、新プロジェクトページが、
http://sourceforge.net/projects/levana/
公式の日本語Wikiが、
http://levana.sourceforge.net/ja/


全然注目を浴びてないプロジェクトですけど、確実に需要はあると思っているので、
少しずつ進めていこうと思ってます。