Spawning waves of enemies C++ - c++

I'm creating a simple game with qt 5.0.1. It's something like Warblade.
I have problem with creating waves of enemies.
int k;
int pos = 100;
for (k = 0; k < 5; k++)
{
pos = 100;
for (int i = 0; i < 9; i++)
{
player->spawn_in_pos(pos);
pos += 100;
}
//QThread::sleep(2);
}
When i use sleep() function, my game just can't run. It's waiting for loop finish and then it shows.
I'm also dealing with second option:
QTimer * timer = new QTimer();
QObject::connect( timer, SIGNAL(timeout()), player, SLOT(spawn_in_pos(pos)) );
timer->start(450);
But it looks like SLOT can't get the position.
Edit:
I just did what #ddriver said, and that helped me a lot.
Now I'm getting some 'laggy' style enemies movement.
Edit2:
I'm moving my enemies down like this:
setPos(x(),y()+1);
with that timer:
// connect
QTimer * timer = new QTimer(this);
connect(timer,SIGNAL(timeout()),this,SLOT(move()));
// start the timer
timer->start(10);
It looks like very smooth movement but probably +1 pixel down and a 10 timer is to less:((

I'm not sure what you are trying to achieve, but in your second option, you cannot get the position, because the timeout doesn't send it.
The signal is timeout(void) and your slot expects an parameter. I guess you lack some basic understanding of the signal/slot mechanism.
The QT Documentation is pretty neat:
http://doc.qt.io/qt-5/signalsandslots.html
And if you just want to create a game out of nothing, here you can find a little tutorial, how to write games in QT:
https://www.youtube.com/watch?v=8ntEQpg7gck

Calling sleep is going to stop the thread from processing anything, which is not what you want to do.
Using C++ 11, you can use the QTimer with a lambda function like this: -
int pos = 100;
int nextWaveTime = 2000; // 2 seconds per wave
for (k = 0; k < 5; k++) // 5 waves of enemies
{
for (int i = 0; i < 9; i++) // 9 enemies per wave
{
QTimer * timer = new QTimer();
timer->setSingleShot(true);
pos = pos + (100*i); // set the pos, which is captured by value, in the lambda function
QObject::connect( timer, QTimer::timeout, [=](){
player->spawn_in_pos(pos);
timer->deleteLater(); // must cleanup the timer
});
timer->start(450 + (k*nextWaveTime));
}
}

In order to pass parameters with signals and slots in Qt, the signal parameters must match the parameters of the slot (or function since Qt 5).
One way to solve the issue is to use a lambda as in TheDarkKnight's answer.
What I would suggest is to use encapsulation - you could create a Spawner object, dedicated to spawning enemies and keep the position internal to it. This way the spawner will manage the position, and you can have something like Spawner::createWave() slot with no parameters, since the position is internal. Then setup the timer and connect it to createWave() and you are set.
Also it is a very bad idea to hardcode stuff like that, you really need more flexibility, the option to change enemy and wave count, the wave time as well as the screen width, so that your game can change those things as it gets harder.
class Spawner : public QObject {
Q_OBJECT
public:
Spawner(int wCount = 5, int eCount = 9, int time = 2000, int sWidth = 1000)
: waveCount(wCount), enemyCount(eCount), currentWave(0), screenWidth(sWidth) {
timer.setInterval(time);
connect(&timer, SIGNAL(timeout()), this, SLOT(createWave()));
}
void set(int wCount, int eCount, int time) {
timer.setInterval(time);
waveCount = wCount;
enemyCount = eCount;
}
void changeWidth(int w) { screenWidth = w; }
public slots:
void start() { timer.start(); }
void stop() {
timer.stop();
currentWave = 0;
}
private slots:
void createWave() {
int pos = screenWidth / (enemyCount + 1);
int step = pos;
for (int i = 0; i < enemyCount; ++i) {
Game::spawnEnemyAt(pos);
pos += step;
}
if (++currentWave >= waveCount) stop();
}
private:
QTimer timer;
int waveCount, enemyCount, currentWave, screenWidth;
};
Create a Spawner object and connect the game new level to start() - it will span the given number waves of enemies evenly across the game screen, when you finish the waves off, you adjust the spawner settings and start a new level.
That encapsulation will come in handy later on as your game becomes less of a test and more like a real game - with increasing difficulty, changing spawning and attack patterns and so on. So it is a good idea to implement it right from the start and build upon a good and flexible design rather than going back and changing stuff around, which may break other code. You really don't want to start without a good design and make design changes later. Thus the need to encapsulate functionality and responsibility and just connect the pieces rather than building on a pile of spaghetti code. In this line of thought, I noticed you are using player->spawn_in_pos(pos); - which is an example of bad design, as spawning should be a responsibility of the Game class, not the Player class. A good design is not only flexible, but also clean. The Spawner object is only responsible for spawning waves of enemies, and its visible interface is limited to start(), stop() and set().
Edit:
class Game : public QObject {
Q_OBJECT
public:
Game() {
if (!scene) scene = new QGraphicsScene(this);
connect(this, SIGNAL(newLevel()), &spawner, SLOT(start()));
}
static void spawnEnemyAt(int x = 0) {
scene->addItem(new Enemy(x, 0));
qDebug() << "enemy created";
}
public slots:
void newGame() {
// initialize game
emit newLevel(); // begin spawning
}
void onLevelEnd() {
// spawner.set(new level settings);
emit newLevel();
}
void onGameEnded() {
// ...
}
signals:
void newLevel();
private:
Spawner spawner;
static QGraphicsScene * scene;
};
// in game.cpp
QGraphicsScene * Game::scene = nullptr;
If you don't want to use static members, you can make spawnEnemyAt() and scene instance members, but then you will have to pass the Game instance to the Spawner in the constructor so that you have a reference to the game the spawner operates on and use game->spawnEnemyAt() instead. This way you can create multiple games with their own dedicated scenes. Or parent the spawner to the game and cast the spawner's parent() to a Game * to access the game instance which is a little hacky, but saves on the extra member by reusing the parent.

Related

Is there a better way to connect an array of QPushButton's to a slot so each button has a unique identifier when pushed?

I am creating a checkers board as part of a university assignment. There has been very little teaching around the GUI side of things. So I may have overcomplicated this project a bit and become stuck while trying to access the clicks from the board GUI.
I have read about lambda's in the documentation https://doc.qt.io/qt-5/qsignalmapper.html for signal mappers, but, I am not sure if it is right for the application I am coding. I have tried to implement the signal mapping so that when a particular button is pushed on the checker's board, the slot can identify it with its unique signal.
If there is a better way, or if lambdas are the best way, could you please explain how to implement this?
At the moment, my current code can't access the clicks made. I also get an error for the connect() function:
no known conversion from 'void QAbstractButton::* (bool)' to 'const char*' for the second argument.
connect(&checkers[i], &QPushButton::clicked, signalMapper, &QSignalMapper::map);
However, this compiles but does not work:
connect(&checkers[i], SIGNAL(clicked), signalMapper, SLOT(map));
Below is more complete code for greater context.
Constructor and board set function:
// Constructor
GameWindow::GameWindow(QWidget *parent):
QDialog(parent), ui(new Ui::GameWindow)
{
ui->setupUi(this);
BoardSetup(4);
}
/* Sets up the initial gameboard display with the other game information displays also being
* initialised. The game board is an array of QpushButtons that have there Icon
* changed according to where the pieces are on the board.
* Called by the GameWindow Constructor*/
void GameWindow::BoardSetup(int size)
{
{
QSignalMapper *signalMapper = new QSignalMapper(this->checkers);
connect(signalMapper, &QSignalMapper::mappedInt, this, &GameWindow::SquareClicked );
bool col=1;
for (int j=0;j<size;j++)
{
col=!col;
for(int i=0; i<size; i++)
{
if(col == 1) // logically check to see what Color the square needs to be.
{
checkers[i+j*size].setGeometry(QRect(width/size*j,width/size*i,width/size,width/size));
checkers[i+j*size].setFixedSize(QSize(width/size,width/size));
checkers[i+j*size].setStyleSheet("background-color: black");
}
else if(col==0)
{
checkers[i+j*size].setGeometry(QRect(width/size*j,width/size*i,width/size,width/size));
checkers[i+j*size].setFixedSize(QSize(width/size,width/size));
checkers[i+j*size].setStyleSheet("background-color: white");
}
col=!col;
signalMapper->setMapping(&checkers[i], i);
connect(&checkers[i], &QPushButton::clicked, signalMapper, &QSignalMapper::map);
}
}
Grid = new QGridLayout;
Grid->setGeometry(QRect(0,0,width,width));
for(int j=0; j<size; j++)
{
for(int i=0; i<size; i++)
{
Grid->addWidget(&checkers[i+j*size], j, i, Qt::AlignmentFlag::AlignCenter);
}
}
GridGroup = new QGroupBox();
GridGroup->setLayout(Grid);
ui->BoardGrid->addWidget(GridGroup);
ui->BoardGrid->setGeometry(QRect(0,0,width,width));
ui->BoardGrid->addWidget(GridGroup);
setWindowTitle("Checkers Game");
ui->P1PieceCount->setFontPointSize(40);
ui->P1PieceCount->setAlignment(Qt::AlignCenter);
ui->P2PieceCount->setFontPointSize(40);
ui->P2PieceCount->setAlignment(Qt::AlignCenter);
ui->TurnCount->setFontPointSize(40);
ui->TurnCount->setAlignment(Qt::AlignCenter);
ui->Player2Name->setAlignment(Qt::AlignCenter);
CheckersBoard(size);
SetInitialDisplay(size);
}
return;
}
How I am trying to access the clicks.
GetClicks() and WaitForClick() functions:
/* This function will get the clicks that are required to make the human move.
* Called by GameWindow::HumanMove */
void GameWindow::GetClicks()
{
connect(this, &GameWindow::SquareClicked, this, &GameWindow::WaitForClick);
}
/* This function will get the clicks that are required to make the human move.
* Called by GameWindow::GetClicks via signal SquareClicked */
void GameWindow::WaitForClick(int square)
{
firstclick = !firstclick;
unsigned int test=square;
if (firstclick ==0 && test != StartPos)
{
StartPos = square;
checkers[square].setChecked(1);
}
else if (firstclick == 1 && test == StartPos)
{
checkers[square].setChecked(0);
}
else if (firstclick==1 && test!= StartPos)
{
EndPos = square;
}
}
The StartPos and EndPos clicked are used in a possible move function to validate the move. It is then updated to the GUI.
Any help on how best to implement this signal and slot mechanism would be greatly appreciated.
Have a look at Qt calculator example?
https://doc.qt.io/qt-5/qtwidgets-widgets-calculator-example.html
you can connect multiple buttons to the same slot.
in slot, find out which button sent the signal using QObject::sender().
Once have the button, extract the position using MyButton::Pos().
(MyButton is the widget used for each of the board button)

In GTKMM, on_draw method stops being called after invalidate occurs in separated thread

Using GTKMM, I'm extending the DrawingArea widget with the idea that an external process provides it with images. My CameraDrawingArea will then display the images at the right size using Cairo.
Each time an image arrives, I store it and I call the invalidate method, which eventually ends up in a call to on_draw, where I can resize and display the image.
My problem is the following:
The first 10 or 20 images are displayed as I expected.
After a while, the images keep coming from the provider process, I keep calling invalidate
but on_draw is not called any more.
To show it here, I've simplified the code so that there is nothing external to the class, and no link with other libraries. I've replaced the process providing the images by a method with for-loops, and the display of the image by printing a simple text in the middle of the widget area:
In the constructor I launch a new std::thread to call the doCapture method in the same instance. I also set up a font description, to use it later.
The doCapture method is a silly CPU eater, that does nothing except calling from time to time the refreshDrawing method, as long as keepCapturing is not false.
refreshDrawing invalidates the whole window's rectangle via a call to invalidate.
Gtk's magic is suppose to call on_draw and provide a Cairo context to draw whatever. In my case, for tests purposes, I draw a brownish centered integer.
The class destructor stops the thread by set keepCapturing to false, and waits for termination with a join.
#include "camera-drawing-area.hpp"
#include <iostream>
CameraDrawingArea::CameraDrawingArea():
captureThread(nullptr) {
fontDescription.set_family("Monospace");
fontDescription.set_weight(Pango::WEIGHT_BOLD);
fontDescription.set_size(30 * Pango::SCALE);
keepCapturing = true;
captureThread = new std::thread([this] {
doCapture();
});
}
void CameraDrawingArea::doCapture() {
while (keepCapturing) {
float f = 0.0;
for (int n = 0; n < 1000; n++) {
for (int m = 0; m < 1000; m++) {
for (int o = 0; o < 500; o++) {
f += 1.2;
}
}
}
std::cout << "doCapture - " << f << std::endl;
refreshDrawing();
}
}
void CameraDrawingArea::refreshDrawing() {
auto win = get_window();
if (win) {
win->invalidate(false);
std::cout << "refreshDrawing" << std::endl;
}
}
bool CameraDrawingArea::on_draw(const Cairo::RefPtr<Cairo::Context>& cr) {
std::cout << "on_draw" << std::endl;
static char buffer[50];
static int n = 0;
sprintf(buffer, "-%d-", n++);
Gtk::Allocation allocation = get_allocation();
const int width = allocation.get_width();
const int height = allocation.get_height();
auto layout = create_pango_layout(buffer);
layout->set_font_description(fontDescription);
int textWidth, textHeight;
layout->get_pixel_size(textWidth, textHeight);
cr->set_source_rgb(0.5, 0.2, 0.1);
cr->move_to((width - textWidth) / 2, (height - textHeight) / 2);
layout->show_in_cairo_context(cr);
cr->stroke();
return true;
}
CameraDrawingArea::~CameraDrawingArea() {
keepCapturing = false;
captureThread->join();
free(captureThread);
}
And this is my header file:
#ifndef CAMERA_DRAWING_AREA_HPP
#define CAMERA_DRAWING_AREA_HPP
#include <gtkmm.h>
#include <thread>
class CameraDrawingArea : public Gtk::DrawingArea {
public:
CameraDrawingArea();
virtual ~CameraDrawingArea();
protected:
bool on_draw(const Cairo::RefPtr<Cairo::Context>& cr) override;
private:
bool keepCapturing;
void doCapture();
void refreshDrawing();
std::thread* captureThread;
Pango::FontDescription fontDescription;
};
#endif
The problem manifests itself as follows:
When starting the application, it faithfully displays 1, 2, 3...
Between 5th and 20th iteration (it's random, but rarely outside these ranges), it stops refreshing.
Because of the cout, I can see that refreshDrawing is called be sure that invalidate is also called, but on_draw isn't.
Also, if I stop the application before it stops refreshing, then it ends up nicely. But, if I stop the application after it stops refreshing, then I see this message below (the ID value varies):
GLib-CRITICAL **: 10:05:04.716: Source ID 25 was not found when attempting to remove it
I'm quite sure that I do something wrong, but clueless about what. Any help would be appreciated.
I also checked the following questions, but they don't seem to be related with my case:
Draw signal doesn't get fired in GTKMM, when derived class doesn't call a superclass's constructor
You can't use GTK methods from any other thread than the one in which you started the GTK main loop. Probably the win->invalidate() call is causing things to go wrong here.
Instead, use Glib::Dispatcher to communicate with the main thread, or use gdk_threads_add_idle() for a more C-style solution.
Based on the answer form #ptomato, I've rewritten my example code. The golden rule is do not call GUI functions from another thread, but if you do, then acquire some specific GDK locks first. That's the purpose of Glib::Dispatcher :
If a Glib::Dispatcher object is constructed in the main GUI thread (which will therefore be the receiver thread), any worker thread can emit on it and have the connected slots safely execute gtkmm functions.
Based on that, I've added a new private member Glib::Dispatcher refreshDrawingDispatcher that will allow threads to safely the invalidate the windows area:
#ifndef CAMERA_DRAWING_AREA_HPP
#define CAMERA_DRAWING_AREA_HPP
#include <gtkmm.h>
#include <thread>
class CameraDrawingArea :
public Gtk::DrawingArea {
public:
CameraDrawingArea();
virtual ~CameraDrawingArea();
protected:
bool on_draw(const Cairo::RefPtr<Cairo::Context>& cr) override;
private:
bool keepCapturing;
void doCapture();
void refreshDrawing();
Glib::Dispatcher refreshDrawingDispatcher;
std::thread* captureThread;
Pango::FontDescription fontDescription;
};
#endif
Then, I've connected the dispatcher to the refreshDrawing method. I do this in the class constructor, which is called during GUI start up and therefore in the main GUI thread:
CameraDrawingArea::CameraDrawingArea():
refreshDrawingDispatcher(),
captureThread(nullptr) {
fontDescription.set_family("Monospace");
fontDescription.set_weight(Pango::WEIGHT_BOLD);
fontDescription.set_size(30 * Pango::SCALE);
keepCapturing = true;
captureThread = new std::thread([this] {
doCapture();
});
refreshDrawingDispatcher.connect(sigc::mem_fun(*this, &CameraDrawingArea::refreshDrawing));
}
Finally, the thread has to call the dispatcher:
void CameraDrawingArea::doCapture() {
while (keepCapturing) {
float f = 0.0;
for (int n = 0; n < 1000; n++) {
for (int m = 0; m < 1000; m++) {
for (int o = 0; o < 500; o++) {
f += 1.2;
}
}
}
std::cout << "doCapture - " << f << std::endl;
refreshDrawingDispatcher.emit();
}
}
And now, this works without further problems.

Updating QChart from QLineSeries in a running while loop

I want to make my QChart dynamically update whenever a point is added to the QLineSeries object attached to it, but it seems that this update only occurs after the while loop I am running has finished. I am using said while loop in interface.cpp that calls a function updatePlot() which adds the data point to the line series, but this only updates the chart after the while loop has completely finished. Pseudo code of what is happening here:
qtwindow.cpp
// Constructor that initializes the series which will be passed into the interface
AlgoWindow::AlgoWindow( ..., TradingInterface* interface, ... ) {
...
QLineSeries* series = new QLineSeries();
QLineSeries* benchmark = new QLineSeries();
QChart* chart = new QChart();
chart->addSeries(series);
chart->addSeries(benchmark);
// Also creates custom axes which are attached to each series
...
}
// Slot connected to a button signal
void AlgoWindow::buttonClicked() {
// Runs the backtest
interface->runbacktest(..., series, benchmark, ...);
}
interface.cpp
void TradingInterface::runbacktest(..., QtCharts::QLineSeries* algoplot, QtCharts::QLineSeries* benchplot) {
// Runs a huge while loop that continuously checks for events
while (continue_backtest) {
if (!eventsqueue.isEmpty()) {
// Handle each event for the bar
} else {
// All events have been handled for the day, so plot
updatePlot(algoplot, benchplot);
}
}
}
void TradingInterface::updatePlot(QtCharts::QLineSeries *algoseries,
QtCharts::QLineSeries *benchseries) {
// Get the date and the information to put in each point
long date = portfolio.bars->latestDates.back();
double equitycurve = portfolio.all_holdings.rbegin().operator*().second["equitycurve"];
double benchcurve = benchmarkportfolio.all_holdings.rbegin().operator*.second["equitycurve"];
// Append the new points to their respective QLineSeries
algoseries->append(date * 1000, equitycurve*100);
benchseries->append(date * 1000, benchcurve*100);
}
This gives me no errors and the while loop completes, but the lines are only plotted after runbacktest() exits. It then plots all the data correctly, but all at once.
What I need to happen is for the QChart to update every time the lines are added, which my guess was to use some form of custom signal-slot listener but I have no clue how to go about that. If the graph will not update until after the function completes, is it even possible within the QChart framework?
Also, I have already tried QChart::update() and QChartView::repaint(). Both produced the same results as without.
EDIT: I tried setting up a new thread that emits a signal back to the main thread whenever the data is completed but it seems to have changed nothing. The QChart still does not update until after all the data has been inputted. I added a couple lines to help debug and it seems like the function which emits the signal runs consistently just fine, but the slot function which receives the signal only runs after the thread has finished. Not only that, but slowing the signals down with a sleep does not make it plot slowly (like I thought), as the QChart still refuses to update until after the final update to addData().
Either remove your while loop and perform the work one step at a time with a timer.
Or run your runbacktest function in another thread and send a signal to update the QChart in the UI's thread when the data is ready.
Either way you need to give control back to the event loop so that the chart can be repainted.
The Qt idiom for running an operation “continuously” is to use a zero-duration “timer”. It’s not a timer really, but Qt calls it one.
You can do the operation in chunks that take approximately a millisecond. For this, invert the control flow. Qt doesn't provide too much syntactic sugar for it, but it's easy to remedy.
Convert this code, which maintains a loop:
for (int i = 0; i < 1000; ++i) {
doSomething(i);
}
into this lambda, which is invoked by the event loop:
m_tasks.addTask([this](i = 0) mutable {
doSomething(i);
++i;
return i < 1000;
});
assuming:
class Controller : public QObject {
Tasks m_tasks;
...
};
where the Tasks class maintains a list of tasks to be executed by the event loop:
class Tasks : public QObject {
Q_OBJECT
QBasicTimer timer;
std::list<std::function<bool()>> tasks;
protected:
void timerEvent(QTimerEvent *ev) override {
if (ev->timerId() != timer.timerId())
return;
for (auto it = tasks.begin(); it != tasks.end(); ) {
bool keep = (*it)();
if (!keep)
it = tasks.erase(it);
else
++it;
}
if (tasks.empty())
timer.stop();
}
public:
using QObject :: QObject;
template <typename F> void addTask(F &&fun) {
tasks.emplace_back(std::forward(fun));
if (!timer.isActive())
timer.start(0, this);
}
};

Implementing QTimer to execute a function for a given time

I have a function that should send data to a raspberry pi for a given period of time depending on the parameter.
//headerfiles
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private:
Ui::MainWindow *ui;
QUdpSocket udpSocket;
// movement Timer
QTimer* movementTimer;
private slots:
void sendDatagram(); // Sends to the RaspberryPi
void processFrameAndUpdateGUI();
void turnLeft(double time);
void turnRight(double time);
void goStraight(double time);
void MovRobot();
};
// MainWindow.cpp
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
movementTimer = new QTimer(this);
//send datagram sends data to tehe PI.
connect(movementTimer, SIGNAL(timeout()), this, SLOT(sendDatagram()));
MovRobot(); // MovRobot() function
}
// controls robot to turn left for specified time
void TurnLeft(double time)
{
s = 1; // sets the value to be sent to the PI.
movementTimer->setInterval(time);
movementTimer->setSingleShot(true);
movementTimer->start();
}
//sendDatagram() slot
void MainWindow::sendDatagram() {
QString datagramOutput = "start," +
QString::number(w) + ',' + QString::number(a) + ',' +
QString::number(s) + ',' + QString::number(d) + ',' +
QString::number(ui->motorSpeedSlider->value()) + ',' +
QString::number(dispenserSignal);
datagramOutput += ",end";
QByteArray datagram;
QDataStream out(&datagram,QIODevice::WriteOnly);
out << datagramOutput;
udpSocket.writeDatagram(datagram,QHostAddress("192.168.0.104"),12345);
}
//MovRobot Function;
void MainWindow :: MovRobot() {
if (ui->pushButton_3->isChecked()) {
MapArea(); // This function maps the area....
// final_plan is a vector<Point2i> that stores positions on the map for
// the robot to move to
for (int i = 0; i < final_plan.size(); i++) {
for (int j = final_plan.size() - 1; j>=0; j--) {
do {
Mat src;
bool bsuccess = cap.read(src);
if (!bsuccess) {
ui->label_34->setText("Status: Can't read frame.");
ui->pushButton_3->setChecked(0);
}
GetRobotPosition(src);
// AngleToGoal calculates the angle of the robot relative
// to the final goal.
double tempAngle = AngletoGoal(final_plan[i][j]);
if (tempAngle>=0) {
turnLeft(TimeToTurn(tempAngle));
}
else {
turnRight(TimeToTurn(tempAngle));
}
double tempDistance = DistancetoGoal(final_plan[i][j]);
goStraight(tempDistance);
} while (DistancetoGoal(final_plan[i][j])<20);
}
}
}
}
The timer should only send the data over to the PI for the duration it in time sendDatagram is the function that sends the data over to the Pi. Is there anything I'm missing here. The timer doesn't start inside the TurnLeft() function and doesn't run at all. Am I going about this wrong?
EDIT: 13/03/2016:
My apologies for the late reply. I've been quite sick the past few days. I've added the relevant parts of the code. MovRobot() is the main function responsible for the movement and this is called in the constructor for MainWindow. I have debugged and stepped through the program and yes, TurnLeft() is called. However, the sendDatagram() slot doesn't actually send anything in the function. To confirm sendDatagram() was actually working, I used another timer to continuously send information to the PI on the robot to control the arm.
// Header File
QTimer* tmrTimer;
private slot:
void processFrameAndUpdateGUI();
// MainWindow Constructor:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
connect(tmrTimer, SIGNAL(timeout()), this, SLOT(processFrameAndUpdateGUI()));
tmrTimer->start(10);
}
void MainWindow::processFrameAndUpdateGUI() {
sendDatagram();
}
The sendDatagram() slot is pretty much the same with the exception of me changing what values are being sent to the PI and this seems to work perfectly.
However, my original problem is, I would like to send the data to the robot with for a specified amount of time as that makes the robot turn x degrees. This is why I've made movementTimer() single shot.
Stepping through my program, I know this line is called within my TurnLeft function.
movementTimer->start();
but the sendDatagram() slot itself doesn't actually send anything to the PI.
Based on your code. The program never called TurnLeft(). So the timer never started. It will be better to start the timer in constrator instead.
timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(MySlot()));
timer->start(1000);
I would try setting the timer to start in the constructor.

Refreshing with repaint() stops working

Today I encountered a problem with repaint() function from QT libraries. Long story short, I got a slot where I train my neural network using BP algorithm. I had tested the whole algorithm in console and then wanted to move it into GUI Application. Everything works fine except refreshing. Training of neural networks is a process containing a lot of computations, which are made in bp_alg function (training) and licz_mse function (counting a current error). Variable ilosc_epok can be set up to 1e10. Therefore the whole process may last even several hours. Thats why I wanted to display a current progress after each 100000 epochs (the last if contition). wyniki is an object of QTextEdit class used for displaying the progress. Unfortunately, repaint() doesnt work as intended. At the beginning it refreshes wyniki in GUI, but after some random time it stops working. When the external loop is finished, it refreshes once again showing all changes.
I tried to change frequency of refreshing, but sooner or later it always stops (unless the whole training process stops early enough because of satisfying the break condition). It looks like at some moment of time the application decides to stop refreshing because of too many computations. Imo it shouldnt happen. I was looking for a solution among older questions and managed to solve the problem when I used qApp->processEvents(QEventLoop::ExcludeUserInputEvents); instead of wyniki->repaint();. However, Im still curious why repaint() stops working just like that.
Below I paste a part of the code with the problematic part. Im using QT Creator 2.4.1 and QT Libraries 4.8.1 if it helps.
unsigned long int ile_epok;
double mse_w_epoce;
for (ile_epok=0; ile_epok<ilosc_epok; ile_epok++) { //external loop of training
mse_w_epoce = 0;
for (int i=0; i<zbior_uczacy_rozmiary[0]; i++) { //internal loop of training
alg_bp(zbior_uczacy[i], &zbior_uczacy[i][zbior_uczacy_rozmiary[1]]);
mse_w_epoce += licz_mse(&zbior_uczacy[i][zbior_uczacy_rozmiary[1]]);
}
//checking break condition
if (mse_w_epoce < warunek_stopu) {
wyniki->append("Zakończono uczenie po " + QString::number(ile_epok) + " epokach, osiągając MSE: " + QString::number(mse_w_epoce));
break;
}
//problematic part
if ((ile_epok+1)%(100000) == 0) {
wyniki->append("Uczenie w toku, po " + QString::number(ile_epok+1) + " epokach MSE wynosi: " + QString::number(mse_w_epoce));
wyniki->repaint();
}
}
You're blocking your GUI thread, so repaints will not work, it's just plainly bad design. You're never supposed to block the GUI thread.
If you insist on doing the work in the GUI thread, you must forcibly chop the work into small chunks and return to the main event loop after each chunk. Nested event loops are evil, so don't even think you'd want one. All this has a bad code smell, so stay away.
Alternatively, simply move your computation QObject to a worker thread and do the work there.
The code below demonstrates both techniques. It's easy to notice that the chopping-up-of-work requires to maintain loop state inside of the worker object, not merely locally in the loop. It's messier, the code smells bad, again - avoid it.
The code works under both Qt 4.8 and 5.1.
//main.cpp
#include <QApplication>
#include <QThread>
#include <QWidget>
#include <QBasicTimer>
#include <QElapsedTimer>
#include <QGridLayout>
#include <QPlainTextEdit>
#include <QPushButton>
class Helper : private QThread {
public:
using QThread::usleep;
};
class Trainer : public QObject {
Q_OBJECT
Q_PROPERTY(float stopMSE READ stopMSE WRITE setStopMSE)
float m_stopMSE;
int m_epochCounter;
QBasicTimer m_timer;
void timerEvent(QTimerEvent * ev);
public:
Trainer(QObject *parent = 0) : QObject(parent), m_stopMSE(1.0) {}
Q_SLOT void startTraining() {
m_epochCounter = 0;
m_timer.start(0, this);
}
Q_SLOT void moveToGUIThread() { moveToThread(qApp->thread()); }
Q_SIGNAL void hasNews(const QString &);
float stopMSE() const { return m_stopMSE; }
void setStopMSE(float m) { m_stopMSE = m; }
};
void Trainer::timerEvent(QTimerEvent * ev)
{
const int updateTime = 50; //ms
const int maxEpochs = 5000000;
if (ev->timerId() != m_timer.timerId()) return;
QElapsedTimer t;
t.start();
while (1) {
// do the work here
float currentMSE;
#if 0
for (int i=0; i<zbior_uczacy_rozmiary[0]; i++) { //internal loop of training
alg_bp(zbior_uczacy[i], &zbior_uczacy[i][zbior_uczacy_rozmiary[1]]);
currentMSE += licz_mse(&zbior_uczacy[i][zbior_uczacy_rozmiary[1]]);
}
#else
Helper::usleep(100); // pretend we're busy doing some work
currentMSE = 2E4/m_epochCounter;
#endif
// bail out if we're done
if (currentMSE <= m_stopMSE || m_epochCounter >= maxEpochs) {
QString s = QString::fromUtf8("Zakończono uczenie po %1 epokach, osiągając MSE: %2")
.arg(m_epochCounter).arg(currentMSE);
emit hasNews(s);
m_timer.stop();
break;
}
// send out periodic updates
// Note: QElapsedTimer::elapsed() may be expensive, so we don't call it all the time
if ((m_epochCounter % 128) == 1 && t.elapsed() > updateTime) {
QString s = QString::fromUtf8("Uczenie w toku, po %1 epokach MSE wynosi: %2")
.arg(m_epochCounter).arg(currentMSE);
emit hasNews(s);
// return to the event loop if we're in the GUI thread
if (QThread::currentThread() == qApp->thread()) break; else t.restart();
}
m_epochCounter++;
}
}
class Window : public QWidget {
Q_OBJECT
QPlainTextEdit *m_log;
QThread *m_worker;
Trainer *m_trainer;
Q_SIGNAL void startTraining();
Q_SLOT void showNews(const QString & s) { m_log->appendPlainText(s); }
Q_SLOT void on_startGUI_clicked() {
QMetaObject::invokeMethod(m_trainer, "moveToGUIThread");
emit startTraining();
}
Q_SLOT void on_startWorker_clicked() {
m_trainer->moveToThread(m_worker);
emit startTraining();
}
public:
Window(QWidget *parent = 0, Qt::WindowFlags f = 0) :
QWidget(parent, f), m_log(new QPlainTextEdit), m_worker(new QThread(this)), m_trainer(new Trainer)
{
QGridLayout * l = new QGridLayout(this);
QPushButton * btn;
btn = new QPushButton("Start in GUI Thread");
btn->setObjectName("startGUI");
l->addWidget(btn, 0, 0, 1, 1);
btn = new QPushButton("Start in Worker Thread");
btn->setObjectName("startWorker");
l->addWidget(btn, 0, 1, 1, 1);
l->addWidget(m_log, 1, 0, 1, 2);
connect(m_trainer, SIGNAL(hasNews(QString)), SLOT(showNews(QString)));
m_trainer->connect(this, SIGNAL(startTraining()), SLOT(startTraining()));
m_worker->start();
QMetaObject::connectSlotsByName(this);
}
~Window() {
m_worker->quit();
m_worker->wait();
delete m_trainer;
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Window w;
w.show();
return a.exec();
}
#include "main.moc"