Resource Manager Design Part 2

posted in Not dead...
Published June 18, 2006
Advertisement
So, today I got thinking about life time and object ownership for the most part.

You see, something occured to me about the class 'shared_ptr in a container in the manager' way of doing things; even when all the client classes have been destroyed our texture handles are still going to be laying around and taking up memory; be it system memory with classes and texture data or both system and VRAM memory in the case of OpenGL and DX.

The problem is, the manager is still going to be holding a reference to the texturehandle classes when everything else has released it, which of course is what keeps it alive, so in order to clean up properly we need to do something so either this is cleaned up as textures are released or is handled automagically.

Now, we could have our client code tell the manager we are done with a texture, but this is underdesireable because it places a burden on all the client code to do this and somewhat goes against the automagic ideas behind RAII. If one piece of client code fails to do this, or even does it at the wrong point, then we have resources hanging around and we have failed.

So, we need another option and this is presented to us in the form of a boost::weak_ptr. This is a class which observes and allows us to retieve a boost::shared_ptr to work with or a NULL if the shared_ptr we are observing is no longer valid.

So, how do we use it? Well, instead of maintain a map of std::string to boost::shared_ptr we maintain a map of std::string to boost::weak_ptr. When we load a texture we perform the lookup as before, however if we find a match we try to get a boost::shared_ptr from the stored boost::weak_ptr, if we succeed then we pass that back to the caller, if not we reload as normal and replace the entry in the map.

This does leave one problem, as we don't know when texture handles die we could end up with a map full of invalid boost::weak_ptr. The solution to this is a 'cleanUp()' function which the client calls to remove all invalid entries.

"But surely this places a burden on the client code?" You might say, and yes you'd be right it does, however its reduced to once place in the code, not having to be duplicated in all classes which use the manager. Also, these objects will be light, so the cost in memory isn't as bad as leaving around potentially large resources as before.

The function its self doesn't need to be called often, in tradional "level" based games either before or after reload (after might be better, depending on how much textures are reused between levels). In a "streaming" game it would form part of a garbage collection pass maybe?

With these thoughts in mind our code changes slightly from the previous version;

textureManagerLib.hpp
#ifndef TEXTUREMANAGERLIB_HPP#define TEXTUREMANAGERLIB_HPP#include #include #include #include namespace TextureManagerLib{	class TextureHandle	{	public:		TextureHandle(GLuint id) : id(id){};		~TextureHandle(){};		void Bind()		{			glBindTexture(GL_TEXTURE_2D,id);		}	private:		GLuint id;	};	typedef boost::shared_ptr handle_t;	typedef boost::weak_ptr handleweak_t;	class TextureManager	{	public:		TextureManager();		~TextureManager();		handle_t LoadTexture(std::string const &filename);		void cleanUp();	protected:	private:		typedef std::map texturemap_t;		texturemap_t texturemap;	};}#endif


textureManagerLib.cpp
#include #include #include #include "textureManagerLib.hpp"#include namespace TextureManagerLib{	TextureManager::TextureManager()	{	}	TextureManager::~TextureManager()	{	}	handle_t TextureManager::LoadTexture(const std::string &filename)	{		texturemap_t::iterator it = texturemap.find(filename);		if(it != texturemap.end())		{			if(handle_t tmp = it->second.lock())				return tmp;		}		GameTextureLoader::ImagePtr img(GameTextureLoader::LoadTexture(filename),GameTextureLoader::FreeTexture);		GLuint id;		glGenTextures(1,&id);		handle_t texture(new TextureHandle(id));		texture->Bind();		if(img->getFormat() == GameTextureLoader::FORMAT_RGBA)			glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA8,img->getWidth(),img->getHeight(),0,GL_RGBA,GL_UNSIGNED_BYTE,img->getDataPtr());		else			glTexImage2D(GL_TEXTURE_2D,0,GL_RGB8,img->getWidth(),img->getHeight(),0,GL_BGR,GL_UNSIGNED_BYTE,img->getDataPtr());		texturemap.insert(std::make_pair(filename,handleweak_t(texture)));		return texture;	}	void TextureManager::cleanUp()	{		for(texturemap_t::iterator it = texturemap.begin(); it != texturemap.end();)		{			if(it->second.lock())				++it;			else				texturemap.erase(it++);		}	}}


So, there you have it, texturemanager v0.2 is complete, now with, in my opinion, a much improved clean up system.
0 likes 1 comments

Comments

rick_appleton
It's been a good read so far. I don't agree with your choice of adding a cleanup function, especially as it can be done automagically by unregistering the handle in the destructor, and cleaning up when the last reference is gone (requires ref-counting), but it's your choice :D

Looking forward to see more.
June 19, 2006 03:31 AM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement

Latest Entries

Advertisement