多倍長整数演算ライブラリ 多倍長整数を利用した銀行サンプルスクリプト 2007/02/03 created by Rayce based on mod2273 ------------------------------------------------------------------------------------ (1)文字列処理に関して mod1782でstrcut関数を実装した当初はstrcut一つあればほぼ全ての文字列処理を実現出来る (script/sample/npc_test_library.txt)という考えから、これ以上の文字列処理関数は不要 だと思っていました。 しかしeAthenaでの幾つかの反応や自分で文字列処理を扱っていくうちに、より柔軟な関数を 用意するべきだという考え方に変わりました。 そこでstrcutに代わる関数としてsubstrの実装が必要だという結論に至りました。 またgotocountのリミットによるinfinity loopが発生することが多々あったので、文字列長を 取得するgetstrlen関数(eAthenaと同名)くらいはあってもいいだろうということにしました。 拡張整数ライブラリであるfunc_longnum.txtでは、文字列処理にsubstrとgetstrlenを利用して いますので、以下のコードをscript.cに追加しなければ作動しません。 substr関数の仕様は基本的にPerlライクです。 開始位置と取得個数に負の値が使えます。 int buildin_getstrlen(struct script_state *st); int buildin_substr(struct script_state *st); {buildin_getstrlen,"getstrlen","s"}, {buildin_substr,"substr","si*"}, /*========================================== * 文字列長を返す *------------------------------------------ */ int buildin_getstrlen(struct script_state *st) { char *str = conv_str(st,& (st->stack->stack_data[st->start+2])); push_val(st->stack,C_INT,strlen(str)); return 0; } /*========================================== * 文字列の任意の部分を取得する *------------------------------------------ */ int buildin_substr(struct script_state *st) { char *str; int len, offset, count; str = conv_str(st,& (st->stack->stack_data[st->start+2])); offset = conv_num(st,& (st->stack->stack_data[st->start+3])); len = strlen(str); if(offset < 0) // 開始位置が負なので末尾から位置を計算 offset = len + offset; if(offset < 0 || offset >= len) { push_str(st->stack,C_CONSTSTR,""); return 0; } if(st->end > st->start+4) { count = conv_num(st,& (st->stack->stack_data[st->start+4])); if(count > 0 && offset + count > len) count = len - offset; // 文字列長を超えるので補正 else if(count < 0) count = len - offset + count; // 個数が負なので末尾から個数分削る } else { count = len - offset; // 引数省略時は最後まで } if(count > 0) { char *buf = (char *)aCalloc(count+1, sizeof(char)); memcpy(buf, str+offset, count); push_str(st->stack,C_STR,buf); } else { push_str(st->stack,C_CONSTSTR,""); } return 0; } ------------------------------------------------------------------------------------ (2)infinity loopに関して 拡張整数の演算にあたり、内部でループを多用しています。 そのためgotocountをあっという間に消費し尽くしてinfinity loopになります。 gotocountは1ループで1つ消費されるため、例えば128×128の二次元配列をループ処理する だけで確実にinfinity loopになるわけです。 これはある意味スクリプトエンジンの誤認として捉えてもよい問題でなので、gotocountは ある程度増やしても問題ありません。 デフォルトのままだと大きな桁を扱う場合に高い確率でinfinity loopになってしまうので script_athena.confのgotocountの値を適度に変更してください。 拡張整数同士の除算は見ての通りループの量がかなり多くなっており、そのためcmdcountが リミットに達することもありました。 cmdcountを増やすのはあまり好ましくないので、その対策として「処理分割」という テクニックを施しています。 「sleep 1;」を挟むことで処理をそこで一旦中断し、負荷を分散させることを意味します。 スクリプトを書いていて非常に重くなりそうな処理が長く続きそうなときは、処理分割を 行うことでラグを防止できると考えられます。 ただしsleepは実行時にキャラクターをデタッチしてしまうので、RIDが必要な場合は必ず あらかじめRIDを保存しておき、処理終了後に再度アタッチしなければなりません。 少々煩雑にはなりますが、スクリプトによる多大な負荷を分散できることは大きいと思います。 ------------------------------------------------------------------------------------ (3)致命的なバグ 現在最新のAthena(mod2273)に致命的なバグがあることが発覚しました。 return命令を用いて文字列型の関数依存変数を返す場合(return '@buf$; など)、 ダングリングポインタが発生します。 運が良いとサーバクラッシュしてくれますが、大抵の場合メモリマネージャで保護されていて 予期しないデータが返ってくることもあるので必ず修正してください。 int buildin_return(struct script_state *st) { if(st->end>st->start+2){ // 戻り値有り struct script_data *sd; push_copy(st->stack,st->start+2); sd = &st->stack->stack_data[st->stack->sp-1]; if(sd->type == C_NAME) { char *name = str_buf + str_data[sd->u.num&0x00ffffff].str; if( name[0] == '\'' && name[1] == '@') { // '@ 変数を参照渡しにすると危険なので値渡しにする get_val(st,sd); + if(isstr(sd)) { // 文字列の場合はaStrdupしないといけない + sd->type = C_STR; + sd->u.str = (char *)aStrdup(sd->u.str); + } } else if( name[0] == '\'' && !sd->ref) { // ' 変数は参照渡しでも良いが、参照元が設定されていないと // 元のスクリプトの値を差してしまうので補正する。 sd->ref = &st->script->script_vars; } } } st->state=RETFUNC; return 0; } ------------------------------------------------------------------------------------ (4)最後に 開発にパッチを出すつもりはもうないので上記のソースコードに関してはご自由にどうぞ。 致命的なバグに関しては1ユーザーの義務として報告だけはしておきます。