boost::logはmutable_constantをshallowコピーする
boost::logのloggerとそれに紐付いたmutable_constantをコピーしようとしてハマったのでメモ。
deepコピーがしたかったのだけど、結果はタイトル通りshallowコピーだった。ドキュメントにもよく読んだら書いてあった。
The shared pimpl design comes significant in a few cases though. One such case is copying the attribute. The copy operation is shallow, so multiple interface objects may refer to a single implementation object. There is no way to deep copy an attribute.
http://www.boost.org/doc/libs/1_57_0/libs/log/doc/html/log/detailed/attributes.html
clone()とかあれば便利なんだけどなさそうだし、どうしてもしたければ自前でmutable_constantを再生成して登録するぐらいしかないのかな・・・
テストコード
#include <iostream> #include <boost/log/sources/logger.hpp> #include <boost/log/attributes/mutable_constant.hpp> using namespace std; namespace lg = boost::log; int main(int argc, char* argv[]) { //このmutable_constantをコピーすると内部変数はどうなるのか? lg::attributes::mutable_constant<int> attr1(1); cout << attr1.get() << endl; //mutable_constantのコピーは内部変数をshallowコピーする lg::attributes::mutable_constant<int> attr2(0); attr2 = attr1; attr2.set(2); cout << attr1.get() << " " << attr2.get() << endl; //mutable_constantをloggerに登録 lg::sources::logger log1; log1.add_attribute("attr", attr1); //loggerのコピーは保持するmutable_constantをshallowコピーする lg::sources::logger log2; log2 = log1; lg::attributes::mutable_constant<int> attr3 = lg::attribute_cast<lg::attributes::mutable_constant<int>>(log2.get_attributes().find("attr")->second); attr3.set(3); cout << attr1.get() << " " << attr2.get() << " " << attr3.get() << endl; return 0; }
実行結果
1 2 2 3 3 3
HaskellでのSDLの使い方
SDLのHaskellバインディングはいくつかありますが、この記事ではHackageのSDLを使って説明しています。Windows環境だとCabalではうまく入らなかったりするので、直接ソースからビルドする必要があるかもしれません。
SDLを使うにはFFIが必要
$ ghc -package SDL --make Main.hs
のようにコンパイルしても、SDL_mainが見つからないなどのエラーが出てコンパイルできません。SDLを使ったプログラムでは本来、ユーザが作成したmain関数はSDL.hのマクロによってSDL_mainにリネームされ、SDLライブラリの内部にある本当のmain関数がSDL_mainを呼び出すことになってるのですが、ghcはその面倒まで見てくれないのでSDL_mainが作られず、エラーになってしまいます。
これを回避するためには、C言語で書いたmainの中からFFIでHaskellを呼び出した上で、元のmainをSDL.hでSDL_mainに書き換えさせる必要があります。
Haskellの関数をエクスポートする
まずは画面を表示するだけのHaskellコードを書いて、Cのmainから呼び出すためのhs_mainをエクスポートします。
{-# LANGUAGE ForeignFunctionInterface #-} module HsMain where import qualified Graphics.UI.SDL as SDL foreign export ccall "hs_main" hs_main :: IO () hs_main :: IO () hs_main = do SDL.init [SDL.InitEverything] SDL.setCaption "SDL test" "" SDL.setVideoMode 300 300 32 [] mainLoop SDL.quit mainLoop :: IO () mainLoop = do e <- SDL.waitEvent case e of SDL.Quit -> return () _ -> mainLoop
ここで、このコードをそのまま
$ ghc --make HsMain.hs
のようにコンパイルしようとすると、HsMain_stub.cとHsMain_stub.hというファイルが生成されます。これがHaskellコードをCから呼び出すためのインターフェースになります。
Cから呼び出す
次に、このインターフェースを通してHaskellコードを呼び出すCコードを用意します。
#include <SDL.h> #include "HsFFI.h" #ifdef __GLASGOW_HASKELL__ #include "HsMain_stub.h" extern void __stginit_HsMain(void); #endif int main(int argc, char* argv[]) { hs_init(&argc, &argv); #ifdef __GLASGOW_HASKELL__ hs_add_root(__stginit_HsMain); #endif hs_main(); hs_exit(); return 0; }
最後に、これらを合わせて
$ ghc -package SDL --make -no-hs-main HsMain.hs c_main.c
のようにコンパイルすると、実行ファイルを生成することが出来ます。
Haskellでシューティングゲームを作ったまとめ
ゲーム製作者コミュニティ in 札幌の春のシューティング祭りプロジェクトで一ヶ月ほどかけて簡単なシューティングを作りました。
Windowsバイナリは紹介ページ、ソースはHackageから落とせます。ビルドにはSDLが必要です。また、絵と音楽はそれぞれコミュニティのぐぐぐさん、RE-Ya!@D.J.無理王さんに頼みました。
感想
まず、全体的にバグはほとんど出ませんでした。唯一ハマったのはIntのオーバーフローぐらいで、それもSDLとのやり取りのためにIntを使っているので仕方ないかな、という感じです。
IOもそれほど障害にはなりませんでした。面倒なのは事実ですが、do記法を使えば簡単に処理できるのですぐに慣れることが出来ました。ただ、始めは非IO関数として定義しようと思っていたものを、乱数を使いたいとかいう雑多な理由で後からしぶしぶIOにするというケースは結構ありました。
一番困ったのは、テトリスの方も書かれていますが、フィールドラベルが関数としてグローバルに出てしまうことでした。後から見ると実際に衝突しそうなのはそれほど多くないにも関わらず、衝突への恐怖や関数名の一貫性のために、フィールド名が不必要に長くなってしまいました。フィールド名に限らず、Haskellではモジュール単位でしか見せる・見せないを決められないので、全般的にスコープが大きすぎるということが多いように思いました。
このあたりのことを考えると、オブジェクト指向言語のクラスが関数に適当なスコープを与えるために存在するように思えてくるほどで、少し違いますがF#のパイプ演算子なんかも(自分で定義すれば作れますが)なかなか羨ましいと思いました。
今回はしなかったこと
起動画面やゲーム画面は型クラスに出来そうでしたが、気づいたときにはほぼ完成していたので結局しませんでした。また、ゲームのリソースなどはモナドとかに出来そうな気がしましたが、相当難しそうだったのでしていません。