Writing an Asset Manager

Started by
11 comments, last by Irlan Robson 10 years, 2 months ago

Singletons are bad tongue.png but here's one way you could clean up after them:
I split the AssetMapSingleton up, so that AssetMap<T> is a normal class, and then AssetMapSingleton<T> creates a global instance of that class.
AssetMap<T> inherits the IAssetMap interface, so that all AssetMaps can be iterated to have a Cleanup function called.

The AssetMap<T> constructor/destruction adds/removes itself from a list inside the global AssetManager -- this allows the asset manager to iterate through each asset map if it needs to. The AssetMap<T> destructor also does a last-minute Cleanup call, in case you forget to.


#include <map>
#include <string>
#include <vector>

class IAssetMap
{
public:
	virtual void Cleanup() = 0;
};
template<class T> struct AssetLoader {};

struct Texture2D
{
	void Release() { /*todo*/ }
};
template<> struct AssetLoader<Texture2D>
{
	static Texture2D* Load(const std::string& name) { /*todo*/ return 0; }
};


struct AssetManager
{
	template<class T> static T* GetAsset(const std::string& name)
	{
		return AssetMapSingleton<T>::instance.GetAsset(name);
	}
	static void CleanupAll()
	{
		if( maps )
		{
			for(auto i = maps->begin(), end=maps->end(); i!=end; ++i )
			{
				(*i)->Cleanup();
			}
		}
	}
	static void RegisterMap( IAssetMap* p )
	{
		if( !maps )
			maps = new std::vector<IAssetMap*>;
		maps->push_back(p);
	}
	static void UnregisterMap( IAssetMap* p )
	{
		assert( maps );
		maps->erase(std::find(maps->begin(), maps->end(), p));
		if( maps->empty() )
		{
			delete maps;
			maps = 0;
		}
	}
private:
	static std::vector<IAssetMap*>* maps;
};


template<class T> class AssetMap : public IAssetMap
{
public:
	 AssetMap() { AssetManager::RegisterMap(this); }
	~AssetMap() { AssetManager::UnregisterMap(this); Cleanup(); }//prevent leaks -- on program shutdown, do a last-minute cleanup
	void Cleanup()
	{
		for(auto i = assets.begin(), end=assets.end(); i!=end; ++i )
		{
			i->second->Release();
		}
		assets.clear();
	}
	T* GetAsset(const std::string& name)
	{
		auto i = assets.find(name);
		if( i != assets.end() )
			return i->second;
		T* asset = AssetLoader<T>::Load(name);
		assets.insert( std::make_pair(name, asset) );
		return asset;
	}
private:
	std::map<std::string, T*> assets;
};

//Creates a global AssetMap<T> for each T that's used
template<class T> struct AssetMapSingleton
{
	static AssetMap<T> instance;
};

void test()
{
	Texture2D *texturePtr = AssetManager::GetAsset<Texture2D>("hero_character");
}
Advertisement

I think I've finally got it! It's almost identical to your implementation, but I've learned quite a bit trying to get it to work. I needed the following classes in my implementation:

-IAssetList

-template<class T> AssetList

-template<class T> AssetLoader

-template<class T> AssetListSingleton

-Resources

The Resources class has been around for a while, and has used to hold the engine's default textures and shaders. I wanted to expand its use to allow programmers to load, release, flush and get resources. I provided template methods in Resources, which happened to be wrapper calls in to AssetListSingleton, and AssetLoader.

I wanted each type of asset to be in their own maps for a faster look-up. For example, Texture2D get its own map, Texture3D gets its own map, etc. AssetList exists as a template class to hold map of the template's type, and its constructor is also used to add itself to Resources. I wanted my Resources class to be the high-level manager that will store pointers to each of these lists in an STL vector for clean-up later on. I couldn't create a vector of type AssetList because it was a template. To get around this, I created an abstract class (interface) called IAssetList that the AssetList template could inherit from. Resources could hold a vector of IAssetList, and IAssetList's constructor would call a method in Resources, passing itself as a pointer, into Resources' AddAssetList() method to be added to the vector. Resource is also treated as a static class with a Shutdown() method that's called by the engine when the app terminates. Programmers can also use Flush() to dump all resources.

AssetLoader is a blank class that is specialized by classes that will be treated as assets. AssetListSingleton is a template class that's used to call its static methods to manipulate its static AssetList instance by Resources wrapper methods. As I understand it, different types of AssetListSingleton are generated whenever a different type of AssetListSingleton is referenced. This will also generate another static AssetMap instance, right? This list is accounted for thanks to its superclass' constructor.

By the way, are C++ templates generated at run-time when needed, or is it during compile-time?

In my engine, the objects loads themself from a file stream. That was a personal design decision. I've tried to implement a ResourceTable<key, T*> with functors and creators, but ended up as a kind of unorganized code. If you want the post is here.

I'm not saying that you can't have a Asset/Resource Manager class to facilitate the "if-not-found, load". I'm saying that if you want a simple and flexible way of doing, the self loading can help.

This topic is closed to new replies.

Advertisement