Sign in to follow this  
Programmer16

Resource and manager class design

Recommended Posts

Programmer16    2321
I'm coding my resource and manager class and I'm looking for tips on my design. The classes are base classes for ANY resource (beit a texture, sound, data surface , etc.) so I might need different API data (for example, for my texture class I need an IDirect3DDevice9 interface pointer, but for say a sound class I would need IDirectMusicLoader and such.) Here's the design:
class Resource
{
protected:
	std::string	m_FileName;
	bool		m_bIsLoaded;
public:
	Resource();
	~Resource();

	virtual	bool Create(void* pData);
	virtual bool Load(const char* pFileName);
	virtual bool CreateFromFile(const char* pFileName, void* pData);
	virtual void Release()	= 0;

	std::string GetFileName();
	bool IsLoaded();
};

class ResourceManager
{
protected:
	std::vector<Resource*>	m_Resources;
public:
	virtual bool Create(void* pData, UINT& nHandle);
	virtual bool Load(const char* pFileName, UINT& nHandle);
	virtual bool CreateFromFile(const char* pFileName, void* pData, UINT& nHandle);
		
	virtual void Release(UINT nIndex);
	virtual void Release();

	Resource* GetResource(UINT nIndex);
	Resource* FindResource(const char* pFileName);
};

Most of the non-pure virtual functions just log a message that the derived class didn't override base function. Now, the developer would create an information class that would be passed for the void* pData arguments. For example, this is my TextureData class:
class TextureData
{
public:
	IDirect3DDevice9	*m_pDevice;
	UINT			m_nWidth;
	UINT			m_nHeight;
	UINT			m_nMipLevels;
	UINT			m_nUsage;
	D3DFORMAT		m_Format;
	D3DPOOL			m_Pool;
	DWORD			m_Filter;
	DWORD			m_MipFilter;
	D3DCOLOR		m_ColorKey;
	D3DXIMAGE_INFO		*m_pSrcInfo;
	PALETTEENTRY		*m_pPalette;
		
	TextureData()
	{
		m_pDevice	= 0;
		m_nWidth	= D3DX_DEFAULT;
		m_nHeight	= D3DX_DEFAULT;
		m_nMipLevels	= D3DX_DEFAULT;
		m_nUsage	= 0;
		m_Format	= D3DFMT_A8R8G8B8;
		m_Pool		= D3DPOOL_MANAGED;
		m_Filter	= D3DX_FILTER_NONE;
		m_MipFilter	= D3DX_FILTER_NONE;
		m_ColorKey	= 0;
		m_pSrcInfo	= 0;
		m_pPalette	= 0;
}
};

This is the biggest part of my concern. To use it, the developer would do something like this:
dftGraphics::TextureData TD_TYPE1;
TD_TYPE1.m_ColorKey = 0xffff00ff;

dftGraphics::TextureData TD_TYPE2;
TD_TYPE2.m_Format = D3DFMT_R5G6B5;
TD_TYPE2.m_Pool = D3DPOOL_DEFAULT;
Now TD_TYPE1 is the default texture settings with a colorkey of hotpink. TD_TYPE2 on the other hand is the default texture settings with no colorkey, 16bit format, and default pool. Now we do this:
TexPool.CreateFromFile("test1.dds", &TD_TYPE1, T1_HANDLE);
TexPool.CreateFromFile("test2.dds", &TD_TYPE2, T2_HANDLE);
Also, does anybody have any suggestions on my iterating functions:
//------------------------------------------------------------------------------------------------
// Name: Release()
//------------------------------------------------------------------------------------------------
void dftCommon::ResourceManager::Release(UINT nIndex)
{
	std::vector<Resource*>::iterator Itr = m_Resources.begin();
	while(nIndex > 0)
	{
		if(Itr == m_Resources.end())
			break;
		--nIndex;
		++Itr;
	}
	(*Itr)->Release();
	m_Resources.erase(Itr);
}

//------------------------------------------------------------------------------------------------
// Name: Release()
//------------------------------------------------------------------------------------------------
void dftCommon::ResourceManager::Release()
{
	std::vector<Resource*>::iterator Itr = m_Resources.begin();
	while(Itr != m_Resources.end())
	{
		Resource* pRes = *Itr;
		pRes->Release();
		delete pRes;
		++Itr;
	}
	m_Resources.clear();
}

//------------------------------------------------------------------------------------------------
// Name: GetResource()
//------------------------------------------------------------------------------------------------
dftCommon::Resource* dftCommon::ResourceManager::GetResource(UINT nIndex)
{
	std::vector<Resource*>::iterator Itr = m_Resources.begin();
	while(nIndex > 0)
	{
		if(Itr == m_Resources.end())
			return 0;
		--nIndex;
		++Itr;
	}
	return *Itr;
}

//------------------------------------------------------------------------------------------------
// Name: FindResource()
//------------------------------------------------------------------------------------------------
dftCommon::Resource* dftCommon::ResourceManager::FindResource(const char* pFileName)
{
	std::vector<Resource*>::iterator Itr = m_Resources.begin();
	while((*Itr)->GetFileName() != pFileName)
	{
		++Itr;
		if(Itr == m_Resources.end())
			return 0;
	}
	return *Itr;
}

Thanks!

Share this post


Link to post
Share on other sites
Koshmaar    989
Hi

I'll say just three things, hope other guys will give more useful responses :-)

1. Read chapter 1.6, "A Generic Handle-Based Resource Manager" by Scott Bilas, from Game Programming Gems #1. It's wonderfull introduction to this topic (resource managers), and probably would help you much, since your design is very similiar to Scott's, though there are some things that could improve your system. Also, next article is very interesting too: "Resource and Memory Management" by James Boer. Clicky.

2. Take a look how people at OGRE team has written their resource manager - again, IIRC it is similiar to your version, while being much more tested, and used in really huge projects.

3.
Quote:
Also, does anybody have any suggestions on my iterating functions:


Don't know what you mean, for me it's nothing wrong with them. BTW, maybe try using std::find and other algorithms?

Share this post


Link to post
Share on other sites
Programmer16    2321
Thanks dude! I knew I had saw a similiar design somewhere (I have that book). The question about my iterator functions was actually directed more toward the GetResource(UINT nIndex). I can use my way or just use the .at() function.

Share this post


Link to post
Share on other sites
Telastyn    3777
Templates are your [slightly off the wall] friend.

By requiring any resource to be derived from the resource class it makes it quite difficult to re-use those resources later [or use them differently in the same app]. Templates would allow the resource class to take a type parameter and inherit from the resource itself rather than vice versa. You'd also likely inherit the templated resource from a virtual base interface. The interface itself would then be stored in the vector so you can handle a variety of resources.

Personally, I like seperating resource managers into different types for each type they hold [since they might and often do require a variety of differing interfaces]. The common parts can be derived from a base class so there isn't a bunch of duplicate work.

That said, it's not as though this won't work, or allow you to do more interesting work.

Share this post


Link to post
Share on other sites
Programmer16    2321
I don't quite understand what you're mean? I derive my own classes from Resource and ResourceManager. For example, I derive Texture from Resource, and then TexturePool from ResourceManager.

Share this post


Link to post
Share on other sites
OrthoDiablo    140
I would really like to see more comments that explain what the functions do exactly.

If you are unsure how good your design is go download CppUnit and write some unit tests. You'll probably be surprised what these suckers can do for you.

Something you might want to consider. If you will inherit from Resourse class to accomodate different resource types, you wont need a void* since each subclass will have its own resource specific members. You might want to look into the RAII principle to get rid of some Create and Release functions. There is also a principle of an object doing only one task. You might want to separate the loading, unloading and the actual usage of the resource in three different objects.

Share this post


Link to post
Share on other sites
Telastyn    3777
[delayed via forum boochy-ness]:

Quote:
Original post by Programmer16
I don't quite understand what you're mean? I derive my own classes from Resource and ResourceManager. For example, I derive Texture from Resource, and then TexturePool from ResourceManager.


Right.

So every single resource has a file like this:


#ifndef TEXTURE...
#define ...

#include "resource_manager.h"

struct texture:public resource{





correct?

What happens when you try to move that code to the next project, without the resource manager?

It would be better to do something like this:


struct resource_interface{
protected:
std::string filename;
bool loaded;
public:
resource_interface();
virtual ~resource_interface();

virtual bool Create( void *data)=0;
virtual bool Load ( const char *fname)=0;
virtual bool CreateFromFile ( const char *fname, void *data); // call load, then create.
virtual bool Release()=0;

std::string GetFileName();
bool IsLoaded();
};

template <typename T>
struct resource:
public resource_interface, public T{
private:
protected:
public:
resource();
virtual ~resource();

// specify function to return casted T

bool Create(void *data){ // cast data to typename T::datatype and call T::create or similar. }
};




See? Instead of having the resources derive from resource, you have resource derived from them. You then just store resource_interface * in the manager. Instead of storing the resources, you store the little glue structure that derives both the interface and the resource you need. The texture code then does not depend on the resource manager, and is free to be used without it.

Plus, you don't need to mess with this code should a new resource format come out. The templated code will inherit from it regardless.

[edit: and this is all a bunch of "sort of" and "like" since I'm not sure I'd do the Create/Load/Release, and I'd almost definately not do any sort of void *. That's gotten me into too much trouble in the past]

Share this post


Link to post
Share on other sites
Programmer16    2321
I'm still not understanding (probably due to a lack of sleep, I've been up for almost 28 hours). My resource class can be used separate of the resource manager. I can just derive Texture from it, override Create(), CreateFromFile() (I haven't overloaded Load() yet) and I can just go to town via:

dftGraphics::Texture MyCursor;

dftGraphics::TextureData CURSOR_TEXDATA;
CURSOR_TEXDATA.m_pDevice = g_pDevice;

MyCursor.CreateFromFile("cursor.png", CURSOR_TEXDATA);

//Begin
g_pSprite->Draw(MyCursor.GetTexture()/*This is in dftGraphics::Texture*/, 0, 0, &g_CursorPos, 0xffffffff);
//End

MyCursor.Release();


The void pointer is for creation data, since I need different API variables. i.e. for D3D I need IDirect3DDevice9, but for DMusic I need IDirectMusicLoader8 and IDirectMusicPerformance8. The only other option (that I can think of) is to use a blank base class:
class RESOURCE_CREATION_DATA{};

So that I can check for during casting.

Thanks for all the input guys!

Share this post


Link to post
Share on other sites
Telastyn    3777
Quote:
Original post by Programmer16
I'm still not understanding (probably due to a lack of sleep, I've been up for almost 28 hours). My resource class can be used separate of the resource manager. I can just derive Texture from it


Right, and what happens when you want to use Texture without the resource manager?

Quote:

The void pointer is for creation data, since I need different API variables. i.e. for D3D I need IDirect3DDevice9, but for DMusic I need IDirectMusicLoader8 and IDirectMusicPerformance8. The only other option (that I can think of) is to use a blank base class:


Or a templated member function. Though this is why I tend to have various managers which inherit from common code. It allows me to specialize the parts that need specialized.

Share this post


Link to post
Share on other sites
Programmer16    2321
Texture works fine without ResourceManager (well, TexturePool, which is derived from ResourceManager.) I started out using Resource without the ResourceManager class.

I would use member template functions, but some resource types might require more than one variable. Plus, member templates can't be virtual.

Quote:
Though this is why I tend to have various managers which inherit from common code. It allows me to specialize the parts that need specialized.

I may just be dumb, but isn't this what my design is. I have my base classes Resource and ResourceManager, which I derive from to specialize the code for each type of resource (for example Texture, Sound, etc.)

Thanks!

Share this post


Link to post
Share on other sites
Telastyn    3777
Quote:
Original post by Programmer16
Texture works fine without ResourceManager (well, TexturePool, which is derived from ResourceManager.) I started out using Resource without the ResourceManager class.


Okay, let me phrase it differently then. I wouldn't require the inclusion of the Resource class' parts. Filename might not always apply. Say if you moved to a .pak file sort of format for your resources.

Quote:

I would use member template functions, but some resource types might require more than one variable. Plus, member templates can't be virtual.

Quote:
Though this is why I tend to have various managers which inherit from common code. It allows me to specialize the parts that need specialized.

I may just be dumb, but isn't this what my design is. I have my base classes Resource and ResourceManager, which I derive from to specialize the code for each type of resource (for example Texture, Sound, etc.)

Thanks!


I didn't see any specializations, so I wasn't sure. I still don't think that the manager class should handle loading and creation specifically. Just keeping track of what has been loaded and passing requests to more specialized areas...

Anyways, I've inadvertantly hijacked this thread enough. Others?

Share this post


Link to post
Share on other sites
Programmer16    2321
I didn't include my other classes:
dftGraphics::Texture

class Texture : public dftCommon::Resource
{
static IDirect3DDevice9* m_pDevice;
IDirect3DTexture9* m_pTexture;
TextureData m_Data;
public:
bool Create(void* pData);
bool CreateFromFile(const char* pFileName, void* pData);
void Release();

IDirect3DTexture9* GetTexture();
};

//------------------------------------------------------------------------------------------------
// Create()
//------------------------------------------------------------------------------------------------
bool dftGraphics::Texture::Create(void* pData)
{
TextureData* pTexData = (TextureData*)pData;

if(!pTexData->m_pDevice || m_pDevice == 0)
{
dftCommon::LogEx("dftGraphics.cpp", __LINE__, "Texture device was not set.");
return false;
}
else
m_pDevice = pTexData->m_pDevice;

m_pDevice->CreateTexture(pTexData->m_nWidth, pTexData->m_nHeight, pTexData->m_nMipLevels, pTexData->m_nUsage, pTexData->m_Format, pTexData->m_Pool, &m_pTexture, 0);
if(!m_pTexture)
{
dftCommon::Log("dftGraphics.cpp", __LINE__, "Failed to load blank texture into memory.");
return false;
}
dftCommon::Log("dftGraphics.cpp", __LINE__, "Loaded blank texture into memory.");
m_FileName = "blank texture";
m_bIsLoaded = true;
memcpy(&m_Data, pData, sizeof(TextureData));
return true;
}

//------------------------------------------------------------------------------------------------
// CreateFromFile()
//------------------------------------------------------------------------------------------------
bool dftGraphics::Texture::CreateFromFile(const char* pFileName, void* pData)
{
TextureData* pTexData = (TextureData*)pData;

if(!pTexData->m_pDevice && m_pDevice == 0)
{
dftCommon::LogEx("dftGraphics.cpp", __LINE__, "Texture device was not set.");
return false;
}
else
m_pDevice = pTexData->m_pDevice;

D3DXCreateTextureFromFileEx(m_pDevice, pFileName, pTexData->m_nWidth, pTexData->m_nHeight, pTexData->m_nMipLevels, pTexData->m_nUsage, pTexData->m_Format, pTexData->m_Pool, pTexData->m_Filter, pTexData->m_MipFilter, pTexData->m_ColorKey, pTexData->m_pSrcInfo, pTexData->m_pPalette, &m_pTexture);
if(!m_pTexture)
{
dftCommon::Log("dftGraphics.cpp", __LINE__, "Failed to load %s into memory.", pFileName);
return false;
}
dftCommon::Log("dftGraphics.cpp", __LINE__, "Loaded %s into memory.", pFileName);
m_FileName = pFileName;
m_bIsLoaded = true;
memcpy(&m_Data, pData, sizeof(TextureData));
return true;
}

//------------------------------------------------------------------------------------------------
// Release()
//------------------------------------------------------------------------------------------------
void dftGraphics::Texture::Release()
{
if(m_pTexture)
{
m_pTexture->Release();
m_pTexture = 0;
dftCommon::Log("dftGraphics.cpp", __LINE__, "%s was unloaded from memory.", m_FileName.c_str());
m_bIsLoaded = false;
}
}

//------------------------------------------------------------------------------------------------
// GetTexture()
//------------------------------------------------------------------------------------------------
IDirect3DTexture9* dftGraphics::Texture::GetTexture()
{
return m_pTexture;
}



And TexturePool

class TexturePool : public dftCommon::ResourceManager
{
public:
bool Create(void* pData, UINT& nHandle);
bool CreateFromFile(const char* pFileName, void* pData, UINT& nHandle);
};

//------------------------------------------------------------------------------------------------
// Create()
//------------------------------------------------------------------------------------------------
bool dftGraphics::TexturePool::Create(void* pData, UINT& nHandle)
{
Texture* pTexture = new Texture;
pTexture->Create(pData);
if(!pTexture->IsLoaded())
{
delete pTexture;
return false;
}
nHandle = m_Resources.size();
m_Resources.push_back(pTexture);
return true;
}

//------------------------------------------------------------------------------------------------
// CreateFromFile()
//------------------------------------------------------------------------------------------------
bool dftGraphics::TexturePool::CreateFromFile(const char* pFileName, void* pData, UINT& nHandle)
{
Texture* pTexture = new Texture;
pTexture->CreateFromFile(pFileName, pData);
if(!pTexture->IsLoaded())
{
delete pTexture;
return false;
}
nHandle = m_Resources.size();
m_Resources.push_back(pTexture);
return true;
}



All the ResourceManager/TexturePool Create and Load functions do is call the Resource/Texture Create, Load, and CreateFromFile function.

I hadn't used compressed files, so I never really thought of that, but I believe that my design would still work. I could just derive say PakFile from Resource, but again I don't really know how they work.

I think that having the user create the texture and then pass it to the manager would be a better design.

Thanks!

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this