WebDebugMenu


ゲーム屋の間ではおなじみのデバッグメニューをブラウザで実装してみました。
WebDebugMenu

デバッグメニューとは、変数や関数を事前に登録しておき、後でメニューから値を書き換えたり関数を呼んだりできるようにする代物で、周囲の人間の話を聞くにコンソールゲーム屋の間では国内外問わずどこでも開発中こういう機能を入れていて、ほぼ共通認識になっているようです。
通常ゲーム内の 2D レンダリング機能やら UI 機能やらを使って実装するのですが、ブラウザで実装すればそれらとの依存を断てるので汎用性を高められそうです。サーバープログラムのようなグラフィック表示がないプログラムへの応用も考えられますし、ブラウザは超強力な UI エンジンでもあるので、一般的なデバッグメニューでは難しい複雑な編集機能も実装できそうです。(STL などのコンテナの中身を弄るとか、スクリプトやシェーダコードを編集できるようにするとか)

HTTP サーバの実装はレベルエディタの時と同じく Poco を使いましたが、Poco を使ってる部分は dll に閉じ込めてあるので、ユーザー側が Poco をリンクする必要はありません。
ブラウザ側は polling で 1 秒ごとに最新のデータを取ってくるようになっていますが、この際ちゃんと表示状態の枝のデータだけ取ってくるようになっています。

現状はまだそこまで複雑なことはできませんが、組み込みは極めて容易かつ最低限必要になりそうな機能は大体揃っています。
Chrome と Safari で動作を確認済み。IE は 9 以前だとスライダが使えません。

以下使用例。
一般的な変数の表示。

int main(int argc, char *argv[])
{
    wdmInitialize();

    int int_value = 1;
    float float_value = 1.1f;
    bool end_flag = false;
    // ごく普通に表示
    wdmAddNode("Test/int_value", &int_value);
    wdmAddNode("Test/float_value", &float_value);
    // 範囲指定 (スライダで操作できるようになる)
    wdmAddNode("Test/ranged_int_value", &int_value, 0, 100);
    wdmAddNode("Test/ranged_float_value", &float_value, 0.0f, 10.0f);
    // const の値を追加すると readonly ノードになる
    wdmAddNode("Test/const_int_value", (const int*)&int_value);
    wdmAddNode("Test/const_float_value", (const float*)&float_value);

    wdmAddNode("Test/end_flag", &end_flag);
    while(!end_flag) {
        // wdmFlush() でブラウザから来たコマンドの処理が行われる
        // == 変数の更新、関数呼び出し、ブラウザへ送るデータの生成などはこのタイミングで行われる
        wdmFlush();
        ::Sleep(100);
    }
    wdmEraseNode("Test");

    wdmFinalize();
}

ブラウザ側の表示

class 内変数の操作。関数呼び出しなど。

class Hoge
{
public:
    Hoge() : m_int_value(0) {}
    int getValue() const { return m_int_value; }
    void setValue(int v) { m_int_value=v; }
    void print() const { printf("Hoge::m_int_value: %d\n", m_int_value); }
    void printx(int v) const { printf("Hoge::m_int_value x %d: %d\n", v, m_int_value*v); }

    // wdmScope は wdmDisable を define しているときは消え失せるスコープ
    // (最終リリースの時は当然デバッグメニューは消える必要があり、
    //  wdmDisable を define するだけで全て消え失せるようになっている)
    wdmScope(
    void addDebugNode(const wdmString &parent)
    {
        wdmString path = parent+wdmFormat("/Hoge:0x%p", this);
        // getter/setter で値を変更するノード (範囲指定付き)
        wdmAddNode(path+"/value", this, &Hoge::getValue, &Hoge::setValue, 0, 100);
        // getter のみの場合 readonly なノードになる
        wdmAddNode(path+"/const_value", this, &Hoge::getValue);

        // 関数を呼ぶノード
        wdmAddNode(path+"/print()", &Hoge::print, this);
        // 引数付き関数を呼ぶノード
        wdmAddNode(path+"/printx()", &Hoge::printx, this);
    }
    )
private:
    int m_int_value;
};

int main(int argc, char *argv[])
{
    wdmInitialize();

    bool end_flag = false;
    Hoge hoge;
    wdmScope( hoge.addDebugNode("Test") );
    wdmAddNode("Test/end_flag", &end_flag);
    while(!end_flag) {
        wdmFlush();
        ::Sleep(100);
    }
    wdmEraseNode("Test");

    wdmFinalize();
}

配列、文字列、SIMD 変数など。(現状文字列はマルチバイト文字を正しく扱えません)

int main(int argc, char *argv[])
{
    wdmInitialize();

    bool end_flag = false;
    int int_array[8];
    __m128 simd_value;
    char char_array[32] = "hoge-";
    char *char_ptr = char_array;

    std::fill_n(int_array, _countof(int_array), 10);
    simd_value = _mm_set1_ps(1.0f);
    // 配列
    wdmAddNode("Test/int_array", &int_array, 0, 100);
    // SIMD
    wdmAddNode("Test/simd_value", &simd_value, 0.0f, 10.0f);
    // 文字列 (char[])
    wdmAddNode("Test/char_array", &char_array);
    // 文字列 (char* もいける。安全性を考えて readonly ノード)
    wdmAddNode("Test/char_ptr", &char_ptr);

    wdmAddNode("Test/end_flag", &end_flag);
    while(!end_flag) {
        wdmFlush();
        ::Sleep(100);
    }
    wdmEraseNode("Test");

    wdmFinalize();
}


[2013/09/21 追記] デバッグ情報を使ってメンバ変数の自動登録ができるように改良しました。runtime member variable editing