What happened to the UnloadTexture() functions?

Hi,

I was just wondering why since we started moving our code into separate classes, we stopped unloading the textures at the end of the program? It seems that we are now only unloading the map texture at the end of the main function.

My c++ knowledge is still quite basic but I know that the compiler does call automatically the destructor for each class at the end of a program if we don’t define one (like we do with the constructor). But in context with UnloadTexture(), my idea was to make us of it.

For example, I would declare the destructor in the BaseCharacter.h file and then define it in the source file. Inside the destructor’s body I would call the UnloadTexture function for each texture, so when the program ends and the destructor gets called it would take care of unloading the textures from memory as well.

BaseCharacter::~BaseCharacter()
{
    UnloadTexture(texture);
    UnloadTexture(tex_idle);
    UnloadTexture(tex_run);
}

Same procedure with the sword in the Character class and the props in the Prop Class.

Now my question is: Is this a correct way to do it? Or am I missing something here?
Instinctively, I think this is still a necessary task to complete at the end of the program. If not, I would like to understand why.

Thanks!

Nicole

Hi N.Alonso

The omitting of the UnloadTexture calls for the characters and props was an accidental oversight on Stephen’s part. That said, the lack of that function does not break your game.

As far as your code and my understanding of C++ is, putting these in the destructor for the class makes sense. Though I’m sure @DanM could have some further insights here.

That appears to already happen. I would assume the point of calling the UnloadTexture function explicitly is to unload it when you no longer need it during the execution of the program.

If you have that in the destructor you need to be careful of copies. If you create a copy and one gets destroyed whilst the other is still using it you would run into issues. I thought having two call the UnloadTexture on the same one would cause a problem like double free does but it doesn’t appear to.

That problem can be eliminated by just deleting the copy constructor. (You could then Unload the texture in a die() function.)

BaseCharacter(const BaseCharacter&) = delete;

Now you have to worry about the move constructor which can either be deleted or solved by copying the texture and setting the other’s id to -1* and hope nothing is using is that id.

You can then get into more advanced problem solving like sharing the resource, for say, a spawner that spawns a bunch of enemies with the same texture and have everything cleaned up when the spawner is destroyed.


* it’s an unsigned int so that would be a very big number (2^32 -1 assuming 32-bit ints).

2 Likes

No longer unloading threw me too, but due to old habits and not knowing RayLib well enough, I am still judicially cleaning up after myself and calling it too.

On a similar note I was surprised to see that we would load the same texture multiple times, instead of leaving texture unassigned until deciding whether it should point to run or idle.

Good info on the destructor vs. copy constructor; these are nuances I’d not have considered given the course thus far (and I don’t remember it from my c/c++ days 25 yrs ago either). Never even heard of a move constructor before o_O

C++11 added move semantics and rvalue references. It allows you to be more efficient when you know the original object isn’t going to be used anymore. Basic example

#include <utility>

class terrible_array
{
    int size;
    int* data;
public:
    terrible_array(int sz, int value) : size(sz), data(new int[size])
    {
        int* current = data;
        for (int i = 0; i < size; i++)
        {
            *current++ = value;
        }
    }
    // copy the data over
    terrible_array(const terrible_array& other) : size(other.size), data(new int[size]) 
    {
        int* current = data;
        int* copy = other.data;
        for (int i = 0; i < size; i++)
        {
            *current++ = *copy++;
        }
    }
    // just steal the guts and leave the other empty
    terrible_array(terrible_array&& other) : size(other.size), data(other.data) 
    {
        other.size = 0;
        other.data = nullptr;
    }
    ~terrible_array()
    {
        delete[] data;
    }
};

int main()
{
    terrible_array arr(5, 100);
    terrible_array copy = arr;
    terrible_array steal = std::move(copy); // using copy after here other than to assign it a value is undefined behaviour.
}

Thanks for the info! I didn’t thought about the possible issue if your using multiple copies of it and one gets destroyed while the others are still running. Thanks for pointing that out. Probably would be better to do this in something like a die() function as you mentioned.
But is good to know that it’s actually not a necessary step to end the program correctly in this case. I know it works fine without calling the function but I was unsure if the textures are being released from memory automatically after ending the program or is it something we have to always care about.

I also never heard of a move constructor before. I’m reading the code example you sent about that. As a beginner I still need a little more time and re-reads to get my head around it :sweat_smile: Now I have some c++ topics to learn more about yay

Huh, I think during my testing I needed a rebuild as when I tested it yesterday I got this in the output for both having Unload and not.

INFO: FILEIO: [Cross.png] File loaded successfully
INFO: IMAGE: [Cross.png] Data loaded successfully (512x512)
INFO: TEXTURE: [ID 3] Texture loaded successfully (512x512 - 1 mipmaps)
INFO: TEXTURE: [ID 3] Unloaded texture data from VRAM (GPU)

And now I’m getting the following for omitting it (note the lack of unload).

INFO: FILEIO: [Cross.png] File loaded successfully
INFO: IMAGE: [Cross.png] Data loaded successfully (512x512)
INFO: TEXTURE: [ID 3] Texture loaded successfully (512x512 - 1 mipmaps)

I was wondering how it magically logged that out yesterday :sweat_smile:. With that said the OS should reclaim any resources when you close the program. Just like if you new a bunch of memory and don’t call delete anywhere you don’t have to go and restart your PC to clear it as closing the program should do that.

To be clear I’m not advocating to never free your stuff and just let the OS clean up your s**t. Just trying to make sure no one starts worrying they’re eating up their VRAM by running their game a bunch of times.

Don’t worry about it lol it even troubles some people that aren’t beginners. But in a nutshell it’s just a way to select a different constructor based on the value category.

In pre-C++11 you have the value categories lvalues and rvalues. lvalues being something that’s typically on the left hand side, and rvalue being something that’s typically on the right hand side e.g.

int value = get_value();

Here value is an lvalue and what is returned from get_value() is an rvalue. C++11 then added rvalue references and made the value categories a little more complicated.
I’m pretty sure every standard since has altered them slightly and made them slightly more complicated. The good news however is the developer doesn’t really have to care about all those nitty gritty details. Mainly just the gist of what an lvalue, rvalue, (and maybe 2 others) are.

Just for some help with learning al this, what stuck with me when learning about move semantics is that std::move does not move.

Thanks, that was good info. To be honest my old usage of C++ was lazy - I pretty much took it as C with new/delete instead of malloc/free, with added classes. This was also back in ~95-99. Even though copy constructors were a thing then (if I recall), I gave them a hard pass.

I get it now though, and the difference between move and copy. I also liked this answer/read of them: https://stackoverflow.com/a/64379301

About UnloadTexture, I did a scan of the raylib source on github for calls to rlUnloadTexture and UnloadTexture, and also set break points on the console log line it does. The only automatic unloads appear to be for a built-in default font texture that is loaded (whether or not you render any text). The rest are only happening manually when you set the calls yourself.

I take your point about it being fairly immaterial, albeit potentially clumsy/lazy, when cleaning up. There shouldn’t be any file handles open to the images at least or anything of that kind, because RayLib unloads the image file data immediately after it creates it as texture data in memory. So OS-level cleanups don’t seem to be a problem, it’s just memory that will be reclaimed when the app is closed.

One thing I forgot to note is that the destructor isn’t necessarily when you want to unload the texture.

For example the code written in the course the enemies are created in the main function and will be destroyed when the program ends but you would want to unload the texture whenever the enemy is destroyed by the player.

3 Likes

This topic was automatically closed 20 days after the last reply. New replies are no longer allowed.

Privacy & Terms