Thiết kế một lớp ResourceManager


17

Tôi đã quyết định tôi muốn viết một lớp ResourceManager / ResourceCache trung tâm cho công cụ trò chơi sở thích của mình, nhưng gặp khó khăn khi thiết kế sơ đồ bộ đệm.

Ý tưởng là ResourceManager có một mục tiêu mềm cho tổng bộ nhớ được sử dụng bởi tất cả các tài nguyên của trò chơi cộng lại. Các lớp khác sẽ tạo các đối tượng tài nguyên, sẽ ở trạng thái không tải và chuyển chúng đến ResourceManager. Sau đó, Trình quản lý tài nguyên sẽ quyết định khi nào tải / dỡ các tài nguyên đã cho, giữ nguyên giới hạn mềm.

Khi một lớp khác cần một tài nguyên, một yêu cầu được gửi đến ResourceManager cho nó, (sử dụng id chuỗi hoặc mã định danh duy nhất). Nếu tài nguyên được tải, thì một tham chiếu chỉ đọc đến tài nguyên được chuyển đến chức năng gọi, (được bao bọc trong một tham chiếu được đếm yếu_ptr). Nếu tài nguyên không được tải, thì người quản lý sẽ đánh dấu đối tượng sẽ được tải vào cơ hội tiếp theo, (thường là ở phần cuối của bản vẽ khung).

Lưu ý rằng, mặc dù hệ thống của tôi thực hiện một số tính năng tham chiếu, nhưng nó chỉ tính khi tài nguyên đang được đọc, (vì vậy số tham chiếu có thể là 0, nhưng một thực thể vẫn có thể theo dõi nó là uid).

Cũng có thể đánh dấu các tài nguyên để tải tốt trước khi sử dụng lần đầu tiên. Dưới đây là một bản phác thảo về các lớp tôi đang sử dụng:

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

};

Vấn đề là, để giữ cho tổng mức sử dụng dữ liệu lơ lửng xung quanh / dưới giới hạn mềm, người quản lý sẽ phải có một cách thông minh để xác định đối tượng nào sẽ dỡ tải.

Tôi đang nghĩ đến việc sử dụng một số loại hệ thống ưu tiên, (ví dụ: Ưu tiên tạm thời, Ưu tiên thường xuyên được sử dụng, Ưu tiên vĩnh viễn), kết hợp với thời gian của quy định cuối cùng và quy mô của tài nguyên, để xác định khi nào nên xóa nó. Nhưng tôi không thể nghĩ ra một sơ đồ hợp lý để sử dụng, hoặc các cấu trúc dữ liệu phù hợp cần có để nhanh chóng quản lý chúng.

Ai đó đã thực hiện một hệ thống như thế này có thể đưa ra một cái nhìn tổng quan về cách thức hoạt động của họ. Có một mẫu thiết kế rõ ràng tôi đang bỏ lỡ? Tôi đã làm điều này quá phức tạp? Lý tưởng nhất là tôi cần một hệ thống hiệu quả và khó lạm dụng. Có ý kiến ​​gì không?


4
Câu hỏi rõ ràng là "bạn có cần các tính năng bạn đặt ra để thực hiện không". Nếu bạn đang làm việc trên PC, chẳng hạn như cài đặt nắp mềm bộ nhớ có thể là không cần thiết. Nếu trò chơi của bạn được chia thành các cấp và bạn có thể xác định tài sản nào sẽ được sử dụng ở cấp độ, chỉ cần tải mọi thứ khi bắt đầu và tránh tải / dỡ tải trong khi chơi trò chơi.
Tetrad

Câu trả lời:


8

Tôi không chắc chắn nếu điều này liên quan đến câu hỏi của bạn 100% nhưng một vài lời khuyên là như sau:

  1. Bọc tài nguyên của bạn trong một tay cầm. Tài nguyên của bạn nên được chia thành hai: mô tả của chúng (thường là bằng XML) và dữ liệu thực tế. Công cụ nên tải TẤT CẢ các mô tả tài nguyên khi bắt đầu trò chơi và tạo tất cả các tay cầm cho chúng. Khi một thành phần yêu cầu một tài nguyên, xử lý được trả về. Bằng cách đó, các hàm có thể tiến hành như bình thường (chúng vẫn có thể yêu cầu kích thước, v.v.). Bây giờ nếu bạn chưa tải tài nguyên thì sao? Tạo một "tài nguyên null" được sử dụng để thay thế bất kỳ tài nguyên nào được cố gắng rút ra nhưng chưa được tải.

Có một bó nữa. Gần đây tôi đã đọc cuốn sách " Thiết kế và triển khai công cụ trò chơi " này và có một phần rất hay nơi nó đi và thiết kế một lớp quản lý tài nguyên.

Không có chức năng ResourceHandle và Memory Budget ở đây là những gì cuốn sách đề xuất:

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

Lưu ý rằng chức năng SetScope đề cập đến Thiết kế động cơ phân lớp trong đó ScopeLevel đề cập đến Cảnh #. Khi một cảnh đã được nhập / thoát, tất cả các tài nguyên theo phạm vi đó sẽ được tải và bất kỳ tài nguyên nào trong phạm vi toàn cầu đều không được tải.


Tôi thực sự thích ý tưởng NULL Object và ý tưởng theo dõi phạm vi. Tôi vừa mới đi qua thư viện trường tôi để tìm một bản sao của 'Thiết kế và triển khai công cụ trò chơi', nhưng không có may mắn. Cuốn sách có đi vào chi tiết cách nó sẽ xử lý ngân sách bộ nhớ không?
Darcy Rayner

Nó chi tiết một vài chương trình quản lý bộ nhớ đơn giản. Cuối cùng, ngay cả một cơ bản nên tốt hơn nhiều so với malloc nói chung vì điều đó có xu hướng cố gắng và là tốt nhất cho tất cả mọi thứ.
Setheron

Cuối cùng tôi đã chọn một thiết kế khá giống với cái này.
Darcy Rayner
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.