Resource Management Suggestions?

Started by
11 comments, last by Bob Janova 17 years, 5 months ago
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
Advertisement
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
http://www.8ung.at/basiror/theironcross.html
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).

This topic is closed to new replies.

Advertisement