Designing a ResourceManager class

Darcy Rayner
  • Designing a ResourceManager class Darcy Rayner

    I've decided I want to write a central ResourceManager/ResourceCache class for my hobby game engine, but am having trouble designing a caching scheme.

    The idea is that the ResourceManager has a soft target for the total memory used by all the game's resources combined. Other classes will create resource objects, which will be in an unloaded state, and pass them to the ResourceManager. The ResourceManager then decides when to load / unload the given resources, keeping the soft limit in mind.

    When a resource is needed by another class a request is sent to the ResourceManager for it, (either using a string id or a unique identifier). If the resource is loaded, then a read-only reference to the resource is passed to the calling function, (wrapped in a referenced counted weak_ptr). If the resource isn't loaded, then the manager will mark the object to be loaded at the next opportunity, (usually at the end of drawing the frame).

    Note that, although my system does do some reference counting, it only counts when the resource is being read, (so the reference count may be 0, but an entity might still be keeping track of it's uid).

    It is also possible to mark resources for loading well in advance of first use. Here is a bit a sketch of the classes I am using:

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

    The trouble is, to keep the total data usage hovering around / under the soft limit, the manager will have to have a smart way of determining which objects to unload.

    I'm thinking of using some kind of priority system, (eg. Temporary Priority, Frequently Used Priority, Permanent Priority), combined with the time of the last dereference, and the size of the resource, to determine when to remove it. But I can't think of a decent scheme to use, or the right data-structures required to quickly manage them.

    Could someone who has implemented a system like this give an overview of how their's worked. Is there an obvious design pattern I am missing out on? Have I made this too complicated? Ideally I need an efficient, and hard to abuse system. Any ideas?

  • I'm not sure if this pertains to your question 100% but a few advice tips are the following:

    1. Wrap your resources in a handle. Your resources should be split into two: their description (usually in XML) and actual data. The engine should load ALL resource descriptions at the start of the game and create all handles for them. When a component requests a resource the handle is returned. That way functions can proceed as normal (they can still request the size etc..). Now what if you haven't loaded the resource yet? Make a 'null resource' that is used to replace any resource that is attempted to be drawn but hasn't been loaded yet.

    There's a bunch more. I recently read this book "Game Engine Design and Implementation" and a has very nice section where it goes and designs a resource manager class.

    Without the ResourceHandle and Memory Budget functionality here is what the book recommends:

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

    Notice that the SetScope functionality refers to a Scene-Layered Engine Design where the ScopeLevel refers to the Scene#. Once a scene has been entered/exited, all resources according to that scope are loaded and any not in the global scope are unloaded.

Tags
c++ design-patterns resource-management
Related questions and answers
  • and traveling throug it. This is due to some odd reason for the "numb_collisions", which is used to count how many collisions have occured since initial collision (if it's 0 it means it IS the initial... /////////////////////////////////////////////////////////////////////////////////////////////////////////////// class Sprite { private: string name; char symbol; float shield; int location[2]; bool alive; public: ///////////////////// Get and SET all the privates...}; // for the old/new screen command // Some nCurses setup int r = 0, c = 0; // current row and column (upper-left is (0,0)) const int nrows = 56, // number of rows in window ncols = 79

  • code in them... Right now for testing purposes, I just have the main loop call Level1 level1; and use the functions, but when I run the game I get a segmentation fault. This is the first time I've...I'm trying to write my level classes by having a base class that each level class inherits from...The base class uses pure virtual functions. My base class is only going to be used as a vector that'll have the inherited level classes pushed onto it...This is what my code looks like at the moment, I've tried various things and get the same result (segmentation fault). //level.h class Level

  • in particular or contains any game attributes, it's just keyframed animation data and some IDs for them and to reference API objects. I use objects such as CFrame to hold pointers to frame data... that make really hard to work with when coding some functions that use them. I was thinking of making ie. SimpleMesh and HierarchyMesh objects, which will also require that the renderer can deal with different types of objects in the same scene. I was also thinking about making a MeshNode class and then make a Mesh object that contains them, but then I have some conflict on where to store some data

  • this on what I had done there, but modifying it to fit with Ogre. It is working but not correctly and I would like some help to understand what it is I am doing wrong. I have a state system and when... code shows the changes I made to get accurate physics. void GameState::createScene() { m_pSceneMgr-&gt;createLight("Light")-&gt;setPosition(75,75,75); // Physics // As a test we want a floor plane...::DEFAULT_RESOURCE_GROUP_NAME, p, 200000, 200000, 20, 20, true, 1, 9000,9000,Ogre::Vector3::UNIT_Z); ent = m_pSceneMgr-&gt;createEntity("floor", "FloorPlane"); ent-&gt;setMaterialName("Test/Floor

  • (); missile-&gt;Init(true, s-&gt;X, s-&gt;Y); bullets-&gt;push_back(missile); } } void CleanUp() { for(unsigned int index = 0; index &lt; bullets-&gt;size(); index...To begin with I am developing a PSP application so I have no clue how to debug a PSP application. That doesn't mean I do not know how debug period. Anyway, when I run the game everything runs fine... it is the pad that is causing the problem. Maybe the bullet is being drawn out of the screen already. But I am not sure since I cannot debug maybe somebody can see the flaw in my code. main.cpp class

  • Dear all, this is going to be tough: I have created a game object factory that generates objects of my wish. However, I get memory leaks which I can not fix. Memory leaks are generated by return new Object(); in the bottom part of the code sample. static BaseObject * CreateObjectFunc() { return new Object(); } How and where to delete the pointers? I wrote bool ReleaseClassType(). Despite... vector container. I have chosen this way because I parse an object map file. I have object type IDs (integer values) which I need to translate them into real objects. Because I have over 100

  • ) + ('D'&lt;&lt;8) + 'I') #define MD2_VERSION 8 class MD2 : public VertexModel { public: MD2(const std::string& Path); ~MD2(); void Draw(timestep_t Time); private..._Commands, m_MD2Header.num_glcommands * sizeof(int)); // Read all the data. for(int i = 0; i &lt; m_MD2Header.num_frames; ++i) { md2_frame_t* Frame = (md2_frame_t*)&Buffer...I'm trying to load MD2 models (stolen from Cube) and my loader seems to be loading the models fine, but I can't say the same for the drawing of the model. Here's my code: typedef float vec3_t[3

  • the polygons to the rendering engine I designed my objects like this: class GlObject{ private: idEnum ObjId; //other identifiers public: void move_obj(); //the movements are the same for all the objects (nextpos = pos + vel) void rotate_obj(); //rotations are the same for every objects too virtual Polygon generate_mesh() = 0; // the polygons are different } and I have 4 different objects in my game: plane, obstacle, player, bullet and I designed them like this: class Player : public GlObject{ private: std::string name; public: Player(); Bullet fire() const

  • () { } Cube::~Cube() { pBuffer-&gt;Release(); // why does this only work when put here? because it's created here? I thnk so, why not iBuffer though? } void Cube::Draw() { render_frame(); } void Cube...Basically when placed in the same file this works fine, but if placed in separate files (and I have tested this) just after Init() is called, the pointer to ID3D10* device's value is suddenly... } // this is the function used to render a single frame void render_frame() { // clear the window to a deep blue and clear the depth buffer to 1.0f device-&gt;ClearRenderTargetView(rtv, D3DXCOLOR(0.0f, 0.2f, 0.4f

Data information