Academic Integrity: tutoring, explanations, and feedback — we don’t complete graded work or submit on a student’s behalf.

I am writing a game in C++, and something I have noticed is that I have many res

ID: 658966 • Letter: I

Question

I am writing a game in C++, and something I have noticed is that I have many resource files which need to be loaded after a particular point in initialization.

For example, OpenGL textures and VAOs can't be created until there's a current OpenGL context. Once loaded, they will never be modified, so it is most convenient to make them global.

If these objects could be created without a current OpenGL context, this would be trivial to do:

const texture ground_texture = load_texture("ground.png");
const texture robot_texture = load_texture("robot.png");
const model robot_model = load_model("robot.obj");
where texture and model are simple POD structs containing (among other things) OpenGL object IDs.

Instead, I can do this:

texture ground_texture;
texture robot_texture;
model robot_model;

void load_resources() {
    ground_texture = load_texture("ground.png");
    robot_texture = load_texture("robot.png");
    robot_model = load_model("robot.obj");
}
and then call load_resources after the OpenGL context is initialized. But that requires about twice as much code (three times if these globals are also declared in a header file as well) and loses the const modifier.

It could conceivably be produced by a simple script to reduce duplication - then the only downside compared to the original non-working code is that the globals aren't const.

There's also this:

class texture_loader;
class model_loader;
static std::vector<const texture_loader*> tex_loaders;
static std::vector<const model_loader*> model_loaders;

class texture_loader {
    mutable texture tex;
    mutable bool initialized;
    const char *filename;
public:
    texture_loader(const char *filename)
        : initialized(false), filename(filename)
    {
        tex_loaders.push_back(this);
    }

    void load() const {
        tex = load_texture(filename);
        initialized = true;
    }

    operator texture() const {
        assert(initialized);
        return tex;
    }
};
// similarly for model_loader

const texture_loader ground_texture("ground.png");
const texture_loader robot_texture("robot.png");
const model_loader robot_model("robot.obj");

void load_textures() {
    for(texture_loader *t : tex_loaders) t->load();
    for(model_loader *m : model_loaders) m->load();
}
but that seems like quite a lot of complexity for something that is conceptually simple. Also, texture_loader and model_loader (and anything_else_loader) would need to be exposed outside the resource loading module in order for other code to be able to use the globals.

Is this a commonly encountered case? How is it typically solved?

Explanation / Answer

Even if it is true that these things will never change, these textures and models are logically part of a specific part of your program: the 3D environment. They only make sense and will only be used within this context. Thus they should not be globals; they should be declared within the relevant context of your program.

A basic principle is that all variables should have the smallest scope that fits their use case and should be declared as close to where they are used as possible. Even if variables will never change, there is benefit to limiting their scope:

It makes the code easier to understand.
It makes the code more flexible to change in the future.
It aids code reuse (you can take a specific piece of code and use it elsewhere without bringing along a bunch of global variables).

Hire Me For All Your Tutoring Needs
Integrity-first tutoring: clear explanations, guidance, and feedback.
Drop an Email at
drjack9650@gmail.com
Chat Now And Get Quote