tisdag 9 augusti 2011

A Nifty Resource Manager

Here's some meat before we get to the pudding. Every hobby game engine (worth a damn) will needs a resource manager of some sort. This being "next-gen" and all, we got a few requirements on our resource manager as well:

  • It should manage ALL types of assets
  • It should support automatic hot loading of ALL assets
  • It should manage resource dependencies

So without further ado, meet the resource interface (a bit trimmed down) from which all our specific resources will inherit:

class IResource
{
    public:
        IResource( string filename );
        virtual void release() = 0;
        virtual void reload() = 0;
        bool needsReloading();

    protected:
        string    m_filename;
        FILETIME  m_lastModifiedTime;
};

This resource system is based on the assumption that each and every resource is stored using a separate file (something which isn't always the case, but for sake of discussion). The needsReloading() function simply checks the modified file time of the resource and returns true if the file has been modified.

The release() function simply releases all memory etc. tied up by the resource, and the reload() function loads the resource in from the file again. Now we just need a manager class (usually a singleton) to keep track of all the resources:

class ResourceManager
{
    public:
    
        template<class T>
        T* getResource( string strFilename )
        {
            if( m_resources.count( strFilename ) > 0 )
                return dynamic_cast<T*>( m_resources[ strFilename ] );

            // create and add new resource
            T* pResource = new T( strFilename );
            m_resources[ strFilename ] = pResource;
            return pResource;
        }

        void update();
        void releaseAll();

    protected:
        map<string, IResource*>    m_resources;
};

The ResourceManager class keeps a map of string (filenames) and resources (specific resources of various types). Every time a resource is requested it checks the map and see if it has already been created (if not it creates it with some template magic and adds it to the map). Note that in a proper engine you'd probably use a hash table and instead of strings you'd use hashed string IDs instead. But for your average hobby project with not too many thousands of assets this should work just fine.

To get a resource you simply call:


IResource* pSomeResource = ResourceManager::getInstance()->
                           getResource<MyResourceType>( "Resources\\MyResource.bin" );

In the update function of the ResourceManager we check whether any assets have been updated and if so, we call the reload function:

void ResourceManager::update()
{
    // loop over all the assets
    for( map<string, IResource*>::iterator it=m_resources.begin(); 
         it != m_resources.end(); ++it )
    {
        // check if assets needs to be reloaded
        if( (*it).second->needsReloading() )
        {
            // reload resource
            (*it).second->reload();
        }
    }
}

Note that this should of course be spread out over time, it's quite unnecessary to check this every single frame. That's more or less all there's to it. Managing dependencies between different assets is also something that should be handled by the resource manager. For example a mesh might add dependencies to stuff like materials (which might govern the vertex format of the mesh). Should the material then be updated, the resource manager will also call the reload() function of all the meshes that are dependent on that material and so forth.

Another thing to note is that the whole automatic hot loading, dependency registration etc. are things that shouldn't be in the final build of course. So the easiest thing to do is to wrap all that logic in a '#ifndef FINAL' preprocessor block.

A bit more fleshed out version is available here: ResourceSystem.zip
(Build & Start the exe, then change the values in the resource text files and watch the magic happen)