6
\$\begingroup\$

Recently, I added this class to my Spiky engine: its basic purpose is to allocate OpenGL-based objects (such as Textures, Shader, Fonts, ...) and then manage them by giving them an ID so the user can pull objects in and out. Any remarks, suggestions or other comments are welcome.

Resourcemanager.h:

#pragma once
#include <memory>
#include <vector>
#include <unordered_map>
#include "../render/Shader.h"
#include "../render/Mesh.h"
#include "../render/Texture.h"
#include "../render/Font2D.h"

namespace Spiky
{
    class ResourceManager;

    void inline InitSpikyCore();

    class ShaderImp
    {
    public:
        friend class ResourceManager;
        friend std::unique_ptr<ShaderImp>::deleter_type;

        std::unique_ptr<glDetail::CShader> const& operator->() const
        {
            return m_shader;
        }
    private:
        explicit ShaderImp(const char* vs, const char* fs)
            :
            m_shader(std::make_unique<glDetail::CShader>(vs, fs))
        {
        }

        explicit ShaderImp(const char* vs, const char* fs, const char* gs)
            :
            m_shader(std::make_unique<glDetail::CShader>(vs, fs, gs))
        {
        }

        ~ShaderImp()
        {
        }

        std::unique_ptr<glDetail::CShader> m_shader;
    };

    typedef ShaderImp const& Shader;

    class MeshImp
    {
    public:
        friend class ResourceManager;
        friend std::unique_ptr<MeshImp>::deleter_type;

        std::unique_ptr<glDetail::CMesh> const& operator->() const
        {
            return m_mesh;
        }

    private:
        explicit MeshImp(Vertex* vertices, unsigned int numVertices, unsigned int* indeces, unsigned int numIndices)
            :
            m_mesh(std::make_unique<glDetail::CMesh>(vertices, numVertices, indeces, numIndices))
        {
        }

        explicit MeshImp(const char* fileName)
            :
            m_mesh(std::make_unique<glDetail::CMesh>(fileName))
        {
        }

        ~MeshImp()
        {
        }

        std::unique_ptr<glDetail::CMesh> m_mesh;
    };

    typedef MeshImp const& Mesh;

    class TextureImp
    {
    public:
        friend class ResourceManager;
        friend std::unique_ptr<TextureImp>::deleter_type;


        std::unique_ptr<glDetail::CTexture> const& operator->() const
        {
            return m_texture;
        }

    private:
        explicit TextureImp(const char* texturePath, GLenum texTarget = GL_TEXTURE_2D, GLfloat filter = GL_LINEAR, GLfloat pattern = GL_REPEAT,
                            GLenum attachment = GL_NONE)
            :
            m_texture(std::make_unique<glDetail::CTexture>(texturePath, texTarget, filter, pattern, attachment))
        {
        }

        explicit TextureImp(int width = 0, int height = 0, unsigned char* data = 0, GLenum texTarget = GL_TEXTURE_2D, GLfloat filter = GL_LINEAR,
                            GLfloat pattern = GL_REPEAT, GLenum attachment = GL_NONE)
            :
            m_texture(std::make_unique<glDetail::CTexture>(width, height, data, texTarget, filter, pattern, attachment))
        {
        }

        ~TextureImp()
        {
        }

        std::unique_ptr<glDetail::CTexture> m_texture;
    };

    typedef TextureImp const& Texture;

    //Inmplement Shader, Mesh, Texture, ... allocators here : 
    class ResourceManager
    {

        friend inline void SpikyInitCore();

    public:

        using ShaderRepo = std::unordered_map<const char*, std::unique_ptr<ShaderImp>>;
        using MeshRepo = std::unordered_map<const char*, std::unique_ptr<MeshImp>>;
        using TextureRepo = std::unordered_map<const char*, std::unique_ptr<TextureImp>>;

        //Shader
        static const ShaderImp& LoadShader(const char* ID, const char* vs, const char* fs)
        {
            shaderObjects.insert(std::pair<const char*, std::unique_ptr<ShaderImp>>(ID, std::unique_ptr<ShaderImp>(new ShaderImp(
                                                                                        (shaderRootDir + std::string(vs)).c_str(),
                                                                                        (shaderRootDir + std::string(fs)).c_str()))));
            return *(shaderObjects.at(ID).get());
        }

        static const ShaderImp& LoadShader(const char* ID, const char* vs, const char* fs, const char* gs)
        {
            shaderObjects.insert(std::pair<const char*, std::unique_ptr<ShaderImp>>(ID, std::unique_ptr<ShaderImp>(new ShaderImp(
                                                                                        (shaderRootDir + std::string(vs)).c_str(),
                                                                                        (shaderRootDir + std::string(fs)).c_str(),
                                                                                        (shaderRootDir + std::string(gs)).c_str()))));
            return *(shaderObjects.at(ID).get());
        }

        static const ShaderImp& GetShader(const char* ID)
        {
            return *(shaderObjects.at(ID).get());
        }

        //Mesh
        static const MeshImp& LoadMesh(const char* ID, Vertex* vertices, unsigned int numVertices, unsigned int* indeces, unsigned int numIndices)
        {
            meshObjects.insert(std::pair<const char*, std::unique_ptr<MeshImp>>(ID, std::unique_ptr<MeshImp>(new MeshImp(
                                                                                    vertices,
                                                                                    numVertices,
                                                                                    indeces,
                                                                                    numIndices))));
            return *(meshObjects.at(ID).get());
        }

        static const MeshImp& LoadMesh(const char* ID, const char* fileName)
        {
            meshObjects.insert(std::pair<const char*, std::unique_ptr<MeshImp>>(ID, std::unique_ptr<MeshImp>(new MeshImp(
                                                                                    (meshRootDir + std::string(fileName)).c_str()))));
            return *(meshObjects.at(ID).get());
        }

        //Texture
        static const TextureImp& LoadTexture(const char* ID, const char* texturePath, GLenum texTarget = GL_TEXTURE_2D, GLfloat filter = GL_LINEAR,
                                             GLfloat pattern = GL_REPEAT, GLenum attachment = GL_NONE)
        {
            textureObjects.insert(std::pair<const char*, std::unique_ptr<TextureImp>>(ID, std::unique_ptr<TextureImp>(new TextureImp(
                                                                                          (textureRootDir + std::string(texturePath)).c_str(), texTarget, filter, pattern, attachment))));
            return *(textureObjects.at(ID).get());
        }

        static const TextureImp& LoadTexture(const char* ID, int width, int height, unsigned char* data = nullptr, GLenum texTarget = GL_TEXTURE_2D,
                                             GLfloat filter = GL_LINEAR, GLfloat pattern = GL_REPEAT, GLenum attachment = GL_NONE)
        {
            textureObjects.insert(std::pair<const char*, std::unique_ptr<TextureImp>>(ID, std::unique_ptr<TextureImp>(new TextureImp(
                                                                                          width, height, data, texTarget, filter, pattern, attachment))));
            return *(textureObjects.at(ID).get());
        }

        static const TextureImp& LoadTextureCustomPath(const char* ID, const char* texturePath, GLenum texTarget = GL_TEXTURE_2D, GLfloat filter = GL_LINEAR, GLfloat pattern = GL_REPEAT, GLenum attachment = GL_NONE)
        {
            textureObjects.insert(std::pair<const char*, std::unique_ptr<TextureImp>>(ID, std::unique_ptr<TextureImp>(new TextureImp(
                                                                                          texturePath, texTarget, filter, pattern, attachment))));
            return *(textureObjects.at(ID).get());
        }

        static const TextureImp& GetTexture(const char* ID)
        {
            return *(textureObjects.at(ID).get());
        }



    private:
        static ShaderRepo shaderObjects;
        static MeshRepo meshObjects;
        static TextureRepo textureObjects;
        static std::string shaderRootDir;
        static std::string meshRootDir;
        static std::string textureRootDir;
    };
}

ResourceManager.cpp:

#include "../core/ResourceManager.h"

namespace Spiky
{
    ResourceManager::ShaderRepo ResourceManager::shaderObjects =      ShaderRepo();
    ResourceManager::MeshRepo ResourceManager::meshObjects =          MeshRepo();
    ResourceManager::TextureRepo ResourceManager::textureObjects =    TextureRepo();
    ResourceManager::Font2DRepo ResourceManager::font2DObjects =      Font2DRepo();
    std::string ResourceManager::shaderRootDir =                      std::string("assets/shaders/");
    std::string ResourceManager::meshRootDir =                        std::string("assets/models/");
    std::string ResourceManager::font2DRootDir =                      std::string("assets/fonts/");
    std::string ResourceManager::textureRootDir =                     std::string("assets/images/");
}
\$\endgroup\$
6
+50
\$\begingroup\$

Don't Repeat Yourself

Consider ShaderImp, MeshImp, TextureImp. They all have the exact same structure: they befriend ResourceManager, hold onto some std::unique_ptr, which is exposable, and are privately constructible. When you see that kind of repetition in class definitions, that calls for a class template:

template <typename T>
class GenericImp
{
public:
    friend class ResourceManager;
    friend typename std::unique_ptr<T>::deleter_type;

    std::unique_ptr<T> const& operator->() const
    {
        return ptr_;
    }

    // might as well also provide this one
    T const& operator*() const
    {
        return *ptr_;
    }
private:
    template <typename... U,
              typename = std::enable_if_t<std::is_constructible<T, U&&...>::value>>
    explicit GenericImpl(U&&... u)
    : ptr_{new T(std::forward<U>(u)...)}
    { }

    std::unique_ptr<T> ptr_;
};

That handles all the Imps:

using ShaderImp = GenericImp<glDetail::CShader>;
using MeshImp = GenericImp<glDetail::CMesh>;
using TextureImp = GenericImp<glDetail::CTexture>;

Don't Repeat Yourself II

Now that we have our Imps, we need some maps to store them in:

template <typename Imp>
using Repo = std::unordered_map<std::string, std::unique_ptr<Imp>>;

using ShaderRepo = Repo<ShaderImp>;
...

Note that having a const char* key type is highly questionable. Using std::string prevents you from having to deal with any lifetime issues.

Don't Repeat Yourself III

Let's collapse all of our Loaders into a single function template. Because we can:

template <typename Imp,
          typename Key,
          typename... Args>
static const Imp& LoadImp(Repo<Imp>& map, Key&& key, Args&&... args)
{
    auto it = map.emplace(std::forward<Key>(key), 
                          std::unique_ptr<Impl>(new Imp(std::forward<Args>(args)...))
                          ).first;
    return *(it->second);
}

Taking advantage of the fact that emplace() gives us where it was put in, we don't need to do the extra search. Now all the other loaders can just forward to that one, e.g.:

template <typename... Args>
static const ShaderImp& LoadShader(std::string const& ID, Args&&... args)
{
    return LoadImp(shaderObjects, ID, (shaderRootDir + args)...);
}

Note that (shaderRootDir + std::string(vs)).c_str() gives you a dangling pointer so you should try to avoid that construct. Prefer std::strings.

Don't Typedef Meaningless Types

When you introduces types like:

typedef TextureImp const& Texture;

That's confusing. The extra typedef adds no value. You could've just written TextureImp const&. It's not worth it. Also, you agree, since you don't actually use it anywhere yourself!

\$\endgroup\$
  • \$\begingroup\$ Thanks :) But I still have one more question : Where could I store global Texture or Shader objects in this kind of resource manager (can't make them static : I need to first init the engine); \$\endgroup\$ – MattMatt Oct 1 '15 at 18:21

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.