Engine design and resources

Started by
37 comments, last by haegarr 15 years, 3 months ago
Hi guys! I'm designing my new engine (full), based on my previous engine (only graphic,
), and I have an open question: have you some class that represent the idea of 'resource'? How do you handle resource management? Thanks everybody!
---------------------------------------http://badfoolprototype.blogspot.com/
Advertisement
Al the stuff tha you load at run time like textures, meshes, sounds should be managed like resource, and the better way i think that are the smart pointers, so you can avoid memory leak and wrong resource unload or duplicate.

I hope that this can help.
Here is my resource management classes:

Resource class - interface:
[source lang=cpp]class SIResource{protected:	unsigned int id;	bool needed;	vector<unsigned int> dependecies;public:	SIResource();	virtual bool load()=0;	virtual bool isLoaded() const=0;	virtual void unload()=0;	virtual unsigned int getID() const;		virtual bool isNeeded() const;	virtual void setNeeded(bool needed);	virtual bool loadIfNeeded();	virtual bool readHeader()=0;	virtual bool readDependencies(const string &dependenciesList);	virtual bool isLoadedLast() const;};

Implementation:
[source lang=cpp]SIResource::SIResource(){	SIResource::id=id;}unsigned int SIResource::getID() const{	return id;}bool SIResource::isNeeded() const{	return needed;}void SIResource::setNeeded(bool needed){	if(needed&&!SIResource::needed)	{		for(vector<unsigned int>::iterator it=dependecies.begin();it!=dependecies.end();++it)			SResourcesManager::markAsNeeded(*it);	}	SIResource::needed=needed;}bool SIResource::loadIfNeeded(){	if(isNeeded()&&!isLoaded())		return load();	if(!isNeeded()&&isLoaded())		unload();	return true;}bool SIResource::readDependencies(const std::string &dependenciesList){	return sAddToUIntVector(dependecies,dependenciesList);}bool SIResource::isLoadedLast() const{	return false;}




Resource manager class - interface
[source lang=cpp]class SResourcesManager{private:	static SResourcesManager *instance;	SResourcesManager();	map<unsigned int,SIResource*> resourceMap;	map<unsigned int,SImage*> loadedImages;	map<unsigned int,SObjectPlacer*> loadedPlacers;	map<unsigned int,SLEntity*> lEntities;	map<unsigned int,SObjectBatch*> loadedBatches;	map<unsigned int,STexture*> loadedTextures;		map<unsigned int,SGUIBatch*> loadedGUIBatches;public:	static void init();	static void destroy();	static bool readFromListFile(string fileName);	static void beginMarking();	static bool markAsNeeded(unsigned int id);	static bool endMarking();	static void addImage(unsigned int id,SImage *image);	static void removeImage(unsigned int id);	static SImage *getImage(unsigned int id);		static void addPlacer(SObjectPlacer *placer);	static void removePlacer(unsigned int id);	static SObjectPlacer *getPlacer(unsigned int id);	static void addLEntity(unsigned int id,SLEntity *lEntity);	static void removeLEntity(unsigned int id);	static void clearLEntities();	static SLEntity *getLEntity(unsigned int id);	static void addAllLEntitiesToWorld(SLWorld *target);	static void addBatch(SObjectBatch *batch);	static void removeBatch(unsigned int id);	static SObjectBatch *getBatch(unsigned int id);	static bool deployBatchesWithConditionMet(SWorld *target);	static void addTexture(unsigned int id,STexture *texture);	static void removeTexture(unsigned int id);	static STexture *getTexture(unsigned int id);	static void addGUIBatch(SGUIBatch *guiBatch);	static void removeGUIBatch(unsigned int id);	static SGUIBatch *getGUIBatch(unsigned int id);};

Implementation:
[source lang=cpp]SResourcesManager *SResourcesManager::instance=0;SResourcesManager::SResourcesManager(){}void SResourcesManager::init(){	if(instance)		destroy();	instance=new SResourcesManager();}void SResourcesManager::destroy(){	for(map<unsigned int,SIResource*>::iterator it=instance->resourceMap.begin();it!=instance->resourceMap.end();++it)	{		it->second->unload();		delete it->second;	}	instance->resourceMap.clear();	instance=0;}bool SResourcesManager::readFromListFile(std::string fileName){	SObjectFileReader reader(fileName);	reader.beginReading();	reader.readNext();	SIResource *tmpResource=0;	while(!reader.isEOF())	{		tmpResource=0;		if(reader.getCommand()=="textureMap")			tmpResource=new STextureMap(reader.getArguments());		else if(reader.getCommand()=="objectPoll")			tmpResource=new SObjectPoll(reader.getArguments());		else if(reader.getCommand()=="level")			tmpResource=new SLevelLoader(reader.getArguments());		else if(reader.getCommand()=="texture")			tmpResource=new STextureLoader(reader.getArguments());		else if(reader.getCommand()=="guiLayout")			tmpResource=new SGUILoader(reader.getArguments());		if(tmpResource)		{			if(!tmpResource->readHeader())				return false;			instance->resourceMap.insert(pair<unsigned int,SIResource*>(tmpResource->getID(),tmpResource));		}		reader.readNext();	}	return true;}void SResourcesManager::beginMarking(){	for(map<unsigned int,SIResource*>::iterator it=instance->resourceMap.begin();it!=instance->resourceMap.end();++it)	{		it->second->setNeeded(false);	}}bool SResourcesManager::markAsNeeded(unsigned int id){	map<unsigned int,SIResource*>::iterator it=instance->resourceMap.find(id);	if(it==instance->resourceMap.end())	{				stringstream err;		err<<"Unable to find resource number ";		err<<id;		err<<".";				SSystem::addError(err.str());		return false;	}	it->second->setNeeded(true);	return true;}bool SResourcesManager::endMarking(){	queue<SIResource*> loadedLast;	for(map<unsigned int,SIResource*>::iterator it=instance->resourceMap.begin();it!=instance->resourceMap.end();++it)	{		if(it->second->isLoadedLast())			loadedLast.push(it->second);		else if(!it->second->loadIfNeeded())			return false;	}	while(!loadedLast.empty())	{		if(!loadedLast.front()->loadIfNeeded())			return false;		loadedLast.pop();	}	return true;}void SResourcesManager::addImage(unsigned int id, SImage *image){	instance->loadedImages.insert(pair<unsigned int,SImage*>(id,image));}void SResourcesManager::removeImage(unsigned int id){	instance->loadedImages.erase(id);}SImage *SResourcesManager::getImage(unsigned int id){	map<unsigned int,SImage*>::iterator it=instance->loadedImages.find(id);	if(it==instance->loadedImages.end())		return 0;	return it->second;}void SResourcesManager::addPlacer(SObjectPlacer *placer){	instance->loadedPlacers.insert(pair<unsigned int,SObjectPlacer*>(placer->getID(),placer));}void SResourcesManager::removePlacer(unsigned int id){	instance->loadedPlacers.erase(id);}SObjectPlacer *SResourcesManager::getPlacer(unsigned int id){	map<unsigned int,SObjectPlacer*>::iterator it=instance->loadedPlacers.find(id);	if(it==instance->loadedPlacers.end())		return 0;	return it->second;}void SResourcesManager::addLEntity(unsigned int id, SLEntity *lEntity){	instance->lEntities.insert(pair<unsigned int,SLEntity*>(id,lEntity));}void SResourcesManager::removeLEntity(unsigned int id){	instance->lEntities.erase(id);}void SResourcesManager::clearLEntities(){	instance->lEntities.clear();}SLEntity *SResourcesManager::getLEntity(unsigned int id){	map<unsigned int,SLEntity*>::iterator it=instance->lEntities.find(id);	if(it==instance->lEntities.end())		return 0;	return it->second;}void SResourcesManager::addAllLEntitiesToWorld(SLWorld *target){	for(map<unsigned int,SLEntity*>::iterator it=instance->lEntities.begin();it!=instance->lEntities.end();++it)		target->add(it->second);}void SResourcesManager::addBatch(SObjectBatch *batch){	instance->loadedBatches.insert(pair<unsigned int,SObjectBatch*>(batch->getID(),batch));}void SResourcesManager::removeBatch(unsigned int id){	instance->loadedBatches.erase(id);}SObjectBatch *SResourcesManager::getBatch(unsigned int id){	map<unsigned int,SObjectBatch*>::iterator it=instance->loadedBatches.find(id);	if(it==instance->loadedBatches.end())		return 0;	return it->second;}bool SResourcesManager::deployBatchesWithConditionMet(SWorld *target){	queue<unsigned int>batchesToRemove;	map<unsigned int,SObjectBatch*>::iterator it=instance->loadedBatches.begin();	while(it!=instance->loadedBatches.end())	{		SObjectBatch *batch=it->second;		if(batch->isDeployingConditionMet(target))		{			if(!batch->place(target))				return false;			if(batch->isRemoveWhenDeployed())				batchesToRemove.push(it->first);			if(batch->isDeleteWhenDeployed())				delete batch;		}		++it;	}	while(!batchesToRemove.empty())	{		instance->loadedBatches.erase(batchesToRemove.front());		batchesToRemove.pop();	}	return true;}void SResourcesManager::addTexture(unsigned int id,STexture *texture){	instance->loadedTextures.insert(pair<unsigned int,STexture*>(id,texture));}void SResourcesManager::removeTexture(unsigned int id){	instance->loadedTextures.erase(id);}STexture *SResourcesManager::getTexture(unsigned int id){	map<unsigned int,STexture*>::iterator it=instance->loadedTextures.find(id);	if(it==instance->loadedTextures.end())		return 0;	return it->second;}void SResourcesManager::addGUIBatch(SGUIBatch *guiBatch){	instance->loadedGUIBatches.insert(pair<unsigned int,SGUIBatch*>(guiBatch->getID(),guiBatch));}void SResourcesManager::removeGUIBatch(unsigned int id){	instance->loadedGUIBatches.erase(id);}SGUIBatch *SResourcesManager::getGUIBatch(unsigned int id){	map<unsigned int,SGUIBatch*>::iterator it=instance->loadedGUIBatches.find(id);	if(it==instance->loadedGUIBatches.end())		return 0;	return it->second;}
-----------------------------------------Everyboddy need someboddy!
In my approach, every resource is typically represented by a (type specialized) Resource instance in a dictionary of the project (a game and also a game's level is a special kind of project). I.e. there is normally a permanent object representation. This is somewhat more complex than the simple resource management, but gives some advantages (of course other systems may also implement such features but perhaps in a different way):

(1) It is simple to attach management attributes to a resource, e.g. whether the actual resource data should be loaded on game start-up, on level load, or or demand only.
(2) It is simple to have (resource type specific) parameters at hand w/o having the actual data being loaded, e.g. the size and format of a texture, or its memory footprint.
(3) It is easy to have prototypes (for instancing) on the resource basis.
(4) The source of the actual resource data can be attached as an Import. The typical types are the FileImport and the ArchiveImport, but an InetImport or whatever is possible, too.

The concept furthur allows for some features that are less runtime engine related but more editor related:

(5) It is more easy for the system and the designer to check for consistency.
(6) It is possible to group the resources. The system provides an automatic type based grouping into ResourceLibrary instances, as well as a designer driven grouping into ResourceFolder. This allows designers a better access to the project's resources/assets.
(7) It is possible to build computed resources. E.g. an ImageResource represents an, well, image; but an image itself can't be loaded directly, so the resource can be used as input for a specialized TextureConverter to provide the data for a TextureResource. Due to this, there is a difference between resources and assets, although only on a logical level.
(8) A kind of compilation is possible. E.g. the above resource computation should not be part of the resulting game level but its result (i.e. the actual asset) should, of course, then an Export can be attached for more or less automatic saving.
(9) A FileImport, as a special type of Import, can be parametrized to observe a file source, so that changes to the source can automatically start an update.
(10) The IOFormat (that is responsible for loading and perhaps saving the actual resource data) can be pre-determined from the resource type, or one can be attached to the Import to enforce the use of a concrete format.
@ Haegarr:

Your design seems really interesting, could you give me some more infos?


@ Someboddy:

Your design is good (and thank you very much for the code), are you using it in a project?


Thanks guys, your replies are great! :D
---------------------------------------http://badfoolprototype.blogspot.com/
Yea, I'm currently working on a small 2D game engine, and this is it's resource management code. It allows me to easily access every image, entity type, and any other type of resource using id's instead of pointers. Also, it uses a dependencies model and a marking method so when a level is changed, if a resource was used in the previous level and is needed for the new level, it will remain in the memory instead of being unloaded and loaded again.
-----------------------------------------Everyboddy need someboddy!
Hi

In my engine, each asset is accessed by an unique name, ie

Texture tex1 = ResourceManager.GetTexture("one");
Texture tex2 = ResourceManager.GetTexture("two");

TileSet tileset = ResourceManager.GetTileset("one");

Each asset has its own realm, so two different assets can have the same name (hope it's clear).

With this approch, I can access each ressource by script for exemple.

In fact, this was how I did before. I found a better way to handle assets. Let's me explain...
Instead of having a method for each kind of asset in my manager, like this :

Texture ResourceManager.GetTexture(string name);
Animation ResourceManager.GetAnimation(string name);
TileSet ResourceManager.GetTileSet(string name);
Sound ResourceManager.GetSound(string name);
Maze ResourceManager.GetMaze(string name);
...

I use generics. So I have only one method to acces every assets :
T ResourceManager.GetAsset<T>(string name);

Texture tex = ResourceManager.GetAsset<Texture>("one");
Sound snd = ResourceManager.GetAsset<Sound>("gun fire");

As a (good) side effect, this allows to implement "Provider". What is a provider ? Simply put, a class that can extend known assets.
My engine knows only base assets (Texture, Animation, StringTable, TileSet, Script...) and the end user is able to extend it by providing its own Providers.
This allow me to make a Jump'n'Run game, D&D game and so on really easily.




- Iliak -
[ ArcEngine: An open source .Net gaming framework ]
[ Dungeon Eye: An open source remake of Eye of the Beholder II ]
Okay...thanks for the ideas :D

But if you want to group resources per assets? (I'm not talking about streaming)
When you enter a level, you have to load all the resources of its asset.
And when you leave, you have to unload all the asset.
In this case you are using some sort of tag, and group your resources by tag, or simply you have a list of resources in which every one has its own tag, so if you want to free them you linearly scan the list using the tag?

And then...what about "empty" resources? Consider a shadowmap...is it considered a resource? It is an empty texture, filled IF the current rendering need the shadows...how do you handle it? Do you insert it in the resource list too?


Thanks again!
---------------------------------------http://badfoolprototype.blogspot.com/
An empty resource should function like any other resource - except it is not connected to a file. In my design, SIResource has no fields nor methods concerning files. Sure, the load function is mainly used to load the resource from a file, but it can just as easily be used to create an empty shadowmap.
-----------------------------------------Everyboddy need someboddy!
Right, but how the object (entity, or stuff like that) use the resource? It stores the pointer to it, say a texture, and then use it? Or store the handle and every time needs to ask the resource manager to get the resource?

I'm using object that are abstract and extend the resource class, thus the "entity" that needs it store a simple pointer...
---------------------------------------http://badfoolprototype.blogspot.com/

This topic is closed to new replies.

Advertisement