ResourceManagerクラスの設計


17

趣味のゲームエンジン用に中央のResourceManager / ResourceCacheクラスを作成することにしましたが、キャッシュスキームの設計に問題があります。

ResourceManagerには、ゲームのすべてのリソースを組み合わせて使用​​される合計メモリのソフトターゲットがあります。他のクラスは、リソースオブジェクトを作成します。リソースオブジェクトはアンロード状態になり、ResourceManagerに渡します。次にResourceManagerは、ソフト制限を念頭に置いて、特定のリソースをいつロード/アンロードするかを決定します。

別のクラスがリソースを必要とする場合、そのリクエストはResourceManagerに送信されます(文字列IDまたは一意の識別子を使用)。リソースがロードされると、リソースへの読み取り専用の参照が呼び出し元の関数に渡されます(参照カウントされたweak_ptrにラップされます)。リソースがロードされていない場合、マネージャーは次の機会(通常はフレームの描画の最後)にロードするオブジェクトをマークします。

私のシステムはいくつかの参照カウントを行いますが、リソースが読み取られているときにのみカウントします(したがって、参照カウントは0かもしれませんが、エンティティはまだそのuidを追跡している可能性があります)。

初めて使用する前にリソースをロード用にマークすることもできます。以下は、私が使用しているクラスのスケッチです。

typedef unsigned int ResourceId;

// Resource is an abstract data type.
class Resource
{
   Resource();
   virtual ~Resource();

   virtual bool load() = 0;
   virtual bool unload() = 0;
   virtual size_t getSize() = 0; // Used in determining how much memory is 
                                 // being used.
   bool isLoaded();
   bool isMarkedForUnloading();
   bool isMarkedForReload();
   void reference();
   void dereference();
};

// This template class works as a weak_ptr, takes as a parameter a sub-class
// of Resource. Note it only hands give a const reference to the Resource, as
// it is read only.
template <class T>
class ResourceGuard
{
   public:
     ResourceGuard(T *_resource): resource(_resource)
     {
        resource->reference();
     }

     virtual ~ResourceGuard() { resource->dereference();}
     const T* operator*() const { return (resource); }
   };

class ResourceManager
{
   // Assume constructor / destructor stuff
   public:
      // Returns true if resource loaded successfully, or was already loaded.
      bool loadResource(ResourceId uid);

      // Returns true if the resource could be reloaded,(if it is being read
      // it can't be reloaded until later).
      bool reloadResource(ResourceId uid)

      // Returns true if the resource could be unloaded,(if it is being read
      // it can't be unloaded until later)
      bool unloadResource(ResourceId uid);

      // Add a resource, with it's named identifier.
      ResourceId addResource(const char * name,Resource *resource);

      // Get the uid of a resource. Returns 0 if it doesn't exist.
      ResourceId getResourceId(const char * name);

      // This is the call most likely to be used when a level is running, 
      // load/reload/unload might get called during level transitions.
      template <class T>
      ResourceGuard<T> &getResource(ResourceId resourceId)
      {
         // Calls a private method, pretend it exits
         T *temp = dynamic_cast<T*> (_getResource(resourceId));
         assert(temp != NULL);
         return (ResourceGuard<T>(temp));
      }

      // Generally, this will automatically load/unload data, and is called
      // once per frame. It's also where the caching scheme comes into play.
      void update();

};

問題は、総データ使用量をソフト制限の下/下に維持するために、マネージャーがアンロードするオブジェクトを決定するスマートな方法を持たなければならないことです。

私は、ある種の優先度システム(たとえば、一時的な優先度、頻繁に使用される優先度、永続的な優先度)を使用して、最後の参照解除の時刻とリソースのサイズを組み合わせて、いつ削除するかを決定しようと考えています。しかし、適切なスキームを使用したり、それらを迅速に管理するために必要な適切なデータ構造を考えることはできません。

このようなシステムを実装した人が、その仕組みの概要を教えてくれませんか。見逃している明らかなデザインパターンはありますか?これをあまりにも複雑にしましたか?理想的には、効率的で使いにくいシステムが必要です。何か案は?


4
明らかな質問は、「実装するために設定した機能が必要ですか」です。たとえば、PCで作業している場合、メモリソフトキャップの設定はおそらく不要です。ゲームがレベルに分割され、そのレベルで使用されるアセットを決定できる場合は、開始時にすべてをロードし、ゲームプレイ中にロード/アンロードをまったく行わないようにします。
テトラッド

回答:


8

これがあなたの質問に100%関連するかどうかはわかりませんが、いくつかのアドバイスのヒントは次のとおりです:

  1. リソースをハンドルでラップします。リソースは、説明(通常はXML)と実際のデータの2つに分割する必要があります。エンジンは、ゲームの開始時にすべてのリソースの説明をロードし、それらのすべてのハンドルを作成する必要があります。コンポーネントがリソースを要求すると、ハンドルが返されます。このようにして、関数は通常どおり続行できます(サイズなどを要求することもできます)。リソースをまだロードしていない場合はどうなりますか?描画しようとしているがまだロードされていないリソースを置き換えるために使用される「nullリソース」を作成します。

さらにたくさんあります。私は最近この本「Game Engine Design and Implementation」を読んで、リソースマネージャクラスを設計する非常に素晴らしいセクションがあります。

ResourceHandleとメモリバジェット機能を使用しない場合の推奨事項は次のとおりです。

typedef enum
{
    RESOURCE_NULL = 0,
    RESOURCE_GRAPHIC = 1,
    RESOURCE_MOVIE = 2,
    RESOURCE_AUDIO = 3,
    RESOURCE_TEXT =4,
}RESOURCE_TYPE;


class Resource : public EngineObject
{
public:
    Resource() : _resourceID(0), _scope(0), _type(RESOURCE_NULL) {}
    virtual ~Resource() {}
    virtual void Load() = 0;
    virtual void Unload()= 0;

    void SetResourceID(UINT ID) { _resourceID = ID; }
    UINT GetResourceID() const { return _resourceID; }

    void SetFilename(std::string filename) { _filename = filename; }
    std::string GetFilename() const { return _filename; }

    void SetResourceType(RESOURCE_TYPE type) { _type = type; }
    RESOURCE_TYPE GetResourceType() const { return _type; }

    void SetResourceScope(UINT scope) { _scope = scope; }
    UINT GetResourceScope() const { return _scope; }

    bool IsLoaded() const { return _loaded; }
    void SetLoaded(bool value) { _loaded = value; }

protected:
    UINT _resourceID;
    UINT _scope;
    std::string _filename;
    RESOURCE_TYPE _type;
    bool _loaded;
private:
};

class ResourceManager : public Singleton<ResourceManager>, public EngineObject
{
public:
    ResourceManager() : _currentScope(0), _resourceCount(0) {};
    virtual ~ResourceManager();
    static ResourceManager& GetInstance() { return *_instance; }

    Resource * FindResourceByID(UINT ID);
    void Clear();
    bool LoadFromXMLFile(std::string filename);
    void SetCurrentScope(UINT scope);
    const UINT GetResourceCount() const { return _resourceCount; }
protected:
    UINT _currentScope;
    UINT _resourceCount; //Total number of resources unloaded and loaded
    std::map<UINT, std::list<Resource*> > _resources; //Map of form <scope, resource list>

private:
};

SetScope機能は、ScopeLevelがScene#を参照するScene-Layered Engine Designを参照することに注意してください。シーンに出入りすると、そのスコープに応じたすべてのリソースがロードされ、グローバルスコープにないリソースはアンロードされます。


NULLオブジェクトのアイデア、およびスコープを追跡するというアイデアが本当に好きです。学校の図書館で「ゲームエンジンの設計と実装」のコピーを探していましたが、運がありませんでした。この本は、メモリバジェットをどのように処理するかについて詳しく説明していますか?
ダーシーレイナー

いくつかの簡単なメモリ管理スキームについて詳しく説明します。最終的には、基本的なものでさえ一般的なmallocよりもはるかに優れているはずです。
セザーロン

私はこれにかなり似たデザインを選んでしまいました。
ダーシーレイナー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.