2013年11月16日土曜日

C++のカスタムアロケータを作ってみる

C++のカスタムアロケータの作り方について,いまいち情報が無いので,
自分で作ってみることに.

環境は,MacBookAir,Mavericks,clang 3.4, libc++でC++11環境に.

とりあえず,メモリの割り当てと解放が行われたときにログを吐くアロケータにしてみる.
いきなり必要と思われる要素を詰め込むと理解が追いつかないので,まずは空のクラスから始める.

#include <iostream>
#include <vector>

using namespace std;

class log_allocator {
};

int main()
{
    vector<int, log_allocator> iv;
}

これをビルドすると,色々エラーが出るけど,とりあえずvalue_typeを提供していないのが問題のようなので,value_typeを用意する.

#include <iostream>
#include <vector>

using namespace std;

class log_allocator {
public:
    typedef int value_type;
};

int main()
{
    vector<int, log_allocator> iv;
}

今度は,deallocateが無い,と言われる.deallocateのインターフェースは,void deallocate(pointer p, size_type n)とのこと.
pointerはポインタ型へのtypedefで,size_typeはsize_t型へのtypedefのようなので,次のようにしてみる.

#include <iostream>
#include <vector>

using namespace std;

class log_allocator {
public:
    typedef int value_type;
    typedef int* pointer;
    typedef size_t size_type;

    void deallocate(pointer p, size_type n)
    {
        cout << "deallocate " << p << " : " << n << endl;
    }
};

int main()
{
    vector<int, log_allocator> iv;
}
これでビルドは通るようになったので,次はvectorに値を追加してみる.
#include <iostream>
#include <vector>

using namespace std;

class log_allocator {
public:
    typedef int value_type;
    typedef int* pointer;
    typedef size_t size_type;

    void deallocate(pointer p, size_type n)
    {
        cout << "deallocate " << p << " : " << n << endl;
    }
};

int main()
{
    vector<int, log_allocator> iv;
    iv.push_back(1);
}
今度は,allocateが無いと言われる.allocateのインターフェースは,pointer allocate(size_type n, allocator::const_pointer hint = 0) となっているけれど,ここではとりあえずhintを無視してみる.
#include <iostream>
#include <vector>

using namespace std;

class log_allocator {
public:
    typedef int value_type;
    typedef int* pointer;
    typedef size_t size_type;

    pointer allocate(size_type n)
    {
        cout << "allocate " << n << endl;
        return nullptr;
    }

    void deallocate(pointer p, size_type n)
    {
        cout << "deallocate " << p << " : " << n << endl;
    }
};

int main()
{
    vector<int, log_allocator> iv;
    iv.push_back(1);
}
nullptrを返すようにしたのに,問題無く動作してしまう.もう少し値を追加してみる.
#include <iostream>
#include <vector>

using namespace std;

class log_allocator {
public:
    typedef int value_type;
    typedef int* pointer;
    typedef size_t size_type;

    pointer allocate(size_type n)
    {
        cout << "allocate " << n << endl;
        return nullptr;
    }

    void deallocate(pointer p, size_type n)
    {
        cout << "deallocate " << p << " : " << n << endl;
    }
};

int main()
{
    vector<int, log_allocator> iv;
    iv.push_back(1);
    iv.push_back(2);
}
もう1つ値を追加すると,セグメンテーションフォールトを起こした. 本来,メモリ割り当てに失敗した場合,bad_allocの例外を投げるので,nullptrが返された場合のチェックはしていないようだ. とりあえず,デフォルトのnewとdeleteを使ってメモリを割り当ててみる.ついでに,もう1つ値を追加するようにしてみる.
#include <iostream>
#include <vector>

using namespace std;

class log_allocator {
public:
    typedef int value_type;
    typedef int* pointer;
    typedef size_t size_type;

    pointer allocate(size_type n)
    {
        pointer p = new int[n];
        cout << "allocate " << p << " : " << n << endl;
        return p;
    }

    void deallocate(pointer p, size_type n)
    {
        cout << "deallocate " << p << " : " << n << endl;
        delete [] p;
    }
};

int main()
{
    vector<int, log_allocator> iv;
    iv.push_back(1);
    iv.push_back(2);
    iv.push_back(3);
}
これで,めでたく動作したわけだけど,今のままだとint型しか受け付けないので,最後にテンプレートにしてみる. テストのために,intからdoubleに切り替えてみる.
#include <iostream>
#include <vector>

using namespace std;

template <typename T>
class log_allocator {
public:
    typedef T value_type;
    typedef T* pointer;
    typedef size_t size_type;

    pointer allocate(size_type n)
    {
        pointer p = new T[n];
        cout << "allocate " << p << " : " << n << endl;
        return p;
    }

    void deallocate(pointer p, size_type n)
    {
        cout << "deallocate " << p << " : " << n << endl;
        delete [] p;
    }
};

int main()
{
    vector<double, log_allocator<double>> iv;
    iv.push_back(1);
    iv.push_back(2);
    iv.push_back(3);
}

とりあえず,これで一段落.本当は,もう少し色々と定義しないといけないことがあるけれど.
毎回差分じゃなく全文を載せたから,無駄に長くなってしまったので,続きはまた今度にしよう.

2013年11月15日金曜日

C++11のchronoライブラリで60FPSと30FPSの間のフレーム数変換

元々60FPSだったものを30FPSに作り替えたりする際に,60FPSでNフレームだったのは30FPSだと・・・
とか逆だったりとかをC++11だと,chronoライブラリあるし,使えるかなぁ,と思って試してみた.

#include <iostream>
#include <ratio>
#include <chrono>
#include <cstdint>

using namespace std;
using namespace std::chrono;

int main()
{
    typedef duration<int32_t, ratio<1, 60>> fps60_t;
    typedef duration<int32_t, ratio<1, 30>> fps30_t;

    // 60FPS -> 30FPS
    fps60_t fps60(10);
    fps30_t fps30 = duration_cast<fps30_t>(fps60);

    cout << fps30.count() << endl;

    // 30FPS -> 60FPS
    fps30 = decltype(fps30)(10);
    fps60 = duration_cast<decltype(fps30)>(fps30);

    cout << fps60.count() << endl;
}

下手に暗黙的な変換にしてしまうのは良くないのは分かってるんだけど,
もうちょい暗黙的な変換欲しいなぁ,と言う気が.

2013年11月13日水曜日

Python APIでコマンドラインからJenkinsのジョブビルド実行

コマンドラインからJenkinsのジョブをビルドしたいな,と思っていたところ,
Python APIがあったので試してみた.

インストール

easy_installでjenkinsapiをインストール
> sudo easy_install jenkinsapi

ビルド実行

import jenkinsapi
from jenkinsapi.jenkins import Jenkins
J = Jenkins(Jenkinsサーバのアドレス[, ユーザ名, パスワード])
J.build_job(ジョブ名)

ユーザ名とパスワードはオプションだけど,設定していないってことはあまりないような.

ドキュメントはどこ?

とりあえずやりたいことは出来たものの,ドキュメントが見当たらないので,他に何ができるかいまいち分からない.
公式ドキュメントはあるものの,上記のJenkinsクラスも載っていないようで,Jenkins側のドキュメントで知った.
ジョブのビルド方法もソースコードを読んで調べたのだけれど,Python業界だと何か方法があるのだろうか?

2013年3月31日日曜日

ゲームプログラマとしての二年目のまとめ

去年も書いたので今年もまとめておこうと思います.

今年度の仕事
今年度は去年から引き続き同じチームで開発をしていました.

キャラクターのモーションの制御システムや,サウンドシステムをチーム向けにカスタマイズしたりしてました.
規模が小さめなのもあって一人で全キャラクターのリソース管理もしていて,なかなかに大変でした.
サウンドシステムは用語も分からなかったりで色々と大変でした.周りにサウンドシステムを担当した人もいなかったので,自分で調べるしかなかったのですが,それはそれで良い勉強になりました.

今年度多用した言葉
今年度多用した言葉から,今年度を振り返ってみます.

「具体的に何がしたいんですか?」
「こういうことができないか?」と言った質問が来ることがあります.可能かどうかだけ答えても良いのですが,この一言を言っておくだけで色々と違ってきます.聞いてみたら,既にある機能を組み合わせれば出来ることだったり,逆にやらせないためにわざと出来ないようにしていることを迂回するための方法として質問してきていたりします.具体的な話が分かると,取れる対処法も違ってくるので,この言葉は今後も言い続けることになりそうです.

「いつまでに必要ですか?」
余裕のあるときは「あっ,やっておきますよ」で済むんですが,余裕が無くなってくるとそうも言ってられません.早めに必要なのか,そうでも無いのか,スケジュールを告げない人はベテランであってもいます.仕事の優先順位を決める上でも重要な情報なので,今後も使う機会は増えていくと思います.

「もっとシンプルに」
短い期間でしたが,後輩の面倒を見ることになりました.その際,プログラムを見て気付くと何度も言っていたのがこの言葉です.どうも最初からアレやコレやと色々な条件を考えすぎていたので,まずはシンプルな条件で動くコードを書いてみるように言っていました.経験が少ない状態で色々考えても抜けや漏れが出るものです.まずは分かる範囲でシンプルなものを作ってみる.作る過程で色々問題があれば,それを検討して設計に手を加える,ということが大事だと思います.時間が無いならともかく,十分な時間があるのであれば,まずは作ってみるのも重要だと思います.

あと,個人的にシンプルなAPIを上手く組み合わせて何かを実現する方が好きなので,APIはシンプルな方が好きです.

直接話すことは大事
今年度も感じたのは,直接話すことが大事ということです.直接話してみたらすんなり解決した,なんてことは良くあることです.あと,話をしていると色々な用語が出てきて,知らなければそれを調べて,で勉強にもなります.この作業が面倒だ,という雑談から,じゃあそれを楽にできるようにしましょうか,と需要を見つけることもあります.

普段から話していると,色々と相談もしてもらいやすくなるので,そこから自分の知識や経験につながったり,仕事につながったりします.雑談も重要なのです.
特にプログラマ以外との会話が大事です。プログラマにとっては当たり前のことも,プログラマ以外にとっては違ったり,逆もあり得ますから。

相手の分かる言葉で説明する
プログラマ以外の人から良く言われるのが,プログラマの説明は分かりにくい,ということです.難しい言葉や意味の分からない横文字を使おうとするからのようです.
ベテランの人たちが言うのだから,社内でもデザイナが分かるように説明してくれるプログラマは少ないのでしょう.ちゃんと相手の分かる言葉で説明しようとしていると,相手も聞きやすくなって,色々と質問してくれるようになります.質問が来るようになると,それを解決するために色々と知識や技術が身に付いていきます.

ベテランが正しいと思うな
十年以上のベテランでも間違えることはあります.こちらからアプローチしないと動かない人もいます.伝えるべき情報が分かっていない人だっています.ダメな人には言わないと分からないんです.こちらで何とかしてしまうのではなく,ちゃんと言ってあげるのも相手のためだ,と思いました.特にここ最近.

Vim
今年度もVimは大活躍してくれました.Vimが無ければ作業効率は10分の1にまで落ち込んでいたことでしょう.需要があると色々と覚えるもので,今年度はテキストオブジェクトとVimFilerを特に活用しました.

Shougoさんのプラグインはもちろん活躍しています.

Jenkins
CIのツールというよりも,自動ビルドシステムにWebインターフェースを付けるのによさそうだったので使ってみたのですが,他にも色々とできそうです.気付くと社内の他の場所でも使うようになっていて,何故か関連ミーティングに出席することになったり,と付き合いは長くなりそうです.

今年度気に入った本
ノンデザイナーズデザインブック
UIデザイナの方に紹介してもらった本です.情報を上手く伝えるために,文章をどう配置したりするか,といった部分がプログラムを如何に読みやすく書くかに通じる部分がある気がします.また,上手く情報を伝えるための資料を書く際にも役立つ気がします.
ノンデザイナーズデザインブックを読み解く
というスライドも参考になります.

ゲームを動かす技術と発想
この本はゲームを作る上で必要な基本的な説明が載っています.この本が他と違うなと思ったのが,プログラマ向けの本ではないということです.実際,うちでもデザイナーがこの本を読んでたりします.プログラマはつい専門的な用語で説明してしまいがちですが,非プログラマでも分かる言葉で説明する努力は大事だと思います.

Web Audio API
Web Audio APIの本ですが,Web Audio API自体が昔ながらのサウンドシステムのモデルに基づいているらしく,自分が今利用しているサウンドシステムを理解する上でも役立ちそうな感じです.サウンドシステムで利用される用語なども理論や定義などが解説されていて,サウンドシステムを作るのではなく利用する側になった場合に色々と役立ちます.もっと早くにこの本に出会えていたらな,と思います.サウンドデータを扱う本は多々あれど,サウンドシステムについて書かれた本は珍しい気がします.

最後に
今年度は開発の後半に突入したこともあり忙しかったですが,それでもとても楽しい日々を過ごせました.デザイナやサウンドとの関わりから色々な知識や経験を得られ,興味の幅を広げることも出来,本当に良い一年でした.
プログラマ以外と話すことは苦手でしたが,やはり話すことは大事だな,と思った一年でもあったので,今後も「話すこと」をしっかりとしていこうと思います.

来年度は社会人3年目として新人の面倒を見ることもあり,「話すこと」はますます重要になりそうです.

ゲームプログラマとしてのまとめなのに,ゲームプログラミングに関する話がほとんど無いなぁ,と思わないでもないですが,実際仕事をしていてゲームプログラミングが特殊なことをしているわけでもないので,よっぽど特殊な部分を担当しない限りは,仕事の仕方とかについて思うことの方が増えますね.

2013年3月14日木曜日

clang::Preprocessorで字句解析をするまで

clangを使ってC++のコードについてのメトリクスを取得したりするためのツールが作れないか,と思って手を出してみたものの,まず字句解析するだけでも一苦労だった.とりあえず色々調べてみた結果,まずはプリプロセッサを作るところかららしいので,clangのDoxygenのドキュメントを参考にプリプロセッサオブジェクトを作ってみた.

環境は,Mac Book AirのMountain Lionで.

// main.cpp
#include 
#include "clang/Lex/PreprocessorOptions.h"
#include "clang/Basic/DiagnosticIDs.h"
#include "clang/Basic/DiagnosticOptions.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/LangOptions.h"
#include "clang/Basic/TargetOptions.h"
#include "clang/Basic/TargetInfo.h"
#include "clang/Basic/FileSystemOptions.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Lex/HeaderSearchOptions.h"
#include "clang/Lex/HeaderSearch.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/Lex/Token.h"

using namespace std;

int main(int argc, char * argv[])
{
    clang::DiagnosticIDs * diagnosticIds =
        new clang::DiagnosticIDs;
    clang::DiagnosticOptions * diagnosticOptions =
        new clang::DiagnosticOptions;
    clang::IgnoringDiagConsumer * diagnosticConsumer =
       new clang::IgnoringDiagConsumer;

    clang::DiagnosticsEngine diagnosticsEngine(
        diagnosticIds,
        diagnosticOptions,
        diagnosticConsumer
    );
    clang::LangOptions langOptions;
    clang::TargetOptions targetOptions;
    targetOptions.Triple = LLVM_DEFAULT_TARGET_TRIPLE;
    clang::TargetInfo * targetInfo = 
        clang::TargetInfo::CreateTargetInfo(
           diagnosticsEngine,
           &targetOptions
        );

    clang::FileSystemOptions fileSystemOptions;
    clang::FileManager fileManager(fileSystemOptions);
    clang::SourceManager sourceManager(
        diagnosticsEngine,
        fileManager
    );

    clang::HeaderSearchOptions * headerSearchOptions =
        new clang::HeaderSearchOptions;
    clang::HeaderSearch headerSearch(
        headerSearchOptions,
        fileManager,
        diagnosticsEngine,
        langOptions,
        targetInfo
    );

    clang::CompilerInstance compilerInstance;

    clang::PreprocessorOptions * preprocessorOptions =
        new clang::PreprocessorOptions;
    clang::Preprocessor preprocessor(
        preprocessorOptions,
        diagnosticsEngine,
        langOptions,
        targetInfo,
        sourceManager,
        headerSearch,
        compilerInstance
    );

    clang::FileEntry const * pFileEntry =
        fileManager.getFile("input.c");
    clang::FileID fileID = 
        sourceManager.createMainFileID(pFileEntry);
    preprocessor.EnterMainSourceFile();

    clang::Token token;
    do {
        preprocessor.Lex(token);

        if(diagnosticsEngine.hasErrorOccurred())
            break;

        preprocessor.DumpToken(token);
        std::cout << std::endl;
    } while(token.isNot(clang::tok::eof));

    return 0;
}


入力のinput.cはこんな感じ.
main()
{
}

Makefileはこんな感じ.結構色々リンクする必要がある.llvm関連については,llvm-configで簡単に設定できる.
# Makefile
main: main.cpp
 clang++ `llvm-config --cxxflags --ldflags --libs` $^ -o $@ -lclangBasic -lclangFrontend -lclangAST -lclangLex -lclangSerialization -lclangSema -lclangDriver -lclangEdit -lclangAnalysis -lclangParse

出力はこんな感じに.
identifier 'main'
l_paren '('
r_paren ')'
l_brace '{'
r_brace '}'
eof ''

コードの説明も書いておこうかと思ったけど力尽きた.
気が向いたらそのうち書こう.

2013年3月6日水曜日

コメントを自動挿入せずに改行する方法

Vimでプログラムを書いていて,単行コメントを何行か入れた後で次の行ではコメントにせずにしたい,
と思ったときにどうすれば良いのか,自動挿入をキャンセルする方法が無いのかな,と思ったけれど,
特にそれらしいオプションが見つからなかったので,適当にキーマップしてみた.

inoremap  :call append('.', repeat(' ', indent('.')))ji

Ctrlを押しながら改行すると,適当なインデント分の空白を入れて次の行から開始できる,という風にしてみた.

2013年2月6日水曜日

FXMLで2つのスライダーを連動させてみた

昼休みに少しずつコードを書いて実験しています.
今日は2つのスライダーを連動させてみました.

まずは,FXML.Simple.fxmlという名前で次のようなXMLを書きます.

<?xml version = "1.0"?>
<?import javafx.scene.Scene?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Slider?>
<Scene width = "640" height = "480" xmlns:fx="http://javafx.com//fxml">
<VBox>
<Slider fx:id = "sliderA" />
<Slider fx:id = "sliderB" value = "${sliderA.value}" />
</VBox>
</Scene>

Sceneの中に,VBoxでSliderを2つ組み込み,それぞれにfx:idでIDを振ります.
そして,valueは式の形でsliderBの方の値はsliderAの値を見るようにします.

Javaの方は至ってシンプル.このFXMLをロードして表示するだけ.
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Simple extends Application {
    public static void main(String [] args){
        launch(args);
    }

    public void start(Stage primaryStage) throws Exception {
        Scene scene = FXMLLoader.load(getClass().getResource("Simple.fxml"));
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

これで,上側のスライダーを動かすと,下側のスライダーも動きます.
でも,下側のスライダーを動かしても上は何の反応も示しません.
双方向にするには,sliderAの方にはsliderBの値を見るようにFXMLに追加してやるだけです.

こんなに簡単にプロパティのバインドができるとは・・・$を使った部分が
どこまでできるのか,新しいオブジェクトの生成が出来るのか,とかはこれから
調べて行こうと思います.