2

Recently, I've tried to exercise SFML with textures. However, I've encountered some problems. SFML website tells me that the most efficient way to use textures is to update sprites. Otherwise, our program may consume a lot of memory. I have one base class and two other classes derived from it. How may I initialize my texture variable so derived classes could use it with their sprites? I've tried initializing texture in Base class constructor and call it in derived classes, but then, I realized that my problem wasn't solved, because calling one constructor twice is a nonsense. This problem is so important for me, because I'm using the State Pattern which creates a lot of dynamically allocated objects.

#include <iostream>

class Base
{
protected:
    sf::Texture texture; //How to initialize this?
    sf::Sprite sprite;
public:
    Base();
};

class Derived1 : public Base
{
public:
    Derived1()
    {
        sprite.setTexture(texture);
    }
};

class Derived2 : public Base
{
public:
    Derived2()
    {
        sprite.setTexture(texture);
    }
};
JFMR
  • 23,265
  • 4
  • 52
  • 76
Thorvas
  • 45
  • 10

1 Answers1

3

The Flyweight Pattern

SFML website tells me that the most efficient way to use textures is to update sprites. Otherwise, our program may consume a lot of memory.

Yes, the relationship between sf::Sprite and sf::Texture corresponds to The Flyweight Pattern, a structural design pattern.

An sf::Texture is a kind of heavyweight object whose data we want to share when possible to save memory. An sf::Sprite is a flyweight object, and multiple sf::Sprite objects can refer to the same sf::Texture object to share its data.

The sf::Sprite only contains information that is specific to a particular use of the texture it refers to, e.g., the position of the texture, its rotation, size – this is the extrinsic state. An sf::Texture object contains data (i.e., the texture image itself) that can be shared by multiple sf::Sprite objects – this is the intrinsic state that is shared by all the sf::Sprite objects that refer to the same sf::Texture.

In the SFML library you will also find this pattern with sf::Text/sf::Font and sf::Sound/sf::SoundBuffer.


Not giving up Flyweight

Keeping the pattern exposed above in mind, you may want to follow it in your design as well:

class Base {
public:
    Base(const sf::Texture& texture): sprite(texture) {}
protected:
    sf::Sprite sprite;
};

class Derived: public Base {
public:
    Derived(const sf::Texture& texture): Base(texture) {}
    // ...
};

The sprite data member is initialized with the sf::Texture passed by reference to the constructor, and multiple Base and Derived objects can share the same sf::Texture.

As an example:

sf::Texture myTexture;
myTexture.loadFromFile("myImageFile.png");

Base b1(myTexture), b2(myTexture);
Derived d1(myTexture), d2(myTexture);

b1, b2, d1 and d2 all share the same sf::Texture instance – myTexture:

Flyweight applied

Of course, none of these objects should outlive myTexture.

JFMR
  • 23,265
  • 4
  • 52
  • 76
  • Everything seems alright, but there is one problem. I mean that line `myTexture.loadFromFile("myImageFile.png");` How can I initialize `texture`, so other objects could use it? `texture` is a variable in my class – Thorvas Jul 29 '20 at 11:08
  • 1
    @Thorvas what I would suggest is to have the texture object outside (instead of as a data member). Then, after initializing the texture, just pass it by reference for initializing the sprite objects. If you insist on having a texture object as a data member, then `sf::Texture::loadFromFile()` should be called inside the constructor's body. – JFMR Jul 29 '20 at 11:16
  • If I manage to `loadFromFile` inside constructor's body, then whenever the object is created, a new texture is assigned to a `sf::Texture`. I just want to make sure that `sf::LoadFromFile` is assigned just once. Declaring and initializing `sf::Texture` inside `main()` makes a lot of problems with my code. – Thorvas Jul 31 '20 at 13:03
  • 1
    @Thorvas That's a consequence of having an `sf::Texture` object as a non-static data member: every instance of the containing class you instantiate does create an `sf::Texture` object. – JFMR Jul 31 '20 at 13:06
  • So you suggest to make `sf::Texture` static? – Thorvas Jul 31 '20 at 13:07
  • 1
    @Thorvas That depends on what you want. If you declare it as `static`, then all `Base` and `Derived` objects will be sharing the same `sf::Texture` object. – JFMR Jul 31 '20 at 13:09
  • That is exactly what I want to do - make several `sf::Texture` objects, initialize them and allow derived classes to use them with their sprites. – Thorvas Jul 31 '20 at 13:10
  • But there is one problem - after making `sf::Texture` static, my compiler says that `static` is an `unresolved external symbol`. After searching for the solution, I realized that I have to define that variable. How may I define this field? – Thorvas Jul 31 '20 at 13:14
  • 1
    @Thorvas In that case, I would suggest you use a *texture manager* instead. Look at [this answer](https://stackoverflow.com/a/62889840/8012646). Then, you pass the texture manager by reference to the constructor. – JFMR Jul 31 '20 at 13:15
  • 1
    @Thorvas The error is probably because you didn't define the storage for the static member. Please, have a look at [this post](https://stackoverflow.com/questions/11300652/static-data-member-initialization). – JFMR Jul 31 '20 at 13:17
  • I've made `texture` member static, but program still runs slowly. Even if I make just one `sf::Texture` object for all objects containing it, then still `Base` constructor will use `loadFromFile()` function multiple times. I'm using State Pattern, so everytime I change the `State`, I create a new `Derived` object, and so, I'm calling `Base` constructor. – Thorvas Jul 31 '20 at 13:32
  • 1
    @Thorvas Yes, if you call `loadFromFile()` every time inside the constructor's body, then you are loading the texture's file every time the constructor is called. To avoid that, you could create another static data member, e.g., `static bool isTextureLoaded` to indicate whether or not the texture has already been loaded. That is, inside the constructor's body, instead of directly calling `loadFromFIle()`, you do the following: `if (!isTextureLoaded) { texture.loadFromFile(...); isTextureLoaded = true; }`. This way, the file for the texture is loaded only once. – JFMR Jul 31 '20 at 14:13
  • 1
    Thank you so much, everything works perfectly! I thought earlier about bool but I was afraid that it would be too simple solution. You helped me a lot! – Thorvas Jul 31 '20 at 18:44
  • 1
    @Thorvas Just one last comment: if different threads are going to construct these objects (i.e., `Base` or `Derived`), then you may want to consider [`std::call_once()`](https://en.cppreference.com/w/cpp/thread/call_once) to initialize the static `sf::Texture` data member by calling its member function `loadFromFile()` in a thread-safe way. You can do some research here in StackOverflow about `std::call_once()` – out of curiosity, I asked (and answered) a [post](https://stackoverflow.com/questions/56099159/initialising-with-stdcall-once-in-a-multithreading-environment) on that. – JFMR Jul 31 '20 at 20:50