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

今回はLevanaの実装側の観点で書いてるので、直接LevanaやLuaには関係ない記事です。
wxWidgetsの使い方とか、処理の支配権とかの観念的な話なので、興味ある方だけどうぞ。長いし。


GUIを持ったOSのAPIでプログラミングをする時は、
マウス処理やなんやらのイベント処理をする為のコーディングってのが結構面倒だったりします。
なので、だいたいのアプリケーションフレームワークとかツールキットとかってのは、
OS特有のイベント処理を請け負うイベントループってのを内部に持ってたりして、
プログラマはイベントに関連付けされた動作だけを記述したコールバック関数を書いて登録すればいいって話。


もちろん、wxWidgetsも例外ではなく、wxAppBaseクラスで定義されたメンバ関数MainLoopが、
イベントループに相当します。ただし、素直にwxWidgetsをフレームワークとして使う人は普通、

DECLARE_APP(myApp);
IMPLEMENT_APP(myApp);

とするのが習わしで、マクロがmain関数ごと定義して、
初期化・イベントループ・終了処理の面倒はライブラリが見てくれるので、意識することは少ないです。
一方、他のアプリのプラグインを作ったりする人は、main関数が使えない為、main定義なしのマクロ、

DECLARE_APP(myApp);
IMPLEMENT_APP_NO_MAIN(myApp);

として、必要なエントリーポイントで同様の処理をする為、

void entry(int argc, char **argv)
{
  wxEntry(argc, char **argv);
}

と書けば、wxEntry関数がIMPLEMENT_APPマクロ同様に初期化・イベントループ・終了処理まで面倒みてくれますが、
逆に言うと、コールバック関数以外で、この一連の処理に介入できません。
(処理の別れた関数を使うケースは続編の記事で書く予定)


ところで、今開発中のLevanaの場合は、Luaが機能の一部を担うというよりも、
むしろLuaインタープリテーションが主体になって動作するのが前提である為、
スクリプト処理はmain関数内(正確にはメインスレッド)で動作すべきなのです。
アイドリング処理内とか、クロックイベントのコールバック内とかで、
スクリプトを細切れにして動作させるのも可能ですが、パッシブな動作は処理速度に欠けるでしょうし、
何より支配する側が支配される側にまわってしまいます。


ややこしい話ですが、現実にはイベントループを自前で持った場合も、
スクリプト処理はアイドリング処理内で動くことになりますが(イベント処理にも優先順位があるので)、
この場合スクリプト処理が支配的と言えます。観念的でどうでもよさげですが、実装側には結構重要な問題かな、と。


もう一個、main関数からスクリプト処理用のスレッド分岐して動作する方法もありそうなので、
wxThreadを使おうとしたんですが、どうやらwxWidgetsのイベント処理とスレッド処理の実装関係上、
スレッドのコンテキストスイッチ時に、イベント処理クラスのアクティベーションやらを行っているみたいで、
うまくLua側からGUIとイベントの操作が利かなくなってハマりました。あいたた。
これはイベント処理をスレッド単位で行おうとしまう為で、サブスレッドで作られたGUIオブジェクトは、
サブスレッドに関連付けられてしまって、メインスレッドのイベントループは空回り状態で、最悪ハングします。
同様のことをwxWidgetsのネットワーク機能でやろうとした人もハマったみたいなので、
wxThreadはイベントの関わらない処理に専念させる以外は使わない方がよさそうです。
boost::threadを使う手も考えましたが、この辺を混在させるとハマったら完全に抜けだせなさそうなので辞めました。


今度はメインスレッドでスクリプト処理とイベント処理をこなす時にも、色々躓いた事項があるんですが、
wxWidgetsのソースコードに潜って考察する必要があり、長くなりそうなので、次回へまわします。


Levanaのユーザの観点での記事も増やさないと進まない気がするので、
実装側と利用者側の観点の記事を交互に書くのが理想でしょうか。あくまで理想ですが。