Sign in to follow this  
greyroot

Resource Management Suggestions?

Recommended Posts

So I'm planning on writing a resource manager for a new project (biggest one I've done yet so I'm trying to make it good from the start). Basically as it stands I plan to have a resource management class that'll store all the mesh data and collision data (mesh data will be in VBO's for openGL, but it'll store the offsets and such). From there I'll have 2 types of objects. AABB and Octree objects. Octree will only be for the terrain stuff, AABB will be for the players and other objects on the map. Past that I'll have a scene manager which will have lists of moving objects (which will all be references to the AABB objects), static objects (also AABB) in order to seperate them for collision purposes and then a list of any terrain objects (incase I seperate them for some reason). The non tree objects will also be stored in a different octree just to keep track of objects on the map. (Not a mesh octree but a object octree). Basically I'm just looking for input whether people think this is a good system or not and if not I'm very open to suggestions on how to change or redo this. Any suggestions are appreciated.

Share this post


Link to post
Share on other sites
My suggestion would be for the resource manager to be completely agnostic of what the resources are – it just stores a collection of loaded resources (std::map<std::string, object> or similar). Actually loading the resources is delegated to 'plugin' classes:
interface ResourceLoader{
object LoadResource(string ResourceName);
}

class ResourceManager {
Hashtable<string, ResourceLoader> loaders;
Hashtable<string, object> resources;
...
void RegisterLoader(string type, ResourceLoader loader){
loaders[type] = loader;
}

object GetResource(string type, string name){
object r = resources[name];
if(r == null) {
r = loaders[type].LoadResource(name);
resources[name] = r;
}
return r;
}
}

class MeshLoader : ResourceLoader {
object LoadResource(string name){
// ... load a mesh
}
}

class Mesh {
// might not be the best place to put this
static MeshLoader loader;
static {
engine.ResourceManager.RegisterLoader(loader = new MeshLoader());
}
}


That makes it easier to extend your system to new types of resource.

The basic idea you have sounds fine to me though.

Share this post


Link to post
Share on other sites
I do a similar plugin style manager, but template/generic the base ResourceManager based on (abstract) return type. [edit: And use WeakReferences/boost::weak_ptrs for the Manager store so that the programmer doesn't need to explicitly deal with the manager to free the resource]

Type safety is your friend.

Share this post


Link to post
Share on other sites
They way I did it was to have a nested map like this:

map <string < map < string, shared_ptr<Resource> > > resMap;

Resources are added by type, which is determined by using the Type function implemented from the Resource interface.


class Graphic : public Resource
{
public:
virtual string Type() { return "Graphic"; }
};


The ResManager class had methods like "Request()" and "Add()" and such. It was very simple.

Share this post


Link to post
Share on other sites
I am using templates for the resource manager but I didn't think it was really necessary to list that.

But Bob, I like the idea of using the hash tables... I was going with straight linked lists at first. Been a while since I've coded, that'll be useful.

But...
Quote:
Original post by Telastyn
[edit: And use WeakReferences/boost::weak_ptrs for the Manager store so that the programmer doesn't need to explicitly deal with the manager to free the resource]

I'm not sure I understand this?
WeakReferences/boost?

Share this post


Link to post
Share on other sites
boost is a set of c++ libraries, primary of which is a set of smart pointers. One of those is weak_ptr, which shares a common pointer with shared_ptr's, but bestows no ownership. Thus when the shared_ptrs are all destroyed, the resource itself is destroyed. This allows the programmer to interact less with the manager. Less interaction means less design consideration needed; less chance of mistake...

If you used a shared_ptr (like in drakkcon's example), the resources would always stay loaded, even when the manager is the only thing referring to them.

A WeakReference is the .NET equivalent.

Share this post


Link to post
Share on other sites
That's a good point about weak references. I was kind of working on a small-level model where the resource manager clears itself at the break between levels (so everything gets flushed anyway), but you're right, for any sort of continuous world a weak-reference resource manager is probably better.

Share this post


Link to post
Share on other sites
Sorry for my question but...If we have an abstract object class like "IResource" ("object" in the Bob Janova's code) and we use a pointer of this class as returned value in "getResource(...)", than advanced methods of derived classes are not accessible from the "IResource" pointer without an external cast to the right derived class!

What is the solution of this problem in C++?? I'm very confused!...

Tnx!

Share this post


Link to post
Share on other sites
Yes, i know, but i think that is a very poor method to resolve the problem so this solution, in my opinion, is not a good OOP design...

Share this post


Link to post
Share on other sites
Hello Zuck,

For a slightly more OO approach, I can see one possible solution to the problem of using a generic interface into a resource system. This solution provides the following client interface.


/**
* Client Code example:
*/


int lives = ResourceAdapter<int>::get( aRepositoryManager, "player1/lives" );
MeshData shipData = ResourceAdapter<MeshData>::get(aRepositoryManager, "player1/lives" );





However if this is to be built on top of a true generic resource system interface, say:


class Respository
{
BaseProperty* getProperty(const std::string& p_URLPath);
}


Then at some point the code is going to need to cast BaseProperty when extracting the int or MeshData (from the client code example). At the moment the best I've been able to do is:

- limit the number of places where client code specifies types, two places, if you count the storage type 'int i' and the template parameter.
- get the runtime to perform type checking and throw an exception when an incorrect type is retrieved. (ensuring the client code never receives an incorrectly cast type)

Anyway down to the code, it's probably best to look at this in a few small chunks. This idea relies on using URLs and Properties. URLs specify values to retrieve. Properties are used to wrap the value you want to return.

The important bit here is the URL contains both the identity of the resource and the type it should be. The BasePropery and Property<T> are wrappers to hold the value that's being returned and its type. Later we'll use these together to perform type checking.

URL and Property classes:

/**
* The URL is key used to identify resources or properties. Think of URL as a configurable
* abstraction of a filename.
* i.e. Filenames identify files (resource/ property) within a file system (repository)
* Configurability is provided by allowing the Resource Package to perform mappings
* for URLs to resources.
*
* The URL class contains the following members:
* @li A path attribute navigates though a series of (possible nested) repository objects
* and ultimately referes to a property.
* @li A type attribute is optional, providing the ability to ensure the URL refers to
* a specific property type. If the property and type do not match an error will
* be generated.
*/

class URL
{
public:
/// Path Iterator, an iterator type for read only path traversal
typedef std::list<std::string>::const_iterator path_iterator;

/// List of path sections
typedef std::list<std::string> stringlist;

/// Attribute map
typedef std::map<std::string,std::string> attribute_map;

/**
* Construct a URL object, using the supplied path and optional type.
* @param p_Path A path of URL steps delimited using forward slashes '/'.
* @param p_Type A optional type identifier.
*/

URL(const std::string& p_Path, type_info const* p_Type);

...
}

/**
* The BaseProperty interface represents a resource element, this contains sometype of value.
* This contained value can be accessed by finding the actual resource type (getResourceType)
* casting to it and using specific accessors
*/

class BaseProperty
{
public:

/**
* Virtual destructor, allow correct destruction through base class pointers
*/

virtual ~BaseProperty(){};

/**
* Accessor method, get the type_info object for the contained value
*/

virtual type_info const* getResourceType() const = 0;

/**
* Accessor method, get the URL that refers to this Property.
*/

virtual const URL& getURL() const = 0;
};


/**
* Property Implementation class, this class provides the ability to carry a templated
* value around.
*/


template<typename T>
class Property : public BaseProperty
{
public:
/**
* Construct a Property<T> using the supplied URL and value
* @param p_Key URL
* @param p_Value value
*/

Property (const URL& p_Key, T p_Value)
:m_Key(p_Key),
m_Value(p_Value),
{}

/**
* Accessor for the contained value
*/

T get()
{
return m_Value;
}

virtual const URL& getURL() const
{
return m_Key;
}

virtual type_info const* getResourceType() const
{
return m_Key.getType();
}

const Property<T>& operator= (const Property<T>& rhs)
{
if ( this != &rhs )
{
m_Key = rhs.m_Key;
m_Value = rhs.m_Value;
}

return *this;
}

private:

/**
* The URL associated with this property.
*/

URL m_Key;

/**
* The value this property currently contains.
*/

T m_Value;
};





So back to our generic resource system, as per everyone else's suggestions.


class Respository
{
BaseProperty* getProperty(const std::string& p_URLPath);
};


We can then retrieve from the generic resource system using the following adapter classes:

Note: all these really do is handle the typing and checking of the request and return type.


/**
* The ResourceManager class wraps the Repository providing a more userfriendly interface.
* It does this by delegating to the Repository, but handling all type (casting) related issues.
*/

class ResourceManager
{
public:

enum Result
{
INTERNAL_ERROR,
INCORRECT_TYPE,
NOT_FOUND,
FOUND
};

/**
* Extract a Property from this ResourceManager.
* @param p_URLPath a URL key for the resource property.
* @param p_OutputProperty pointer to store the output Property. This pointer must
* be valid (may not be NULL)
* @return true, indicating the property is successfully retrieved.
*/

template<typename T>
Result getProperty(const std::string& p_URLPath, Property<T>* p_OutputProperty)
{
URL identifier (p_URLPath, &typeid(T));
BaseProperty* matchingProperty = m_RootResource->getProperty (identifier);

if (matchingProperty != NULL)
{
if ( matchingProperty->getResourceType() == &typeid(T) )
{
Property<T>* typedProperty = dynamic_cast<Property<T>*>(matchingProperty);

if (typedProperty != NULL)
{
*p_OutputProperty = *typedProperty;
return FOUND;
}

// should never happen, if typeids match - you'd hope a dynamic_cast will work?
return INTERNAL_ERROR;
}

return INCORRECT_TYPE;
}

return NOT_FOUND;
}

private:
Respository m_RootResource;
}


/**
* An adapter class that calls a templated member function based on desired return type.
*/

template <typename T>
class ResourceAdapter
{

public:

/**
* Extract a resource value from the ResourceManager. Or throw an exception
* if the resource does not exist.
* @param p_Manager the ResourceManager to extract from.
* @param p_Identifier a 'URL' type key for the resource value.
* @return resource value.
*/

static T get (ResourceManager& p_Manager, const std::string& p_Identifier)
{
Property<T> typedProperty;
ResourceManager::Result result = p_Manager.getProperty(p_Identifier, &typedProperty);

if ( result == ResourceManager::FOUND )
{
return typedProperty.get();
}
else
{
// ToDo: add more meaning full errors based on ResourceManager::Result
throw URLNotFoundException( URL(p_Identifier, &typeid(T)) );
}
}
}





So the code still contains type casting, but it's compiler generated whenever a resource is retrieved.

If you've made it this far, was it worth the effort of going to all this just to avoid manually having to write a few casts?

I guess I enjoyed trying to write this, and I liked the notation in the client code - but on balance I'm not so sure this is the right design for generic resource handling.

However hopefully this will get you thinking :)

Cheers,

Tom

Share this post


Link to post
Share on other sites
just use a dynamic_cast, if it return 0 you did cast to a wrong type and should throw an exception or try another type,

the next solution would be to use a system like mine:
http://www.gamedev.net/community/forums/topic.asp?topic_id=424540

Share this post


Link to post
Share on other sites
In the end, if you want a general resource loader that can load anything, you're going to have to cast at some point. You can use a templated resource manager if you want a separate cache for each type of thing.

IMHO, saying something is 'bad OO design' is meaningless and can stop you seeing a good solution. If being 'good OO' means wrapping the cast in lots of objects to pretend it's not there, that's a waste of time and code imo. If it's bad design full stop, then don't do it, but if you think it is please explain why because it looks okay to me :). Of course in C++ you want a cast that checks if it's valid and throws an exception if not (my code is C# where a cast automatically does that).

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