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\$
2
\$\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\$
  • \$\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 Sep 25 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 Sep 25 at 8:25
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\$

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\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.