使える言語の棚卸し

ちょうど面白い話をしてるところなので、自分が使える言語を整理してみた。しかしよく考えるとゲームプログラマってアセンブリC言語C++ は絶対使えるし(使えないと就職できん)、さらに VLSL、HLSL あたりは使えてもおかしくないよね……

その前に、VMXはどこに入るの?

VMXは、あれはあれでひとつの低レベルプロセッサ制御な気がしてならないんだけど、まぁ言語でいえばC言語になるのか……

業務で使えると言い張る言語


よく考えると、プログラマブルシェーダ言語って VMX と同じで、ハードウェア制御技術であってプログラミング言語じゃないかも……そのときは2つ引いてね。

特定用途なら使ったことある言語
一応カウントアップしておく
そういう意味では

業種的に絶対セットな言語だから気づかなかったけど、昨日 Listener vs function pointer という話を書いたが、あの話自体、 C++ におけるC言語的アプローチと強引にみなせば(←かなり苦しいけど;;)、henrichiさんの話でいう解法の手が増えたという視点での話だったのかも。 C の低レベル技法アセンブリベースという考え方も面白いかもね。

その点 JavaC# はあまり……ガベコレはリアルタイム系ではまだ使えねーという印象以外はどうのというのはない。なんだかんだでCファミリだからだめ(アプローチが増えるというほどのこともなし)かな?

ところで Max Script とか MEL とか使える人は、こういうときリストアップの対象になるんかな……んなこと言ってたら UnrealScript とかも入りそうだけど…… 履歴書には書くよねぇ。

Listener か function pointer か

 アセンブリC言語の場合、モジュール化したプログラムの特定の状態で通知や処理の移譲を受けるために関数ポインタを利用する。このテクニックはC++でも変わらず使われている。関数ポインタと、ペアになる void* アドレスの登録を受け付ければ大抵の状況に対応できる。また、このペアを複数登録するための(単方向の)リストコンテナは1要素あたり僅か12バイト程度なので、必要に応じて比較的気楽にコールバックの複数登録を受け付けることができる。

 C++ではクラスが使用できるため、 OGRE3D でも好まれている Listener クラスを使って、もう少し "それっぽく" まとめることができる。

class CEnemey {
    //
    // ....
    //

public:
    class Listener {
    public:
        Listener() {}
        virtual ~Listener() {}
        virtual bool notifyDamaged(const CEnemy*) = 0;
        virtual bool notifyKilled(const CEnemy*) = 0;
        virtual bool notifyDestructed(const CEnemy*) = 0;
    };

    /// \a pListener を通知リストに加える
    void addListener(Listener *pListener);
    
    /// \a pListener を通知リストから抜く
    void removeListener(Listener *pListener);
};

 この実装方法には、個人的に次のようなメリットがあると考えている:

  1. void* の static_cast が減る。
  2. 既存のクラスが Listener クラスを implement して、そのまま対象に addListener(this) できる。
  3. 監視専用サブクラスのインスタンスを作って addListener することもできる。


 たとえばチームリーダーが殺されたら一目散に逃げる AI に切り替わるような敵は(C++で実装するのはどうかとも思うが、例えとして)、 CEenemy::Listener を実装して、チームリーダーに addListener(this) できる。

 他方、ゲームスクリプトから、特定の敵が特定の状態になったときにスクリプト内定義のユーザー関数を呼ぶといった処理を実装する場合は、リスナー機能だけのサブクラスを作れば済む。

class CEventListener : public ::CEnemy::Listener {
    //
    // ....
    //

public:
    CScriptEventListner(...);
    ~CScriptEventListener() {}
    bool notifyDamaged(const CEnemy*);
    bool notifyKilled(const CEnemy*);
    bool notifyDestructed(const CEnemy*) {}
};

 ……と一見スマートにいきそうなのだが、実はこの方法、「このリスナーだけの存在(インスタンス)を誰が delete するのか」という点で、非常に大きな問題を抱えている。もし、

{
  CEventListener *pListener = new CEventListener();
  pEnemy->addListener(pListener);
}

 ……なんてコードを書いたらメモリリークになる。 delete this でも使わない限り notify 後にこのインスタンスをメモリ上で始末するにはメインシステム側に何らかの仕組み(たとえばタスクシステムのような)が必要になってしまう。

 ひとつの解法はスマートポインタを使うことだが、リスナー周りの API を主に使うのが自分なのか、他人なのかで判断が変わってくる。今回のようなケースでは、 addListener(this); が示すように、リスナー受け入れをスマートポインタに限定すると周りの人にとって一気に使いにくいものになってしまう。

 関数ポインタだと何の問題も発生しない。

static bool XXXX_notifyKilled_callback(const CEnemy *pEnemy, void *ptr)
{
    // 中継する
    CScriptEventListner *pListener =
        static_cast<CScriptEventListener*>(ptr);
    pListener->notifyKilled(pEnemy);

    delete pListener; // 用済みなので消す(これが可能になる)

    return false; // もう通知を受信しない
}

 Listener クラスを用いたデメリットとしてはこんなところだろうか:

  1. void* の static_cast は減るが、リスナー単体のインスタンス生成が増えるのでメモリリークに要注意(誰が delete するのか)
  2. メモリ効率において、スリムな関数ポインタと比較して、 vtable ぶん肥満する。(全ての通知仮想関数を実装したときのみ関数ポインタと同等の効率になる)
  3. みんなで話し合ってると「どう考えても関数ポインタを使う方がスマート」という結論に達する。(^^;


 タスクシステムやジョブシステムに相当するものがあれば、生成したリスナー単体のインスタンスもどこかのコンテナに格納して外から delete してもらえるので心配が要らない。もしそういう統合的なマネージャがないなら、あるフラグが立っていたときに Observer が delete してくれるという組み方を作っておくのも手だと思う(若干危ういが)

 "本当にきちんと" やろうとすると、リスナーが孤児にならないように管理の仕組みをしっかり作ることになる。しかし、いちいちそんなことするならやっぱり関数ポインタでいいやってなってくる。

(そういう意味では汎用のタスクシステムがあれば、こういうケースでは最強)

 ちなみに、デメリットにメモリ効率について書いたが、据え置き機 + PSP ではまず誤差範囲だろう。

突きつけられる問題

 先日のエントリにトラバをいただきました。m(__)m

縦から見る、横から見る、斜めから見る

複数の言語を扱えるようになろうね、というのは表題のような事に尽きるのではないだろうか。一つの問題を解決するのに解き方をどう考えるのか。その「考え方」が色濃く出るのが言語なのだろう、と(別にプログラムが書けない)私は理解している。

なので、ゲーム業界と IT 業界だからというより「普通、仕事として割り切ってやってるだけ〜というのでなくて、普段から興味を持ってプログラムに接しましょうね。」というだけ話のような。

 僕がネットで見た議論とかは「複数の言語が扱えないプログラマは○ねだよね」的な論調で怖かったのだけど(なんとかの誤解とかいうエントリのリンクを追ってったら、そんな凄い話になっていっていた。無くした。orz)、上のような話だとすれば理解できる。たとえばまつもとさんもそんな書き方ですし。

 僕的には「ほぉほぉ、あっちの世界では、そうやってキャリア形成していくのか」みたいな感じで読んでいっていたので、上で指摘されているような「ちょっといい話」だったのだとしたら、そうなのか(^_^A;; というちょっとした拍子抜け感があります。(^^)

 ただ、上の「一つの問題を解決するのに解き方をどう考えるのか」これを具体的に見ていくと、例えば、

  • 誘導ミサイルを(ゲーム性を保ちながら)格好よく飛ばす
  • AIが操作している乗り物をカーブに合わせてナウくドリフトさせる
  • 流体の外的影響に対するレスポンス


 といった問題を解決する解き方は、(トラバをいただいた話も話として理解できます、が)色んな言語を知ってるから解けたとかそういう類の話ではない気がする。やはり、数学をどう使うかという話であり、あとはどれだけ引き出しを持っているか---現実の体験、知識、映画、あるいはライバル製品---が、解決に寄与してくれる気がする。

 で、上のお話がストンと腑に落ちてくるかどうかががやっぱ業種によって違うのではないかと思うのです。他にもたくさんの業種が存在してそこにプログラマはいるんですが、上の話で「ふむふむなるほど」となる人と「いや誘導ミサイルは……」とかになる人が、僕はいると思う。

 考え方(?)で問題を解くことが主な業種もあれば、数学で問題を解くことが主な業種もあれば、電気工学で問題を解くことが主な業種などがあるんじゃないかと。だからやっぱり「業種によって違うんじゃないか」という見解をまだキープしてます。

 それがあるから「普段から興味を持ってプログラムに接しましょうね」的な話が、適切な業種で、適切な話として成立するのかなと。やっぱりこれはうちの業種で優先的に成立するかというと怪しい気がする。もちろん、何が何に役立つか分からないからそういう意味で切り捨てることはないけども、前エントリで書いたように「数学は鉄板」なので、やっぱりオススメ話は数学中心になる。

 逆に IT の会社だと、朝、出勤した途端に企画さんから誘導ミサイルの飛ばし方について相談されるなんてことはないと思う。では、代わりに、どんな話をしているのか。この部分の想像力が僕に欠落しているのかな。

「普段からどのような問題を解決しなければならない人たちなのか」

 という部分が見えないまま、その "解決" にあたる部分の ITPro の記事とか、ブログで唱えられる推奨行動、 BBS の議論を読んでいるから、読んでて色々なことが分からなくなっちゃうのかも。

※発端となった記事を探しておきますね。