GUI und Processing in einem Thread möglich?



  • Hallo in die Runde,

    ich bin hier im Forum neu und hoffe mal, dass ich hier einigermaßen den richtigen Themenbereich getroffen habe. In der Suche und auch allgemein bei Google habe ich leider nicht das gefunden, was ich gesucht habe. Daher hier jetzt einen neue Diskussion.

    Es geht generell um ein Design-Problem zu dem ich gerne einmal eure Meinung hören würde.

    Ich versuche gerade ein relativ simple "GameEngine" für pixelbasierte ganz einfache Spiele auf einer LED-Matrix zu schreiben. Das Ding soll es dann möglich machen relativ einfach Objekte mit einem definierten Aussehen durch die Matrix zu bewegen, zu verändern, kollidieren zu lassen und so weiter.
    Grundsätzlich gibt es dann für die LED-Matrix eine "Output-Driver" Klasse, die Farbdaten aus meiner Matrix auf der LED-Matrix darstellt.

    Vereinfacht läuft das Spiel dann wie folgt ab:

    int main()
    {
        GameEnging g;
        g.init(64,32);
        g.run();
    }
    
    void GameEngine::run() {
        while (gameNotFinished()) {
            readInputs();
            recalc();
            repaint();
        }
    }
    

    Aus main heraus wird eine Dauerschleife gestartet, hier werden nacheinander jeweils die Eingaben des Spielers eingelesen. Dann die Veränderungen im Spiel berechnet und am Ende wird der Output-Driver zum Aktualisieren seiner Pixel aufgerufen.

    Momentan habe ich die Output-Driver Klasse mit dem Strategy Design-Pattern so umgesetzt, dass man relativ einfach neue Output-Driver für andere Ausgabe-Geräte schreiben kann. In meinem Fall habe ich das zum Beispiel für eine einfache GUI gemacht, die mir die Pixel in einem QTWidget QMainWindow darstellt. So kann ich dann direkt auf meinem Notebook schon testen wie das ganze aussieht bevor ich es auf die LED-Matrix übertrage.
    Außerdem ist es so auch möglich, dass die Ausgabe auch auf verschiedenen Ausgabe-Geräten gleichzeitig dargestellt werden kann.

    Jetzt komme ich zu meinem Problem:
    Ich würde gern ein Konstrukt nutzen, wo meine GUI gestartet wird und dann periodisch von der repaint-Funktion wieder aktualisiert wird. Leider hab ich aber das Problem, dass mit der QT-GUI durch den Aufruf von QApplication.exec() dauerhaft mein main-thread blockiert wird.
    Die Funktion a.exec() returned nämlich leider erst sobald die GUI wieder geschlossen wird.

    #include "mainwindow.h"
    #include <QApplication>
    
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
        MainWindow w;
        w.show();
        a.exec(); // hier nach geht es erst weiter, wenn das Fenster wieder geschlossen ist
    }
    

    In meinem Fall bedeutet das dann, dass ich entweder die Programm-Logik meines Spiels oder den Aufruf der GUI in einen separaten thread schieben müsste. Das führt allerdings direkt zum nächsten Problem.
    Erstens möchte ich mir zwar generell gerne die Möglichkeit offenhalten die unterschiedlichen Programmteile mal in unterschiedliche Threads zu verschieben, generell sollte es aber auch zumindest möglich sein alles nur in einem Thread ausführen zu können, falls das Ganze mal auf einem einfachen µController laufen soll. Zweitens, selbst wenn ich für die GUI tatsächlich einen zweiten Thread nutze, scheint es so zu sein, als wenn QApplications immer nur im main-thread laufen könnten?
    Das finde ich persönlich ziemlich ungünstig, weil dann die GUI zentraler Bestandteil des Projekts ist und nicht mehr einfach (zum Beispiel für einen µController) deaktiviert werden kann.

    Jetzt zu meiner Frage:
    Ich habe leider mit dem Thema Multi-Threading noch nicht so wirklich Erfahrung sammeln können und frage mich jetzt, wie ich hier am geschicktesten weiter mache? Komme ich beim Einsetzten einer GUI um das Thema mehrere Threads herum? Und da dies mit QT offenbar nicht möglich ist, kennt ihr eine GUI-Umgebung mit der so etwas möglich ist?
    Mir ist übrigens bewusst, dass wenn alles in einem Thread läuft das Fenster solange wie "readInputs()" und "recalc()" ausgeführt werden steht und keine Interaktion ermöglicht. Das ist für mich aber im Grunde kein Problem, da es über keine Buttons oder ähnliches verfügt, sondern einzig über eine pixelige Grafik die regelmäßig aktualisiert werden soll.

    Ich hoffe ich habe mein Anliegen einigermaßen verständlich erklärt.
    Falls dem nicht so ist, liefere ich gerne weitere Antworten nach. In diesem Fall bitte einfach fragen 🙂

    Vielen Dank schon mal im Voraus für eure Hilfe.

    Viele Grüße
    mezorian



  • exec startet die Eventloop. Du hast neben Threads zwei Möglichkeiten:

    1. du verwirfst deine Endlosschleife und hängst deine Funktionen in die Eventloop
    2. du startest die Eventloop nicht. Stattdessen musst du die Events mit processEvents in deiner Schleife selber abarbeiten


  • @manni66

    Hallo 🙂 vielen vielen lieben Dank für deine Antwort 🙂
    Ich brauchte zwar ein paar Tage bis ich deine Idee ausprobieren konnte aber jetzt funktioniert es genauso wie ich mir das vorgestellt habe.
    Vielen Dank, du bist mein Held!

    Also für alle nochmal zum mitschreiben, das a.exec kann gestrichen werden, wenn man sich selbst darum kümmert die events der GUI abzuarbeiten.

    Hieraus

    #include "mainwindow.h"
    #include <QApplication>
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
        MainWindow w;
        w.show();
    
        return a.exec();
    }
    

    wird dann quasi das hier:

    #include "mainwindow.h"
    #include <QApplication>
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
        MainWindow w;
        w.show();
    
        //return a.exec();
        while (true) {
            a.processEvents();
        }
    }
    

    Und alternativ könnte man das Ganze sogar in eine Klasse auslagern, so wie ich es mir eigentlich gewünscht habe.
    Quick and dirty sieht das dann so aus:

    #ifndef TEST_H
    #define TEST_H
    
    #include "mainwindow.h"
    #include <QApplication>
    
    class test
    {
    public:
        test() {};
        void doSmth(int argc, char *argv[]) {
            QApplication a(argc, argv);
            MainWindow w;
             w.show();
             //return a.exec();
    
             while (true) {
                 // do smth.
                 a.processEvents();
             }
    
        }
    };
    
    #endif // TEST_H
    
    #include "test.h"
    
    int main(int argc, char *argv[]) {
        test t;
        t.doSmth(argc,argv);
    }
    

Log in to reply