CFastDraw/CFastPlaneのように親子関係のあるクラスについて




DirectDrawを"こてこて”にwrapしたのがCFastDrawです。
同じくDirectDrawSurfaceを"こてこて”にwrapしたのが、CFastPlaneです。

CFastPlaneのサーフェースを生成するには、親玉であるCFastDrawにお願いする必要があります。これは、DirectDrawにDirectDrawSurfaceの生成を依頼する必要があることからも当然の帰結と言えます。

また、CFastDrawは、CFastPlaneのポインタを数珠つなぎにして(listとして)保持しています。解像度が切り替わったときにサーフェースはロストするので、そのときにCFastPlane::Restoreを呼び出す必要があるからです。

ところが、テキストサーフェースのように、テキストの長さによって生成されるサーフェースの大きさが異なるようなCFastPlaneの場合、サーフェースのupdateの際に、"自立的に”サーフェースを生成する必要が出てきます。もう少し具体的に言えば、CFastPlane::CreateSurfaceを、CFastDrawのポインタなしに呼び出したいのです。そのため、CFastPlaneは、自分を生成するためのcreater(具体的にはCFastDrawへのポインタ)を常に保持しておかなくてはなりません。

このため、CFastPlaneはそのコンストラクタでCFastDrawのポインタを必要とする(あるいは、あとでCFastDrawのポインタを設定するためのSetFastDrawというメンバ関数を有する)のです。

まあ、ここまでは自然な設計と言えるでしょう。

ところが、実際に使用する段階になるとこれが結構面倒なのです。たとえば、サーフェースに対してcacheのようなメカニズムを提供するCPlaneLoaderを用意したとき、このCPlaneLoaderは、CFastPlaneをnewする必要が出てきて、それに伴い、CFastDrawのポインタを渡してやらなくてはならなくなります。CFastDrawのポインタを渡すのがPlaneLoaderとしての汎用性に乏しいというならば、CFastPlaneのfactoryを渡してやるかです。(yaneSDK3rdの実装は後者になっています)

まあ、CPlaneLoaderの場合は、コンストラクタで一度渡すだけなので許せるとは思うんですが、サーフェースを一枚使うだけなのに、わざわざCFastDrawを持ち出してくるのがちょっと面倒なのです。

というのも、たいていのゲームではシーン管理(⇒CSceneControl)を行なっていると思うんですが、子シーンがメンバとしてCFastPlaneを持つならば、このCFastPlaneのコンストラクタでCFastDrawが必要である以上、子シーンにいちいち、CFastDrawを子シーンのコンストラクタに対して渡していかなくてはならないことになるからです。

どうにもこのへんの仕組みがまどろっこしい。

CFastDraw自体は、ユーザーが用意するCAppFrame派生クラスがメンバとして保持しているので、子シーンのコンストラクタに対して、CAppFrame派生クラスのポインタを渡して、こいつに

枠-1
    CFastDraw* GetFastDraw() { return &m_vFastDraw;}


のようなアクセッサを用意して、ここから頂戴するというスタイルになります。

これはこれで別にかまわないとは思うんですが、なんだか面倒くさい。

それと、サーフェースはISurfaceという共通の基底クラスを持っています。生成されたサーフェースは、汎用性を持たせるためにISurfaceのメンバを用いて転送することを推奨しています。(こうしておけば、あとで他の種類のサーフェースに切り替えることができるからです)

そこで、生成したサーフェースはISurfaceポインタにアップキャストして保持します。ポインタで保持すると解放するの忘れたり危険なので、スマートポインタで保持します。

スマートポインタをいちいち持ち出すのが面倒なので、ISurfaceのsmart_ptrをwrapしたCPlaneというクラスを用います。これは、サーフェースのfactoryも兼ねているので、単に

list-1
    CPlane plane;


と書けば、コンストラクタでfactoryからサーフェースを生成します。この直後に

list-2
    plane->Load("test.jpg");


のようにISurfaceのメンバを用いて描画を行ないます。

ところで、このCPlaneに対してサーフェースのfactoryは、いつ、誰が渡したのでしょうか?

CPlaneには、このfactoryを設定/取得する関数があります。

枠-2
    static void SetFactory(const smart_ptr<IPlaneFactory>& pFactory) { pFactory_ = pFactory; }
    static smart_ptr<IPlaneFactory> GetFactory() { return pFactory_; }

    static ThreadLocal<smart_ptr<IPlaneFactory> > pFactory_;


見てのとおり、ThreadLocalで保持しています。これはマルチウィンドゥのときは、ひとつのウィンドゥ、ひとつのスレッドが走っていることを前提とするため、ウィンドゥごとに異なる描画手段を用いるかも知れないので、少なくともスレッドごとに別のfactoryを保有しておく必要があると(私が)判断したためです。

ところが、この方式だと、1つのスレッドで、複数の種類のサーフェースを用いたいときには、CPlaneでことあるごとにfactoryを設定してやる必要が出てきます。

CPlaneの

枠-3
    static void SetFactory(const smart_ptr<IPlaneFactory>& pFactory) { pFactory_ = pFactory; }


を用いて、さいさい設定するのは、まっぴらごめんです。そこで、CPlaneに対して、コンストラクタでFactoryを直接渡して、それを構築してもらうことを考えます。つまり、こういうコンストラクタも用意します。

枠-4
    CPlane(const smart_ptr<IPlaneFactory>& pFactory);
    /// surfaceのfactoryを渡せば、それを用いて生成して保持する


このサーフェースのfactoryをいちいちユーザーが書くのが面倒なので、CFastDrawとfactoryをくっつけたCFastPlaneFactoryというクラスを用意して、普段はこいつを使うことにします。

つまり、
1.CAppFrameの派生クラスのメンバとして、CFastPlaneFactoryをメンバとして持たせ、

list-3
public:
    CFastDraw* GetDraw() { return GetDrawFactory()->GetDraw(); }
    CFastPlaneFactory* GetDrawFactory() { return& m_vFastPlaneFactory; }
protected:
    CFastPlaneFactory m_vFastPlaneFactory;


このように書きます。

2.サーフェースを用いたい子シーンのクラス(CScene派生クラス)では、CPlaneかCPlaneLoaderを用います。

3.CPlaneのfactoryは、CFastPlaneFactoryが設定してくれているので、CFastPlaneを用いる場合は特に再設定する必要はありません。CPlaneLoaderについても同様です。他の描画メソッドを用いたいときにだけ設定します。

4.サーフェースを自前で生成して、こちょこちょやりたいときはCPlaneを用いますが、たいていのゲームでは、画像をファイルから読み込んでそれを表示することになるので、頻繁に使うのはCPlaneLoaderのほうでしょう。