eratohoまとめ

Emueraについての補足

このページの趣旨

主に妊)|д゚)の人がEmueraに関する様々な補足を行うためのページです。
(追加してほしい内容等あったらIRCまでどうぞ)
最新版および公式wikiはこちら

Q&A

Q:なぜEmueraは起動に時間がかかるのか
A:Emueraは起動時に全てのコードを読みIF~ENDIFやCALL等の対応のチェックをしています。そのため起動に時間を要します。しかし、このおかげで実際のスクリプトの処理はeramakerより速くなっています。
(描画処理が律速なので実感することはできませんが)
さらに起動時にエラーチェックできるのもこの実装のおかげです
ここらへんは完全に起動速度と起動後の処理速度等がトレードオフになってます。
1.753以降の文字列読み込みパース処理の高速化で起動時間は大幅に改善されました

Q:Emueraの描画遅すぎ
A:実のところC#はあまりグラフィックの描画に優れた言語ではありません。そのためこれは半ば仕様と言えます。現在のGDI・GDI+ではなくOpenGLやDirectXを使えば速くなる「かも」しれませんが、そこまで手を出す余力は作者にはありません。挑む勇気のある方がいればやってみるといいかもしれません。

以下、Workaround:
多くの場合はデフォルトの設定でそれなりの速度になるはずですが、RadeonHD系はPowerPlayの仕様によってGDIの描写が遅いので、GDI+を使うGraphics+イメージバッファ使用の設定にした方が速くてちらつきの少ない描写になるようです。
(HD4XX0系ならCCCでクロック固定という方法もあるが、素人にはおすすめできない)
また、古いマシンではFPS設定を落とすのも効果的です。

Q:○○実装して!
A:グラフィックの表示、音の再生、描画処理を増やすもの、基本実装を作り替えるもの:ぶぶ漬けいかがどす?
 その他:eramakerとの互換性が保てない場合→残念ながら却下
     それ以外のケース→実現可能か判断してからお答えします

Q:PRINTC系にBOLD指定のFONT使うとずれる…
A:え~、誠に申し訳ない話になりますが仕様です。直すには根本的なところから作り直さないといけないのでかなり厳しいです。
(1/15補足:Emu)の人と議論した結果、Emu)の人が時間取れたらこの部分に手を加えるということになりました)

Q:_Repalace.csvでできる内容と書式は?
A:以下の通りです
  • お金の単位
内容:所持金の表示でのお金の単位を指定
書式:お金の単位, <単位に使う文字>
  • 単位の位置
内容:所持金の単位を数値の前に付けるか後ろに付けるか
書式:単位の位置, <前or後>
  • ファイル読み込み中の表示
内容:起動読み込み時に詳細表示しない場合に表示される文字列を指定
書式:起動時簡略表示, <表示する文字列>
  • SHOPで販売アイテムとして認識するアイテム数
内容:SHOPで販売アイテムとして取り扱うITEMの数値の上限を指定
書式:販売アイテム数, <上限ITEM番号>
  • DRAWLINEに使う文字
内容:DRAWLINEの線に使う文字(列)を指定
書式:DRAWLINE文字, <使う文字列>
  • BARで使う文字の指定
内容:BARに使う文字の指定(文字列を用いた場合、正常動作を保証しません)
書式:BAR文字1, <値の範囲内の表示に使う文字>
   BAR文字2, <値の範囲外の表示に使う文字>
  • システムメニュー文字列
内容;読み込み終了後のシステムメニューの文字列を指定
書式:システムメニュー0, <最初からはじめるを置き換える文字列>
   システムメニュー1, <ロードしてはじめるを置き換える文字列>
  • COM_ABLE初期値
内容:@COM_ABLEXXがない場合にその返り値をどうするかを指定
書式:COM_ABLE初期値, <0 or 1>
  • 汚れの初期値
内容:STAIN変数の初期値を指定
書式:汚れの初期値, <各配列要素の初期値(/で区切る)>
  • TINPUT系の時間切れ時の表示内容
内容:TINPUT系で時間切れになったときに表示する文字列
書式:時間切れ表示, <表示する文字列>

Emueraの仕様の補足

CONTINUEの処理に関して

ループ処理に欠かせないCONTINUEとBREAKの2命令
しかし、ループから強制的に抜けるBREAKの単純明快さの一方、
CONTINUEの処理は意外と勘違いされている。
この勘違いはDO~LOOP構文で出現する。
DO
 (処理)
 CONTINUE
LOOP 0
このコード、CONTINUEがあるので一見無限ループにも見えるが、
実はDO~LOOPの中は1回しか実行されない。
これはCONTINUEが本質的にはループの最後に飛ぶという仕様だからである。
つまり、このループの処理は
CONTINUE→LOOP 0の判定→判定結果は偽なのでループを抜ける
ということになっている。

文字列とRETURNFに関する面妖な仕様

ユーザー定義の式中関数では識別子#FUNCTIONSを宣言することで
RETURNFで文字列を返す関数にすることができる。
ところが、このRETURNF文字列に関してはやや面妖な仕組みになっている。
RETURNFで文字列を返す場合の書式は、
PRINT系での表示や文字列代入等とは異なるルールになっている。
具体的には以下のとおりになる。
文字列の種類 書式
単純文字列 "文字列" RETURNF "テスト"
文字列変数 変数名 RETURNF STR:0
FORM文字列 @"FORM文字列構文" RETURNF @"%STR:1%{A:2}\@(LOCAL) ? あ # い\@"
最後のFORM文字列については@"~"を使わないとエラーになったりするので、
特に注意が必要である。

三重配列とその制限

Emueraでは三重配列を用意している。
TA:XX:XX:XX および TB:XX:XX:XX
配列要素は100×100×100で計100万個である。
なお、制限として配列サイズを変えることができない
1732aより100万個を上限に配列サイズを変更可能に

インクリメント、デクリメントの拡張

前置・後置、文中での使用等色々対応してあります。
ほぼ普通のCプログラムと同様の感覚で使えるはずです。

文字列演算

最近のEmueraでは文字列演算で*=を使えるようになっていたりする。
使い道があるかどうかは実装した本人にも謎。

PRINTCPERLINE()の意味

configに「PRINTCを並べる数」という項目があるが、実はこれは調教コマンドの表示ぐらいでしか使われていない。
つまり、スクリプトからPRINTC系を使った場合は設定に応じた自動的な改行は行われない。
それでもUSERCOMのコマンドなんかを調教コマンドと同じように表示したい!という場合にこのコマンドの出番となる。
ようは、このコマンドで設定を読み取り、それにあわせて改行を行うようなコードを書けばよいのである。

DO~LOOP命令

書式:
DO
  (命令)
LOOP (条件)
補足:
WHILE~WENDループは最初にWHILEの条件を満たさないと中の処理は一切行われない。
それに対し、DO~LOOPでは1回ループ内の処理を行った後LOOPの行でループするか否かを判定する。
最低でも1回は処理が行われていることが確定しているなら、DO~LOOPを使った方がスマートなコードになる。

REUSELASTLINEの仕様の変化

REUSELASTLINEは最初に実装された[私家改造]1.52q rev.2と本家に取り込まれた1.60以降で仕様が異なっている。
具体的には元々は、
  • 前の行を消して、次の行を追加する時に消去される行を表示する
だったものが
  • 次の行を追加する時に消去される行を表示する
に変わっている。

そのため、INPUTに対し、無効な入力の時に入力した値を消そうとした場合、
元の仕様であれば、
REUSELASTLINE (警告文)
のみでよかったが、現在は
CLEARLINE 1
REUSELASTLINE (警告文)
とする必要がある。

なお、@USERXXX系の処理では今までどおり
REUSELASTLINE_ (_は半角スペース)
のみで動作するようになっている
(この場合の処理は内部的に行われるのでCLEARLINEが必要ない)

なお、この仕様の違いについては、また変えるのも混乱を招くということで元に戻さないことが決まっている(作者同士の直接対話によって決定)

Bit演算系命令

使えると便利だが、とにかくややこしいことに定評のあるBit演算。
Emueraでは1pNのような2進法表記が実装されたり、多くのBit演算向け演算子が追加されたりと格段にBit演算をやりやすい実装にはなっている。
とはいえ、プログラミング経験者ならともかく、そうでなければこの実装でもつらいというのが実際の所。
そこで、以上の方法とは別に、GETBITSETBITCLEARBITINVERTBITを用意している。
書式:
GETBIT((操作する変数)、(ビット位置))  
…対象変数の2^(ビット位置)のビットを取得
(GETBITは文中関数として使える)
SETBIT (操作する変数)、(ビット位置)  
…対象変数の2^(ビット位置)のビットを1にする
CLEARBIT (操作する変数)、(ビット位置) 
…対象変数の2^(ビット位置)のビットを0にする
INVERTBIT (操作する変数)、(ビット位置) 
…対象変数の2^(ビット位置)のビットを反転(0→1、1→0)にする

この方法の利点は、
  • いちいち対象になるビットの値を計算する必要がない。
  • ビット位置の指定は1pNと違い変数が使えるので処理の一元化を狙える。
欠点は
  • わかってる人からすればまだるっこしい。
  • 演算子等を使った方が格好良く見える
である。

Shift-JISとUnicodeでの文字列処理の違い

Emueraでは基本的に文字列長取得の命令が3つ用意されている。
STRLEN (文字列)
STRLENS (文字列式)
STRLENFORM (FORM構文)
これらはいずれも、Shift-JISとしての文字列長を取得している。
これに対して、
STRLENU (文字列)
STRLENSU (文字列式)
STRLENFORMU (FORM構文)
はユニコードとして文字列長を取得する。
最大の違いはShift-JISは漢字1文字を2文字とカウントするのに対して、ユニコードでは漢字も1文字でカウントするところである。

LOCAL変数であっても避けるべきこと

LOCALは非常に使い勝手のよい変数であるが、それでもERBの仕様上避けるべきである事象は存在する。
  • 同じイベント関数間を跨いだ使用
 イベント関数はERBの仕様により複数定義してよいことになっている。
 これはLOCALも全ての関数で共用になることを意味しており、
 同じイベント関数間を跨ぐ形のLOCALに依存する処理は
 他の同名関数の割り込みによって破綻する恐れが極めて高い
 こういう目的にはTFLAG等を使うことをおすすめする
  • LOCAL@~の使用
 LOCAL変数はその関数の外からLOCAL@で参照ができるようにはなっている
 しかし、これは「デバッグのため」の措置であり、
 値を確認するだけの場合であれば特に問題にはならないが、
 代入し始めるとバグの要因にもなるし、デバッグも難しくするため
 通常のコードでこれを使うことは全く薦められない
 (将来的にはLOCAL@~は読み込み専用にしたいところ)
 それをしないで済む実装系を考えることをオススメする

CALLFと疑似セッター

 CALLF命令は
式中関数を呼び出し、その返値は無視する
 と使い道がなさげな命令である。
 しかし、次のような使い方が存在する
@SET_VALUE(ARG, ARG:1)
#FUNCTION
RETURNF VALUE("SET", ARG, ARG:1)

@GET_VALUE(ARG)
#FUNCTION
RETURNF VALUE("GET", ARG)

@VALUE(ARGS, ARG, ARG:1)
#FUNCTION
IF ARGS == "GET"
  RETRUNF LOCAL:ARG
ELSEIF ARGS == "SET"
  LOCAL:ARG = ARG:1
ENDIF
 こうすると、関数@VALUEのLOCALにLOCAL@を使わずに参照可能になる。
 一文字変数を使わずに複数の関数をまたいで配列を参照したい場合に有用ではある。

色々小難しい小話

私家改造でのスクリプト処理高速化の裏話

最近の私家改造では、スクリプト処理の高速化をはかったわけだが、
その中身は非常にトリッキーなものである。

これまでのスクリプト処理は簡単に書くとこんな感じである
(メインルーチン)→(スクリプト実行開始)→(処理準備)
→(スクリプト実処理)→(無限ループ判定ルーチン)
→(メインルーチン)
これが1行ごとに繰り返されていたのが既存のコードである。
見ればわかるように、これは非常に効率の悪い処理である。
これを現在の私家改造では以下のように作り替えた。
(メインルーチン)→(スクリプト実行開始)
→{(処理準備)→(スクリプト処理)→(無限ループ判定)}×n
→(メインルーチン)
ポイントは2行目。こちらの処理ではメインルーチンに戻る必要が生じない限り、
スクリプトの実行を延々とループさせることで、関数の呼び出し回数を減らすことに成功している。
さらに、一部の使用頻度の高い処理については特別な処理パスにすることで、
より処理時間を減らすことでREPEAT~RENDなどの高速化に成功している。

Emueraのエラーチェックの内容

Emueraは起動後コードを読み込み、エラーがあればそれを表示してくれる。
この表示には2パターンあり、
  • ファイル読み込み時に表示されるエラー
    • "*****.ERB読み込み中…"の下に表示されるエラー
  • ファイル読み込み後の構文チェックで表示されるエラー
    • ファイル読み込み終了後、タイトル表示までに表示されるエラー
であり、これはコードチェックのタイムスケールの違いによって区分される。
具体的には以下のように順番にエラーをチェックしている。
  • ファイル読み込み時のエラーチェック
  • 1 解釈可能な行かの確認におけるエラーチェック
    • a.[SKIPSTART]や#で始まる行
      • 正しく用いられているか確認、エラーならエラー表示
    • b.関数宣言行
      • 宣言の書式が正しいか確認、エラーならエラーを表示
    • c."+" or "-"で始まる行
      • 前置のインクリメント・デクリメントであるか確認、そうでないならエラー表示
    • d.命令・変数で始まるはずの行
      • まず、最初の来る文字列が命令もしくは変数かを確認
      • どちらでもなければエラー表示
      • そもそも最初に来る文字列が不正("\"や"$"などのあるはずのない記号を含んでいる)な場合は例外を吐いてここで読み込み打ち切り(1731t以降では打ち切らないように変更)
    • e.変数への代入行と予想される場合
      • 代入の形になっているかチェック、なってなければエラーを表示
  • 2 ファイル読み込み後の構文チェックで行われるエラーチェック
    • a.宣言された関数の中身のチェック
      • #FUNCTION宣言された関数なら、使えない命令がないかのチェック
      • 「起動時に引数を解析する」場合は引数が正しい書式になっているかチェック
      • IF~ELSEIF~ELSE~ENDIFやREPEAT~RENDなどの対となって使われる命令の対応関係のチェック
      • CALLなどの飛び先チェック(CALLFORMのような実行時に飛び先が決定される命令はチェックせず)

PRINT系とPRINTFORM系の処理量の違い

変数の中身を含まない平文を表示するときにはPRINT系とPRINTFORM系どちらが内部処理量が少ないか。
答えは(問題にするまでもなく予想はつくだろうが)PRINT系である。
PRINT系は引数をそのまま表示するため非常に処理量が少ない。

PRINT系
引数チェック:引数をそのまま文字列として代入
命令実行内容:引数を取り出し、そのまま表示用関数に渡すだけ

PRINTFORM系
引数チェック:引数を取り出し、変数の存在などをチェックしstring.Format書式に対応した文字列として保存
命令実行内容:引数を取り出し、書式に変数の中身を入れる処理を行い、表示用文字列を作成して表示用関数に渡す

このようにPRINT系とPRINTFORM系では平文だとしても処理量には雲泥の差がある。

IF文の条件判定の順番

次のようなIF文を考える
IF A && (B || (C && D))
この構文において条件A~Dはどのような順番で判定されるだろうか?
(簡単化のため短絡評価は考えないものとする)
単純に考えれば()内が優先されるので、C→D→B→Aとなると思われるが、実際のEmueraの処理ではA→B→C→Dの順で判定される。
なぜこのようになるかと言うと、これはEmueraでの構文の処理法に由来する。
上の構文はEmueraでは次のように解釈される。
1, Y = C && Dとして、
   IF A && (B || Y)
2. X = B || Yとして、
   IF A && X
で、実際の処理は次のようになる。
1. A && Xを評価
 1a. 左辺のAを評価
  1b. 右辺のXを評価→X = B || YなのでB || Yの評価へ進む
2. B || Yを評価
 2a. 左辺のBを評価
 2b. 右辺のYを評価→Y = C && DなのでC && Dの評価へ進む
3. C && Dの評価
 3a. 左辺のCを評価
 3b. 右辺のDを評価
以上より判定される順番はA→B→C→Dとなる。
考えればわかるが、判定結果は正しくなるため、この方法には何の問題はないし、短絡判定は左辺優先なのでむしろこうした方が合理的なのである。
(そもそも左辺が偽なら右辺の()内は見る必要がないので処理が減るし、左辺が真の場合でも行われる処理は右辺の()内を先に評価した場合と変わらないため、総合的に見れば処理量は減ることになる。
そういう意味ではEmueraのみを想定しているコードではエラーにならない範囲で最も成立しにくい条件を最初に持ってくるというのは極限論的には無意味なコーディングではない。
もちろん現実的な話をするなら、Q&Aに書いたようにEmueraの処理の律速は描画処理なので、ここにこだわったところで処理速度に有意な差が出ることはない。)

なぜVARSETは速いのか

変数配列全体を指定した値で初期化するVARSET命令は同様の実装である
REPEAT N(配列の大きさ)
   A:N = 0
REND
に比べて、処理速度にして軽く見積もっても1万倍以上は速い。
(VARSETは配列要素数が100万個でもほぼ一瞬で処理が終わる)
これにはちゃんとした理由がある。
VARSETは呼び出されると、内部で与えられた配列を呼び出し、指定された値を代入するループ処理を行う。
この内部で行われるループ処理は実行時に最適化され、非常に高速に実行される。
一方、REPEAT~RENDの場合は以下のような処理が行われる。
1. REPEATに入る
(最初ならCOUNTを0にセット、
RENDから飛んできたらCOUNTに1を追加し終了判定)
2. A:COUNT = 0を実行
3. RENDの行は何もせずにREPEATの行に戻る
(この1~3が設定された回数繰り返される)
このようにREPEAT~RENDの場合は変数への値代入処理だけでなく、3行のコードを駆動するための処理が増える。
結果、単純に処理量が増えるだけでなく、処理の複雑化により実行時の最適化も難しくなるため非常に時間がかかってしまう。
一般に、ERBで書かれたスクリプトと同じ処理をする内部命令を実装すれば、内部命令の方が格段に速くなることがほとんどである。
(特に配列に対してループを回すような処理であれば)
ただし、有意な差が出るような長く重いコード自体少ないのでこの違いを体感できる事例はそれほど多くはなかったりする。

コード読み直しの仕様とメモリ消費量の増加

今のEmueraにはERBファイル再読込の機能があるが、これは使う度にメモリ消費量が増加していく。
これには避けられない要因がある。
EmueraのERBコードの処理では、起動時に全てのコードを一行ずつ読み込み、クラスに押し込めて管理しているが、このとき、今いる行の次の行の情報を保持するようになっている。
さて、ERBの再読込はシステム側の入力待ちだけでなくINPUTなどの入力待ち命令中でも行えるが、
この時今いるINPUTを含む関数が記述されているファイルが再読込されてコードの情報が置き換えられるといったいどうなるか?
答えは簡単である。
Emueraは実行するコードを見失いエラーを出す。
そのため、再読込時には既存のコードを捨てることができない。
よって、すでに読み込んであるコードとは別に新たにコードをスタックする仕組みになっている。
そして、関数の定義情報を書き換えることで新規コードの方を実行するようになっている。
この仕様により動作中の関数は一旦終了し再度呼ばれるまでは古いコードに従い動作することになるが、これは防ぎようがない事象である。
また、再読込後の古いコードであるが、再読込ファイルのコードの追加後は新旧の区別が困難であるため、消さずに放置している。そのため、再読込の度にコードのスタックが増大しメモリ消費量が増えていくことになる。
そのため、開発中等で必要になる場合以外は使わない方が安全な機能ではある。

タグ:

+ タグ編集
  • タグ:

このサイトはreCAPTCHAによって保護されており、Googleの プライバシーポリシー利用規約 が適用されます。

最終更新:2010年07月06日 23:56