2013年11月17日日曜日

カスタムアロケータで割り当てサイズ制限

"C++のカスタムアロケータを作ってみる"に続いて,寝る前にもうひとまとめ.

カスタムアロケータにmax_sizeを定義して,数値を返すようにすると,一度に割り当てられる要素の数をその数で制限できる.
ということで,適当に4個にして実験してみる.

#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;
    }

    size_type max_size() const noexcept
    {
        return 4;
    }
};

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

5個目を追加しようとした時点で,length_errorの例外が発生する.
あくまでも1度に割り当てられる要素数の最大値であって,全体として割り当てられる要素数の最大値ではない.
具体的には,allocate(N)を実行したときに,N <= max_size()なら割り当てに成功する,という意味だそうな.

ちなみに,このmax_sizeが定義されていない場合はnumeric_limits<size_type>::max()がmax_sizeとして使われる.
特定の関数が定義されていない場合に別の関数が呼び出されるようにするためのテンプレート周りのテクニックについては,どこかで解説されていた気がするけど,忘れた.

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に追加してやるだけです.

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

2013年1月27日日曜日

TreeItemを開くときと閉じるときのイベント処理

TreeViewの中のTreeItemが開いたときと閉じたときのイベント処理を書いてみた.
まずは,EventHandlerインターフェースを継承して,イベントハンドラを作る.
開くときのイベントは,TreeItemにbranchExpandedEventというのがあったので,
それかどうかを判定してみる.閉じるときのイベントはbranchCollapsedEventらしい.

import java.io.File;
import javafx.event.EventHandler;
import javafx.scene.control.TreeItem;

public class FileTreeModificationEventHandler
    implements EventHandler<
        TreeItem.TreeModificationEvent<File>>
{
    public void handle(
        TreeItem.TreeModificationEvent<File>
        event
    )
    {
        if(event.getEventType() ==
            TreeItem.<File>branchExpandedEvent())
        {
            System.out.println("expanded");
        }else if(event.getEventType() ==
            TreeItem.<File>branchCollapsedEvent())
        {
            System.out.println("collapsed");
        }
    }
}

これを,前に作ったFileTreeItemのコンストラクタで登録させる.

    public FileTreeItem(final File f){
        super(f);

        // 開くときのイベントを登録
        addEventHandler(
            TreeItem.<File>branchExpandedEvent(),
            new FileTreeModificationEventHandler());
        // 閉じるときのイベントを登録
        addEventHandler(
            TreeItem.<File>branchCollapsedEvent(),
            new FileTreeModificationEventHandler());
    }

これで,開いたり閉じたりする度にexpandedやcollapsedが表示される.
ただ,これだと木の奥の方に行くほど何度もexpandedが表示されるので,
イベントを一回処理したら終わりにしてみる.

    public void handle(
        TreeItem.TreeModificationEvent<File>
        event
    )
    {
        if(event.getEventType() ==
            TreeItem.<File>branchExpandedEvent())
        {
            System.out.println("expanded");
            event.consume();
        }else if(event.getEventType() ==
            TreeItem.<File>branchCollapsedEvent())
        {
            System.out.println("collapsed");
            event.consume();
        }
    }

Eventのconsumeを呼び出すと,その後のイベントが伝わっていくのを抑えられる.
で,抑えてみた結果を実行すると,木が開かなくなった.expandedの方のconsumeを
消すと,今度は開くけど閉じることが出来なくなった.どうもイベントは木の根から
呼び出されていくらしい.イベントの発生源から親に向かって呼び出されていくと思っていた.
イベントのハンドラは根にだけ登録して,getSource()で処理する感じになるのかな.

で,これ書くときに一番引っかかったのがジェネリクスに対応した
staticメソッドを呼び出すときは,クラス名. の後に型名を書くということ.
クラス名に型名を指定していてずっと引っかかっていた.
Javaのジェネリクスちゃんと勉強しないとなぁ.

2013年1月25日金曜日

JavaFX TreeViewとTreeItem

JavaFXのTreeViewを使ってみた.ファイルツリー的なものを作ろうと思ったら,TreeItemのドキュメントにすでに書いてあった.
で,それを自分なりに整理して書いてみた.

まずは,ファイルツリーの要素を表すクラスを,TreeItem<File>を継承して作る.

import java.io.File;
import javafx.collections.ObservableList;
import javafx.collections.FXCollections;
import javafx.scene.control.TreeItem;

// ファイルツリーの根や分岐や葉を表すクラス
// 根は値を持たないものとする
public class FileTreeItem extends TreeItem<File> {
    public FileTreeItem(){
        this(null);
    }

    public FileTreeItem(final File file){
        // 要素の値を設定
        super(file);
    }

    // 木の要素が葉かどうか(末端かどうか)を返す
    // 要素が持つ値は根の場合はnullで,
    // それ以外では通常のFileオブジェクトになる
    @Override
    public boolean isLeaf(){
        File file = getValue();
        return file != null && file.isFile();
    }

    @Override
    public ObservableList<TreeItem<File>> getChildren(){
        // 既に一度子要素を作っている場合はそのキャッシュを利用する
        ObservableList<TreeItem<File>> children = super.getChildren();
        if(!children.isEmpty())
            return children;
 
        // 根にファイルを追加する前に通る
        File file = getValue();
        if(file == null)
            return children;

        // ファイルの場合
        if(!file.isDirectory()){
            return FXCollections.emptyObservableList();
        }

        // ディレクトリの場合
        File [] files = file.listFiles();
        for(File f : files){
            children.add(new FileTreeItem(f));
        }
        return children;
    }
}

根の持つ値をnullにしたのは,TreeViewが当たり前ではあるが根として1つしか要素を持てないため.
MacやLinuxでは/だけがルートなので良いが,Windowsの場合CドライブやDドライブと言った
複数のルートが存在するので,何も値を持たない根を用意し,その下にルートを置くようにしたいから.

表示部分は次のように.

import java.io.File;
import javafx.application.Application;
import javafx.scene.control.TreeView;
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){
        // ルートファイルのリストを取得
        File [] rootFiles = File.listRoots();
        FileTreeItem root = new FileTreeItem();
        // 根の要素に各ルートファイルを追加していく
        for(File f : rootFiles){
            root.getChildren().add(new FileTreeItem(f));
        }

        // 根の要素を渡してファイルツリーのビューを作成
        TreeView treeView = new TreeView(root);
        // 無駄なnullのルートは表示しないようにする
        treeView.setShowRoot(false);
        // TreeViewをシーンに追加して表示する
        Scene scene = new Scene(treeView);
        primaryStage.setWidth(640);
        primaryStage.setHeight(480);
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

Macでは動作確認したけど,これでWindowsでも上手く各種ドライブが
表示されるかどうかは未確認.

Javaはジェネリクスが出た頃くらいから触っていなかったのでドキュメントを読むのも
一苦労だ・・・.コンパイラに型が間違ってるとか言われまくった.

2013年1月19日土曜日

JavaFX入門

Pythonをやろうと思っていたはずが,JavaFXの勉強を始めていました.
今年も興味がうつろいやすそうです.「うつろわざるもの」になりたい.

JavaFXは,AWTやSwingのようなJavaのGUIフレームワークです.
ちょっとマルチプラットフォームでGUIのあるツールを作りたくなったため,触ってみました.

もっともシンプルなコード(Simple.java)はこんな感じ.
import javafx.application.Application;
import javafx.scene.Group;
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 {
        primaryStage.setScene(new Scene(new Group()));
        // Macでは幅と高さを指定しておかないとウィンドウが見えない
        // primaryStage.setWidth(100);
        // primaryStage.setHeight(100);
        primaryStage.show();
    }
}

で,これをコンパイルするためにはJavaFXのjarをクラスパスに追加する必要があります.
JavaFXはJava SE 7からは付いてきているようなので,最新版のJava SEを入れれば入っています.

Windows
Javaのインストールフォルダ\jre\lib\jfxrt.jar

Mac
`/usr/libexec/java_home`/jre/lib/jfxrt.jar

このパスをクラスパスに追加してJavaを実行すれば,ウィンドウが表示されます.

JavaFXでは,WPFのXAMLのようにFXMLというXMLでGUIを記述できます.
さらに,GUIをWYSIWYGエディタで作成できるJavaFX Scene Builderも提供されています.
上の例をFXML(Simple.fxml)で書くとこんな感じ.

<?xml version = "1.0"?>
<?import javafx.scene.Scene?>
<?import javafx.scene.Group?>
<Scene width = "100" height = "100">
  <Group />
</Scene>

上のコードと比較して何となく気付くかもしれませんが,オブジェクト間の関係と
XMLの入れ子関係が関連しているようです.
import文はXMLのProcessing Instructionとして指定するようです.

このFXMLのファイルを先ほどのクラスと同じ階層に置いて,
このファイルを使ってウィンドウを表示するコードは次のような感じに.

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Group;
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 {
        primaryStage.setScene(
            (Scene)FXMLoader.load(
                getClass().getResource("Simple.fxml")));
        // Macでは幅と高さを指定しておかないとウィンドウが見えない
        // primaryStage.setWidth(100);
        // primaryStage.setHeight(100);
        primaryStage.show();
    }
}

FXMLLoaderというのが提供されているわけです.
getClass().getResource("Simple.fxml")はこのクラスのある場所から指定した
リソース(この場合ファイル)のパスを取得してくれるメソッドのようです.

他にもCSSで見た目を変えられる機能もあるようなので,つい色々試したくなってきます.
目的のツールを作ることを忘れてしまいそう.

2013年1月12日土曜日

プログラミングとデザイン(設計ではない)

 UIデザイナーの方におすすめされて,ノンデザイナーズデザインブック,という
 本を買って読みました. 


 この本は,デザインの素人でもいくつかの原則を意識すればデザインは良くなる
 んですよ,ということを解説しています.その原則は,素人の自分でも確かに意
 識すれば分かる,という内容でした.その原則は次の4つです.

  • 近接
    • 関連のあるものは近くにまとめる
  • 整列
    • 右揃えや左揃えなど全体で揃える
  • コントラスト
    • 違いをはっきりとさせる
  • 反復
    • 全体で同じことを繰り返すことで,類似の情報を見つけやすくする

 相当ざっくりとまとめました.で,これを読んでいてふと思ったことがあり
 ます.読みやすいコードって,この原則が結構当てはまるのではないかと.

  • 近接
    • 関連のあるコードは近くにあると見つけやすい
  • 整列
    • インデントがある,揃っているコードは読みやすい
  • コントラスト
    • ちょっと思いつきません
  • 反復
    • コメントのスタイルが全体で同じ形を繰り返している

 こちらも結構ざくっ,としてますが,何が言いたいかと言うと,プログラマが
 何かしらのソースコードが美しいと言ってもプログラマ以外には理解されない,
 と良く言われますが,実は理解されるんじゃなかろうか,と思ったわけです.
 アーティストから見たデザイン的に良い文章(つまりコード)は,プログラマに
 とっても読みやすいコードで,プログラマにとって読みやすいコードとはアー
 ティスト的にデザインの良いコードなのでは無いか,と.

 後は,何かを以てコントラストが表せれば良いのですが.

2013年1月7日月曜日

2013年の抱負

新年明けましておめでとうございます.

基本的に熱しやすく冷めやすい性格なので,ブログも継続しない.というわけで,今年の目標は「継続」にしようと思います.

あと,巳年なのでPythonをやってみようかと.
単に言語としてのPythonを習得するだけでも良いのだけれど,ゲームプログラマとしては,Blenderとかのプラグインとか何か作れるようになると良いなぁ,と.

というわけで,Pythonの学習ネタでしばらくブログを書けたらな,と思う年明けでした.

部屋の片付けも継続しないとな・・・