Hatena::ブログ(Diary)

陰間茶屋

2016-05-05

SUPER MAOU LANDで使っているカスタムRTTI

| 04:35

SUPER MAOU LANDで使っているカスタムRTTIの自分用メモ。


(SUPER MAOU LAND作ってます)

SUPER MAOU LAND



もともとはUnityっぽくGetComponent<Hoge>();などとやりたいために作り出したもの。

そのためにカスタムRTTIが必要になったので、その実装内容をメモしておく。


カスタムRTTIとかの前にC++でどうやってGetComponent<Hoge>();風な実装しようか考える。

するとインスタンスに対してHoge型かどうかを尋ねる関数は必要だと思う。

なのでまずこんな感じの関数を作ってみる。

//キャスト可能ならtrue
template<class T>
bool IsA(Component *component){
    return (component->getRTTI() == T::TYPE());
}

テンプレートを使うことによって、インスタンス(component)の型情報と、任意の型(T)の型情報の比較をしてるだけ。

すると、キャストする関数はこんな感じに書ける。

//キャスト(変換できないならnullptr)
template<class T>
T* Cast(Component *component){
    if( IsA<T>(component) ){
        return static_cast<T*>(component);
    }
    return nullptr;
}

先ほど定義したIsA()を呼び出して、trueならstatic_castするだけ。

これをうまく使えばGetComponent<Hoge>();風な実装ができそうな気がしますね?


さて、IsA()の実装内容からして、オレオレComponentにはgetRTTI()とTYPE()の実装が必要になった。

つまり、こんな感じの実装にしとく必要がある

class ComponentBase{
public:
    using RTTI = const std::string *;       //カスタムRTTIの実体は文字列のポインタ
    virtual RTTI getRTTI() const = 0;       //実装は子供に任せます
};

//コンポーネントその1
class HogeComponent : public ComponentBase{
public:
    RTTI getRTTI() const override{
        return TYPE();                      //実装内容はTYPE()に任せます
    }
    static RTTI TYPE(){                     //s_typeが型情報になります。他の型情報とかぶらない様に注意してください
        static const std::string s_type = "HogeComponent";
        return &s_type;
    }
};

//コンポーネントその2
class FugaComponent : public ComponentBase{
public:
    RTTI getRTTI() const override{
        return TYPE();                      //実装内容はTYPE()に任せます
    }
    static RTTI TYPE(){                     //s_typeが型情報になります。他の型情報とかぶらない様に注意してください
        static const std::string s_type = "FugaComponent";
        return &s_type;
    }
};

getRTTI()はインスタンスに対して呼び出されるので、仮想関数として定義しときます。

TYPE()は、T::TYPE()の様に呼び出されるので、staticメンバ関数として定義します。


さて、ここでもう一度IsA()の実装を振り返って、実装内容を確認します。

例えば、IsA<HogeComponent>(component);の呼び出しがどうなるかというと、こんな感じになります。

bool IsA(Component *component){
    return (component->getRTTI() == HogeComponent::TYPE());
}

インスタンスが持つ型情報と、型が持つ型情報が比較されています。

componentがHogeComponentであれば、getRTTI()とTYPE()が同じものを返すので、trueが返ります。

また、componentがFugaComponentであれば、getRTTI()とTYPE()が違うものを返すので、falseが返ります。


実際には、getRTTI()とTYPE()の定義がメンドイので、マクロにして使っています。

#define IMPLEMENT_COMPONENT_RTTI(className) \
    ComponentBase::RTTI getID() const override{             \
        return TYPE();                                      \
    }                                                       \
    static ComponentBase::RTTI TYPE(){                      \
        static const std::string s_type = #className;       \
        return &s_type;                                     \
    }                                                       \

すると、実装はこんな感じになります。

class ComponentBase{
public:
    using RTTI = const std::string *;       //カスタムRTTIの実体は文字列のポインタ
    virtual RTTI getRTTI() const = 0;       //実装は子供に任せます
};

//コンポーネントその1
class HogeComponent : public ComponentBase{
    //ここに好きなように色々書く
public:
    IMPLEMENT_COMPONENT_RTTI(HogeComponent);
};

//コンポーネントその2
class FugaComponent : public ComponentBase{
    //ここに好きなように色々書く
public:
    IMPLEMENT_COMPONENT_RTTI(FugaComponent);
};

わぁ、かんたん!

この仕組みはゲーム中のイベントとかにも持たせておくと、イベント毎の処理を捌きやすくなって色々と捗る。

マクロは好きくないけど他にいいやりかたが思いつかなかったのでこれでいいや、って感じ。

かしこいカスタムRTTIの実装だと継承関係とか辿ったりとかやるんだろうけど個人製作のゲームでそこまでいらんだろーって事で、ひとまずこれで。

つーかそもそもdynamic_cast使っちゃえばこんなもん実装する必要はないのだけれど・・・。


あとはこの仕組みを使ってGetComponent<Hoge>();できるようにするだけなんだが、その実装メモについては後で書くことにする。

後で書く。・・・といいなぁ。

2016-05-04

ZYXの順の回転行列からオイラー角を抽出するまとめ

| 01:59

ZYXの順の回転行列からオイラー角を抽出するまとめ。自分用メモ。

 R = ¥left( ¥begin{array}  m_{00}&m_{01}&m_{02} ¥¥ m_{10}&m_{11}&m_{12} ¥¥ m_{20}&m_{21}&m_{22} ¥end{array} ¥right)

 R_x = ¥left( ¥begin{array}  1&0&0 ¥¥ 0&cos(x)&sin(x) ¥¥ 0&-sin(x)&cos(x) ¥end{array} ¥right)

 R_y = ¥left( ¥begin{array}  ¥cos(y)&0&-sin(y) ¥¥ 0&1&0 ¥¥ sin(y)&0&cos(y) ¥end{array} ¥right)

 R_z = ¥left( ¥begin{array}  ¥cos(z)&sin(z)&0 ¥¥ -sin(z)&cos(z)&0 ¥¥ 0&0&1 ¥end{array} ¥right)

 R_{zyx} = ¥left( ¥begin{array} ¥cos(z)cos(y) & sin(z)cos(x)+cos(z)sin(y)sin(x) & sin(z)sin(x)-cos(z)sin(y)cos(x) ¥¥ -sin(z)cos(y) & cos(z)cos(x)-sin(z)sin(y)sin(x) & cos(z)sin(x)+sin(z)sin(y)cos(x) ¥¥ sin(y) & -cos(y)sin(x) & cos(y)cos(x) ¥end{array} ¥right)


行列を眺めると、

 m_{20} = sin(y)なので、 y = asin(m_{20})

 ¥frac{m_{21}}{m_{22}} = ¥frac{-sin(x)}{cos(x)} = -tan(x)なので、 x = atan(-m_{21}, m_{22})

 ¥frac{m_{10}}{m_{00}} = ¥frac{-cos(z)}{sin(z)} = -tan(z)なので、 z = atan(-m_{10}, m_{00})


あとは、例外として、 m_{20} = sin(y) = ¥pm1のときを考える。( cos(y) = 0になるので上記の割り算で都合が悪い。)

こういうときは適当に x = 0として計算しちゃうのが良いらしい。

すると、 sin(x) = 0なので m_{01},  m_{11}の値が整理されるので、

 ¥frac{m_{01}}{m_{11}} = ¥frac{sin(z)cos(x)}{cos(z)cos(x)} = ¥frac{sin(z)}{cos(z)} = tan(z)

よって、 z = atan(m_{01}, m_{11})



参考url:

http://d.hatena.ne.jp/It_lives_vainly/20070829/1188384519

http://qiita.com/q_tarou/items/46e5045068742dfb2fa6

2013-11-16

std::asyncめも

| 21:40

std::asyncなるものがあると聞いたので、とりあえずどんな感じで使えばいいのかをめも。

まずはこちらから。

#include <iostream>
#include <thread>
#include <future>
#include <chrono>
using namespace std;

class Hoge{
public:
  Hoge(){ cout << "Hoge::Hoge()" << endl; }
  ~Hoge(){ cout << "Hoge::~Hoge()" << endl; }

  void operator()(){
    cout << "thread start" << endl;
    this_thread::sleep_for(chrono::seconds(5));
    cout << "thread end" << endl;
  }
};

int main(int argc, char **argv){
  Hoge hoge;

  async(launch::async, ref(hoge)); //これはスレッドの終了を待つ
  cout << "---" << endl;

  auto f = async(launch::async, ref(hoge)); //これはfのデストラクタでスレッドの終了を待つ

  cout << "main end" << endl;
  //f.wait();
  return 0;
}

実行結果

$ ./a.exe
Hoge::Hoge()
thread start
thread end
---
thread start
main end
thread end
Hoge::~Hoge()

使い方は、asyncに実行したい関数オブジェクトを渡すだけ。

async(launch::async, ref(hoge));

のように、戻り値を受け取らないように書いた場合は、そこでスレッドの終了を待つみたい。


auto f = async(launch::async, ref(hoge));

のように、戻り値(型はstd::futureになるみたい)を受け取った場合は、std::future::wait()などが用意されてるので、それを使えばスレッドの終了待ちができるみたい。

今回はwait()使ってないけど、fのデストラクタで終了待ちしてくれるみたいでした。


また、スレッドで投げた例外を受け取りたい場合、std::future::get();を使えばいいみたい。

#include <iostream>
#include <thread>
#include <future>
#include <chrono>
using namespace std;

class Hoge{
public:
  Hoge(){ cout << "Hoge::Hoge()" << endl; }
  ~Hoge(){ cout << "Hoge::~Hoge()" << endl; }

  void operator()(){
    cout << "thread start" << endl;
    this_thread::sleep_for(chrono::seconds(5));
    throw 999;
    cout << "thread end" << endl;
  }
};

int main(int argc, char **argv){
  try{
    Hoge hoge;
    auto f = async(launch::async, ref(hoge));
    cout << "---" << endl;
    f.get();
    cout << "---" << endl;
  }catch(int i){
    cout << i << endl;
  }

  cout << "main end" << endl;
  return 0;
}

実行結果

$ ./a.exe
Hoge::Hoge()
---
thread start
Hoge::~Hoge()
999
main end

f.get();ってやってるところで、例外を受け取ってます。

すると、catch節にいくので、次のcout << "---" << endl;は呼ばれません。

get()は例外の他に、returnの値も受け取ることができるらしいです。


なんかスレッド周りの話が続いてるけど、スレッドおっ立てるのが趣味って訳じゃないです。

今日のめもおしまい。

2013-11-04

std::threadつかってみる

| 00:19

std::threadの使い方メモ。

まずは基本から。

#include <iostream>
#include <thread>
using namespace std;

class Hoge{
public:
  Hoge() : m_count(0){/**/}

  void operator()(){
    for(int i=0; i<10000000; ++i){
      m_count++;
    }
  }

  int m_count;
};


int main(int argc, char **argv){
  Hoge hoge;
  thread tr(ref(hoge));
  tr.join();

  cout << hoge.m_count << endl;

  return 0;
}

処理としては別スレッドカウントアップさせてるだけ。


operator()を実装してるオブジェクトをstd::threadに渡してあげると、スレッドが走る。終了待つときはjoin()使う。

オブジェクトのコピーを発生させたくないときは、上の例のようにref(hoge)ってやる。なので、オブジェクトコピーされてもいい場合はthread tr(hoge);でもおk。今回は最後に結果表示したかったので、ref使ってみました。


このプログラムのmainを

int main(int argc, char **argv){
  Hoge hoge;
  thread tr1(ref(hoge));
  thread tr2(ref(hoge));

  tr1.join();
  tr2.join();

  cout << hoge.m_count << endl;

  return 0;
}

ってやっちゃうと、カウンタ変数m_countがスレッド間で共有されてるので、きっちりとした値が出てきません。

えぇい!mutexだ!mutexをよこせ!!

ということでstd::mutex使ってみる。


mutex使う版

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;

class Hoge{
public:
  Hoge() : m_count(0){/**/}

  void operator()(){
    for(int i=0; i<10000000; ++i){
      lock_guard<mutex> lock(m_countMutex);
      m_count++;
    }
  }

  int m_count;
  mutex m_countMutex;
};


int main(int argc, char **argv){
  Hoge hoge;
  thread tr1(ref(hoge));
  thread tr2(ref(hoge));

  tr1.join();
  tr2.join();

  cout << hoge.m_count << endl;

  return 0;
}

lock_guard<mutex> lock(m_countMutex);ってやってるところでmutexをロックしてます。

アンロックしとらんじゃないかー!!って、思うかもしれないけど、lock_guardの変数の寿命が切れたときにアンロックされるので大丈夫です。

これで、20000000まできちんとカウントアップされるはずです。(´・∀・`)ヘー


なんか間違ってたらごめん

2013-10-25

マルチスレッドでロックをかけずに読み書きするとどうなんのか

| 01:04

久しぶりの日記だがメモ代わりに書いとこうと思ったので書く。


マルチスレッドで同じ変数の読み書きするときはロックしましょうねーって教わったのは確かなんだが、なんで読むときもロックかけんのー?って思ったので動作確認してみた。

プログラムはこんな感じ

#include <iostream>
#include <pthread.h>
using namespace std;

typedef long long VAL;
volatile VAL g_val = 0;

void *threadFuncWrite(void *args)
{
  for(int i=0; i < 1000000; ++i){
    g_val = (i&1)? 0 : 0x1234abcd12345678LL;
  }
  return NULL;
}

void *threadFuncRead(void *args)
{
  for(int i=0; i < 1000000; ++i){
    VAL g = g_val;
    if(g!=0 && g!=0x1234abcd12345678LL){
      cout << hex << g << endl;
    }
  }
  return NULL;
}


int main(int argc, char **argv)
{
  pthread_t thw, thr;

  pthread_create(&thw, NULL, threadFuncWrite, NULL);
  pthread_create(&thr, NULL, threadFuncRead, NULL);
  
  pthread_join(thw, NULL);
  pthread_join(thr, NULL);

  return 0;
}

g_valに0か0x1234abcd12345678を書き込むスレッドと、g_valの値を読んで0か0x1234abcd12345678以外の値になってたらその値を出力するスレッド、の2つを作って走らせてるだけ。

これを何回か実行してみると、

$ ./a.exe
12345678
1234abcd00000000
12345678
12345678
12345678
1234abcd00000000
12345678
12345678
12345678
12345678
12345678
12345678
1234abcd00000000
1234abcd00000000
12345678

おぉー値が狂っておる!

再現しないときはループさせる数増やせばすぐに分かると思う。

ちなみにintとかで試したときは起こらなかったんで、1命令で読み込めるビット数とかその辺りが関係するんだろーなーとか思いつつもそこまで詳しい話は分からんかった。


とりあえず読み込みのときもロックしなくちゃいけないんだなーって思いましたまる。