Template content manager in c++

Started by
5 comments, last by phil_t 10 years, 5 months ago

I am trying to write a content manager for my game. It will load files from the drive, or if the file has been requested before, it will return the same data without loading it from the drive again. When I think about the way the content manager works in XNA, you have to specify the type of content you want to load as a templated type. My question is what happens to the backing storage solution when I template different types of content? I just want to have to create one ContentManager instance, but have it Load<>() multiple types of data. I am confused as to how to do this.

My understanding of templates is that, if I just template the function, it will create multiple copies of the function for each type I use. I am just confused as to how this affects the storage as I will need to insert and get data of the type of the function.

Advertisement

Well XNA is based on .net, so it is using generics. C# generics syntax might look similar to c++ templates, but they really are quite different.

.net can query the type of the generic at runtime and cast to that type (that's how XNA's ContentManager works - all content resource types are stored in the same dictionary).

You could probably achieve something similar If your content manager stores some type identifier along with each resource object (or use RTTI?) allowing it to validate the type before casting it and handing it back.

A function is not data, you don't insert and get data of the type, you just call. You can have a class with methods like this however. If you only want one ContentManager for all your types, the that class would not be a template. In that case you would have one class that has multiple template methods.

For caching you need a way to know how to destroy resources properly, which would mean storing the type of each object instance your class, because unlike load, your ContentManager destructor cannot be a template. One way to do this is called type erasure, which effectively uses inheritance to keep track of the types, so that a virtual destructor is called on each cached resource (Google for more details). An advantage to this method it does not require you to know every type that may get loaded when you write the class. Another way is to simply have a enum or similar that's kept with each object identifying its type so that you can manually free the right amount of data and do any other cleanup.


My understanding of templates is that, if I just template the function, it will create multiple copies of the function for each type I use. I am just confused as to how this affects the storage as I will need to insert and get data of the type of the function.
Your understanding is correct. In case of a name clash, the parameters will implicitly "pass" the "type of the function" to be called... and thus returned. Their type, statically determined.

Be careful with that as the compiler might infer a different type from what you expect.

If parameters are ambiguous, you'll have to explicitly "pass" the "type of the function" using angular brackets.

At the end of the day, I think templated managers are a promise not held. Just think at how different a static texture is to be managed WRT a dynamic one. Dynamic texture VS dynamic vertex buffer? Sure, we can have "template parameters" dealing with those differences, but at the end of the day, I think it's better for everyone to just bite the bullet and write explicitly the various implementations.

Previously "Krohm"

This can surely be done in a simple manner.


// Base class for all resources
class Resource
{
public:

    virtual void Load(const std::string & sourcePath);
    virtual void Unload();
};

// Resource manager
class ResourceManager
{
    std::map< std::string, Resource * > m_Resources;
public:

    template< class T >
    T * GetResource(const std::string & sourcePath)
    {
        if(m_Resources[sourcePath] != 0)
        {
            return dynamic_cast< T * >(m_Resources[sourcePath]);
        }

        T * newT = new T();

        newT->Load(sourcePath);
        m_Resources[sourcePath] = newT;
        return newT;
    }
};

So what happens is when you use the resource manager to get a resource.
It will first check to see if we have already loaded the resource and try to return that one.
If we havent loaded it, load it and store it so we later on can get it.


void Engine::Init()
{
    ResourceManager * resourceManager = GetResourceManager();
    TextureResource * textureResource = resourceManager->GetResource< TextureResource  >( "path/to/texture.png" );
}

As you can see above, its very straight forward how to use it smile.png

What is also worth mentioning is that in this example im using std::map to store the resources. You should really experiment with different types of storage to find whats best for your needs.

This can surely be done in a simple manner.


// Base class for all resources
class Resource
{
public:

    virtual void Load(const std::string & sourcePath);
    virtual void Unload();
};

// Resource manager
class ResourceManager
{
    std::map< std::string, Resource * > m_Resources;
public:

    template< class T >
    T * GetResource(const std::string & sourcePath)
    {
        if(m_Resources[sourcePath] != 0)
        {
            return dynamic_cast< T * >(m_Resources[sourcePath]);
        }

        T * newT = new T();

        newT->Load(sourcePath);
        m_Resources[sourcePath] = newT;
        return newT;
    }
};

So what happens is when you use the resource manager to get a resource.
It will first check to see if we have already loaded the resource and try to return that one.
If we havent loaded it, load it and store it so we later on can get it.


void Engine::Init()
{
    ResourceManager * resourceManager = GetResourceManager();
    TextureResource * textureResource = resourceManager->GetResource< TextureResource  >( "path/to/texture.png" );
}

As you can see above, its very straight forward how to use it smile.png

What is also worth mentioning is that in this example im using std::map to store the resources. You should really experiment with different types of storage to find whats best for your needs.

That dynamic cast is unnecessary IMO. If you know the type, just use a static_cast, no need to check if it's valid. If it's not then you'll get a runtime error and it's the programmer's fault, not the user's. Also you may want to consider using a std::shared_ptr rather than a raw pointer, or returning by reference and having a Release method.

anax - An open source C++ entity system


That dynamic cast is unnecessary IMO. If you know the type, just use a static_cast, no need to check if it's valid. If it's not then you'll get a runtime error and it's the programmer's fault, not the user's. Also you may want to consider using a std::shared_ptr rather than a raw pointer, or returning by reference and having a Release method.

A static_cast to the wrong type will result in (probably) a crash when the Resource object is finally accessed at a later time (or possibly worse than a crash - if the vtables match up, then just some unexpected behavior). This will be harder to debug, so it's best to offer some validation up front if you know the programmer who called the method messed up. There are probably other solutions though, if you're not using RTTI.

This topic is closed to new replies.

Advertisement