Название: Зависание ui при обработке нажатия кнопки. Многопоточность.
Отправлено: Barik от Декабря 21, 2020, 17:08
Добрый вечер, я начал работать с qt недавно по решению нашей команды и не совсем могу понять как мне решить данную проблему. У меня есть некоторый игровой клиент, в котором после введения имени игрока и нажатия на кнопку старта, происходит подключение к socket серверу и получения с него данных (информация о игроке, карта и т.д.). void MainWindow::on_startButton_clicked() { if (ui->userNameForm->toPlainText() != "") { ui->startMenu->hide(); ui->graphview->show();
qDebug() << "clicked";
game_ = new Game(); // создание новой игры
QString userName = ui->userNameForm->toPlainText(); // чтение ника с формы
game_->connectToServer();
game_->login(userName); // отправка имени пользователя и создание game_->player()
game_->getMap(); // получение карты с сервера (3 слоя)
game_->makeMap(); // формирование карты из 3 слоёв в 1 -> game_->map()
this->setMap(game_->map()); // отрисовка в ui this->update();
// отображение инфы о пользователе, полученной с сервера ui->userName->setText("Name: " + game_->player().name()); ui->userTown->setText("City: " + game_->player().town().name()); ui->userPopulation->setText("Population: " + QString::number(game_->player().town().population()) + " / " + QString::number(game_->player().town().populationCapacity())); ui->userProducts->setText("Products: " + QString::number(game_->player().town().product()) + " / " + QString::number(game_->player().town().productCapacity()));
ui->userSomething->setText("Rating: " + QString::number(game_->player().rating()));
game_->gameCycle(); // логика игры (много обращений к серверу и различных махинаций с game_.player()) } } Моя проблема в том, что при выполнении game_->gameCycle(); UI зависает и не отображает карту, до того момента, как gameCylce() закончится, я понимаю, что проблема в том, что все эти функции выполняются в одном потоке, но как мне лучше решить эту проблему? Пробовал в многопоточность, но проблема в том, что я не понимаю что из этого мне надо выполнять в другом потоке. Пробовал все методы game_ выполнять в другом потоке ( как тут https://stackoverflow.com/questions/14051276/how-to-implement-frequent-start-stop-of-a-thread-qthread (https://stackoverflow.com/questions/14051276/how-to-implement-frequent-start-stop-of-a-thread-qthread)), но, тогда отображение информации о пользователе выполняется быстрее, чем получение информации с сервера и это крашит клиент. В общем не знаю что тут делать вообще... И может кто подскажет как лучше организовать структуру проекта, чтобы я мог обновлять состояние карты параллельно с логикой игры Все предложения по улучшению кода приветствуются Как я пробовал делать: MainWindow.h#ifndef MAINWINDOW_H #define MAINWINDOW_H
#include <QMainWindow> #include "Map.h" #include "Game.h" #include "QThread" #include "Worker.h"
QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE
class MainWindow : public QMainWindow { Q_OBJECT
public: explicit MainWindow(QWidget *parent = nullptr); ~MainWindow();
void setMap(std::shared_ptr<Map> m); void setGame(Game *game) { game_ = game; };
Game& game() { return *game_; }; signals: void startWork(Game *game); void stopWork();
private slots: void on_startButton_clicked(); void on_logoutButton_clicked();
private: Ui::MainWindow *ui; Game *game_; Worker *worker_; QThread *gameThread_; };
#endif // MAINWINDOW_H
MainWindow.cppMainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); ui->graphview->hide(); ui->postInfo->hide();
gameThread_ = new QThread; worker_ = new Worker; worker_->moveToThread(gameThread_);
connect(this, SIGNAL(startWork(Game*)), worker_, SLOT(StartWork(Game*))); connect(this, SIGNAL(stopWork()), worker_, SLOT(StopWork()));
// QTimer *timer = new QTimer(this);
// connect(timer, SIGNAL(timeout()), this, SLOT(on_startButton_clicked())); }
MainWindow::~MainWindow() { delete ui; }
void MainWindow::setMap(std::shared_ptr<Map> m) { ui->graphview->setMap(m, game_->player()); }
void MainWindow::on_startButton_clicked() { if (ui->userNameForm->toPlainText() != "") { ui->startMenu->hide(); ui->graphview->show();
qDebug() << "clicked";
game_ = new Game();
emit startWork(game_); gameThread_->start();
QString userName = ui->userNameForm->toPlainText();
// game_->connectToServer();
// game_->login(userName);
// game_->getMap();
// game_->makeMap();
// Thread *thread = new Thread(this, game_->map()); // thread->start();
this->setMap(game_->map()); this->update();
ui->userName->setText("Name: " + game_->player().name()); ui->userTown->setText("City: " + game_->player().town().name()); ui->userPopulation->setText("Population: " + QString::number(game_->player().town().population()) + " / " + QString::number(game_->player().town().populationCapacity())); ui->userProducts->setText("Products: " + QString::number(game_->player().town().product()) + " / " + QString::number(game_->player().town().productCapacity()));
ui->userSomething->setText("Rating: " + QString::number(game_->player().rating()));
// qDebug() << game_->map()->graph().idx(); // qDebug() << game_->map()->trains()[0].waysLength(); // qDebug() << game_->map()->graph().idx().at(game_->player().town().pointIdx()); // game_->gameCycle(); } }
void MainWindow::on_logoutButton_clicked() { emit stopWork(); // game_->disconnect(); ui->startMenu->show(); ui->graphview->hide(); }
Worker.h#ifndef WORKER_H #define WORKER_H
#include <QObject> //#include "Socket.h" #include "Game.h"
class Worker : public QObject { Q_OBJECT public: explicit Worker(QObject *parent = nullptr);
signals: void SignalToObj_mainThreadGUI(); void running(); void stopped();
public slots: void StopWork(); void StartWork(Game * game);
private slots: void doWork();
private: volatile bool isStopped, isRunning; // Socket *socket_; Game *game_; };
#endif // WORKER_H
Worket.cppWorker::Worker(QObject *parent) : QObject(parent), isStopped(false), isRunning(false) {}
void Worker::doWork() { if (!isRunning || isStopped) { return; }
game_->connectToServer();
game_->login("Sanya"); game_->getMap(); game_->makeMap();
qDebug() << "Worker started to work"; emit SignalToObj_mainThreadGUI(); // QMetaObject::invokeMethod(this, "doWork", Qt::QueuedConnection); }
void Worker::StopWork() { qDebug() << "Stop"; isStopped = true; isRunning = false; emit stopped(); }
void Worker::StartWork(Game *game) { game_ = game; qDebug() << "Start"; isStopped = false; isRunning = true; emit running(); doWork(); }
Game.cpp void Game::connectToServer() { qDebug() << "Connecting";
socket_ = new Socket(); this->socket_->connect(); }
void Game::login(QString userName) { qDebug() << "LogIn";
QJsonObject data; data["name"] = userName; this->socket_->sendData(Request(Action::LOGIN, data)); QJsonObject userData = socket_->getData(); qDebug() << userData;
player_ = new Player(userData); }
void Game::logout() { qDebug() << "LogOut";
this->socket_->close(); }
void Game::disconnect() { qDebug() << "Connection closed";
this->socket_->close(); }
void Game::tick() { this->socket_->sendData(Request(Action::TURN, QJsonObject())); socket_->getData(); }
void Game::getMap() { socket_->sendData(Request(Action::MAP, QJsonObject({{"layer", 0}}))); layer_0 = socket_->getData();
socket_->sendData(Request(Action::MAP, QJsonObject({{"layer", 1}}))); layer_1 = socket_->getData();
socket_->sendData(Request(Action::MAP, QJsonObject({{"layer", 10}}))); layer_2 = socket_->getData(); }
void Game::makeMap() { qDebug() << "Draw map"; map_ = std::make_shared<Map>(layer_0, layer_1, layer_2, *player_); map_->makeWays(); }
Название: Re: Зависание ui при обработке нажатия кнопки. Многопоточность.
Отправлено: ecspertiza от Декабря 25, 2020, 18:09
У вас прямой вызов gameCycle, он обрабатывается в основном потоке. Нужно более корректно реализовать запуск потока. И при старте треда начать обработку gameCycle.
Название: Re: Зависание ui при обработке нажатия кнопки. Многопоточность.
Отправлено: Kinley11 от Декабря 26, 2020, 15:24
Не увидел в вашей новой реализации вызова GetCycle, точнее этот вызов у вас закоменчен. А так не вызывайте значит отрисовку, до того момента, пока не получите сигнал о том, что GetCycle в другом потоке закончил свое выполнение.
Я смотрю вы вроде и сигнал в воркере добавили, только ни с чем его не связали. Также вы не должны вызывать тот же экземпляр Game, который в потоке крутится. Т.е. запустили в потоке выполняться Game, выполнился он, забились в нем нужные вам структуры - возвращаете его сигналом в MainWindow и уже только сейчас можете взять что либо из Game.
|