Проектування класу ResourceManager


17

Я вирішив, що хочу написати центральний клас ResourceManager / ResourceCache для мого ігрового двигуна, але у мене виникають проблеми при розробці схеми кешування.

Ідея полягає в тому, що ResourceManager має м'яку ціль для загальної пам'яті, яка використовується всіма ресурсами гри разом. Інші класи створять ресурсні об’єкти, які будуть у не завантаженому стані, і передадуть їх ResourceManager. Потім ResourceManager вирішує, коли завантажувати / вивантажувати задані ресурси, пам’ятаючи про м'який ліміт.

Коли ресурс потрібен іншому класу, запит надсилається до ResourceManager для нього (або використовуючи ідентифікатор рядка або унікальний ідентифікатор). Якщо ресурс завантажений, то посилається на функцію виклику лише для читання посилання на ресурс (загорнуте у посилається, зараховується слабкий_птр). Якщо ресурс не завантажений, менеджер позначить об'єкт, який потрібно завантажити, при наступній нагоді (зазвичай в кінці малювання кадру).

Зауважте, що, хоча моя система робить деякий підрахунок посилань, вона підраховує лише тоді, коли ресурс зчитується, (тому кількість посилань може бути 0, але організація все ще може відслідковувати, що це не так).

Можна також позначити ресурси для завантаження заздалегідь до першого використання. Ось трохи ескіз класів, які я використовую:

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
Очевидне питання "чи потрібні функції, які ви поставили перед собою". Якщо ви працюєте на ПК, встановлення м'якої кришки пам'яті, мабуть, зайве, наприклад. Якщо ваша гра розбита на рівні, і ви можете визначити, які активи будуть використовуватися на рівні, просто завантажте все на початку та уникайте завантаження / розвантаження взагалі під час гри.
Тетрад

Відповіді:


8

Я не впевнений, чи це стосується вашого питання на 100%, але декілька порад:

  1. Загорніть свої ресурси в ручку. Ваші ресурси слід розділити на два: їх опис (як правило, у XML) та фактичні дані. Двигун повинен завантажувати ВСІ описи ресурсів на початку гри та створювати для них усі ручки. Коли компонент запитує ресурс, повертається ручка. Таким чином функції можуть протікати як звичайні (вони все ще можуть вимагати розмір тощо). А що робити, якщо ви ще не завантажили ресурс? Створіть "нульовий ресурс", який використовується для заміни будь-якого ресурсу, який намагався отримати, але ще не завантажений.

Є ще купа. Нещодавно я прочитав цю книгу " Дизайн та реалізація ігрового двигуна ", і є дуже приємний розділ, де він знаходиться та розробляє клас управління ресурсами.

Без рекомендованих функцій бюджету 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 посилається на Сцену №. Після введення / виходу з сцени всі ресурси відповідно до цього обсягу завантажуються, а будь-який, що не знаходиться в глобальному масштабі, вивантажується.


Мені дуже подобається ідея NULL Object та ідея відстеження обсягу. Я щойно переглядав свою шкільну бібліотеку, полюючи на копію "Дизайну та впровадження ігрового двигуна", але не пощастило. Чи читає книга детально, як вона оброблятиме бюджет пам'яті?
Дарсі Рейнер

У ньому детально описано кілька простих схем управління пам'яттю. Зрештою, навіть базовий повинен бути набагато кращим, ніж загальний малок, оскільки це, як правило, намагається бути найкращим у всьому.
Сетерон

Я в кінцевому підборі обрав дизайн, схожий на цей.
Дарсі Рейнер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.