ErlangでAESとかの暗号化を行いたいときに使う「crypto」ですが、OpenSSLのライブラリ(シェアードオブジェクトやDLL)をPort経由で使ってるんですね。
Simple dungeonで、Mnesiaに保存する際にパスワードや連絡先メールアドレスなどを暗号化しようとしているのですが、この構成だと、多数のErlangプロセスが好き放題呼ぶと、ここが隘路になるので、使いどころに注意が必要な感じです。
まあ、ちょっとやそっとじゃここがボトルネックになるほどの負荷はかからないと思うのですが、いったんボトルネック判定されたら、そこからの対策が難しいので、たまーに参照するぐらいの頻度にしておきます。。。
2012年01月06日
Erlangアプリケーションの「設定ファイル」をどう読みこませるか
Simple dungeonで、Mnesiaのノードリストや、移動処理でのインターバル値(1ブロック歩くのにかかる秒数)などをカスタマイズできるようにしたいなと思って、「設定ファイル」をどう作ればいいか調べました。
YAMLとか.ini、.confファイルのような、インストールしたアプリケーションの動作をカスタマイズするためのファイルフォーマットがあるかと思ったら、Erlangには file:consult/1 という関数が用意されていて、Erlang termで書いておけばそのままアトムや文字列、数値、リストなどを読み込んでくれるんですね。これは便利。
XMLで作って読ませることも考えたのですが、今のところはそこに注力する必要はないので、素直にfile:consult/1を使うかたちで行くことにします。
YAMLとか.ini、.confファイルのような、インストールしたアプリケーションの動作をカスタマイズするためのファイルフォーマットがあるかと思ったら、Erlangには file:consult/1 という関数が用意されていて、Erlang termで書いておけばそのままアトムや文字列、数値、リストなどを読み込んでくれるんですね。これは便利。
XMLで作って読ませることも考えたのですが、今のところはそこに注力する必要はないので、素直にfile:consult/1を使うかたちで行くことにします。
2011年11月06日
Eat your own dog food
話題の3Dゲーム開発ツール、UnityをSimple dungeonに接続するコードを書いて、動かしてみました。
"Eat your own dog food"、という言葉がハラオチしましたw
Simple dungeonを実際のゲームのバックグラウンドとして動かすときの不足点が見えてきています。移動させるだけの接続コードながら、以下の2点が既に問題です。
・移動情報はできれば最終地点までの全パスを通知するのがいいかも。現在は「直近1パス」だけで、これだと、アニメーションが「walk」「idle」の細切れな繰り返しになってしまう(1サイクルだけidleが挟まったりとか、動きが見苦しくなる)
・「きっかけ」情報は状態の通知とは別におこなう必要がある。今は状態の全送信だけ。立ち上がる、手を振る、など、おそらく行動のきっかけ情報はUnityクライアントにはアニメーション等の開始指示として必須。
これを解消するにはサーバー側の変更をしないといけません。
"Eat your own dog food"、という言葉がハラオチしましたw
Simple dungeonを実際のゲームのバックグラウンドとして動かすときの不足点が見えてきています。移動させるだけの接続コードながら、以下の2点が既に問題です。
・移動情報はできれば最終地点までの全パスを通知するのがいいかも。現在は「直近1パス」だけで、これだと、アニメーションが「walk」「idle」の細切れな繰り返しになってしまう(1サイクルだけidleが挟まったりとか、動きが見苦しくなる)
・「きっかけ」情報は状態の通知とは別におこなう必要がある。今は状態の全送信だけ。立ち上がる、手を振る、など、おそらく行動のきっかけ情報はUnityクライアントにはアニメーション等の開始指示として必須。
これを解消するにはサーバー側の変更をしないといけません。
2011年07月20日
mnesia:transaction/1固有の返値を
Simple dungeonはぼちぼちと更新中。
ちょっと迷ってるのが、各種データの保存/読み込み処理の、成功/失敗判断の条件。
今のコードはデータの保存にmnesiaを使っています。
したがって、mmoasp:change_passwordのような関数はすべてmnesia:transactionを中で呼んでます。返値も、mnesia:transactionの結果をほぼ素で返したいところ。
mnesia:transaction/1は、成功すると{atomic, _}、失敗すると{ng, _}を返すわけですが、呼び出し側で、「{atomic, ok}が帰ってきたら書き込み成功」と書いてしまっていいものかどうか。
「mnesiaを隠蔽した方がいいのではないか」という気もしつつ、「どうせ当面はmnesiaでいくんだし」という気もする。
YAGNIの原則からすると、「mnesia以外のストレージへの切り替え機能をつけるまでは、素直に{atomic,ok}で書いておけ」という気もするのですが、Erlang初学者としては、ここの「{atomic」を上位コードのcaseとかwhenにバキバキ書くというのが、どうにも気持ちが悪い。
他の人の書いたコードを読むか、Erlang MLに質問投げてみるか、、、
ちょっと迷ってるのが、各種データの保存/読み込み処理の、成功/失敗判断の条件。
今のコードはデータの保存にmnesiaを使っています。
したがって、mmoasp:change_passwordのような関数はすべてmnesia:transactionを中で呼んでます。返値も、mnesia:transactionの結果をほぼ素で返したいところ。
mnesia:transaction/1は、成功すると{atomic, _}、失敗すると{ng, _}を返すわけですが、呼び出し側で、「{atomic, ok}が帰ってきたら書き込み成功」と書いてしまっていいものかどうか。
「mnesiaを隠蔽した方がいいのではないか」という気もしつつ、「どうせ当面はmnesiaでいくんだし」という気もする。
YAGNIの原則からすると、「mnesia以外のストレージへの切り替え機能をつけるまでは、素直に{atomic,ok}で書いておけ」という気もするのですが、Erlang初学者としては、ここの「{atomic」を上位コードのcaseとかwhenにバキバキ書くというのが、どうにも気持ちが悪い。
他の人の書いたコードを読むか、Erlang MLに質問投げてみるか、、、
2011年04月20日
キャラクタに対する「ガベージコレクション」
Simple dungeonでは、戦闘で倒されたNPCなど、セッション一覧から除外したいキャラクタが定期的に出るわけですが、その一方で、別のキャラクタにしてみれば「一定距離内にいるキャラクタ全員にメッセージ送信」という事象も絶えず発生します。
このとき、マップからキャラクタを除外するタイミングを誤ると、計算対象としてリストアップされてるのにいざ計算しようとするとDBからselectできずにエラー、というような事態が起きます。
キャラクタのマップからの除去(simple dungeonではsessionsテーブルからの除去)はよく考える必要があります。
・・・なんて、他人事のように書いちゃいけませんね。
現にrev.126がテストを通りません。
現在、対応を検討中です。
とほー。
このとき、マップからキャラクタを除外するタイミングを誤ると、計算対象としてリストアップされてるのにいざ計算しようとするとDBからselectできずにエラー、というような事態が起きます。
キャラクタのマップからの除去(simple dungeonではsessionsテーブルからの除去)はよく考える必要があります。
・・・なんて、他人事のように書いちゃいけませんね。
現にrev.126がテストを通りません。
現在、対応を検討中です。
とほー。
2011年04月18日
erlシェルからの起動と、-detached指定でデーモン起動したときの動作が違う(デタッチするとサービスが落ちる)
Simpledungeonをスーパバイザ挙動を使って動かす作業中です。
erlシェルからの起動と、-detached指定でデーモン起動したときの動作が違う(デタッチするとサービスが落ちる)という現象に出くわしました。
コマンドラインで
erl -pa /opt/yaws/lib/yaws/ebin
と起動して、erlang shellから
simpledungeon:start().
と入力すると、サービスはちゃんと動く。
でも、同じバイナリで
erl -pa /opt/yaws/lib/yaws/ebin -noinput -noshell -run simpledungeon start -detached &
というふうに、デーモン起動すると、なぜかサービスが動いてないわけです。
いろいろ見て回って、最終的に檜山さんのまとめた"helloちゃんと作るシリーズ"の記述に答えがありました。
http://erlang.g.hatena.ne.jp/m-hiyama/20090114/1231891538
ここ。
要するに、デーモン起動したときだけサービスが動かないというのは、start_linkで起動してるのに親シェルがデタッチしたためにサービスが止まった、ということのようです。
で、檜山さんのスケルトンを参考に、unlinkの1行を追加したところ、Erlangシェルでスーパバイザを起動したときと同じように、サービスが動き続けてくれました。
=======================
修正前:
{ok, Pid} = supervisor:start_link({local,?MODULE},?MODULE,[]),
=======================
修正後:
{ok, Pid} = supervisor:start_link({local,?MODULE},?MODULE,[]),
unlink(Pid),
=======================
うー、つかれた。
start_linkしてunlinkするんだったら、素直に「start」させてくれよ、という気もしますが、そこはOTPの長年の熟成の中で入ってないわけですから、ひとまずこの作法に従うことにします。まあ、unlinkがいるのは、シェルから起動するアプリケーション最上位のsupervisorだけ、それ以外は全部linkしててほしいので、この構成は正しいと考えるのが妥当でしょうね。
erlシェルからの起動と、-detached指定でデーモン起動したときの動作が違う(デタッチするとサービスが落ちる)という現象に出くわしました。
コマンドラインで
erl -pa /opt/yaws/lib/yaws/ebin
と起動して、erlang shellから
simpledungeon:start().
と入力すると、サービスはちゃんと動く。
でも、同じバイナリで
erl -pa /opt/yaws/lib/yaws/ebin -noinput -noshell -run simpledungeon start -detached &
というふうに、デーモン起動すると、なぜかサービスが動いてないわけです。
いろいろ見て回って、最終的に檜山さんのまとめた"helloちゃんと作るシリーズ"の記述に答えがありました。
http://erlang.g.hatena.ne.jp/m-hiyama/20090114/1231891538
ここ。
要するに、デーモン起動したときだけサービスが動かないというのは、start_linkで起動してるのに親シェルがデタッチしたためにサービスが止まった、ということのようです。
で、檜山さんのスケルトンを参考に、unlinkの1行を追加したところ、Erlangシェルでスーパバイザを起動したときと同じように、サービスが動き続けてくれました。
=======================
修正前:
{ok, Pid} = supervisor:start_link({local,?MODULE},?MODULE,[]),
=======================
修正後:
{ok, Pid} = supervisor:start_link({local,?MODULE},?MODULE,[]),
unlink(Pid),
=======================
うー、つかれた。
start_linkしてunlinkするんだったら、素直に「start」させてくれよ、という気もしますが、そこはOTPの長年の熟成の中で入ってないわけですから、ひとまずこの作法に従うことにします。まあ、unlinkがいるのは、シェルから起動するアプリケーション最上位のsupervisorだけ、それ以外は全部linkしててほしいので、この構成は正しいと考えるのが妥当でしょうね。
supervisorでハマッた
分かってしまえば、ものすごくシンプルなんだけど、simpledungeon開発中にハマッた件。
Erlang/OTPの、supervisor ビヘイビアで、start_linkに渡すスーパバイザ名と、start_childに渡すスーパバイザ名の書き方に違いがあるということに気づかず、謎のエラーが出続けて1週間ほどタイムロスしてました。
●スーパバイザの起動
supervisor:start_link({local,?MODULE},?MODULE,[])
のように書いたわけです。この記述には問題はありません。
●子プロセスであるyawsを起動するところ
子プロセスの起動側に問題がありました。
誤:[supervisor:start_child({local,?MODULE}, Ch) || Ch <- ChildSpecs]
正:[supervisor:start_child(?MODULE, Ch) || Ch <- ChildSpecs]
ずっと、誤の書き方をしていて、下のようなエラーメッセージが出続けていました。なんでnodedownやねん、と。このエラーメッセージの原因が分からなかったわけです(相対ディレクトリ指定ができないのかなとか、どっかがリストになってないのかな、とか)。でも、全然原因は違ってました。。。
=エラーメッセージ:=============
1> simpledungeon:start().
** exception exit: {{nodedown,simpledungeon},
{gen_server,call,
[{local,simpledungeon},
{start_child,
{yaws_log,
{yaws_log,start_link,[]},
permanent,5000,worker,
[yaws_log]}},
infinity]}}
in function gen_server:call/3
in call from simpledungeon:'-start_yaws/0-lc$^0/1-0-'/1
in call from simpledungeon:start_yaws/0
in call from simpledungeon:start/0
2>
===================
原因はシンプルで、start_childの第1引数には、start_linkのときに指定していた{local, 名称}タプルではなく、名前のアトムを渡さないといけなかったのですね。
http://www.erlang.org/doc/man/supervisor.html
にある、supervisor:start_child/2の定義。
=======================
start_child(SupRef, ChildSpec) -> Result
Types:
SupRef = Name | {Name,Node} | {global,Name} | pid()
Name = Node = atom()
ChildSpec = child_spec() | [term()]
Result = {ok,Child} | {ok,Child,Info} | {error,Error}
Child = pid() | undefined
Info = term()
Error = already_present | {already_started,Child} | term()
=======================
SupRefが、「どのスーパバイザに子プロセスを起動させるか」の参照なわけですが、{global,Name}がある一方で、{local,Name}はなく、そのかわりにNameがあるわけです。
というわけで、スーパバイザとスーパバイズ対象の子プロセスをどちらもローカルで立ち上げるのであれば、start_linkでは{local, Name}を指定し、start_childではNameを渡すという起動方法になる、、、のですよ。
はまった。
Erlang/OTPの、supervisor ビヘイビアで、start_linkに渡すスーパバイザ名と、start_childに渡すスーパバイザ名の書き方に違いがあるということに気づかず、謎のエラーが出続けて1週間ほどタイムロスしてました。
●スーパバイザの起動
supervisor:start_link({local,?MODULE},?MODULE,[])
のように書いたわけです。この記述には問題はありません。
●子プロセスであるyawsを起動するところ
子プロセスの起動側に問題がありました。
誤:[supervisor:start_child({local,?MODULE}, Ch) || Ch <- ChildSpecs]
正:[supervisor:start_child(?MODULE, Ch) || Ch <- ChildSpecs]
ずっと、誤の書き方をしていて、下のようなエラーメッセージが出続けていました。なんでnodedownやねん、と。このエラーメッセージの原因が分からなかったわけです(相対ディレクトリ指定ができないのかなとか、どっかがリストになってないのかな、とか)。でも、全然原因は違ってました。。。
=エラーメッセージ:=============
1> simpledungeon:start().
** exception exit: {{nodedown,simpledungeon},
{gen_server,call,
[{local,simpledungeon},
{start_child,
{yaws_log,
{yaws_log,start_link,[]},
permanent,5000,worker,
[yaws_log]}},
infinity]}}
in function gen_server:call/3
in call from simpledungeon:'-start_yaws/0-lc$^0/1-0-'/1
in call from simpledungeon:start_yaws/0
in call from simpledungeon:start/0
2>
===================
原因はシンプルで、start_childの第1引数には、start_linkのときに指定していた{local, 名称}タプルではなく、名前のアトムを渡さないといけなかったのですね。
http://www.erlang.org/doc/man/supervisor.html
にある、supervisor:start_child/2の定義。
=======================
start_child(SupRef, ChildSpec) -> Result
Types:
SupRef = Name | {Name,Node} | {global,Name} | pid()
Name = Node = atom()
ChildSpec = child_spec() | [term()]
Result = {ok,Child} | {ok,Child,Info} | {error,Error}
Child = pid() | undefined
Info = term()
Error = already_present | {already_started,Child} | term()
=======================
SupRefが、「どのスーパバイザに子プロセスを起動させるか」の参照なわけですが、{global,Name}がある一方で、{local,Name}はなく、そのかわりにNameがあるわけです。
というわけで、スーパバイザとスーパバイズ対象の子プロセスをどちらもローカルで立ち上げるのであれば、start_linkでは{local, Name}を指定し、start_childではNameを渡すという起動方法になる、、、のですよ。
はまった。
2011年04月17日
Supervisor配下で起動するためのyaws_api:embedded_start_conf/1,2,3,4
Yawsの本家にて、yawsを別のスーパーバイザ管理下で起動するための方法の説明記事として、Running yaws embedded in another applicationが掲載されています。
Simple dungeonでこれを使おうと思ったら、当初「yaws_api:embedded_start_conf/1,2,3,4」の関数がないというエラーが発生。
Yaws1.87とやや古かったのが原因でした。最新のYaws 1.89に上げるとこの関数が利用可能でした。
Simple dungeonでこれを使おうと思ったら、当初「yaws_api:embedded_start_conf/1,2,3,4」の関数がないというエラーが発生。
Yaws1.87とやや古かったのが原因でした。最新のYaws 1.89に上げるとこの関数が利用可能でした。
2011年04月07日
2011年03月31日
Jenkins-CIのプロジェクト設定はこんなです
Simpledungeonについて稼働中のJenkinsはこちら。
Jenkins-CIとEUnitで自動テストを構築の記事で、Jenkins-CIでErlangのテストを開始したよと書いたのですが、プロジェクト設定の画面があったほうがいいかなと思い、このエントリを書いてます。
antとかでビルドするようですが、よく知らないのでシェルでmakeとmake testを実行してます。最初Trac LightningのHudsonでやってたときは、nmakeとnmake testとしていました。
Jenkins-CIとEUnitで自動テストを構築の記事で、Jenkins-CIでErlangのテストを開始したよと書いたのですが、プロジェクト設定の画面があったほうがいいかなと思い、このエントリを書いてます。
antとかでビルドするようですが、よく知らないのでシェルでmakeとmake testを実行してます。最初Trac LightningのHudsonでやってたときは、nmakeとnmake testとしていました。