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

Author Topic: Trying to solve the white square riddle  (Read 3507 times)

0 Members and 1 Guest are viewing this topic.

DojoMike

  • Newbie
  • *
  • Posts: 9
    • View Profile
Trying to solve the white square riddle
« on: June 20, 2017, 04:44:23 am »
Hey, what's up,

I've recently started with SFML, and I've run into the infamous "white square problem".  The documentation explains the problem, but doesn't give any hints at solutions.  I get that under the hood, it uses a pointer to a texture, but that doesn't explain what "breaks" this pointer or what can be done to keep it "intact" (lol).

So I'm writing this simple 2D shooter, and I have a player class and a laser class.  The player class has a vector of lasers, and draws them all in its "update" method.  The objects are created and added to the vector, but this is where I'm hitting the white square bug.  Here's some code:

// Player class

#include <vector>
#include <SFML/Audio.hpp>
#include <SFML/Graphics.hpp>
#include "PlayerLaser.h"

class Player
{
public:

    /**
     * Constructor - initialize instance variables
     */

    Player();

    /**
     * Updates the player's position based on keyboard input
     * @param[in] Pointer to the keyboard event
     */

    void HandleEvents(sf::Event * event);

    /**
     * Update and draw the object to the screen
     * @param[in] Pointer to the window to draw to
     * @param[in] The player's speed
     */

    void Update(sf::RenderWindow * window, float speed);
private:
    sf::Texture texture;
    sf::Sprite sprite;
    bool MovingLeft, MovingRight, MovingUp, MovingDown;
    int index;
    std::vector<PlayerLaser> Lasers;
};

Player::Player()
{
    // Set up the sprite
    if (!texture.loadFromFile("bin\\debug\\graphics\\player.png"))
        throw "Failed to load player texture.";
    sprite.setTexture(texture);
    sprite.setTextureRect(sf::IntRect(0, 0, 64, 32));
    sprite.setPosition(320, 320);

    // Init other variables
    MovingUp = false;
    MovingDown = false;
    MovingLeft = false;
    MovingRight = false;
    index = 0;
}
void Player::HandleEvents(sf::Event * event)
{
    if (event->type == sf::Event::KeyPressed)
    {
        if (event->key.code == sf::Keyboard::Left)
            MovingLeft = true;
        else if (event->key.code == sf::Keyboard::Right)
            MovingRight = true;
        else if (event->key.code == sf::Keyboard::Up)
            MovingUp = true;
        else if (event->key.code == sf::Keyboard::Down)
            MovingDown = true;
        else if (event->key.code == sf::Keyboard::Space)
        {
            PlayerLaser pl(sprite.getPosition().x, sprite.getPosition().y);
            Lasers.push_back(pl);
        }
    }
    else if (event->type == sf::Event::KeyReleased)
    {
        if (event->key.code == sf::Keyboard::Left)
            MovingLeft = false;
        else if (event->key.code == sf::Keyboard::Right)
            MovingRight = false;
        else if (event->key.code == sf::Keyboard::Up)
            MovingUp = false;
        else if (event->key.code == sf::Keyboard::Down)
            MovingDown = false;
    }
}
void Player::Update(sf::RenderWindow * window, float speed)
{
    // Update the player's animation
    index++;
    if (index == 1000)
        sprite.setTextureRect(sf::IntRect(0, 32, 64, 32));
    if (index == 2000)
    {
        sprite.setTextureRect(sf::IntRect(0, 0, 64, 32));
        index = 0;
    }

    // Update the player's position
    if (MovingLeft && sprite.getPosition().x > 0)
        sprite.move(speed * -1, 0);
    else if (MovingRight && sprite.getPosition().x < 640 - 64)
        sprite.move(speed, 0);
    if (MovingUp && sprite.getPosition().y > 0)
        sprite.move(0, speed * -1);
    if (MovingDown && sprite.getPosition().y < 480 - 32)
        sprite.move(0, speed);

    // Draw the player
    window->draw(sprite);

    // Draw the lasers
    for (PlayerLaser pl : Lasers)
        pl.Update(window, speed + (speed / 2));
}

// Main
#include <SFML/Audio.hpp>
#include <SFML/Graphics.hpp>
#include "Background.h"
#include "Player.h"
#include "PlayerLaser.h"

int main()
{
    // Create the main window
    sf::RenderWindow window(sf::VideoMode(640, 480), "3017");

    // Create the background and player objects
    Background background;
    Player player;

    // Test
    PlayerLaser pl(32, 32);

    /*
    // Create a graphical text to display
    sf::Font font;
    if (!font.loadFromFile("arial.ttf"))
        return EXIT_FAILURE;
    sf::Text text("Hello SFML", font, 50);

    // Load music to play
    sf::Music music;
    if (!music.openFromFile("nice_music.ogg"))
        return EXIT_FAILURE;

    // Play the music
    music.play();
    */


    // Start the game loop
    while (window.isOpen())
    {
        // Process events
        sf::Event event;
        while (window.pollEvent(event))
        {
            // Close window: exit
            if (event.type == sf::Event::Closed)
                window.close();
            else if (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Escape)
                window.close();
            else
                player.HandleEvents(&event);
        }
        // Clear screen
        window.clear();

        // Draw the background
        background.Update(&window, 0.025);
        player.Update(&window, 0.1);

        // test - no white square for this instance
        pl.Update(&window, 0.2);

        // Update the window
        window.display();
    }
    return EXIT_SUCCESS;
}
 

So the laser object works, if it's created at compile time, before the while loop.  But if it's created by the player object pressing space, it gets whitesquared.  So are objects not allowed to be created at runtime or something?  I suppose if I create every single instance at compile time it'll avoid the problem, but I'd really like to know if there's an actual solution available.  Unfortunately my knowledge of SFML's internals, vector's internals and whatever else the heck could be causing this to even come up with a logical theory; so for now I'm left Googling and guessing. (hair--;) lol

Any clues would be greatly appreciated.
« Last Edit: June 20, 2017, 08:57:26 am by eXpl0it3r »

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 10823
    • View Profile
    • development blog
    • Email
Re: Trying to solve the white square riddle
« Reply #1 on: June 20, 2017, 09:25:47 am »
SFML isn't here to teach you C++, we don't have to explain every possible scenario when things could break, instead it should be enough to explain, that it breaks when the texture goes out of scope while still being used or when the texture gets moved to a different memory location. ;)

In many cases this happens when people use textures as members of their entities and then push back the entity into a vector, which creates a copy, thus invalidating the sprites "pointer" to the texture (copy = new memory location).

As such the general solution is to use something​ like a resource holder that manages the heavy resources independently of the entities.
I can recommend Thor's resource holder: http://www.bromeon.ch/libraries/thor/tutorials/v2.0/resources.html

As for your problem, you didn't mention whether you have issues with the player or the player laser class, nor did you provide the laser class' code. So I can't really tell you what's wrong your case.

Also you should always use [code=cpp][/code] when posting code.
« Last Edit: June 20, 2017, 09:29:23 am by eXpl0it3r »
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

DojoMike

  • Newbie
  • *
  • Posts: 9
    • View Profile
Re: Trying to solve the white square riddle
« Reply #2 on: June 20, 2017, 01:53:55 pm »
Well first off, I apologize for all the toe-stomping I seem to have done.
  • I didn't see an icon for the code thing, but figured there must be some text-based syntax to set up a code block as code.  I will make sure to do it from here on out.
  • I didn't think I was asking to be taught C++; sorry if I was.  I thought the white square thing was specific to SFML.  But anyway it won't happen again.
Now getting back to the problem at hand, the laser class is nearly identical to the player class, except for the vector and player movement, which is why I didn't include it (but I will if you really want it).  And every class (background, player, and even laser) works if I create them before the while loop in main.

So the problem definitely seems to be with the vector.  I'm still not sure what is causing that pointer to go out of scope; the objects' constructors set it, and in theory the object is still in scope because the vector hasn't gone out of scope, but obviously I'm wrong there (and apparently it's something so super-basic that I cam across as being asked to learn C++).  I've tried using a vector of pointers to laser objects, but pressing Space in that scenario crashed the program; maybe I just need to fight with doing things that way and see if that could potentially lead to any change.

But until then, it sounds like the only way to keep textures permanent is some kind of resource manager class like the one you suggested.  Just the fact that someone felt the need to create a tool like that kinda tells me I'm not the first SFML noob to get whammied with this problem.  ;D

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 10823
    • View Profile
    • development blog
    • Email
Re: Trying to solve the white square riddle
« Reply #3 on: June 20, 2017, 02:08:51 pm »
Everyone has to start somewhere, I'm mainly just pointing out that your currently missing some deeper understanding of the language you chose to write in.
C++ is very hands-on and SFML only provides a very simple interface, everything else is left to the user to deal with. Which is why others have created libraries to deal with certain problems that have popped up in the past and will pop up again, but which aren't really something SFML has to handle.

When you create an object (e.g. a texture) you will allocate some memory (on the stack or heap) which the object lives in. Now you can use a pointer or a reference to point/reference the given memory location (e.g. what the sprite does). Now when you create a copy of the object (e.g. texture) you'll allocate some more memory at a different memory location. If you now destroy the first object (on purpose or by letting it go out of scope) the create pointer/reference will point/reference to an object that no longer exists.

Now if you create an object say on the stack (without new) and you push it back into a vector, it doesn't (necessarily) move it into the vector, but instead it creates a copy of the object. If your entity class (player) holds another object (texture), the object of the entity class will also be copied, thus your object (texture) ends up at a new memory location.

Additionally std::vector guarantees to store objects contiguously in memory, that means if you have 10 objects in a vector, they'll all be laid out next to each other in memory. But since the vector is dynamic, meaning you can add or remove objects from it, the allocated block of memory must be able to grow (or shrink). Because of that the vector will every few push_backs move all the objects it holds from one memory location to a new one which is a bigger block of contiguous memory.
This reallocation can and should be prevented when the amount of objects is know before hand, by reserving the required space in the vector.

This is knowledge that every C++ developer has to learn at one point. ;)
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

DojoMike

  • Newbie
  • *
  • Posts: 9
    • View Profile
Re: Trying to solve the white square riddle
« Reply #4 on: June 21, 2017, 01:48:18 am »
Gotcha.  That particular tidbit was knowledge that this C++ developer didn't know... until now.  :)

Seriously though, you're absolutely right that there is a deeper understanding of C++ that is necessary if you're going to be any good, and at times it seems to be infinitely deeper than any other language I know, so I appreciate your bearing with me.

So in the short-term, I've deleted the vector from the code and just have 5 instances of the laser object that can be visible at any given time, and pressing the shoot button just uses the first one that's off the screen.  But in the long-term I'd be interested to find out a more reliable storage method than vectors, or maybe a way to prevent the accidental destruction of that texture pointer - but for now I got a workaround that gets the job done.  Thanks. :)