Merancang kelas ResourceManager

17

Saya telah memutuskan saya ingin menulis kelas ResourceManager / ResourceCache pusat untuk mesin game hobi saya, tetapi saya mengalami kesulitan merancang skema caching.

Idenya adalah bahwa ResourceManager memiliki target empuk untuk total memori yang digunakan oleh semua sumber daya game digabungkan. Kelas-kelas lain akan membuat objek sumber daya, yang akan berada dalam keadaan tidak diturunkan, dan meneruskannya ke ResourceManager. ResourceManager kemudian memutuskan kapan memuat / membongkar sumber daya yang diberikan, dengan mengingat batas lunaknya.

Ketika sumber daya dibutuhkan oleh kelas lain, permintaan dikirim ke ResourceManager untuknya, (baik menggunakan string id atau pengidentifikasi unik). Jika sumber daya dimuat, maka referensi baca-saja ke sumber daya diteruskan ke fungsi pemanggilan, (dibungkus dengan lemah_ptr dihitung direferensikan). Jika sumber daya tidak dimuat, maka manajer akan menandai objek yang akan dimuat pada kesempatan berikutnya, (biasanya di akhir menggambar bingkai).

Perhatikan bahwa, meskipun sistem saya melakukan penghitungan referensi, ia hanya menghitung ketika sumber daya sedang dibaca, (jadi penghitungan referensi mungkin 0, tetapi suatu entitas mungkin masih melacaknya).

Juga dimungkinkan untuk menandai sumber daya untuk memuat jauh sebelum penggunaan pertama. Berikut ini sedikit sketsa kelas yang saya gunakan:

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();

};

Masalahnya adalah, untuk menjaga agar total penggunaan data tetap berada di sekitar / di bawah batas lunak, manajer harus memiliki cara cerdas untuk menentukan objek mana yang dibongkar.

Saya sedang berpikir untuk menggunakan semacam sistem prioritas, (mis. Prioritas Sementara, Prioritas yang Sering Digunakan, Prioritas Permanen), dikombinasikan dengan waktu dereferensi terakhir, dan ukuran sumber daya, untuk menentukan kapan harus menghapusnya. Tapi saya tidak bisa memikirkan skema yang layak untuk digunakan, atau struktur data yang tepat diperlukan untuk mengelolanya dengan cepat.

Bisakah seseorang yang telah menerapkan sistem seperti ini memberikan gambaran tentang bagaimana mereka bekerja. Apakah ada pola desain yang jelas saya lewatkan? Apakah saya membuat ini terlalu rumit? Idealnya saya membutuhkan sistem yang efisien, dan sulit disalahgunakan. Ada ide?

Darcy Rayner
sumber
4
Pertanyaan yang jelas adalah "apakah Anda memerlukan fitur yang ingin Anda terapkan". Jika Anda menggunakan PC, pengaturan soft cap memori mungkin berlebihan, misalnya. Jika gim Anda dibagi menjadi beberapa level, dan Anda dapat menentukan aset apa yang akan digunakan di level tersebut, muat semua saja di awal dan hindari bongkar / muat sama sekali selama bermain game.
Tetrad

Jawaban:

8

Saya tidak yakin apakah ini berkaitan dengan pertanyaan Anda 100% tetapi beberapa saran adalah sebagai berikut:

  1. Bungkus sumber daya Anda dalam pegangan. Sumber daya Anda harus dibagi menjadi dua: deskripsi mereka (biasanya dalam XML) dan data aktual. Mesin harus memuat SEMUA deskripsi sumber daya di awal permainan dan membuat semua pegangan untuknya. Ketika komponen meminta sumber daya, pegangan dikembalikan. Dengan begitu fungsi dapat berjalan seperti biasa (mereka masih dapat meminta ukuran dll.). Sekarang bagaimana jika Anda belum memuat sumber daya? Buat 'sumber daya nol' yang digunakan untuk menggantikan sumber daya apa pun yang dicoba ditarik tetapi belum dimuat.

Ada banyak lagi. Saya baru-baru ini membaca buku ini " Desain dan Implementasi Mesin Game " dan memiliki bagian yang sangat bagus di mana ia pergi dan merancang kelas manajer sumber daya.

Tanpa fungsionalitas ResourceHandle dan Memori Anggaran inilah yang direkomendasikan buku ini:

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:
};

Perhatikan bahwa fungsionalitas SetScope merujuk pada Desain Mesin Lay-Scene di mana ScopeLevel merujuk ke Scene #. Setelah adegan telah masuk / keluar, semua sumber daya sesuai dengan ruang lingkup itu dimuat dan yang tidak ada dalam ruang lingkup global diturunkan.

Setheron
sumber
Saya sangat menyukai ide Objek NULL, dan ide melacak ruang lingkup. Saya baru saja melalui perpustakaan sekolah saya mencari salinan 'Desain dan Implementasi Game Engine', tetapi tidak berhasil. Apakah buku ini menjelaskan secara terperinci bagaimana ia akan menangani anggaran memori?
Darcy Rayner
Ini merinci beberapa skema manajemen memori sederhana. Pada akhirnya bahkan yang dasar harus jauh lebih baik daripada malloc umum karena itu cenderung mencoba dan menjadi yang terbaik untuk semua hal.
Setheron
Saya akhirnya memilih desain yang sangat mirip dengan ini.
Darcy Rayner