ホーム < ゲームつくろー! < ツール編


その11 リソースディクショナリ(辞書)クラス


 ハッシュ値をキーとしてリソースを登録したり検索したりできるディクショナリ(辞書)テンプレートクラスです。

名前 バージョン 公開日
リソースディクショナリクラス 1.00 2010. 1.30

バージョンレポートはこちら

 ゲーム製作をする上で特にメモリを大量に使用するリソースなどは重複を避けたいところです。リソースディクショナリはリソースに割り振られたハッシュ値をキーとしてリソースをストックします。同様の事はstd::map等のツリーコンテナでもできますが、キーをハッシュ値に限定しインターフェイスも絞って扱いやすくしてあります。 

 このクラスは非参照のリソースを辞書からはずすrefleshメソッドをサポートしています。そのため、登録するリソースクラスは自身の参照カウント数を返すGetRefNumメソッドを持っている必要があります。マルペケのツールクラスであるスマートポインタ(Dix::sp<T>)はGetRefNumメソッドを持っているため、リソースオブジェクトをスマートポインタに包むとこのクラスをすぐに使えます。ただ、参照カウンタ数さえ返す仕組みがあれば変換関数を通す事で別のメモリ管理クラスのオブジェクトも辞書に登録可能です。詳しくは下記のリファレンス及びサンプルコードをご覧下さい。

 このクラスを使うにはDixSmartPtr.hとDixHash32.hが必要です。


○ 定義

名前空間 ヘッダー
Dix ResourceDictionary.h


○ リソースディクショナリクラスメンバメソッド

公開メンバメソッド 説明 使い方 備考
ResourceDictionary()
コンストラクタ ResourceDictionary<Image> dictionary; -
~ResourceDictionary( void ) デストラクタ - -
void clear() クリア ResourceDictionary<Image> dictionary;
Image image("image01.bmp");
dictionary.regist( Hash32("image01"), image );
dictionary.clear();
リソースの参照数によらず辞書をまっさらにします。
bool find( Hash32 hash ) リソースがあるかをチェック ResourceDictionary<Image> dictionary;
dictionary.find( Hash32("image01") );
引数のハッシュ値でリソースが登録されているかをチェックします。このメソッドはチェックするだけで値を返す事はしません。
bool get( Hash32 hash, T& out ) リソースを取得 ResourceDictionary<Image> dictionary;
Image image;
dictionar.get( Hash32("image01"), image )
指定のハッシュ値のリソースを取得します。リソースが無い場合はfalseが返されます。また第2引数に渡したオブジェクトは変更されません。
void reflesh()
void reflesh( unsigned (*refFunc)(T &) )
参照されていないリソースを辞書から削除 ResourceDictionary<Image> dictionary;
Image image("image01.bmp");
dictionary.regist( Hash32("image01"), image );
dictionary.reflesh();

dictionary.reflesh( RefNumFunc );
リソースディクショナリ以外誰も参照していないリソースを辞書から削除(解放)します。Dix::sp<T>を使用している場合はこの段階でリソースはメモリから削除されます。
関数を渡すバージョンは、T型のオブジェクトから参照カウンタを算出する方法を提供します。関数の戻り値は保持しているオブジェクトの参照カウンタ数です。詳しくは下記のサンプルコードを参照下さい。
bool regist( Hash32 hash, T resoruce ) リソースを登録します ResourceDictionary<Image> dictionary;
Image image("image01.bmp");
dictionary.regist( Hash32("image01"), image );
辞書にリソースを登録します。ハッシュ値はそのリソースに対して一意である必要があります。同じハッシュ値を持つ別のリソースは登録に失敗し、メソッドはfalseを返します。
bool remove( Hash32 hash ) 指定のリソースを辞書から削除 ResourceDictionary<Image> dictionary;
Image image("image01.bmp");
dictionary.regist( Hash32("image01"), image );
dictionary.remove( Hash32("image01");
指定のハッシュ値に対応するリソースを辞書から除きます。削除が成功した段階で辞書はそのリソースの参照をやめますが、リソース自体を削除するわけではありません。よって、そのリソースを参照している他のオブジェクトは引き続きそのリソースを参照し続けられます。


○ バージョンレポート

v1.00
・ 初出実装

○ 使い方

#include <tchar.h>
#include "ResourceDictionary.h"
#include "DixSmartPtr.h"
#include "DixComPtr.h"

// 仮のCOMクラス
class ID3DXHoge : public IUnknown {
    ULONG ref;
public:
    ID3DXHoge() : ref(1) {}
    virtual ~ID3DXHoge() { if(!ref) delete this; }

    // 以下は面倒ですが継承が必要なメソッド・・・
    virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void __RPC_FAR *__RPC_FAR *ppvObject) { return S_OK; }
    virtual ULONG STDMETHODCALLTYPE AddRef(void) { return ++ref; }
    virtual ULONG STDMETHODCALLTYPE Release(void) { return --ref; }
};

// Dix::Com_ptrから参照カウンタ数を返す関数
// しまったなぁ、名前統一しておくんだっだ・・・と思いました(^-^;
template<class T>
unsigned GetRefNum( T &src ) {
    return src.GetRefCnt();
}


// 独自のイメージクラス
class Image {
    char *image;

public:
    Image() : image() {}
    ~Image() { delete[] image; }
    // イメージ作成(仮ですよ、これ)
    void create( const char *data, unsigned size ) {
        image = new char[size];
        memcpy( image, data, size );
    }
    const char *get() const { return image; }
};

typedef Dix::sp<Image> SPImage;


// メイン
int _tmain(int argc, _TCHAR* argv[])
{
    struct ImageInfo {
        Dix::Hash32 name;
        Dix::sp<Image> image;

        ImageInfo(const char *name) : name(name) {}
    };

    // 仮のイメージを作成
    ImageInfo imageInfo("image0.bmp");
    imageInfo.image.SetPtr( new Image() );
    const char data[] = "これがイメージデータだと思いねぇ。実際は色データが並ぶんですよ。";
    imageInfo.image->create( data, sizeof(data) );

    // 辞書にイメージリソースを登録
    Dix::ResourceDictionary<SPImage> dictionary;
    dictionary.regist( imageInfo.name, imageInfo.image );

    imageInfo.image = 0; // mainのスタックから消しときます。参照しているのは辞書だけになります。

    // リソース取得
    SPImage image1;
    dictionary.get( imageInfo.name, image1 );

    // リソース出力テスト
    printf("--リソース出力テスト1--\n");
    if ( image1.GetPtr() )
        printf("image -> %s\n", image1->get() );
    else
        printf("あれ?(^-^;\n");

    image1 = 0;

    // 辞書リフレッシュ
    // イメージはここで消えます
    dictionary.reflesh();

    // もう1回リソースを取得・出力してみる
    dictionary.get( imageInfo.name, image1 );
    printf("--リソース出力テスト2--\n");
    if ( image1.GetPtr() )
        printf("辞書からリソースが消されて無いなぁ\n");
    else
        printf("辞書から正しくリソースが消されました\n");


    // 応用編 //

    // 参照カウントを返す変換関数を用意しておけば
    // Dix::Com_ptrも扱えます!
    ID3DXHoge *hoge = new ID3DXHoge;
    Dix::Com_ptr<ID3DXHoge> cpHoge(hoge);
    Dix::ResourceDictionary<Dix::Com_ptr<ID3DXHoge> > hogeDictionary;
    hogeDictionary.regist( Dix::Hash32("hoge"), cpHoge );
    hogeDictionary.reflesh(GetRefNum<Dix::Com_ptr<ID3DXHoge> >); // ここで参照カウンタ数を返す関数を渡す
    cpHoge = 0;
    hogeDictionary.reflesh(GetRefNum<Dix::Com_ptr<ID3DXHoge> >); // ここで参照カウンタ数を返す関数を渡す

    return 0;
}

 リソースディクショナリは辞書に載せたいオブジェクトをマルペケのスマートポインタに包むと直ぐに使えます。上はImageというイメージクラス(仮)を定義し、それをスマートポインタに包み辞書に登録しています。登録したリソースをgetメソッドで取得し出力するとうまく取得できているはずです。refleshメソッドを呼ぶと辞書が保持している「誰も参照していないリソース」を削除します。こういうのは、例えばゲームのあるシーンが終了した後に不必要となったリソースを一括で削除する時に非常に役立ちます。

 clearメソッドはディクショナリをまっさらにします。これは参照数によらず辞書を完全に初期化します。refleshメソッドと使い分けると便利です。

 ディクショナリクラスはrefleshメソッド以外は単なるstd::mapと同じ振る舞いをします。refleshメソッド内では保持しているオブジェクトの参照カウンタ数をチェックする必要があるため、デフォルトでマルペケのスマートポインタの持つGetRefNumメソッドを使っています。ただ、このメソッドが無いために辞書を使えないのもどうかと思い、refleshメソッドに変換関数を渡せるバージョンも用意しました。上のサンプルの「応用編」以下がその例です。ここではDirectXのオブジェクトの削除管理をするDix::Com_ptrクラスのオブジェクトを辞書に渡しています。Com_ptrクラスはGetRefNumメソッドは持っていませんが、参照カウンタを返すメソッドであるGetRefCntメソッドは持っています(やっちまった感バリバリ(^-^;)。そこでグローバルなGetRefNum関数を用意してrefleshメソッドに渡しています。これでDirectXのオブジェクトもCom_ptrを通して辞書に登録できます!

 ディクショナリ内部ではstd::mapを使ってリソース管理をしていますが、マップ自体もスマートポインタで包んでいます。このため、ディクショナリを引数に渡してもリソースの大量コピーが発生する事はありません(^-^)。