c++sfml

SFML Text not drawn when inside a struct and a pointer


This is a minimal reproducible example:

#include <memory>
#include <SFML/Graphics.hpp>
 
class Label
{
    public:
        Label(sf::String msg, float y)
        {
            if (!font.loadFromFile("arial.ttf"))
            {
                printf("Error loading font!\n");
            }

            text.setString(msg);
            text.setPosition(0, y);
        }

        sf::Font font;
        sf::Text text = sf::Text("Label", font, 40);
};

int main()
{
    sf::RenderWindow window(sf::VideoMode(800, 600), "SFML window");
 
    // label is a local variable
    Label label1 = Label("Label1", 0);

    // label is inside a pointer
    std::unique_ptr<Label> label2 = std::make_unique<Label>("Label2", 50);

    struct Labelstruct
    {
        Label label;
    };

    // label is inside a struct
    struct Labelstruct label3 = { Label("Label3", 100) };

    // label is inside a struct inside a pointer:
    std::unique_ptr<Labelstruct> label4 = std::make_unique<Labelstruct>(Label("Label4", 150));
 
    while (window.isOpen())
    {
        sf::Event event;
        while (window.pollEvent(event))
        {
            if (event.type == sf::Event::Closed)
                window.close();
        }
 
        window.clear();

        window.draw(label1.text);
        window.draw(label2->text);
        window.draw(label3.label.text);
        window.draw(label4->label.text);

        window.display();
    }
 
    return EXIT_SUCCESS;
}

Explanation: The class Label wraps up a sf::Text. Then I use four different ways to initialze and draw it:

  1. A local variable -> no problem
  2. A pointer -> no problem
  3. Inside a struct -> no problem
  4. A pointer to the struct holding the label -> PROBLEM

"Label4" is not drawn. I can see a point where the label should appear:

enter image description here

Why does a pointer to a struct cause this problem?

For your convenience, this is the command I used to compile the thing:

g++ test.cc -std=c++20 -lsfml-window -lsfml-system -lsfml-graphics

Solution

  • from the sf::font docs.

    It is important to note that the sf::Text instance doesn't copy the font that it uses, it only keeps a reference to it. Thus, a sf::Font must not be destructed while it is used by a sf::Text (i.e. never write a function that uses a local sf::Font instance for creating a text).

    std::unique_ptr<Labelstruct> label4 = std::make_unique<Labelstruct>(Label("Label4", 150));
    

    This line creates a Label then uses it to move-construct another Label that is inside Labelstruct, the main constructor of Label only runs once, and the Font object it owns is destroyed, it is moved into the new object. but as the docs say the Text object is keeping a reference to the Font object that is destroyed.

    your Label object needs to store a std::shared_ptr to the font, so the font reference is never invalidated.

    class Label
    {
        public:
            Label(sf::String msg, float y)
            {
                if (!font->loadFromFile("arial.ttf"))
                {
                    printf("Error loading font!\n");
                }
    
                text.setString(msg);
                text.setPosition(0, y);
            }
    
            std::shared_ptr<sf::Font> font = std::make_shared<sf::Font>();
            sf::Text text = sf::Text("Label", *font, 40);
    };
    

    Note that sf::Font objects are expensive. ideally, the Font should be created on the main stack (or in a resource manager). you should have 1 Font object in your application and pass raw pointers to it to your labels, there is no reason for each Label to own a separate font.