设计一个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上工作,则设置内存软上限可能是多余的。如果您的游戏分为多个级别,并且可以确定要在级别中使用哪些资产,则只需在开始时加载所有内容,而在游戏过程中完全避免加载/卸载。
四分

Answers:


8

我不确定这是否与您的问题100%相关,但以下是一些建议提示:

  1. 将资源包装在句柄中。您的资源应分为两部分:它们的描述(通常是XML)和实际数据。引擎应在游戏开始时加载所有资源描述,并为其创建所有句柄。当组件请求资源时,将返回句柄。这样,功能可以照常进行(他们仍然可以要求尺寸等。)。现在,如果您还没有加载资源呢?创建一个“空资源”,该空资源用于替换任何试图绘制但尚未加载的资源。

还有很多。最近,我读了《游戏引擎设计与实现》这本书,其中有一个非常不错的部分,它设计了资源管理器类。

本书不建议使用没有ResourceHandle和Memory Budget功能的情况:

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对象的想法,以及跟踪范围的想法。我刚刚在学校图书馆里寻找“游戏引擎设计和实现”的副本,但是没有运气。这本书是否详细介绍了如何处理内存预算?
达西·雷纳

它详细介绍了一些简单的内存管理方案。最终,即使是基本的malloc也应该比一般的malloc好得多,因为这往往会尝试并成为所有事物的最佳选择。
Setheron 2011年

我最终选择了一个与此非常相似的设计。
达西·雷纳
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.