2
\$\begingroup\$

I am trying to Implement resource manager for my hobby game engine. The problem I am trying to solve is that I want one centralized resource manager, which delegates resource loading to a bunch of "subsystems" which are each responsible for managing their own type of resource, like in the code below.

struct Resource {
    std:: extensionType;
    std::string path;
    int refCount;
};

class ResourceLoader {
    virtual ResourceType* loadResource(std::string) = 0;
    virtual void unloadResource(ResourceType*) = 0;
};

class ResourceManager {
    std::map<std::string, ResourceLoader*> _loaders;

    void addResourceLoader(std::string extensionType, FileLoader* loader);
    template<typename T>
    T* loadResource(std::string filePath);
    void reloadAll();
}

class TextureResource: public Resource {
    TextureHandle* handle;
};

class TextureLoader: public ResourceLoader {
    ResourceType* loadResource(std::string); //load + return a TextureResource
    void unloadResource(ResourceType*); //unload texture
};

//Later on, to use:
int main(int argc, char* argv[]) {
     ResourceManager resourceMgr;
     TextureLoader textureLoader;
     resourceMgr.addResourceLoader(textureLoader);
     TextureResource* texture = resourceMgr.loadResource<TextureResource*>("SOMETEXTURE.PNG");
     return 0;
}

This is just an example of my implementation. My problem shows up with more specific types of resources like Shaders. I want to store shaders as a ShaderSet or ShaderProgram ( in OpenGL every shader program has its own ID ), so when I am loading ShaderProgram, I need to specify at least vertex shader and fragment shader ( vertex filepath and fragment filepath ), but with my current logic I can't think of an elegant solution for this problem.

for example to have something like this:

T* loadResource(std::string vs_filePath, std::string fs_filepath);

I can't figure out how can I have ShaderProgram saved in the same resource manager class, any Ideas?

\$\endgroup\$

3 Answers 3

2
\$\begingroup\$

Another way would be to implement type-erasure with inheritance and to implement the loading parameters with variadic templates. I think the end result is fairly clean, but offers no protections (you could probably write them yourself with dynamic_casting and proper type trait usage) against trying to load a shader with a single string parameter.

Note that I didn't test this in actual use, but the following should compile and give you the basic idea of how type-erasure is used here:

#include <string>
#include <map>

enum class ResourceType {
    Texture,
    Shader
};

struct Resource {
    std::string Path;
};

struct TextureResource : Resource {
    int ID;
    static constexpr auto Type = ResourceType::Texture;
};

struct ShaderResource : Resource {
    int Program, VertexShader, FragmentShader;
    static constexpr auto Type = ResourceType::Shader;
};

struct ResourceLoaderBase { 
};

template <typename ResourceT, typename... Arguments>
struct ResourceLoaderImplBase : ResourceLoaderBase {
    virtual ResourceT* load(Arguments... arguments) = 0;
};

struct TextureLoader : ResourceLoaderImplBase<TextureResource, std::string> {
    TextureResource* load(std::string path) override {
        // ...
        return nullptr;
    }
};

struct ShaderLoader : ResourceLoaderImplBase<ShaderResource, std::string, std::string> {
    ShaderResource* load(std::string vert, std::string frag) override {
        // ...
        return nullptr;
    }
};

struct ResourceManager {
    std::map<ResourceType, ResourceLoaderBase*> loaders;

    void addResourceLoader(ResourceType type, ResourceLoaderBase* loader);
    template <typename ResourceT, typename... Arguments>
    ResourceT* loadResource(Arguments... arguments)
    {
        return static_cast<ResourceLoaderImplBase<ResourceT, Arguments...>*>(loaders.at(ResourceT::Type))->load(arguments...);
    }
};
\$\endgroup\$
1
\$\begingroup\$

You could always use a variadic template for your arguments. I wrote a similar system for fun a while back and it had something like this:

template<typename Resource, typename... Args>
struct ContentLoader : public ContentLoaderBase
{
    ContentLoader(std::function<Resource(const std::string&, Args...)> l)
    {
        load = l;
    }
    std::function<Resource(const std::string&, Args...)> load;
};

To which you could then create a ContentLoader

std::function<Shader(const std::string & name, const char* vertFilePath, const char* fragFilePath)> loadShader =
            [](const std::string& name, const char* vertFilePath, const char* fragFilePath)
        { // ... };

Then register it with your manager:

m_ContentManager.manage<Graphics::Shader>(new ContentLoader(loadShader));

With client usage:

auto shader = m_ContentManager.load<Graphics::Shader>("BasicShader", "./resource/shaders/basic.vert", "./resource/shaders/basic.frag");

You could then access all your ContentLoaders inside your manager and call load on them with a variadic template. This way you can pass in whatever you want as arguments. My take on this looks like this:

...
auto loader = static_cast<ContentLoader<T, Args...>*>(m_Loaders[std::string(typeid(T).name())]);
auto resource = std::make_shared<T>(static_cast<T>(loader->load(name, args...)));
...

Where I essentially just store all my loaders in a map to be retrieved later.

It's slightly different then your approach with inheritance and I am not quite happy with how my solution turned out in the end (it's a bit gnarly and registering loaders looks a bit funky to me) but it does meet the requirement of having an arbitrary number of arguments for your loader and I am sure you can adapt it to fit your scheme.

\$\endgroup\$
2
  • \$\begingroup\$ I think that In some cases I need the private data members of ContentLoader, like in case where I want to store glProgramID, and with your implementation I am not sure if i will be able to do this. \$\endgroup\$
    – pureofpure
    Commented Sep 25, 2019 at 7:54
  • \$\begingroup\$ @pureofpure you wouldn't save that in the ContentLoader in this case, but in the Shader object (where the ID belongs). ContentLoader is just a dumb wrapper for a function, essentially. Once you've called load you get a resource back and you squirrel that resource away in a vector. \$\endgroup\$
    – Mikael
    Commented Sep 25, 2019 at 8:25
-1
\$\begingroup\$

In my opinion shaders have too many dependencies to load it as separate resource. It's better to have a resource Material that will contain shaders, input layout, textures (as separate resources) and etc.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .