Bienvenue, Invité. Merci de vous connecter ou de vous inscrire.
Avez-vous perdu votre e-mail d'activation ?

Auteur Sujet: Threads et SFML ?  (Lu 2551 fois)

0 Membres et 1 Invité sur ce sujet

MyPix

  • Full Member
  • ***
  • Messages: 117
    • Voir le profil
    • E-mail
Threads et SFML ?
« le: Juillet 05, 2017, 11:49:36 am »
Bonjour !

J'aimerais écrire un moteur de jeu tile-based pour la SFML, en utilisant cette architecture, centrée sur un "Message Bus". http://www.gamasutra.com/blogs/MichaelKissner/20151027/257369/Writing_a_Game_Engine_from_Scratch__Part_1_Messaging.php

Pour implémenter cela, j'aurais besoin d'une fonction "tick" appelée plusieurs fois par secondes, qui sera utilisé pour "rafraichir" les systèmes. (Leur permettre de lire les messages et d'en poster des nouveaux)

Cette fonction doit tourner le plus de fois possible chaque seconde, indépendamment des FPS. (Pour éviter que le jeu ne soit plus "réactif" sur un écran 144Hz que sur un écran 60hz (VSync activée))

J'ai donc essayé quelque chose : faire 2 Threads, un pour le rendering et un pour le "tick", avec ce code ci : (Je fais des tests pour le moment (pour planifier tout correctement avant de me lancer dans le développement), bien sur ce n'est pas le code du moteur ^^)
#include <SFML/Graphics.hpp>
#include <iostream>
#include "Screen.h"
#include <thread>

using namespace std;

void drawingThread(sf::RenderWindow *window)
{
        while (window->isOpen())
        {
                sf::Event event;
                while (window->pollEvent(event))
                {
                        if (event.type == sf::Event::Closed)
                                window->close();
                }
                window->clear(sf::Color::Black);
                window->display();
        }
}

void tick(const sf::RenderWindow *rw)
{
        while(rw->isOpen())
                cout << "Tick!" << endl;
}
int main()
{
        // create the window
        sf::RenderWindow window(sf::VideoMode(800, 600), "My window");
        thread t1(drawingThread, &window);
        thread t2(tick,&window);

        t1.join();
        t2.join();
        cin.get();
        return 0;
}

Cela ne marche pas, j'ai une erreur "Failed to Activate the window context" ou quelque chose comme cela.

J'essaie donc autre chose :Garder le rendering dans le thread principal, et décaler le "tick" dans un autre thread.

#include <SFML/Graphics.hpp>
#include <iostream>
#include "Screen.h"
#include <thread>

using namespace std;


void tick(const sf::RenderWindow *rw)
{
        while(rw->isOpen())
                cout << "Tick!" << endl;
}
int main()
{
        // create the window
        sf::RenderWindow window(sf::VideoMode(800, 600), "My window");
        thread t2(tick,&window);

        t2.join();
        while (window.isOpen())
        {
                sf::Event event;
                while (window.pollEvent(event))
                {
                        if (event.type == sf::Event::Closed)
                                window.close();
                }
                window.clear(sf::Color::Black);
                window.display();
        }
        cin.get();
        return 0;
}
 

Cela ne marches toujours pas ! Ma fenêtre reste blanche, inanimée..


Je pense que cela est surtout dû à une incompréhension des threads de ma part (et peut être aussi une mauvaise utilisation des pointeurs?) -> le pointeur n'agis pas comme je le pense, et "bloque" l'instance de RenderWindow, mais comment puis-je résoudre cela ? Quelle est la bonne "méthode" à adopter ?

Egalement, j'en profite pour poser une question sur les threads en général. Dans ma classe "MessageBus" j'aurais, probablement, des pointeurs vers les différent systèmes. Et, depuis le thread "tick" je devrais appeler une fonction "refresh" (le tick) dans la classe MessageBus, j'aurais donc un code ressemblant à cela :
#include <vector>
using namespace std;
class GESystem
{
        // La Classe "modèle" pour chaque système, qui permet de faire du polymorphisme.
        //...
        public:
                virtual void tick() = 0;
};
class MessageBus
{
        public:
                vector<GESystem *> sys;
                void refresh()
                {
                        for(int k(0);k<sys.size();k++)
                                sys[k]->tick();
                }
                // Note : Dans cette fonction tous les modules sont rafraichis, sauf le module "Render", qui lui est rafraîchi seulement quand on a besoin d'afficher quelque chose à l'écran (faire le rendu !)
};

// Plus loin dans le code
void tickthread(MessageBus *mb)
{
        while(window.isOpen())
                mb->refresh();
}


(Avec tickthread qui sera la fonction utilisée par le Thread pour appeler la fonction "refresh" sur l'instance de MessageBus (utilisée par le moteur de jeu) le plus de fois possible chaque seconde.)

Cette structure est-elle correcte, et marchera t-elle avec les threads ? Je ne connais pas bien les threads, donc je ne sais pas si cela va poser problème ou pas. Si cela pose problème, comment avoir une architecture saine avec ce genre de prérequis ? (Nécessite d'appeler une fonction sur une instance de MessageBus du thread principal)

D'une manière plus générale : Cela pose problème d'appeler une méthode d'une instance d'une classe située dans un autre thread (On accède à l'instance via un pointeur) ?
(Mon dieu, j'espère que j'explique bien la situation ici xD)


Pour finir, j'ai l'impression que cette architecture est peu adaptée à cette situation, dois-je plutôt essayer de revoir toute mon architecture plutôt que de résoudre ces problèmes ? (Si vous avez d'autres exemples d'architecture, je suis preneur ^^)

Ou alors, méthode simple : Je limite le jeu à, par exemple, 60FPS peu importe la situation, comme cela je fait tout dans la boucle principale. (Note : je n'aime pas cette solution, car cela implique de lier toute la logique du moteur de jeu aux FPS et je ne trouve pas cela "sain", surtout en ce qui concernera les animations..)

EDIT : Dernière question. Est-ce grave si le jeu est plus "réactif" car il tourne à 144hz et pas 60hz ? Je veux dire, est-ce normal ? Les autres moteurs de jeu agissent aussi comme cela ? (La GameLoop est liée aux FPS ?)

Mes explications sont probablement peu claires, je reste donc à votre disposition pour toute question.

Merci :)
« Modifié: Juillet 05, 2017, 11:57:04 am par MyPix »

Laurent

  • Administrator
  • Hero Member
  • *****
  • Messages: 32498
    • Voir le profil
    • SFML's website
    • E-mail
Re: Threads et SFML ?
« Réponse #1 le: Juillet 05, 2017, 04:18:38 pm »
Le problème est que tu bloque immédiatement ton thread principal pour attendre la fin du thread secondaire (t2.join()). Donc en pratique ton thread n'en est pas un, son exécution n'est pas parallèle. Le concept du thread c'est qu'il s'exécute en parallèle pendant que ton thread principal continue à faire autre chose, pas que ce dernier attende le thread secondaire à rien faire. Donc enlève simplement le t2.join() et ça marchera mieux.
Laurent Gomila - SFML developer

MyPix

  • Full Member
  • ***
  • Messages: 117
    • Voir le profil
    • E-mail
Re: Threads et SFML ?
« Réponse #2 le: Juillet 05, 2017, 04:48:02 pm »
Ooh, erreur de ma part ! Donc, j'ai ce code ci :
#include <SFML/Graphics.hpp>
#include <iostream>
#include "Screen.h"
#include <thread>

using namespace std;

void drawingThread(sf::RenderWindow *window)
{
        while (window->isOpen())
        {
                sf::Event event;
                while (window->pollEvent(event))
                {
                        if (event.type == sf::Event::Closed)
                                window->close();
                }
                window->clear(sf::Color::Black);
                window->display();
        }
}

void tick(const sf::RenderWindow *rw)
{
        while (rw->isOpen())
        {
                cout << "Tick!" << endl;
        }
}
int main()
{
        // create the window
        sf::RenderWindow *window = new sf::RenderWindow(sf::VideoMode(800, 600), "My window");
        thread t1(drawingThread, window);
        thread t2(tick, window);
        t1.join();
        cin.get();
        return 0;
}

Mais j'ai toujours l'erreur "failed to Activate the window context" :/

EDIT : Après avoir fouillé dans les forum, j'ai lu qu'il fallait désactiver la fenetre dans ce thread pour l'utiliser dans l'autre. J'ai donc ajouté window->setActive(false);apres la création de la fenetre et ça marche !

Par contre, pour ma question :
D'une manière plus générale : Cela pose problème d'appeler une méthode d'une instance d'une classe située dans un autre thread (On accède à l'instance via un pointeur) ?

C'est correct ou pas ? (Comme ici : Je crée une instance de Sf::RenderWindow dans le thread principal, et je l'utilise dans l'autre thread. Cela ne semble pas poser soucis à petite échelle, mais il y a t-il des choses dont je doit me méfier ? (Au niveau de l'accès aux variables par exemple?)

« Modifié: Juillet 05, 2017, 04:52:06 pm par MyPix »

Laurent

  • Administrator
  • Hero Member
  • *****
  • Messages: 32498
    • Voir le profil
    • SFML's website
    • E-mail
Re: Threads et SFML ?
« Réponse #3 le: Juillet 05, 2017, 05:36:14 pm »
Il y a plein de choses desquelles se méfier avec la programmation multi-thread, qui s'apprennent avec des articles / tutoriels, pas sur les forums ;)
Laurent Gomila - SFML developer

MyPix

  • Full Member
  • ***
  • Messages: 117
    • Voir le profil
    • E-mail
Re: Threads et SFML ?
« Réponse #4 le: Juillet 05, 2017, 06:31:28 pm »
J'essaierais de prendre un livre sur le sujet quand je voudrais approfondir. Pour le moment, je vais tenter à ma manière, si je remarque que c'est foireux/pas stable, je lirais un peu sur le sujet :p

 

anything