バインダ改修中

結構何度も書き直してしまう関数のバインダ周りなんですが、また書き直してます。
これまでの記述方法でも書けるのですが、次のような新記法も追加することになりました。

XTAL_BIND(Vector){
  Xdef_method(normalize); // このような、もっと簡単な記述方法の追加
  Xdef_method(length);
}

また、引数の最後をArguments* または const ArgumentsPtr& とすることで、簡単に可変引数を受け取れるようになりました。

標準ライブラリのバインド

たとえばluaでは、数学系の関数をluaから使いたいという場合、

lua_State *L = lua_open();
luaopen_math(L);

というように、luaopen_math()を呼ぶ必要があります。これはsquirrelでもそうだったはずです。
使わないライブラリでメモリを圧迫しないようにできるわけですが、ちょっと面倒にも思います。

Xtalでは必要なときに自動的にバインドされる機構を入れました。

f: math::sin(1); // ここでmathのメンバがバインドされる

Xtalでは、使わないときはバインドされないし、使う際は何もC++側をいじることなく使うことができるわけです。


これは、ユーザーが作ったライブラリも同様です。

XTAL_PREBIND(UserClass){ // PREBINDでは、コンストラクタ、継承関係を記述
  it->inherit(cpp_class<UserBaseClass>()); // UserBaseClassを継承させる
  it->def_ctor(ctor<UserClass>()); // コンストラクタを登録
}

XTAL_BIND(UserClass){ // メンバを登録
  it->def_method("aaa", &UserClass::aaa);
  it->def_method("bbb", &UserClass::bbb)
  it->def_method("ccc", &UserClass::ccc)
  it->def_method("ddd", &UserClass::ddd)
  it->def_method("eee", &UserClass::eee)
  it->def_method("fff", &UserClass::fff)
}

と書いておけば、クラスのメソッドを呼び出すときなどに自動的にXTAL_BINDが実行され、メンバが登録されます。
XTAL_PREBINDは継承関係のチェックがなされる際、またはインスタンスの生成がされる際に呼び出されます。

eval関数追加

デバッガの実装のために「ブレークポイントにて停止中に式を評価する機能」が必要だったので実装していたのですが、ついでにbuiltin::eval関数として追加することにしました。

foo: 100;

eval("foo + 10").p; //=> 110;

また、永らく使用不可能となっていたixが、evalを使うことでシンプルに実装しなおすことができたので、復活することになりました。

16byteアライメントを持つメンバを持つオブジェクトに対応

これまでxnewで生成されたオブジェクトは16byteアライメントを持っていてもいなくても、関係なくmallocで取得したメモリに配置していました。
普通のnewも同様に、関係なく配置します。

なのでアライメントの問題はどうしようもないだろう、と思っていたのですが、
ゲームにおいては、16byteアライメントを持つことはよくありうる、といったこともあり、対応することにしました。

どうやって対応するか、この一週間悩んでやっと実装できました。


xnewで生成されたオブジェクトは、16byteアライメントを持つメンバがある場合16byteアライメントを保障します。
持たないオブジェクトに関してはこれまでどおりです。

#include <xmmintrin.h>

struct Vec128{
	__m128 a;
};

struct Spr{
	Vec128 v;
};

XTAL_BIND(Spr){
	it->def_var("v", &Spr::v);
}

void test(){
	Spr* p = new Spr();
	_mm_add_ps(p->v.a, p->v.a); // これは危険


	SmartPtr<Spr> s = xnew<Spr>();
	_mm_add_ps(s->v.a, s->v.a); // xnewで確保した場合は大丈夫
}
      • -

今回の実装の余波


AllocatorLibは次のようなインターフェイスと変更となりました。

class AllocatorLib{
public:
	virtual ~AllocatorLib(){}
	virtual void* malloc(std::size_t size);
	virtual void free(void* p, std::size_t size);

	virtual void* malloc_align(std::size_t size, std::size_t alignment);
	virtual void free_align(void* p, std::size_t size, std::size_t alignment);

	virtual void out_of_memory(){}
};

malloc_align, free_alignが増えています。
ただし、これらは実装しなくてもかまいません。デフォルトの実装でmallocを使って整列するメモリを返すようになっています。
memalignなど、もっと効率のよい関数がある場合にオーバーライドしてください。

  • -

また、RefCountingBase, Baseという基底クラスも修正しました。
仮想関数はすべてon_という接頭をつけた名前にリネームし、非仮想関数としました。
たとえばvirtual void rawmember();はvoid on_rawmember();となります。
デストラクタも非仮想とし、仮想関数を排除しました。
ただし、特別な仕組みを導入したため、それらは仮想関数と同様にオーバーライドできます。

メモリアロケータ書いた


ええ、車輪の再発明だということは承知しておる!
Xtal全体がまぁ再発明ともいえるんですけど。

Xtalはユーザーが自由にmalloc, free関数を登録できるようになってるんですが、
「メモリ確保関数を用意して登録するのめんどくさい。単純にメモリのここからここまで使ってほしいんよ」ということもあると思うんです。
そのために、
・クラス一つで完結している
・メモリ領域を指定するだけ
・グローバル変数などを使用しない
・まあいろいろととにかくシンプル
そんなライブラリを作ろうということで作りました。


チャンクの管理は赤黒木で行っています。
要求されたサイズにもっとも近く、またアドレス値が小さいチャンクを検索します。
管理領域のサイズは、フリーチャンクのときは16byte(というかポインタ4つ分)、使用チャンク時は8byte(というかポインタ二つ分)です。
赤黒木のノードとしての色と、使用中かどうかのフラグは、前チャンクを指すポインタに埋め込んでます。

性能評価して、まるでダメなら素直にdlmalloc組み込むか…。
しかし、メモリアロケータの性能評価ってどう計ったら効果的なんだろか?

以下ソース。 続きを読む

Rubyでバイナリデータを読み書きする


Rubyでバイナリデータを読み書きするのには、pack, unpack使うじゃないですか。

僕もねぇ、Ruby使い始めてもう何年も経つんですけど、pack, unpackっていつまでたっても覚えられないし、
使うとなんか汚い煩雑なコードになっていくんですね。そんなことってないですか?


なんかライブラリないかなーとググりまして、こちらで紹介されているライブラリを一通り見てみました。
http://d.hatena.ne.jp/kenhys/20070522/1179848262
・binaryparser
・calibre-binaryreader
・bindata
・BitStructEx
しかし、なんかどれも複雑というか、いまいち直感的じゃないという気がします。


自分の理想の使い方の形は次のような感じです。
include(BinaryData)


# float3要素のベクトル構造体定義
Vector = struct{
  # Vectorの最初の要素はxで、float 32 bitの型という意味
  var x: F32

  # Vectorの二つ目の要素はyで、float 32 bitの型という意味
  var y: F32
  
  # まぁ!みて!このz要素の定義を!まるでJavaScriptの定義みたいだわ!
  var z: F32
}

# 3x3のマトリックス構造体定義
Matrix = struct{
  # Matrixの最初の要素はrowsで、ユーザーが定義したVector構造体の三つの要素を持つ配列という意味
  var rows: Vector[3]

  # ユーザーが定義した構造体をネストして、しかも配列として使えるということね!
}

#
Model = struct{
  # 頂点の数をinteger 32 bit型として持つ
  var vertex_count: I32

  # 上のvertex_countを長さとして使えるのね!
  var vertices: Vector[:vertex_count]

  var matrix: Matrix
}

# モデル構造体のインスタンス生成
model = Model.new

# 値を格納するのも、直感的な記法なのね!
model.vertex_count = 2;
model.vertices[0].x = 1
model.vertices[0].y = 2
model.vertices[0].z = 3
model.vertices[1].x = 4
model.vertices[1].y = 5
model.vertices[1].z = 6

model.matrix.rows[0].x = 10
model.matrix.rows[1].y = 20
model.matrix.rows[2].z = 30


sio = StringIO.new

# バイナリとして書き込む
Model.write_to_stream(sio, model)

#これでsioにはC言語で次のような感じで書いて出力したバイナリと同じフォーマットで入ってる
# struct Vector{ float x, y, z; };
# struct Matrix{ Vector rows[3]; };
# struct Model{ int vertex_count; Vector vertices[vertex_count]; Matrix matrix; };
# Model model = {...};
# fwrite(fp, &model, 1, sizeof(model));

# ストリームを一番最初に戻す
sio.pos = 0

# バイナリから読み込んで復元する
model_deserialized = Model.read_from_stream(sio)

# 文字列化して出力
print model_deserialized
文字列化されたプリント結果
{:vertex_count=>2,
 :vertices=>[{:x=>1.0, :y=>2.0, :z=>3.0}, {:x=>4.0, :y=>5.0, :z=>6.0}],
 :matrix=>
  {:rows=>
    [{:x=>10.0, :y=>0.0, :z=>0.0},
     {:x=>0.0, :y=>20.0, :z=>0.0},
     {:x=>0.0, :y=>0.0, :z=>30.0}]}}
で、そんな理想なライブラリの下地を作ってみました。
続きを読む

文字列リテラルを渡されて生成されたStringオブジェクトは、メモリ確保が起こらないようにした

もともと文字列の長さがsizeof(int)-1のものは、即値になるようにしてメモリ確保が起きないよう工夫していましたが、
今回、文字列リテラルを渡されて生成されたStringオブジェクトも即値になるようにしました。

void foo(){
  StringPtr str = xnew<String>(XTAL_STRING("This is a pen."));
  // 変数strに直接文字列リテラルへのポインタが埋め込まれている

  AnyPtr a = XTAL_STRING("Hello, World");
  // 変数aに直接文字列リテラルへのポインタが埋め込まれている
}