Bir ResourceManager sınıfı tasarlama


17

Hobi oyun motorum için merkezi bir ResourceManager / ResourceCache sınıfı yazmak istediğime karar verdim, ancak önbellek şemasını tasarlarken sorun yaşıyorum.

Fikir, ResourceManager'ın tüm oyun kaynakları tarafından kullanılan toplam bellek için yumuşak bir hedefi olması. Diğer sınıflar, yüksüz durumda olacak kaynak nesneleri oluşturur ve bunları ResourceManager'a iletir. Ardından, ResourceManager verilen sınırı ne zaman yükleyeceğini / boşaltacağına karar vererek yazılım sınırını göz önünde bulundurur.

Başka bir sınıf tarafından bir kaynağa ihtiyaç duyulduğunda, bunun için ResourceManager'a bir istek gönderilir (bir dize kimliği veya benzersiz bir tanımlayıcı kullanarak). Kaynak yüklüyse, kaynağa salt okunur bir başvuru çağıran işleve iletilir (başvurulan sayılan bir zayıf_ptr'e sarılır). Kaynak yüklenmemişse, yönetici yüklenecek nesneyi bir sonraki fırsatta işaretler (genellikle çerçevenin çiziminin sonunda).

Sistemim bazı referans sayımları yapsa da, yalnızca kaynak okunurken sayıldığını unutmayın (bu nedenle referans sayısı 0 olabilir, ancak bir varlık yine de kullanıcı kimliğini izliyor olabilir).

İlk kullanımdan önce yükleme için kaynakları işaretlemek de mümkündür. İşte kullandığım sınıfların bir taslağı:

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

};

Sorun, toplam veri kullanımının geçici sınırın etrafında / altında kalmasını sağlamak için yöneticinin hangi nesneleri kaldıracağını belirlemenin akıllı bir yoluna sahip olması gerekecek.

Ne zaman kaldırılacağını belirlemek için bir tür öncelik sistemi (örn. Geçici Öncelik, Sık Kullanılan Öncelik, Kalıcı Öncelik), son dereference süresi ve kaynağın boyutu ile birlikte kullanmayı düşünüyorum. Ancak, kullanmak için iyi bir plan veya hızlı bir şekilde yönetmek için gerekli doğru veri yapılarını düşünemiyorum.

Böyle bir sistemi uygulayan biri, nasıl çalıştığına dair genel bir bakış verebilir mi? Kaçırdığım açık bir tasarım deseni var mı? Bunu çok karmaşık hale getirdim mi? İdeal olarak, etkili ve kötüye kullanımı zor bir sisteme ihtiyacım var. Herhangi bir fikir?


4
Açık olan soru "uygulamak için belirlediğiniz özelliklere ihtiyacınız var mı?" Bir PC üzerinde çalışıyorsanız, örneğin bir bellek yazılım kapağı ayarlamak muhtemelen gereksizdir. Oyununuz seviyelere ayrılırsa ve hangi varlıkların seviyede kullanılacağını belirleyebilirseniz, başlangıçta her şeyi yükleyin ve oyun sırasında yükleme / boşaltma yapmaktan kaçının.
Tetrad

Yanıtlar:


8

Bunun% 100 sorunuzla ilgili olup olmadığından emin değilim, ancak birkaç öneri aşağıdadır:

  1. Kaynaklarınızı bir sapa sarın. Kaynaklarınız ikiye ayrılmalıdır: açıklamaları (genellikle XML olarak) ve gerçek veriler. Motor, oyunun başında TÜM kaynak açıklamalarını yüklemeli ve bunlar için tüm tutamaçları oluşturmalıdır. Bir bileşen bir kaynak istediğinde, tanıtıcı döndürülür. Bu şekilde işlevler normal şekilde devam edebilir (yine de boyut isteyebilir vb.). Peki kaynağı henüz yüklemediyseniz ne olacak? Çizilmeye çalışılan ancak henüz yüklenmemiş herhangi bir kaynağın yerini almak için kullanılan bir 'boş kaynak' yapın.

Bir sürü daha var. Yakın zamanda bu kitabı okudum " Oyun Motoru Tasarımı ve Uygulaması " ve bir kaynak yöneticisi sınıfı tasarladığı çok güzel bir bölüm var.

ResourceHandle ve Bellek Bütçesi işlevselliği olmadan, kitabın önerdiği özellikler:

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 işlevinin, ScopeLevel öğesinin Sahne # öğesini ifade ettiği Sahne Katmanlı Motor Tasarımına başvurduğuna dikkat edin. Bir sahneye girildikten / çıkıldıktan sonra, bu kapsama göre tüm kaynaklar yüklenir ve global kapsamda olmayan tüm kaynaklar kaldırılır.


NULL Object fikrini ve kapsamı takip etme fikrini gerçekten seviyorum. Okul kütüphanemde 'Oyun Motoru Tasarımı ve Uygulaması' nýn bir kopyasýný avladým, ama ţansým yoktu. Kitap, bir bellek bütçesini nasıl ele alacağı konusunda ayrıntıya giriyor mu?
Darcy Rayner

Birkaç basit bellek yönetimi şemasını detaylandırıyor. Nihayetinde, temel bir şey bile genel malloc'dan çok daha iyi olmalıdır, çünkü bu her şey için en iyisi olmaya çalışır.
Setheron

Buna oldukça benzer bir tasarım seçtim.
Darcy Rayner
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.