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

Author Topic: sf::Thread (Loading Demo)  (Read 4972 times)

0 Members and 2 Guests are viewing this topic.

SFMLNewGuy

  • Jr. Member
  • **
  • Posts: 65
    • View Profile
sf::Thread (Loading Demo)
« on: May 21, 2020, 06:44:15 pm »
Hello,

So I'm messing with another concept, threads. I was trying to follow the SFML tutorial on threads, but it's not doing what I thought it would. I have no experience dealing with threads and just know roughly what Mutex and a Thread are.

I have this demo of a 500x500 tilemap of rectangles. And my original concept was finding ways to do tile culling. I solved, but I still get the white window until it loads everything up, then it runs perfectly. So I thought this would be a good time to try out threads and see if I can display a "Loading" sf::Text until it has gathered all the sprites up.

Also, I tried to use std::bind when initializing the thread, but it doesn't work because it is a deleted function. I'm using msvc19. But I got it to work with a lambda, but the results were the same. I put a comment giving the error when I try the std::bind and when I use the lambda. I

Quote
/*Failed to activate the window's context Failed to activate OpenGL context: The requested resource is in use.*/

So I'm assuming it obviously is trying to access data that's being used? ...Right ???? Any help would be appreciated, have a nice day!

#include <SFML/Graphics.hpp>
#include <iostream>
#include <functional>

sf::Vector2u WINDOW_SIZE{1400,900};
constexpr unsigned TPS = 60; //ticks per seconds
const sf::Time     timePerUpdate = sf::seconds(1.0f / float(TPS));
sf::Vector2f TILE_SIZE{64.f,64.f};

struct Tile
{
        sf::RectangleShape spr;
};


void loadingText(sf::RenderWindow& window, sf::Font& font, const std::string& text) {
        sf::Text loadText{ text,font,25 };
        loadText.setPosition((float)window.getSize().x / 2.f, (float)window.getSize().y / 2.f);
        loadText.setFillColor(sf::Color{ 88,225,0 });
        loadText.setOutlineThickness(1.f);
        loadText.setStyle(sf::Text::Style::Underlined | sf::Text::Style::Bold);
        window.draw(loadText);
}


int main() {
        sf::RenderWindow window{ sf::VideoMode{WINDOW_SIZE.x,WINDOW_SIZE.y},"" };
        //      window.setFramerateLimit(60);
        window.setPosition(sf::Vector2i{ window.getPosition().x,0 });

        bool lostFocus = false;
        sf::Clock clock;
        auto lastTime = sf::Time::Zero;
        auto lag = sf::Time::Zero;
        auto dt = 0.f;
        unsigned ticks = 0;
        auto view = window.getDefaultView();

        sf::Font font;
        if (!font.loadFromFile("fonts/arial.ttf"))
                std::cout << "Failed to load arial.ttf\n";


        sf::Thread thread([&]() {
                loadingText(window, font, "Loading");
                });
        /*Failed to activate the window's context Failed to activate OpenGL context: The requested resource is in use.*/
        thread.launch();

//***sf::Thread thread(std::bind(&loadingText, window, font, "Loading...")); *** (Doesn't Work)

/*Severity      Code    Description     Project File    Line    Suppression State
Error   C2280   'std::_Binder<std::_Unforced,void (__cdecl *)(sf::RenderWindow &,sf::Font &,
const std::string &),
sf::RenderWindow &,sf::Font &,
const char (&)[11]>::_Binder(const std::_Binder<std::_Unforced,void (__cdecl *)(sf::RenderWindow &,
sf::Font &,
const std::string &),
sf::RenderWindow &,sf::Font &,
const char (&)[11]> &)'
: attempting to reference a deleted function    E:\Libaries\SFML\include\SFML\System\Thread.inl 70
*/


        constexpr int Map_Width = 500;
        constexpr int Map_Height = 500;
        std::vector<Tile> tiles;

        sf::RectangleShape camera{ TILE_SIZE };
        camera.setFillColor({ 255,255,255,100 });
        camera.setOutlineThickness(1.f);
        camera.setOutlineColor(sf::Color::White);

        for(auto y = 0; y < Map_Height; ++y)
                for (auto x = 0; x < Map_Width; ++x) {

                        auto tile = new Tile;
                        tile->spr.setSize(TILE_SIZE);
                        tile->spr.setFillColor({ 0,255,0,15 });
                        tile->spr.setOutlineColor(sf::Color::White);
                        tile->spr.setOutlineThickness(1.f);
                        tile->spr.setPosition(x * TILE_SIZE.x, y * TILE_SIZE.y);

                        if (y >= 240 && y <= 260 && x >= 240 && y <= 260)
                                tile->spr.setFillColor({ 200,25,22 });

                        tiles.emplace_back(*tile);
                }

        view.setCenter(Map_Width / 2.f * TILE_SIZE.x, Map_Height / 2.f * TILE_SIZE.y);
        while (window.isOpen()) {

        auto time = clock.getElapsedTime();
        auto elapsed = time - lastTime;
        lastTime = time;
        lag += elapsed;

        //Fixed time update
        while (lag >= timePerUpdate)
        {
            ticks++;
            lag -= timePerUpdate;
            //x fixedUpdate(elapsed);
        }

                        sf::Event event{};
                        while (window.pollEvent(event)) {
                                if (event.type == sf::Event::Closed ||
                                    event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Escape)
                                        window.close();
                                if (event.type == sf::Event::KeyPressed) {
                                        switch (event.key.code) {
                                                case sf::Keyboard::Enter: std::cout << "Enter Pressed\n"; break;
                                                case sf::Keyboard::Space: std::cout << "Space Pressed\n"; break;
                                                default: break;
                                        }
                                }
                                if (event.type == sf::Event::Resized) {
                                        // update the view to the new size of the window
                                        view.setSize(static_cast<float>(event.size.width),
                                                           static_cast<float>(event.size.height));
                                }
                                if (event.type == sf::Event::LostFocus)     { lostFocus = true; }
                                if (event.type == sf::Event::GainedFocus) { lostFocus = false; }
                        }

                        if (!lostFocus) {
                                auto mousePos = sf::Mouse::getPosition(window);
                                auto mouseWorldPos = window.mapPixelToCoords(mousePos, view);

                                window.setTitle("Mouse Position: (" + std::to_string(int(mouseWorldPos.x / 64.f)) + ", " +
                                        std::to_string(int(mouseWorldPos.y / 64.f)) + ")");

                                if (sf::Mouse::isButtonPressed(sf::Mouse::Left)) {
                                        if (mouseWorldPos.x >= 0 && mouseWorldPos.y >= 0 &&
                                                mouseWorldPos.x < WINDOW_SIZE.x &&
                                                mouseWorldPos.y < WINDOW_SIZE.y) {
                                        }
                                }

                                //! ** INPUT SECTION **
                                if (sf::Keyboard::isKeyPressed(sf::Keyboard::W)) {
                                        view.move(0, -100 * elapsed.asSeconds());
                                }
                                if (sf::Keyboard::isKeyPressed(sf::Keyboard::A)) {
                                        view.move( -100 * elapsed.asSeconds(),0);

                                }
                                if (sf::Keyboard::isKeyPressed(sf::Keyboard::S)) {
                                        view.move(0, 100 * elapsed.asSeconds());

                                }
                                if (sf::Keyboard::isKeyPressed(sf::Keyboard::D)) {
                                        view.move(100 * elapsed.asSeconds(), 0);

                                }

                                //! ** UPDATE SECTION**                
                                window.setView(view);
                        }

               
                        //! ** DRAW SECTION **
                        window.clear();

                        camera.setPosition(view.getCenter());

                        int cam_posX = camera.getPosition().x;
                        int cam_posY = camera.getPosition().y;
                        cam_posX /= TILE_SIZE.x;
                        cam_posY /= TILE_SIZE.y;
                        int minMaxWidth = 12;
                        int minMaxHeight = 12;

                        //std::cout << "Camera: " << camera.getPosition().x << ", " << camera.getPosition().y << "\n";
                        //std::cout << "View: " << view.getCenter().x << ", " << view.getCenter().y << "\n";
                        //std::cout << "posX: " << posX << "\n";
                        //std::cout << "posY: " << posY << "\n";
                        for (auto y = cam_posY - minMaxHeight < 0 ? 0 : cam_posY - minMaxHeight;
                                      y <= cam_posY + minMaxHeight && y < WINDOW_SIZE.y; y++) {
                                for (auto x = cam_posX - minMaxWidth < 0 ? 0 : cam_posX - minMaxWidth;
                                              x <= cam_posX + minMaxWidth && x < WINDOW_SIZE.x; x++) {
                                        window.draw(tiles[y * Map_Width + x].spr);
                                }
                        }
                        window.draw(camera);

                        window.setView(window.getDefaultView());               
                        window.display();
                }
       
        return EXIT_SUCCESS;
}




Athenian Hoplite

  • Newbie
  • *
  • Posts: 19
  • Nenikikamen !
    • View Profile
Re: sf::Thread (Loading Demo)
« Reply #1 on: May 21, 2020, 07:19:48 pm »
First off you should really be using std::thread or std::async. No reason to use SFML's implementation since C++11.

Other than that there are a few problems even discarding the error you get. First off always better to pass specifically what you wish to use in the lambda's capture (window& etc) instead of global reference (&).

The function you called in the thread should be the function that is doing all the work (creating the rectangles) and not the one drawing text on screen. Why create a thread for something so trivial as creating a text instance and calling draw on it? Further if you call draw on the text in the thread's function then after you call window.clear() in your update loop then you have to call draw on the text again.

What you want is for text to appear as the shape creation is being done. So you create shapes on a separate thread, while the main thread continues its execution and draws that text on screen. When creation is done the main thread removes loading text and goes on with its business.

Minimal example:
    // Atomic variable guarantes that only one thread messes with it at a time
    std::atomic<bool> done(false);

    // Create shapes on another thread
    std::thread t([&done] {
        // DOING HEAVY DUTY WORK HERE
        done = true; // Set variable in the end to signal that its done
    });

/* ......... */

   // Meanwhile in the game's update loop somewhere

   if (done)
   {
       // Remove loading text
       // Draw tiles with culling
   }
   else
   {
      window.draw(loadingText);
   }

/* ......... */

 

This is just one way (a very simple and bad way) to do this. Really this should be done using std::promise. Threads are a very interesting but also very difficult topic that brings it's own very wide set of challenges. If you want to learn more about threads (which I encourage you to do because it's a very interesting topic) you should really be doing that rather than learning about them with the use of SFML.

On a further note for culling in a 2D environment you are much better off using a quad tree in which you place your drawable entities as a more general approach.
« Last Edit: May 21, 2020, 07:58:26 pm by Athenian Hoplite »
"By will of the Athenian People be it resolved:
If anyone rises up against the people for tyranny or join in establishing the tyranny or overthrow the People of the Athenians and the democracy in Athens, whoever kills him who does these things shall be blameless." - Athenian Law 337 BC

SFMLNewGuy

  • Jr. Member
  • **
  • Posts: 65
    • View Profile
Re: sf::Thread (Loading Demo)
« Reply #2 on: May 23, 2020, 10:24:37 pm »
Hello,

I appreciate the message and I'll give it a shot later on tonight. I just wanted to thank you real quick.

SFMLNewGuy

  • Jr. Member
  • **
  • Posts: 65
    • View Profile
Re: sf::Thread (Loading Demo)
« Reply #3 on: May 24, 2020, 11:38:30 am »


Awesome, so I got it to work. But I'm wondering if this is actually okay to do? Keep in mind this is all just a boilerplate concept example, so I'm not striving for efficiency. But I had to make a little draw function and call it at the end of each tile emplace_back to the "Tile" vector. Because where it is freezing up, I want the "Loading..." text since it's technically loading the tiles. If I do it down by the draw method, it will never be shown because it's only hanging up as the tiles load. Note*By hanging up I mean turns completely white, almost like a 'stop responding' visual and then when it gets to draw works out great*

I hope that makes sense?

void draw(sf::RenderWindow& window, sf::Text& text) {
        window.clear();
        window.setView(window.getDefaultView());
        window.draw(text);
        window.display();
}

// Atomic variable guarantes that only one thread messes with it at a time
        std::atomic<bool> done(false);
        // Create shapes on another thread
        std::thread t([&done] {
                // DOING HEAVY DUTY WORK HERE
                done = true; // Set variable in the end to signal that its done
                });

for(auto y = 0; y < Map_Height; ++y)
                for (auto x = 0; x < Map_Width; ++x) {
                        //std::unique_ptr<Tile> tile(new Tile());
                        std::shared_ptr<Tile> tile(new Tile());
                        tile->spr.setSize(TILE_SIZE);
                        tile->spr.setFillColor({ 0,255,0,15 });
                        tile->spr.setOutlineColor(sf::Color::White);
                        tile->spr.setOutlineThickness(1.f);
                        tile->spr.setPosition(x * TILE_SIZE.x, y * TILE_SIZE.y);
                        tile->spr.setOrigin(TILE_SIZE.x / 2.f, TILE_SIZE.y / 2.f);

                        if (y >= 240 && y <= 260 && x >= 240 && x <= 260)
                                tile->spr.setFillColor({ 200,25,22 });
                        tiles.emplace_back(std::move(tile));

                        if (!done)    //******** DRAWING LOADING HERE *******
                                draw(window, loadText);
                }
                       while(window.open()){ ....}
 

So I'm just asking if this is actually okay to do and how could you prevent drawing out of the game loop? From everything I've learned this just seems out of place and this could be why I've struggled in the past. How would you improve on this if I was going for efficiency and correctness? This has already taught me so much because I've always had a really hard time understanding Loading bar concepts. So this simple example is very informative. Thanks!

EDIT: I tried to make the text flicker, and it's not doing what I intended so I'll wait for your response because I'm for sure doing something wrong. Thanks
« Last Edit: May 24, 2020, 11:40:54 am by SFMLNewGuy »

Athenian Hoplite

  • Newbie
  • *
  • Posts: 19
  • Nenikikamen !
    • View Profile
Re: sf::Thread (Loading Demo)
« Reply #4 on: May 24, 2020, 12:38:25 pm »
When creating the thread you are suposed to assign it a function to do work. What you posted here creates a thread that simply changes the value of "done" to true. My comment "// DOING HEAVY DUTY WORK HERE" actually was meant to be replaced by the work you wanted done there, meaning tile creation code.

The separate thread (meaning separate from the main thread), will build the tiles and when its done it will set "done" to true.
In the update loop while "done" is still false (meaning the separate thread has still not finished building the tiles since in the end it sets "done" to true) you draw the text. Your update loop keeps going checking every time time it loops if "done" is true. When the thread finishes your update loop will detect that "done" is now true and will draw the tiles instead.

Remember that you will need to reassign the sf::View you normally use because while "done" is false you are setting the default view to draw the text.

Note: Since you are creating the tiles on a separate thread with smart pointers they will go out of scope when the thread function finishes due to RAII. You need to actually allocate the memory for the tiles outside the thread function or move the created memory to a variable in a scope outside the thread to do this the vector itself would need to be wrapped in a smart pointer.

Example with memory allocation in main thead.

// Still in main thread
std::vector<std::shared_ptr<Tile>> tilesVector;
tilesVector.reserve(Map_Height * Map_Width); // This creates the tiles with default constructor ( Tile() )

std::atomic<bool> done(false);

// Create thread to execute the bellow code while main thread continues normal execution
std::thread t([&done, &tilesVector] {
// Tile creation IS the heavy duty work
for(auto y = 0; y < Map_Height; ++y)
{
    for (auto x = 0; x < Map_Width; ++x)
    {
       Tile * tile = tilesVector[y * Map_Width + x].get(); // Get a pointer to tile to change its attributes
       tile->spr.setSize(TILE_SIZE);
       tile->spr.setFillColor({ 0,255,0,15 });
       tile->spr.setOutlineColor(sf::Color::White);
       tile->spr.setOutlineThickness(1.f);
       tile->spr.setPosition(x * TILE_SIZE.x, y * TILE_SIZE.y);
       tile->spr.setOrigin(TILE_SIZE.x / 2.f, TILE_SIZE.y / 2.f);

       if (y >= 240 && y <= 260 && x >= 240 && x <= 260)
       {
             tile->spr.setFillColor({ 200,25,22 });
       }
                               
       // tiles.emplace_back(std::move(tile)); This line no longer needed, we already have our tile vector
    }
}

// After building the tiles we set variable to true to signal that our work
// (tile creation) on this separate thread is done.
done = true;

});

/* Meanwhile main thread continued to execute and do its thing */

/* ... Update loop ... */

while(window.open())
{
    /*
    ..... Any other stuff you do on the update loop here ....
    */


   window.clear();

   if ( ! done) // If the thread has not finished it's work yet
   {
      window.setView(window.getDefaultView());
      window.draw(text);
   }
   else
   {
       // Here tile drawing code
   }
   
   window.display();
}

 
« Last Edit: May 24, 2020, 12:53:45 pm by Athenian Hoplite »
"By will of the Athenian People be it resolved:
If anyone rises up against the people for tyranny or join in establishing the tyranny or overthrow the People of the Athenians and the democracy in Athens, whoever kills him who does these things shall be blameless." - Athenian Law 337 BC

SFMLNewGuy

  • Jr. Member
  • **
  • Posts: 65
    • View Profile
Re: sf::Thread (Loading Demo)
« Reply #5 on: May 25, 2020, 07:28:35 am »
Got it, I feel so stupid. My comment text is very transparent and I skipped over that obvious part about "HEAVY WORK". It's all working! Thank you very much!

EDIT: I wanted to add that it all works, but that way of initializing the tiles doesn't work for me. I don't know if it has to do with MSVC or what. But it's NULL (the Tile when grabbing at 0,0), so I've always just emplaced or pushed back at the end because I've seen someone do that as well.
« Last Edit: May 25, 2020, 08:39:03 am by SFMLNewGuy »