Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: Is it possible to use STL classes to store graphics or sounds?  (Read 2771 times)

0 Members and 1 Guest are viewing this topic.

DojoMike

  • Newbie
  • *
  • Posts: 9
    • View Profile
Hey guys,

I've been on a roll with SFML, creating a simple scrolling shooter, but so far the only way I've been able to keep references to graphics available long enough to be used is by storing them in fixed-size arrays or class properties for every instance.  Initially I thought it was possible to use vectors, maps, etc. but quickly found out that the way those classes work internally is to make copies, so anything I create is destroyed the second it goes out of scope.  Sounds don't play, and graphics get the white square bug, if I use anything out of the STL.

But then again, I'm thinking that might not actually be the case.  I've only been doing C++ for about 6 months, and it's the first language I've learned where memory management is an issue.  So odds are that there's some other way I just haven't learned yet.  Take for example these 2 versions of the same class:

// This is the version that works, using a class property for every instance
#ifndef SOUND_PLAYER_H
#define SOUND_PLAYER_H
#include <map>
#include <SFML/Audio.hpp>

class SoundPlayer
{
public:
    static SoundPlayer * Get();
    void PlayerLaser();
private:
    static SoundPlayer * Instance;
    SoundPlayer();
    sf::SoundBuffer player_laser, enemy1_explode;
    sf::Sound sound;
};

SoundPlayer * SoundPlayer::Instance = NULL;

SoundPlayer * SoundPlayer::Get()
{
    if (Instance == NULL)
        Instance = new SoundPlayer();
    return Instance;
}
SoundPlayer::SoundPlayer()
{
    if (!player_laser.loadFromFile("bin\\debug\\sounds\\player_laser.wav"))
        throw "Error loading the player laser sound";
    if (!enemy1_explode.loadFromFile("bin\\debug\\sounds\\enemy1_explode.wav"))
        throw "Error loading the enemy 1 explosion sound";
}
void SoundPlayer::PlayerLaser()
{
    sound.setBuffer(player_laser);
    sound.play();
}

#endif
 
// And this is the one that doesn't work, using maps
// I've tried maps of objects, and maps of pointers (which is what's shown here)
// and both don't seem to have what it takes to work in SFML - but they've been around for decades so it's obviously just me being a noob :)
#ifndef SOUND_PLAYER_H
#define SOUND_PLAYER_H
#include <map>
#include <SFML/Audio.hpp>

class SoundPlayer
{
public:
    static SoundPlayer * Get();
    void PlayerLaser();
private:
    static SoundPlayer * Instance;
    SoundPlayer();
    std::map<std::string, sf::SoundBuffer*> buffers;
    std::map<std::string, sf::Sound*> sounds;
};

SoundPlayer * SoundPlayer::Instance = NULL;

SoundPlayer * SoundPlayer::Get()
{
    if (Instance == NULL)
        Instance = new SoundPlayer();
    return Instance;
}
SoundPlayer::SoundPlayer()
{
    sf::SoundBuffer b1;
    if (!b1.loadFromFile("bin\\debug\\sounds\\player_laser.wav"))
        throw "Error loading the player laser sound";
    buffers.insert(std::make_pair("player_laser", &b1));

    sf::Sound s1;
    s1.setBuffer(b1);
    sounds.insert(std::make_pair("player_laser", &s1));
}
void SoundPlayer::PlayerLaser()
{
    sounds["player_laser"]->play();
}

#endif
 

Both compile fine, and both "look" like they should work, but because of however these STL classes work "under the hood" they just don't seem to work well with SFML, making the idea little more than wishful thinking.  From what I've read (including another question on this forum) the way they work is to make copies of the objects, and I haven't been able to figure out a way around that obstacle yet.  I've tried vectors/maps of objects, and also pointers (shown above), and none of it seems to work... so it's gotta be me just being a noob (lol).

But it's no biggy if it actually can't be done.  Fixed arrays and class properties per object are working reliably, and I've got a pretty cool little game going using them as my go-to for everything SFML-related.  Heck, even the NES could only have up to 64 objects on the screen at once, so my player object only being able to use up to 5 laser instances or there only being up to 16 enemies is not an issue.  But there's a reason the STL was created, problems with the fixed-size way of doing things that it was meant to solve, and I bet there's a better way to use it that I just haven't figured out yet.

So how do you guys handle textures, sprites, sound buffers, sounds, and other stuff like that?  I'm sure I'm not the first guy who's ever had this question (and since the white square issue is brought up in the documentation it must be super-common) but so far I haven't managed to fish up any answers on Google, so I figured I'd ask the pros. :)

Thanks in advance. :)

AFS

  • Full Member
  • ***
  • Posts: 115
    • View Profile
Re: Is it possible to use STL classes to store graphics or sounds?
« Reply #1 on: June 26, 2017, 11:28:22 pm »
Yes, you can. Usually using a vector is enough for most cases, but you could use lists or maps as well if needed, just make sure that you know the differences between them, as each have both advantages and disadvantages. If you are just starting out, I advice just using vectors, as they are pretty straightforward.

It's true that the elements are destroyed when the container goes out of scope, but that's the case for any variable, so I don't really see the problem. You just need to make sure to declare your vector/list/map in the appropriate scope, like in your main, for example, and the elements shouldn't be destroyed until your game is closed.

The white square problem happens when your sprite is using a texture that no longers exists, so it's not a problem about the STL containers per se, but more about scope/lifetime. Take the following example:


int main() {

    sf::Sprite sprite;

    int number = 1;

    if (number == 1) {
        sf::Texture texture;
        texture.loadFromFile("sprite1.png");
        sprite.setTexture(texture);
    }

}
 

The code above will produce the white square problem because the texture is declared inside the "if" statement, so when the code inside the brackets ends, the texture will no longer exists, and you'll get a white square when you draw your sprite.

If you do the following instead:


int main() {

    sf::Texture texture;
    sf::Sprite sprite;

    int number = 1;

    if (number == 1) {
        texture.loadFromFile("sprite1.png");
        sprite.setTexture(texture);
    }

}
 

Then it's not a problem, the texture won't get destroyed when the "if" statement ends due to being declared outside of it. The same applies if you are using a vector of textures, or a class, or anything, really.

As to why your particular code doesn't work, I can't really say anything because I'm not familiar with maps, and besides, you only provided us the code of your class, but not the code where you actually use it. However, I can say that you seem to be overcomplicating yourself by using an static instance. I'm no expert at C++, but that looks like bad design, and it seems like it would bring tons of trouble. Maybe your problem is related to that.
« Last Edit: June 26, 2017, 11:34:42 pm by AFS »

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 11030
    • View Profile
    • development blog
    • Email
Re: Is it possible to use STL classes to store graphics or sounds?
« Reply #2 on: June 27, 2017, 06:55:52 am »
You're creating a local object and then take the address of said object. Once the function ends the local object goes out of scope and the address you've saved becomes invalid.

There are three solutions I can see:

Insert the whole object and not just thr address. This also means you need to adjust the container to hold the objects and not just pointers. Inserting the whole object will create a copy, which isn't always desired.

To overcome the additional copy, you can create the object directly in the container with the C++11 emplace (or emplace_back) function. Once again, this requires your container to hold the objects and not just the addresses.

With the previous two methods the lifetime of the object is basically bound to the time the object is part if the container. Meaning you don't have as much control over it. To gain back full control, you can use a std::unique_ptr inside the containers. The smart pointer's syntax is very similar to a raw pointer, except for the huge plus, that the lifetime is bound to the smart pointer, so you'll pretty much prevent all memory leaks.
Additionally this has the benefit that your objects will stay allocated in one place and references to it won't get invalidated by operating on the container. For example a std::vector uses a continuous block of memory. If the allocated space gets too small, it will move all objects to a different memory location, which can cause issues (e.g. white square problem).

Finally, for managing resources, I highly recommend you use something like Thor's resource holder. The important part being that you separate the "heavy" resources and the "light" game objects. If you store a texture and a sprite or a sound buffer and a sound in the same class, your design is questionable.

As for your code, using the Singleton pattern makes no sense. Why should there only be a single sound player instance? If you're trying to limit accessibility due to it being a global variable, you're simply misusing the pattern, it was not created for limiting global access.
Instead you should find a better way that doesn't require the use of global variables.
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/