第70回 How To STL(STL活用講座) 99/9/5

そもそも、事の発端は、yaneuraoGameSDKがVC5.0ではコンパイルできないということだったように思う。原因はわかっている。VC5.0までは、テンプレート機能がサポートされとらんのだ。C++は、もう言語仕様は固まったのかと思っていたら、ここのところに来て、STL(Standard Template Library)やら何やらが標準化された。現段階で、どこまでがドラフトなのかは、この手の動向にまったく興味のないやねうらおの知るところではないが、どうせ、VC5.0でコンパイルできんのなら最初からSTL使うんだったと、反省しきりである。

STLは、非常にエレガントで泥団子のようなC言語にはもったいないものがある(また、反感買いそうやけどね) そんなわけで、yaneuraoGameSDKは、STLで、全面的に書き直しを始めた。それが、先週の月曜日のことである。参考書は、翔泳社の「STL標準講座」 はっきり言って、こんな本、買うんじゃなかった。いま考えてみれば、オンラインヘルプだけで十分だった。なんとゆーか、テンプレートは、あまり見慣れていないもんで、STLはめっちゃ難しい印象を持っていたのだが、そんなことはなかった。たとえば、vectorが可変長配列のためのテンプレートである。

#include <vector>
using namespace std;

typedef vector<MyClass> MyClassArray; // これで、MyClassの可変長配列いっちょあがり!

たったこれだけのことである。本なんていらない。使うだけならば、サンプルを見れば十分だし、STLのコンテナは一般的なデータ構造だし、STLのアルゴリズムの概念は、LISPの関数閉包に似ているし、こんなものならば見ただけでわかったのに!

この本、επιστημη(えぴすてーめー)が監修しており、帯にはでかでかと「C++のコードが100分の1に短くなる」だとか書いてあるが、すっかり馬鹿を見た。プログラミングのメーリングリストで、この人、良く見掛けるし、技術面ではそれなりに信頼もしてるけど今回だけはちょっと騙されたような気がする。えぴすてーめーよ、てーめーは、何をどう監修してやがるんだ。コンチクショウ!(一応、買ったから文句ぐらい言わせて!) まあ、入門者には、この本は、わかりやすくていい本なんだけどね。

そんなわけで、5日間、会社から帰ってきてずーっと、STLで書き直していたのである。まったくもって非生産的な時間である。そして、やっとコンパイル、デバッグ(これがまた大変なのだわ)、ふと見ると実行ファイルのサイズが192K→260Kに。ががーん。がぶ飲みしたいとき、がぶ飲みしたいとき、5日間かけてやっと書き直したプログラムが、実は改悪だったとき!(明治がぶ飲みミルクティーCMより)

計算したら、ベクタークラス一つにつきだいたい5Kぐらい実行ファイルサイズが増えている。まあ、改悪ってことはないのかも知れないけど、きっと、いまRPGみたく教会に行ったら、「やねうらおよ、久方ぶりじゃの。どれ。お主の経験値を調べてしんぜよう…。……。……。前回ここに来たときから経験値は32あがっておるぞ。しかし、残念じゃが、お主は次のレベルにまであと25312の経験値が必要じゃ。どうも、疲労が溜まっているようだから、ここでゆっくりして行くが良い」とか言われてしまうんだろうな。


第71回 割り込みイベント処理(timeBeginPeriodの周辺) 99/9/9

なんと、VC++5.0でも、HRESULTだけwindows.hにdefineしておけば、yaneGameSDKのコンパイルが出来るようである。いまごろ、そんなんゆうても遅いわー。もう、ぜーんぶSTLで書きなおしちゃったもんねー。(書きなおしちゃったもんねーやあらへんがな…)

今回は、割り込み処理についてである。ちょっと高度な内容だし、実は、音楽関係でお世話になってるGooさんにだけメールで説明するつもりだったのだけど、メールで長々とやりとりしてるとホームページの更新が停滞して、「やねうらおってまだ生きとんのかい!?」とか、まことしやかに噂されると、悲しくて悲しくて毎日の食事が7回に増えて食費に困るのである。(なんや食費だけの問題かい…) ちなみに、やねうらおの一日の食事は4回である。会社帰りに食べて、家に帰ってからも食べるからである。そもそも江戸時代は、2回だったと聞いているし、文明の発展と共に食事の回数というのは増えて然るべきでないかというのが、やねうらおの持論である。(誰も信じちゃいねぇ) そんなわけで、2300年の未来からタイムマシンでやってきたやねうらおは一日の食事の回数が4回なわけである。(誰が未来からやってきたって??)

まあ、そんなことはどうでもいい。MIDI再生ルーチンのように、定期的な割り込み処理を行なうプログラミングには、いろいろノウハウが必要である。まず、timeSetEvent。こいつの精度の問題である。Windows95/98では、きちんと、delay[ms]ごとに1回呼び出されないのである。

「えっ?そんなのtimeBeginPeriodでタイマーの分解能を上げれば良いんでないの?」だとか、「timeSetEventのuResolution(第2パラメータ)を0にすれば良いんでないの?」だとか言ってくる人が多いのだが、どちらも違う。文献によってはそう書かれているもの、そう読めるものもあるが、完全に間違いである。

そんなことは、実際に実験してみればすぐにわかる。timeSetTimeのCALLBACK関数のなかでtimeGetTimeした値を出力し続けてみれば良い。きちんと連続した数値になっていないことは、一目瞭然である。(負荷状況で異なるが、無負荷の状態でも1%ぐらい呼び出されるべき時間に呼び出されていない) よって、CALLBACKルーチンが呼び出された回数で、タイミングを取ってはいけないのである。CALLBACKルーチンのなかででも、timeGetTimeしなくてはならない。

このへん、WindowsNTでは事情が違う。分解能(安定性)はともかく、一定時間内の平均呼び出し回数は保証される。よって、CALLBACKルーチンのなかで、呼び出しごとにtimeSetEventで設定した、delay時間だけ経過していると仮定して良い。

ということは、NTならばCALLBACKルーチンのなかでtimeGetTimeしなくて良いかというと、そうも言えない。何らかの事情でマシンに負荷がかかると、それが終わったあと、怒涛のごとく割り込みルーチンが一気に呼び出されるのである。ということは、timeGetTimeした値と、現在の処理位置(MIDIならば再生位置)からあまりにも遅れをとっているようならば、データを間引くような処理が必要である。また、遅いマシンのことも考慮して、一回の割り込み処理回数が一定処理量以上にならないようにリミッタをかける必要もある。

それから、この文章でtimeGetTimeと何度も書いているが、高精度タイマ(いわゆるマルチメディアタイマ)が存在しない環境では、こいつの分解能が恐ろしく悪いので、timeGetDevCapsで調べて、あまりにも精度が悪いようならば、GetTickCountを使うような処理が必要である。さらに、timeGetTimeと言っても、実際に内部的にはtimeGetTimeはtimeGetSystemTimeを間接的に呼び出すので、そのオーバーヘッドを軽減するためにはtimeGetSystemTimeを利用すべきである。

まず、ここまでが割り込み処理の常識。

それでは、timeSetEventの分解能の指定は、timeBeginPeriodと無縁かというとそうでもない。まず、注意が必要となるのは、timeBeginPeriodの実装である。オンラインヘルプの注釈、

Remarks
Call this function immediately before using timer services, and call the timeEndPeriod function immediately after you are finished using the timer services.

You must match each call to timeBeginPeriod with a call to timeEndPeriod, specifying the same minimum resolution in both calls. An application can make multiple timeBeginPeriod calls as long as each call is matched with a call to timeEndPeriod.

これを読む限りでは、timeEndPeriodと1対1に対応させれば、いくらでもネスト出来るように読み取れる。しかし、それは甘いのだ。そんな実装にはなっていない。なっていないにもかかわらず、あまり誰も知らないのはそもそも、Windows95/98では、この値は、初期状態で1だから、ダミー関数だと思われている節があるのだ。実際に、これはダミー関数だと書いてある書籍もあるらしい。嘘言うな!と言いたい。WindowsNTで、timeBeginPeriodせずにループ中でtimeGetTimeの値を吐き出してみれば、それが10([ms])ぐらいの間隔の離散的な数値をとることがわかる。timeBeginPeriod(1);としておけば、timeGetTimeは、1([ms])間隔の連続的な数値となる。そうなのだ。断じてtimeBeginPeriodはダミー関数などではない。

ところが、ネスティングの処理は特殊だ。実際、timeBeginPeriod(1);を無限ループにして呼び出してみれば良い。メモリマネージャで見ても使用メモリは1バイトも増えない。本当にネストできるのならば、timeEndPeriodが呼ばれたときに、前の分解能に復帰させるために、前回のtimeBeginPeriodの値を次々にスタックに積んでいかねばならないから、使用メモリは増えないとおかしいのである。これが増えないということは、つまり、単純なネスティング処理ではないのだ。

また、オンラインヘルプには、

Return Values
Returns TIMERR_NOERROR if successful or TIMERR_NOCANDO if the resolution specified in uPeriod is out of range.

と書いてあるが、これすらもで、範囲外に対してTIMERR_NOCANDOは返さない。逆アセして調べたが、設定されている値(逆アセしたところ11と比較している)以上ならば、何もせずにTIMERR_NOERRORを返す。というか、この記述では、何がout of rangeなのか明確でないので、仕様と言えばそうかも知れない。

翔泳社のWindowsAPIバイブル3のP.898では、timeBeginPeriodの説明として「最小タイマ精度をミリ秒単位で設定します。この最小精度は、timeEndPeriod関数の場合にも同じ値を使う必要があるため、タイマ識別子のようなものと考える必要があります」とある。That’s right!つまり範囲内(実験した環境では1〜10のはず)の識別子を使って、timeBeginPeriodとtimeEndPeriodは一対一に対応させる必要があるわけだ。このへんは、timeEndPeriodをトレースすれば、この引数に対応するものをテーブルから持ってきて分解能を設定しなおしていることがわかる。ちゅーことは、

timeBeginPeriod(1);
timeBeginPeriod(1);
timeEndPeriod(1);
timeEndPeriod(1);

なんてやった場合、おかしいことになる。(どうなるかは自分の目で確かめてね!) だから、timeGetDevCapsで得られた最小値を使ってクラスのコンストラクタでtimeBeginPeriodし、デストラクタでtimeEndPeriodするようなプログラムを組んではいけないのである。よくそうやっている人がいるけど、このようにVC++6.0のオンラインヘルプに嘘書きまくってある状況下なので、それも致し方なしということかも知れない。

では、timeSetEventとtimeBeginPeriodの関係というと、詳しく調べたわけではないので、間違っていたら申し訳ないが、ひょっとしたらtimeSetEventの第2パラメータを引数としてtimeBeginPeriodを間接的に呼び出しているのではないか?ということである。それが証拠に、timeSetEventで設定されたCALLBACKルーチンのなかでtimeGetTimeすると、timeSetEventのuResolutionで設定された間隔で値が返ってくる。

ちゅーことは、timeSetEventとtimeBeginPeriodを併用したりしていると知らず知らずのうちにネストしていることになっていたりしていけないんでないかなー。まー、timeSetEventでCALLBACKされてる最中にtimeBeginPeriodしないんなら、それでいいと思うんだけどね。

ちゅーことで、ここまでが割り込み処理プログラミングの必須知識。まー、こんだけきちんと理解してる人って、滅多にいないんだろうけど…。それにしても久しぶりに、真面目に書いたら疲れちゃったよ。(2300年の未来からやってきたとかゆーのが、真面目なんか?>俺...)


第72回 バランス感覚(yaneGameScript2000の言語仕様について) 99/9/10

yaneGameScript、あれから自分でサンプル書いてるときに、こんなもん使えっかー!と思って、&&,||,%%,++,--,for,do,whileを速攻で追加した。これで、さらにC言語に近づいたというわけだ。構造体については、サポートするかどうか検討中なのだが、下手な方法でサポートすると、コンパイルの遅い(コンパイルは、実行時に行なっているので、コンパイルが遅いことは致命的)ものになってしまうので、考え中である。また、何かの弾みに自分のスクリプトに無性に腹が立ったならば追加したい。

それはともかく、yaneGameScriptについて初心者の人からいろいろメールが来る。ド素人の人も含まれてはいるが、ある程度マニアの人もいて、「最近、JavaとかyaneGameScriptとか、C言語ライクな言語って多すぎません?いっそ、PASCALのように

    procedure calc(var  x,y : integer)
    begin
        〜

なんて書けたほうがカッコイイと思うのですが」なんて趣旨のメールもある。僕にだってプログラミング言語については、人十六倍(類語:人一倍)の美意識があるからして、C言語よりPASCAL風表記のほうが美しいことぐらいわかる。TurboPascalの経験だって長いし、その体系の美しさに惹かれずにはいられない。

しかしやねぇ、それを採用(いまさら)してはどうか?という話になると、なんでやねん!と言わねばならない。まず、利用者数の違い。C言語のほうが、PASCALよりも圧倒的に利用者が多いはずだし、書店で参考書籍を探したとき、PASCALの参考書となるとDelphiのものぐらいしか見当たらない。その点、C言語の参考書となれば、数が多いし、何よりC言語の言語仕様のなかにグラフィック用のライブラリや、そういった機種依存性のあるライブラリ・関数が一切含まれていないからして、間違ってMFCの参考書でも買わない限り、機種依存な話題には一切触れていない、基礎に詳しい書籍が簡単に見つかるということである。

スクリプトとか、こういうものは、利用者に使ってもらえて初めて一人前であって、他人に使ってもらうために設計しているのに、自分の好みをそこに介在させる余地などないわけである。関数型言語でなく、Prologのような論理型言語とか、表明によるプログラミングだとか、A*(A star)探索を利用した推論言語だとか、パラダイムが根底から違うならば、比較検討に値するのだが、CとPASCALでは表記の違い以上のものがあるわけでもないし(特にサブセットを比較すればなおさらである)、PASCAL風の表記を採用することに何のメリットもないわけである。

まあ、「PASCALのほうがカッコイイ!」と言ったメールの主が、技術的に稚拙だと言うわけでもない。おそらくは、それなりのプログラマだろう。(ひょっとすると、やねうらおよりもずっと優れた技術者であることだって有り得る) しかし、それでもなお甘いなぁと思うのは、自分の好みを優先させてしまうバランス感覚のなさである。あるいは、そういった偏執性がプログラマとして技術を獲得していくためのモチベーションとなっているのだという気もしないではないのが…。


第73回 MIDIの道は、一日にしてならずぢゃ(シビアなタイミング) 99/9/14

タイミングと変換したら鯛 民具とか変換されて、ばびったがな。あたしゃ中国人じゃないんだから!それやったら、あれか。ウオッチングゆうたら、魚 珍具か!パンチングゆうたら、班 珍具か!マイッチングゆうたら、舞 珍具か!そして、豚珍漢珍一休参!と書いてトンチンカンチン一休さんか!(わけわかんないって>俺)

そんなことは、どうでもいいのだが、MIDI再生ルーチンを自作するのは難しいのである。MCIを使えば、お気楽極楽な一発再生なのだが、ループがあると停止してしまうことや、ファイルからしか再生できないこと、フェードイン/フェードアウト/テンポチェンジのような動的な操作に弱いことなど、欠点多数である。MIDI再生ルーチンと言っても、SMF(Standard MIDI Format)のヘッダーの構成については、インターネットを探せばいくらでも資料はあるし、肝心のデータ部も、メッセージ→ウエイト数の繰り返しなのでシンプルそのものである。ヘッダーさえ読み込んでしまえば、もう勝ったも同然である。そのへんについては、詳しく知りたい人は、yaneuraoGameSDKのCMIDIOutをご覧になると良い。

ところが、このウエイトを計算する部分が、案外難しい。そもそも、DirectSoundでさえ、Windows95/98では割り込み処理が甘く、長時間の連続的な演奏でテンポがずれてくるというのに、それほど正確なタイミングで再生する必要があるのかどうかは、やねうらおの知るところではないのだが、決められた時間に決められた音が出るというのは、音楽を再生しているんだから最低限の要件という気もするし、そのへんMCIによる再生は、意外としっかりしている。

一番ややこしい部分は、テンポチェンジの実装である。ウェイト数は、現在のテンポ相対で与えられるわけで、テンポチェンジに対して、どう処理するかが問題となってくる。再生ルーチンは、インターバルタイマで呼び出されているわけで、第71回でも説明したように、割り込み処理ルーチンのなかでNowPos = timeGetTime();して、その現在時刻NowPos(=現在の再生ポジション)までの分を再生しなくてはならない。ところが、その途中時刻TmpPosにテンポチェンジが混じっていたりするわけである。ということは、テンポチェンジのポイントを再生した瞬間、そこから、現在時刻まで(NowPos-TmpPos)を新しいテンポにスケーリングしなおしてやる必要がある。(NowPos = TmpPos + (NowPos-TmpPos)* NewTempo / OldTempo; ) 手抜きなシーケンサでは、このへんの処理をやっていないので、テンポチェンジを連発するとタイミングがずれてきたりする。これは、非常にダサイ。オゾン層ほどの厚みのある、やねうらおのプライドが許すはずもない。(すでに破けとるって…>オゾン層)

ところが、この割り算の誤差が問題となってくる。発生するテンポのずれは、一般に、アルゴリズム自体は正しい場合がほとんどで、計算誤差が累積して生じていると考えるのが自然である。この場合もそうで、割り算は誤差の原因となるので、これをなんとかして避けなければならない。回避手段としては、下駄履き(2^n掛けておく)させてprecisionを上げるなどである。n=16ならば、2^16=65536であって、5桁近く精度があがっているだろうし、それで不満ならn=32、それで桁があふれて困るならば小数以下を別のULONGか、ULONGLONG(無符号64ビット型整数)にでも入れるなど、実装はいろいろ考えられる。

ともかく、音楽データ(BMSデータもこの例外ではありましぇん!)にテンポチェンジはつきもので、この手の処理に疎い人は、必ずこの部分でつまずくわけである。あらかじめ、開始時刻から何[ms]のポイントでどの音を再生するのかを計算しておくのも手だが、再生しながら次の再生ポイントを計算タイプの再生方式ならば、この問題は避けて通れないのである。


第74回 Office2000アップグレード(製品版って誰が買うんやろ?) 99/9/15

あらかじめ断っておくが、やねうらおは、Office95の正規ユーザーである。

その、Office95の正規ユーザーたるやねうらおが、先日、Office2000のプレミアムを購入した。アップグレード版で3万弱だったが、これが製品版(非アップグレード版)ならば、この5万以上する。しかし、このアップグレードは、Word95とかからでもアップグレード可能なわけで、普通の人ならば、2,3千円で売られている昔のマイクロソフトの製品と、このアップグレード版を抱き合わせ買うだろうから、製品版を買う人なんていないはずである。

そこで、アップグレード版を買ってきて、さっそくインストールしようと思ったのだが、Office95は紛失してしまっていたことに気づいた。Office2000のインストールは、途中でアップグレード対象製品の入ったCDを入れろと出てきて、そこでCDを入れ替えてOKを押せば良いのだが、そこでOffice95はないもんだから、OKだけ押すと、インストール画面は終わってしまう。おや?と思ったよ。だって、チェックするためにCDのアクセスランプが付いとらんのだから。CDのランプをつけずに読み込めるっちゅーたら、CD挿入時に検出していたCDのヴォリュームラベルだけとちゃうんか?……!!…ひょっとして…。

試しに、ヴォリュームラベルを「Offpro」(Office95のCDヴォリューム名)に変更して、CD−Rにコピーしてみた。そいつを使ってインストールしてみる。途中でアップグレード対象製品の入ったCDを入れろと出てきたら、CDドライブを選択してOK。おっ!どうやら、うまくやりすごせたようである。無事にインストールは終了した。

何度でも断っておくが、やねうらおは、Office95の正規ユーザーである。また、Officeアップグレード対象商品を持っていない人がこのテクを使って、アップグレード版をインストールした場合、法に触れるような気がするが、そのへんはやねうらおの知るところではない。


第75回 ReadMe毒(貴重なご意見を頂戴する) 99/9/15

http://www.din.or.jp/~glit/TheOddStage/

のReadMe毒のところで、このコーナーの批判記事が掲載されている。面白い奴もいるもんやなーとか思って興味深く読ませていただいた。ちゅーか、「こんなん書いたったから、読みやー」ぐらいメールで教えてくれれば良さそうなものなのにとか思うよ(笑) 意地悪だなぁ。僕は、こういうページがインターネット上でもっと増えれば良いのにと思っている。(皮肉ではない)

さて、この記事中のGNU関連の指摘に、特に反論する気はないのだけど、その他のところに簡単に突っ込みだけ入れておこう。

 「第6B回 市販のゲームを検証する(自分のを検証されるのは嫌:p) 99/8/21」で、いままでフリーソフトの発展に貢献してきた LHA , LZH をけちょんけちょんにけなすという始末 ( しかし yaneGameSDK は LZH で圧縮されている)。「権力」ではない GNU よりは「権力の固まり」のマイクロソフトがお気に入りなんだろう。

LZH形式の圧縮率の悪さ、そして圧縮の遅さをけなすことと、LZHを使うこととはまったく矛盾しない。圧縮法として、お世辞にも良いとは言えないLZH形式がディファクトスタンダードになっている現状に疑問を抱きながらも、それを利用していくのとは何ら矛盾しな〜い!

同様に、Microsoftをけなすことと、Microsoft社の製品を使うことはまったく矛盾しない。マイクロソフトのやりクチを不快に感じながら、Microsoftと(Microsoftの製品と)関わっていかなければならない状況にいる人っちゅーのは、いくらでもいるわけで、「嫌なら使うな!」だとか「文句を言うなら使わなきゃいいのに」とでも言いたいようだけど、そういう単純な問題ではないのである。

「東京って怖いわー」と感じながら、東京を離れられへんとか、「いまの会社、ややねん」と思いながらも、やめるにやめられない人とか、君に言わせれば、そういう人たちもやっぱし矛盾してるんですかねぇ?

 「コールバック中で timeGetTime や timeGetSystemTime や GetTickCount を呼んで演奏位置をしるべし」と書いてあるし、割り込み処理中では一定以上の処理が行われないようにリミッタをかけるべしと書いてある。しかもそれは「常識」らしい。へえ、じゃあ実装はどうなっているのか、と思い、氏の yaneSDK100b1 をダウンロードして yaneMIDIOutput.cpp/h を見てみるが、そのような実装はされていない。

彼は、添付されている更新履歴のほうを読み落としているようなので仕方ないと思うが、これは、ぷよーんさんのgplからそのまま流用させてもらったものである。というか、仮にこれが僕がプログラムしたものだったとしても、「これが常識やねん」「これは、こうなっているべきやねん」とここで語りつつ、その通りに実装していなかったからと言って、何が悪いというのだろうか?何がどう矛盾しているというのだろうか?前者は、こうすればええねん!かくあるべき!という仕様や理想のフェーズであって、後者とは明らかに違う。たとえば「乱数発生には、モンテカルロ積分法がええよー」と、おおっぴらにその有効性を説いていたとしても、実践的に(納期面・プログラムのステップ数・実行速度・実装の容易さを考慮して)線形合同法で実装したりすることはあるやろー?「STLはサイズでかくなるから使う奴は馬鹿!」と言いつつも、「プロトタイプなんでとりあえずSTLで」だとか、逆に「STLを使うとソースコードは100分の1になるよ」と言っている人が、「ここはvectorでなく、フツーの配列でいいや」ということもあるわけだ。

どうも、君の批評(批判?)見てたら、俺は、ここで書いたことは、その通りしやんとあかんみたいやんけ?そしたら、あれか…これは、誓約書なんか!?(笑) 阿呆!最初に日記帳やゆうてるやろ!しっかり読め!あるいは、しっかり読むなよ!(どないやっちゅーねん…)

なんか、毒舌っつーからせっかく期待して読んだのに、切り口にゃ全然、鋭さがないし、論理展開は稚拙だし、もっともっとしっかりと毒舌してもらわなきゃ困るよ!!とか言って、新たな波紋を喚起し、今回は文章を閉じることにしよう。ちゅーか、意地悪せずに、批判記事書いたら、教えてよー。(泣)


第76回 続ReadMe毒(貴重でないご意見には突っ込みを入れる) 99/9/16

今回は、前回の続きである。まー、例の彼の批判文、ホントどうでもいいことも含まれていて、相手するのも馬鹿らしい部分もある。まあ、相手にするのが馬鹿らしくない範疇で、突っ込んでみる。

ゲームプログラマーで世界一のプログラマーを目指しているのであれば、PS2 の開発環境となる Linux についても知るべきであるし、GNU についても明るくなっているべきであろう。

なんか、それはねー、余計なお世話っちゅー気がするぞ(笑) たとえばやねぇ、僕が君のメールアドレス等から「ははーん。お前、早稲田の学生しゃんかいな!それも3年やんけ!」って知ったからゆうて、「よー、それで早稲田入れたなぁ。裏口ちゃんのん?」だとか、「お前、そんな程度の頭で卒業できんのけ?やばいんちゃうんけ?」とか言ったとしたら「んなことほっといてくれ!」てなるやろー。それとおんなしやん?やねうらおは、仕事の関係でしばらくWindowsの開発しかせーへんのや!あまり、やりたくもないけど、仕方ないんや(笑) だからして、Linuxやってる余力は、ないんや。よちよちさんやさかいに、勉強してる余力もないんや。でも、世界一のプログラマを目指してんのや。目指すぐらい勝手でしょーに(笑) そんなとこ、ツッコミ入れんといてよー。たとえて言うなら、山田君ザブトン1枚持ってきなさい!だよー。(それ、たとえちゃうやろ…>俺)

氏は、トップページで yaneGameScript2000 について「このスクリプトで市販ソフトを作ったので安定性と性能は実証済みと言える」と言っているが、誰が実証したのかは知らないが、市販ソフトを作ったからと言ってそれが実証されるわけではないことは周知の事実だ。「市販ソフトを作った→安定性と性能が実証される」なんてあり得ないことは Windows を見ていれば分かる。

なんか、早稲田君(こんな呼ばれかたイヤか?)は、どうもそのへんの事情に疎そうなので、説明させてもらうけど、市販するからには、テストプレイヤーちゅーもんがおってやねー、何台もマシン使って、いろんな環境でテストするのよ。テストプレイ&デバッグ期間もそれなりに(ちゅうても今回は一週間しかなかったけど)あるのよ。なかったけど、あるのよ。あったけど、なかったのよ。(どないやねん。ワレは、日本語の不自由な外人かい!>俺) まーせやから、バグは殲滅されてるかっちゅーと、そうでもないんやけどね。まー、それくらい行間から適当に読み取ってよー。ちゅーか、それくらい読みとられへんのなら、やねうらお批評なんて1億5千万年早いよ。(こんなんゆうから、また反感買うんやね。失礼)

「第73回 MIDIの道は、一日にしてならずぢゃ(シビアなタイミング) 99/9/14」でタイミングについて語ってるが、それについても「テンポチェンジの誤差なんか、自分の耳でわかるようになってから」という事である。

早稲田君もそうだけど、どうも、この部分は、誤解されていることが多いようなので、ここで解説する。テンポチェンジの誤差は、耳でわかるかというと、そりゃ当然、わかる。いや、誰でもわかる。なんでかっちゅーと、再生ポジションがずれてくるのだから…。

もうわかったかも知れないけど、たとえばMCIで再生してるのと、テンポチェンジの処理に誤差を生み出すMIDI再生ルーチンで再生するのとでは、再生位置にずれが発生するのよ。同時にその二つを再生してたとしたら、どうなるかいね?つまり、ネットワークごしに他のマシンでMIDI再生してて、それとセッションしようと思うと、あちらさんがMCIで再生してたりすると、テンポチェンジのところからずれてくるわけよ。そしたら、カウンターの向こうからグラサンした怖いおっさんが出てきて、「どっちがズレとんねん?お前か!?お前かボケぇ!お前、なんちゅー実装しとんねん、パケカー!」って怒鳴られるわけ。この例がわかりにくければ、CDやWaveと同期を取ってMIDI再生しているとでも考えてもらえれば結構。

特に、私のは、音ゲー(音のタイミングに合わせて何かをするゲームね)に使用したりする可能性があるので、その再生ポジションのずれは致命的なのよ。決められた時刻に決められた音が再生されないといけない。つまり、分解能の問題を言っているのではないのよ。分解能は、別に描画タイミング(60FPSならば1/60秒)より、細かければそれでOK。割り込み周期も安定しているに越したことはないけど、それほどシビアな要求はしていない。逆に、再生ポジションが、timeGetTimeで得られる時間から大きくズレるのが許容できないだけで。

この手の同期がシビアに要求される場合は、一般には、内部タイマーを別で用意して、ゲーム自体はそいつと同期させるべきなのだけど、CD再生やWave再生をその内部タイマーと同期させるのは、不可能なので(もとい、面倒なので)、そういうわけにもいかないというわけよ。(もっとも、DirectSoundはどうも長いWaveを再生しているとズレてくるので、そのへんをどう解決するかはまた別の課題としてあるわけだけど)

ユーザーに使ってもらいたいから C 言語風の記述にするのだ、と。C 言語のユーザーは非常に多く、そこには市場があるのだ。問題は、その yaneGameScript2000 の言語仕様である。

永久ループを作る loop など、C 言語に無いようなキーワードはあるが、switch ステートメントが「没」らしい。ここで、C 言語系の Java や JavaScript 、C ++、Objective C を考える。switch ステートメントについてだけを見ても、これらの C 言語系の言語はすべて C 言語の switch ステートメントをほとんどそのまま踏んでいる。

たしかに switch ステートメントには break 記述忘れをするなど危険な面もあり、Java や JavaScript では C に完全互換性がある必要はないのだから、 switch の仕様変更をしても問題はなかったはずである。それなのに、これらの C 言語よりも後に出た言語が switch の仕様を変えてないのは、これこそまさに「そこに C ユーザーがあるから」である。「PASCAL 風の文法対 C 風の文法」と「C の文法対 yane... の文法」ではたしかに比較のレベルが違うのであるが、それにしても矛盾しているので笑ってしまう。だれでも矛盾はある物だが、もっともらしい口調が笑いを誘うのだ。

(シンタックス補正はやねうらおの手による)

switch文をC言語の後継たる言語系で、ことごとく踏襲されているという彼の指摘はなかなか興味深い。意外とセンスを感じさせる。(早稲田君は、こんなん言われたら不快かな?)

僕のスクリプトのほうで、switch文を採用しなかったのは、彼も指摘している「break 記述忘れをするなど危険な面もあり」というのが気になったのと、switch文の最適化を効率良く実装するのが手間だったので(一般的にswitch文は、出来る限りジャンプテーブルとしてコード生成されるのが普通だし、ユーザーもそれを期待している)、どうせ最適化しないならばと、代替的に、拡張構文としてalt文を用意したのである。

ちゅーかやねぇ、switch文なんかより、構造体はどうなってん!とまず最初に突っ込んで欲しいよ(笑) Cユーザーが最初に戸惑う部分は、おそらくそっちのほうのような気がする。あらかじめ言っとくけど、これ見て「なんや、ワレ、構造体、実装しとらんかったんか」なんてReadMe毒に追加するのは勘弁して欲しい(笑)

何度も言うけど、PASCALのサブセットにせずに、Cのサブセットにしたのは、コンパイラデザインの段階での話。Cのサブセットにすることを決めてから、そのうちどの機能を実装していくことにするかっちゅーのは、すでに納期や実装の容易さ、処理の軽さとの格闘なのよ。そこに矛盾を感じとって嘲笑の対象としようとする早稲田君には、やねうらおも「甘いなー。やっぱなんだかんだ言っても学生の発想やなー」とか言って、笑い返さなければなるまい。わはははは。げはははは。ぐはははは。(こんなん書くからまた反感買うんやね…気を悪くしたらゴメンよ)


第77回 ゲームプログラマにプログラミング理論は必要か?(初心者様のご質問にお答えする) 99/9/17

いまテレビを見ていたのだが、ニュースで「特に、大阪市中部の八尾市で一時間に64mmの猛烈な雨量を観測するなど…」と言っていた。これが何のことかわからない人は、第6E回を読み返してもらえば良いと思うのだが、やねうらおの住む八尾市で異常気象が繰り返し発生しているというのが、実証されたような気がして怖いのである。ちなみに、例のあやしげな研究所の人には、まだウイローは持って行っていない。このままでは、そのうち、やねうらおの家も床下浸水の憂き目に逢着するかも知れない。

さて。最近、やねうらおの手元には、「ゲームプログラマになりたいのですが、どんな勉強をしたら良いですか」なんて質問がよく来る。「そんなん知らんわー。好きなよーにせー!」とも言えないし(笑)、来るごとに無い知恵をしぼって返事を書かせてもらっている次第である。

今回は、FAQ化してきた、この質問についてゆっくり考えてみたい。その前に、まずは、この質問である。

初歩的な質問で(でもホントは初歩なのかどうかもわかりません?)申し訳ないのですが
Z80マイコンをCで(システムロード社のMINI-Cをつかってます)プログラム作成中なんですが、乱数をどのように作ったらいいのか、わかりません。

自分なりにいろいろ探してみたんですが、わかりません。何か、公式やパターンのようなものが、あったら教えてほしいのですが…。お願いします。

これは、ある電子工作系のメーリングリストで出ていた質問なのだが、ちょっと興味深いのでとりあげてみた。MINI-Cというのは、やねうらおは不勉強で知らないのだが、きっとMINIなだけあってrand関数などはライブラリのなかに存在しないのだろう。

ここで問われている「乱数発生のためのアルゴリズム」だが、これは、数値計算の分野ではFAQで、最近の書籍でならば『NUMERICAL RECIPES in C』(C言語による数値計算のレシピ/日本語版)が技術評論社から出ていて、それに詳しい。余談になるが、この本の翻訳者の一人、僕がプログラマとして尊敬している奥村晴彦氏は、数年前まで神奈川県立久里浜高校の教諭だったと思うのだが、この本を見る限り、松坂大学の助教授になられたようである。やはりあれだけの人だから、実績が認められてのことだとは思うが、なんでまた政治経済学部なのかなぁ…。(笑)

話がそれた。先のメーリングリストで質問した彼は、おそらく会社で開発しているのだと思う。なんで電子工作のメーリングリストでこんなことを質問するんだろう?って思う人もいるかも知れないけど、このいくらか前に「ハードウェアで乱数を発生させる方法」について、盛んに議論されていたので、たぶん、それに対する便乗だろう。しかし、あいにく、彼のこの質問に答える人はいなかったようだ。まー、ハードウェアのメーリングリストだから仕方ない意味もあるが、実のところ、プログラマの中にでも乱数の発生アルゴリズムについて知っている人が、どれだけいるというのだろう?(この場合、この質問に答えるためにはZ80の知識も必要で、「日立の互換チップを使っているのでなければ、リフレッシュレジスタのRレジスタを利用して、インラインアセンブラ使ってrandomizeすべし」ってことも指摘しておいてやる必要があるだろうが…)

プログラマとして仕事をしている人のうち、数値計算についてひと通り、すなわち前述の『NUMERICAL RECIPES in C』に載っている程度のアルゴリズムについて、表層だけであろうと知っている人っていうのは、案外少ないのではなかろうか。僕は、コンピュータサイエンスはほとんどオールマイティ(と言いたいところだが、実のところ乱読ぎみに本を買い漁っているので、広く浅く知っているに過ぎないのだが)なのだが、ゲームプログラマには数値計算なんて要求されていないことがほとんどで、3D関係まで扱うプログラマでさえ大学教養程度の数学と高校の物理程度の知識しかないことがほとんどだし、「文系だったもんで数学は高校一年までしかやってないよー」なんて人も現役バリバリのゲームプログラマだったりする。はっきり言わせてもらって、とてもサブーイ状況のような気がする。

そんな状況だから、プログラミング理論なんて、理論らしき理論を知らず、かつ、学校教育でプログラムなんて一切習っていないような、やねうらおが、未経験のままゲーム業界に飛び込んで、いとも簡単に某ゲーム会社のチーフプログラマになれたりするわけで(ここでハッキリ言っておきますが、チーフっつーのは、名前だけです。いまのところチーフらしきことは何ひとつやってましぇん!)自分でもこの業界の甘さを「ええんかいにょー」と指をくわえながら第三者的に傍観している。

だからと言って、これを聞いた若葉マークプログラマ君はやねぇ、学校でプログラミング理論の勉強に励む人に向かって「λ計算?定理証明系?お前、馬鹿だねぇー。そんなことやめときなよ。プログラミング理論なんてウザイだけだよ。実際、ゲーム会社でチームプログラマをやってる、やねうらおって言う人がいるんだけど、その人なんか、なーんも理論らしき理論なんて知らないよ!おまけに、学校教育でプログラムを学んだわけではなく、すべて独学だってさ!」だなんて決して言わないで欲しい(笑)

とは言うものの、(これは、僕自身の個人的な感想だが)、プログラミングの理論書は、ひと通りプログラミングをマスターするまで読むべきではないような気はする。…第一、難しすぎるし、たとえばプログラミングの正当性の証明だなんてわかったところで、しばらく何の役にも立ちそうにない。ちっとも実践的でない。生産的でもない。BNF記法なんて知らなくてもプログラムは出来るし、離散数学なんて知らなくても十分プロとしてやっていける。(しかし、少しでも多くの理論に精通していれば、それがアドバンテージとなるのだが)

また、僕の知人の数学が嫌いな文系プログラマのなかには、「数値計算?んなもんウザイぜー。勝手にやってろ馬鹿〜!」って人もいる。彼もれっきとしたプロのプログラマである。彼は自分の後輩に「プログラマになるのに数学なんていらないぜー。俺も数字は嫌いだぜ〜!」なんて吹聴しているが、「それは違うんでないの?」という気もする。(まあ、何も言う気はないけどね)

この甘いコンピュータ業界では、彼のようなハナクソプログラマでさえプロとして金を頂戴できるし、あまっさえやねうらおのようなへっぽこプログラマがチーフプログラマになれたりするご時世であって、若葉君は金をもらっていそうだからってむやみやたらと信用するべきではないと思うぞ。もちろん、やねうらおの言葉も例外ではない。ちなみに、やねうらおが、このプログラミングの世界でこの人の言葉ならば無前提に信用するに値すると思っているのは、D.E.Knuth教授だけである。


第78回 バックトレーサが欲しいんだってば!(欲しい欲しい欲しい) 99/9/24

今度は、大雨洪水警報である。近所の学校はどうも休みになったらしく、電車はガラすきだった。晴れていて、空には雲ひとつないのにぴかぴかっと光ったかと思うと猛烈な雷の音が。こんな現象に出くわしたのは生まれて初めてである。某研究所の人は、雲ひとつなくても雷を発生させたり、狙ったところに雷を落としたり出来るというのだろうか。ひょっとして、某研究所の人は、2300年の未来からタイムマシンに乗ってやってきたのではなかろうか。ひょっとして、ウイローより、ドラ焼きとかのほうが好まれるのだろうか。やっぱりネズミが怖いんだろうか。しっぽを引っ張ると動かなくなってしまうんだろうか。足は地面から数ミリ浮いているんだろうか…。まあ、今日の寝言はそれくらいにしておこう。

最近、WindowsAPIの内部をぐりぐりと探ったりすることがある。他人のソフトもぐりぐり探らなければならないことがある。いやーん、えっちーと言われようが、ぐりぐりやらねばならないのだ!

VisualStudioでも、あるポイントでブレークをかけて、コールスタックを調べることが出来るから、それなりのことは出来るのだけど、このポイントからさかのぼってトレース(バックトレース)したいときがある。

これが出来ると出来るとでは大違いなんだよ!いや、出来ると出来るだったら、何も違わないじゃんか。何、言ってんだよ俺。

ともかく、欲しい欲しい欲しい!流れ星が落ちるまでに三回言えたら叶うって聞いて、学校の屋上で一晩中流れ星を観察していて風邪を引いたことのあるやねうらおが欲しいって言うんだから、間違いない。(何がどう間違いないんや。目的語はどれや!)

VisualStudioになんでそういう機能ないのかなぁ。(なんや、お前が、自分で作るんとちがうんかい…) 他力本願寺なやねうらおからのお願い。(目的語はありましぇん。もちろん、この文章にも目的語はありましぇん。ごめんにゃり)


戻る