Jump to content
  • Advertisement
Sign in to follow this  
m.r.e.

Is this practical for a resource manager?

This topic is 706 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi, I wrote a simple resource manager for a project of mine. I'm concerned how I implemented it. To use the manager you would derive from "Resource" for any resource type like images, sounds etc.. The base class "Resource" adds and removes its self to a static instance of ResourceManager in its constructor and deconstructor. There are a couple global functions that make calls to ResourceManager to initialize and release any allocated resources.

 

As in the TODO comments, I DO have other classes that handle loading files. I just haven't added them to it yet. I don't want to break anything that isn't broke yet. My goal is pretty much the same for any resource management system. I would like a more modern approach to handle loading and unloading resources. I currently load and unload them directly. Also, I have a script parser I wrote that uses a markup style syntax similar to XML that I would like to use with this. Plus, archive support like PAK or whatever.

 

Just curious if I'm headed down the wrong path. I don't really wanna waste time ya know. ;)

// Resource.h

class Resource
{
	friend class ResourceManager ;
	Resource* prev ;
	Resource* next ;
	
public :
	Resource( const char* path = "" ) ;
	virtual ~Resource( ) ;
	virtual void Free( ) = 0 ;

protected :
	static ResourceManager* list ;
	char path[ MAX_STRING ] ;
} ;


// ResourceManager.h
class ResourceManager
{
public :
	ResourceManager( ) ;
	virtual ~ResourceManager( ) ;
	bool Init( ) ;
	void Shutdown( ) ;
	void Insert( Resource* node ) ;
	void Remove( Resource* node ) ;

protected :
	Resource* list ;
} ;

// Resource.cpp
ResourceManager* Resource::list = GetResourceManager( ) ;

Resource::Resource( const char* path )
{
	prev = NULL ;
	next = NULL ;
	strcpy( this->path, path ) ;
	list->Insert( this ) ;
}

Resource::~Resource( )
{
	list->Remove( this ) ;
}

bool ResInit( )
{
	return GetResourceManager( )->Init( ) ;
}

void ResShutdown( )
{
	GetResourceManager( )->Shutdown( ) ;
}

Resource* ResGet( const char* path )
{
	// TODO : find extension, load TGA, WAV, etc.., return new instance...
}

// ResourceManager.cpp
ResourceManager::ResourceManager( )
{
	list = NULL ;
}

ResourceManager::~ResourceManager( )
{
	Shutdown( ) ;
}

bool ResourceManager::Init( )
{
	return true ;
}

void ResourceManager::Shutdown( )
{
	Resource* temp ;

	while ( list != NULL ) {
		temp = list->next ;
		list->Free( ) ;
		delete list ;
		list = temp ;
	}
}

void ResourceManager::Insert( Resource* node )
{
	node->prev = NULL ;
	node->next = list ;
	if ( list != NULL ) {
		list->prev = node ;
	}
	list = node ;
}

void ResourceManager::Remove( Resource* node )
{
	if ( list == node ) {
		list = list->next ;
		if ( list != NULL ) {
			list->prev = NULL ;
		}
	}
	else
	{
		if ( node->prev != NULL ) {
			node->prev->next = node->next ;
		}
		if ( node->next != NULL ) {
			node->next->prev = node->prev ;
		}
	}
}

ResourceManager* GetResourceManger( )
{
	static ResourceManager manager ;
	return &manager ;
}

Edited by m.r.e.

Share this post


Link to post
Share on other sites
Advertisement

After some thinking *gasp*. I'm worried about "static" variables and how/when they are initialized and when I can depend on them as being initialized. Looks like I need to brush up on the basics because I forgot. Example: I assume that ResourceManager will be created as soon as GetResourceManager() is called. But "Resource" uses the static variable "list" that is initialized with GetResourceManager() globally. Its sound like it could be very buggy and the obvious solution would be NOT to use a static variable such as "list" and instead call GetResourceManager() directly. I kind of shot my self in the foot there. So, in a way, the ResourceManager is created when Resource(s) are... I think I just answered my own question.

 

EDIT: Please forgive my terrible spelling and punctuation. Lame, I had to google that word "punctuation" to make sure I spelled it right, is that bad?

Edited by m.r.e.

Share this post


Link to post
Share on other sites

I would say that your resource should know nothing about what is used to store it or how to traverse through other resources (next/prev etc)

It is the responsibility of whatever loads/stores/unloads your resources to worry about that. The job of the resource is to be a resource :)

Share this post


Link to post
Share on other sites

Okay, so I rewrote some of this and fleshed it out a bit. The loaded is divided into two interfaces: ResourceFilter and ResourceManager. I changed, or renamed Resource to ResourceObject. Both ResourceObject and ResourceFilter link with ResourceManager in their constructors. This is awesome because all I have to do to handle new resource types is derive from ResourceFilter and pass the name of the extension to the base class constructor. I have to define an instance of the class somewhere and I usually put it in the implementation(right above the constructor). Then Boom! new resource handler. The only static variable now is the one in GetResourceManager(). The ResourceManager is created once this function is called. I'm still kinda iffy... about this, like, maybe it should be moved else where or something but anyways. All of this works, I tested it with some junk I had and it successfully loads and displays images and plays sounds minus a few reinterpret_casts.

I can't upload files to show anyone yet, so you can't really see the meat and potatoes. But this sounds practical to me now... finally got it done. On the down side, I have to declare new resource filters as friends in the ResourceObject sub class. I worked around this by adding another middle-man that is more type-specific. For example, ImageLoader will be used to load images and it also had protected methods that will generate the Image objects for me. This way Image only needs to friends ImageLoader and sub-classes of ImageLoader use it's methods to generate, bind and load into memory. The cool thing about this is that the filters don't need to know anything about each other or anything else in fact. They only need to know what a ResourceFilter is and the type of ResourceObject they'll be working with. No need to #include it anywhere because of the auto-linking implemented in the base class. ResourceManager will auto-use it.

Soon, I will change from string containing file path to a File object. This way, the filters work directly with that file which... could be a buffered file... or maybe it's a socket based file... all it knows is that it's a File. This should allow me to do some pretty cool things and help with net content. I still want to incorporate this poor excuse for an RDP(recursive descent parser for those of you who may not know) I made. I hoping to load groups of resources at a time and maybe even style them or something like that. For example:

<resource>
<image path="res\\images\\smallFont.tga">
edit {
    color: red;
    fade-color: yellow;
    fade-timer: 1.0;
}
</image>
</resource>

Something like that. I have a working parser but I implement the style part yet.


I would say that your resource should know nothing about what is used to store it or how to traverse through other resources (next/prev etc)

It is the responsibility of whatever loads/stores/unloads your resources to worry about that. The job of the resource is to be a resource :)

 

Yeah? You have a good point and I think the same thing. But my States in the game have links like that as well. So I figured if I could do that there with such a crucial part of the program, then why no resources? Do you think I should wrap them in someway? That sounds more logical then what I did. Maybe I should do that. It shouldn't be that hard to implement. Most containers have custom nodes anyways so...

Share this post


Link to post
Share on other sites

So WozNZ, I think you're right. I should decouple the framework a little. The problem was when I wanted to create and image. How would I be able store that in the container correctly? Resources have to be dynamically allocated and their constructors are private so no one could instantiate one. If they did, it would crash the program when the resource manager tries to delete it. The only way I found I could allocate an image is by a friend. When the resource manager is shutdown, all resources that have been allocated are freed and deleted. How would you do this in a way that one could allocate dynamically or static with out crashing the program when the resource manager frees them? Flag it maybe? Can you give an example of your process please?

Share this post


Link to post
Share on other sites
Linked lists are generally a hardware unfriendly way to structure a container these days. A vector of pointers to resources is generally more cache friendly and does not require the intrusion of the prev and next pointers into Resource.

Actually the container structure should never require the objects it stores to contain container-specific members. Take a look at std::list<T> for example.

Share this post


Link to post
Share on other sites

Thanks for the replies ;) So I took some of your advice, or in the middle of it any ways. ResourceObject and ResourceFilter no longer hold "container specific" members. They both still link with ResourceManager in the constructor and deconstructor but use a std::list. No more *friend* classes allows more dynamic content. ResourceManager is still the primary facade and/or proxy that all calls to loading/caching resources goes through. It maintains that std::list for any resources created: dynamically or static. ResourceManager will Free()  any resources if they haven't been all ready when it Shutdown() is called. All resource DO have their own loader: a derivative of ResourceFilter. For images, this is ImageLoader and ImageLoader is also abstract. I wrote a TGA loader a long time ago that I have encapsulated in this, for example.

 

This is how it works so far. All resources and resource-loaders are pretty much all lumped together for now. I haven't created a separate container for each type yet. I might use templates similar to what was explained in an article I read on this site. Getting there I guess...

Share this post


Link to post
Share on other sites

I have been on working this all day. Thanks for all the help guys, good advice. This system still needs a little polish, but it works. This is what I have so far:

// Resource.h
//
class Resource
{
	friend class ResourceLoader ;
	friend class ResourceLoaderFactory ;

	public :

	Resource( ) ;
	virtual ~Resource( ) ;


	virtual void Bind( ) = 0 ;
	virtual void Free( ) = 0 ;
} ;

// ResourceLoader.h
//
class ResourceLoader
{
	public :

	ResourceLoader( ) ;
	virtual ~ResourceLoader( ) ;


	virtual Resource* Load( std::string path ) = 0 ;
} ;

// ResourceLoaderFactory.h
//
class ResourceLoaderFactory
{
	struct Object
	{
		std::string name ;
		Resource* inst ;
	} ;

	struct Loader
	{
		std::string name ;
		ResourceLoader* inst ;
	} ;

	std::list<Object> objects ;
	std::list<Loader> loaders ;


	public :

	ResourceLoaderFactory( ) ;
	virtual ~ResourceLoaderFactory( ) ;

	bool Init( ) ;
	void Shutdown( ) ;
	void AddLoader( std::string name, ResourceLoader* ) ;
	Resource* Find( std::string path ) ;
	Resource* Load( std::string path ) ;
} ;

// ResourceManager.h
//
extern bool ResInit( ) ;
extern void ResShutdown( ) ;
extern Image* ResLoadImage( std::string name ) ;
extern Sound* ResLoadSound( std::string name ) ;




// Resource.cpp
//
Resource::Resource( )
{
}


Resource::~Resource( )
{
}

// ResourceLoader.cpp
//
ResourceLoader::ResourceLoader( )
{
}


ResourceLoader::~ResourceLoader( )
{
}


// ResourceLoaderFactory.cpp
//
ResourceLoaderFactory::ResourceLoaderFactory( )
{
	objects.clear( ) ;
	loaders.clear( ) ;
}


ResourceLoaderFactory::~ResourceLoaderFactory( )
{
	Shutdown( ) ;
}


bool ResourceLoaderFactory::Init( )
{
	return true ;
}


void ResourceLoaderFactory::Shutdown( )
{
	for ( auto it = objects.begin( ) ; it != objects.end( ) ; it++ ) {
		( *it ).inst->Free( ) ;
		delete ( *it ).inst ;
	}
	objects.clear( ) ;
}


void ResourceLoaderFactory::AddLoader( std::string name, ResourceLoader* inst )
{
	for ( auto it = loaders.begin( ) ; it != loaders.end( ) ; it++ ) {
		if ( ( *it ).name == name ) {
			( *it ).inst = inst ;
			return ;
		}
	}

	Loader temp ;
	temp.name = name ;
	temp.inst = inst ;
	loaders.push_back( temp ) ;
}


Resource* ResourceLoaderFactory::Find( std::string path )
{
	for ( auto it = objects.begin( ) ; it != objects.end( ) ; it++ ) {
		if ( ( *it ).name == path ) {
			return ( *it ).inst ;
		}
	}

	return NULL ;
}


Resource* ResourceLoaderFactory::Load( std::string path )
{
	Resource* temp ;

	temp = Find( path ) ;
	if ( temp != NULL ) {
		return temp ;
	}

	std::string name = path.substr( path.find_last_of( "." ) + 1, path.length( ) ) ;
	if ( name.empty( ) == true ) {
		return NULL ;
	}

	for ( auto it = loaders.begin( ) ; it != loaders.end( ) ; it++ ) {
		if ( ( *it ).name == name ) {
			Object obj ;
			obj.name = path ;
			obj.inst = ( *it ).inst->Load( path ) ;
			if ( obj.inst != NULL ) {
				objects.push_back( obj ) ;
				return obj.inst ;
			}
			return NULL ;
		}
	}

	return NULL ;
}

// ResourceManager.cpp
//
ResourceLoaderFactory gfxman ;
TGA tga ;
ResourceLoaderFactory sfxman ;
WAV wav ;


bool ResInit( )
{
	if ( gfxman.Init( ) == false ) {
		return false ;
	}
	gfxman.AddLoader( "tga", &tga ) ;

	if ( sfxman.Init( ) == false ) {
		return false ;
	}
	sfxman.AddLoader( "wav", &wav ) ;


	return true ;
}


void ResShutdown( )
{
	sfxman.Shutdown( ) ;
	gfxman.Shutdown( ) ;
}


Image* ResLoadImage( std::string name )
{
	return reinterpret_cast<Image*>( gfxman.Load( name ) ) ;
}


Sound* ResLoadSound( std::string name )
{
	return reinterpret_cast<Sound*>( sfxman.Load( name ) ) ;
}
Edited by m.r.e.

Share this post


Link to post
Share on other sites

I rewrote again. This time I used a std::map and did away with *Resource* objects all together. A lot less code now and it works much the same way as the original.

std::map<std::string, Image*> imageCache ;
std::map<std::string, Sound*> soundCache ;


bool ResInit( )
{
	imageCache.clear( ) ;
	soundCache.clear( ) ;

	return true ;
}


void ResShutdown( )
{
	for ( auto it = soundCache.begin( ) ; it != soundCache.end( ) ; it++ ) {
		( *it ).second->Free( ) ;
		delete ( *it ).second ;
	}
	soundCache.clear( ) ;

	for ( auto it = imageCache.begin( ) ; it != imageCache.end( ) ; it++ ) {
		( *it ).second->Free( ) ;
		delete ( *it ).second ;
	}
	imageCache.clear( ) ;
}


bool ResLoadImage( std::string path, Image** out )
{
	std::string ext ;
	bool ret ;

	for ( auto it = imageCache.begin( ) ; it != imageCache.end( ) ; it++ ) {
		if ( ( *it ).first == path ) {
			( *out ) = ( *it ).second ;
			return true ;
		}
	}

	ext = path.substr( path.find_last_of( "." ) + 1, path.length( ) ) ;
	if ( ext.empty( ) == true ) {
		return false ;
	}

	Image* image = new Image( ) ;
	if ( image == NULL ) {
		return false ;
	}

	if ( ext == "tga" ) {
		ret = TGA_LoadImage( path.c_str( ), image ) ;
	}

	if ( ret == true ) {
		imageCache.insert( std::make_pair( path, image ) ) ;
		( *out ) = image ;
		return true ;
	}

	delete image ;

	return ret ;
}


bool ResLoadSound( std::string path, Sound** out )
{
	std::string ext ;
	bool ret ;

	for ( auto it = soundCache.begin( ) ; it != soundCache.end( ) ; it++ ) {
		if ( ( *it ).first == path ) {
			( *out ) = ( *it ).second ;
			return true ;
		}
	}

	ext = path.substr( path.find_last_of( "." ) + 1, path.length( ) ) ;
	if ( ext.empty( ) == true ) {
		return false ;
	}

	Sound* sound = new Sound( ) ;
	if ( sound == NULL ) {
		return false ;
	}

	if ( ext == "tga" ) {
		ret = WAV_LoadSound( path.c_str( ), sound ) ;
	}

	if ( ret == true ) {
		soundCache.insert( std::make_pair( path, sound ) ) ;
		( *out ) = sound ;
		return true ;
	}

	delete sound ;

	return ret ;
}

Its a work in progress but I like it.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!