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에서 작업하는 경우 메모리 소프트 캡 설정이 불필요 할 수 있습니다. 게임이 레벨로 나뉘어져 있고 레벨에서 어떤 자산을 사용할지 결정할 수 있다면 게임 시작시 모든 것을로드하고로드 / 언로드를 피하십시오.
— Tetrad

답변:


8

이것이 귀하의 질문 100 %와 관련이 있는지 확실하지 않지만 몇 가지 조언 팁은 다음과 같습니다.

  1. 손잡이로 자원을 감싸십시오. 리소스는 설명 (일반적으로 XML)과 실제 데이터의 두 가지로 나뉩니다. 엔진은 게임 시작시 모든 리소스 설명을로드하고 이에 대한 모든 핸들을 만들어야합니다. 구성 요소가 리소스를 요청하면 핸들이 반환됩니다. 그렇게하면 함수가 정상적으로 진행될 수 있습니다 (여전히 크기 등을 요청할 수 있습니다). 리소스를 아직로드하지 않은 경우 어떻게해야합니까? 가져 오려고 시도했지만 아직로드되지 않은 모든 리소스를 바꾸는 데 사용되는 '널 리소스'를 만듭니다.

더 많은 것이 있습니다. 나는 최근에이 책 " Game Engine Design and Implementation "을 읽었 으며 리소스 관리자 클래스를 디자인하고 다루는 섹션이 아주 훌륭합니다.

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이 Scene #을 나타내는 Scene-Layered Engine Design을 나타냅니다. 장면이 입력 / 종료되면 해당 범위에 따른 모든 리소스가로드되고 전역 범위에없는 리소스가 언로드됩니다.


나는 NULL 객체 아이디어와 범위를 추적하는 아이디어를 정말 좋아합니다. 방금 '게임 엔진 디자인 및 구현'을 찾기 위해 학교 도서관 사냥을했지만 운이 없었습니다. 이 책은 메모리 예산을 어떻게 처리 할 것인지에 대해 자세히 설명합니까?
— Darcy Rayner

몇 가지 간단한 메모리 관리 체계에 대해 자세히 설명합니다. 궁극적으로 기본적인 것조차도 일반 malloc보다 훨씬 뛰어나야합니다.
— Setheron

나는 이것과 비슷한 디자인을 골랐다.
— Darcy Rayner
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.