I've been working on a simple dialog widget that should display GMT time at a rate of 10 Hz. Since the system I'm working on runs for days and days, it should be stable.
On some overnight runs, I've noticed that my "BAR" program is running at 100 % after several hours of execution. I'm flummoxed as to why this occurs, but I've been able to narrow it down to three functions:
I'm using a simple function called ace_time to obtain time of day:
inline double ace_time(void)
{
struct timeval tv;
struct timezone tz;
gettimeofday(&tv, &tz);
return (double) tv.tv_sec + 1e-6 * (double) tv.tv_usec;
}
And then I obtain the milliseconds, seconds, minutes, etc, from the return of this function. I then use QTime to format it:
QTime time(hours, minutes, seconds, milliseconds);
QString timeStr = time.toString("hh:mm:ss.zzz");
And then set the text in my label:
clock->setText(timeStr);
I'm confused why I would get 100 % cpu usage, unless gettimeofday, QTime or setText are doing things that I'm not expecting.
Have the experts here noticed any of these functions behaving strangely?
I'm using Qt 4.8, if that helps.
Looking forward to getting some ideas to solve this. Thanks!
Adding more code:
I want to have two bars. A top and a bottom bar. So I've written a BarBase class and a TopBar class. I also needed to write a custom QLayout to help me with the layout. I highly doubt the layout manager is causing this because it is only called when the bar is resizing and the geometry needs to be recalculated.
class BarBase : public QWidget
{
public:
BarBase()
{
setFixedHeight(barHeight);
setContentsMargins(5, 0, 5, 0);
QPalette palette;
QColor color(50, 252, 50);
palette.setColor(QPalette::Background, color);
setAutoFillBackground(true);
setPalette(palette);
}
virtual ~BarBase();
protected:
QLabel *createWestLabel(const QString &);
QLabel *createCenterLabel(const QString &);
QLabel *createEastLabel(const QString &);
private:
QLabel *createLabel(const QString &, Qt::Alignment)
{
QLabel *label = new QLabel(str);
label->setAlignment(Qt::AlignVCenter | alignment);
//label->setFrameStyle(QFrame::Box | QFrame::Raised);
QFont font("Times");
font.setPixelSize(barHeight - 4);
font.setBold(true);
label->setFont(font);
return label;
}
};
And here is my class for the TopBar only
class TopBar : public BarBase
{
Q_OBJECT
public:
TopBar()
{
Layout *layout = new Layout;
classification = createCenterLabel("Classification");
layout->addWidget(classification, Layout::Center);
hostname = createWestLabel("Hostname");
layout->addWidget(hostname, Layout::West);
layout->addWidget(createWestLabel(":"), Layout::West);
software = createWestLabel("Software");
layout->addWidget(software, Layout::West);
runMode = createEastLabel("SIM");
layout->addWidget(runMode, Layout::East);
layout->addWidget(createEastLabel(":"), Layout::East);
clock = createClockLabel("-dd::hh::mm::ss.z");
layout->addWidget(clock, Layout::East);
deadman = new QTimer;
connect(deadman, SIGNAL(timeout()), this, SLOT(updateLocalGMT()));
deadman->start(100); // 10 ms;
setLayout(layout);
setWindowTitle(tr("Top Bar"));
}
virtual ~TopBar();
public slots:
void updateLocalGMT()
{
double milliseconds = fmod(ace_time(), 86400.0) * 1000;
bool sign = (milliseconds >= 0.0);
if (!sign)
{
milliseconds = -milliseconds;
}
const int millisecondsToDays = 86400.0 * 1000.0;
const int millisecondsToHours = 3600.0 * 1000.0;
const int millisecondsToMinutes = 60 * 1000.0;
const int millisecondsToSeconds = 1000.0;
double days = floor(milliseconds / millisecondsToDays);
milliseconds -= days * millisecondsToDays;
double hours = floor(milliseconds / millisecondsToHours);
milliseconds -= hours * millisecondsToHours;
double minutes = floor(milliseconds / millisecondsToMinutes);
milliseconds -= minutes * millisecondsToMinutes;
double seconds = floor(milliseconds / millisecondsToSeconds);
milliseconds -= seconds * millisecondsToSeconds;
QTime time(hours, minutes, seconds, milliseconds);
/*
if (!time.isValid())
{
INFO("Invalid input to QTime [day, hour, min, sec, ms]: [%f %f %f %f %f]",
days, hours, minutes, seconds, milliseconds);
}
*/
QString timeStr = time.toString("hh:mm:ss.zzz");
timeStr = timeStr.left(timeStr.length() - 2); // to remove the last two z
timeStr.prepend((sign) ? "+" : "-");
timeStr.prepend("<code style='color:white'>");
timeStr.append("</code>");
// timeStr = timeStr.left(timeStr.length() - 2);
// qDebug() << currentTime;
clock->setText(timeStr);
}
private:
QLabel *classification;
QLabel *hostname;
QLabel *software;
QLabel *runMode;
QLabel *clock;
QLabel *createClockLabel(const QString &text)
{
QLabel *label = new QLabel(text);
label->setAlignment(Qt::AlignVCenter);
QFont font("Monospace");
font.setStyleHint(QFont::TypeWriter);
font.setFixedPitch(true); // enforces monospace
font.setPointSize(18);
font.setBold(true);
label->setFont(font);
int pixelWidth = label->fontMetrics().width(label->text());
label->setFixedWidth(pixelWidth);
return label;
}
QTimer *deadman;
};
The QTimer can fall behind and queue up many notifications if your thread is busy - this will cause your processing to happen far more than you expect.
To workaround this you should do something like:
void timer_slot()
{
if diff(now - last_time) < timer_interval
return; // Timer has come in too early so don't do anything
last_time = now;
}
If you want to reproduce this just block your thread with a sleep() and you'll notice that the timer slot will be called for as many times as it should have been called while the thread was blocked.
The QTimer docs state that:
"Accuracy and Timer Resolution
Timers will never time out earlier than the specified timeout value and they are not guaranteed to time out at the exact value specified. In many situations, they may time out late by a period of time that depends on the accuracy of the system timers.
The accuracy of timers depends on the underlying operating system and hardware. Most platforms support a resolution of 1 millisecond, though the accuracy of the timer will not equal this resolution in many real-world situations.
If Qt is unable to deliver the requested number of timer clicks, it will silently discard some."
But I've found this not to be true on Windows 7 x64 if the thread is blocked/busy.
deadman->start(10); // 10 ms;
(Note: answer written before question was fixed to have 100 ms interval)
You have 10ms interval here, which is 100Hz, not 10Hz. In your question you say "the system I'm working on runs for days and days", so it sounds like you have an embedded system of some sort? Maybe it has trouble keeping up with 100Hz timer.
Note that updating the label 100 times a second will cause QWidget::update() to be called that often, and the widget will be painted from event loop very often, even if you do not call repaint() (which you should not do anyway).
Related
I need help. Here is my code (i show main part):
// Arduino Reader
serial.setPortName("/dev/ttyACM0");
serial.open(QIODevice::ReadWrite);
serial.setBaudRate(QSerialPort::Baud9600);
serial.setDataBits(QSerialPort::Data8);
serial.setParity(QSerialPort::NoParity);
serial.setStopBits(QSerialPort::OneStop);
serial.setFlowControl(QSerialPort::NoFlowControl);
connect(&serial,SIGNAL(readyRead()),this,SLOT(getNewData()));
...
void MainWindow::getNewData()
{
std::clock_t starter = std::clock();
QByteArray data = serial.readAll();
QDataStream in(serial.readAll());
getData = getData + data;
int start = getData.indexOf(":START:");
int end = getData.indexOf(":END:",start);
QString nowWork = getData.mid(start,end-start+5);
if (nowWork.startsWith(":START:") && nowWork.endsWith(":END:")){
QStringList mod = nowWork.remove(":START:").remove(":END:").split(":");
int xPoint = mod[0].toInt();
int yPoint = mod[1].toInt();
int zPoint = mod[2].toInt();
int aPoint = mod[3].toInt();
int bPoint = mod[4].toInt();
int cPoint = mod[5].toInt();
addQuery(1,xPoint,yPoint,zPoint);
addQuery(2,aPoint,bPoint,cPoint);
getData = getData.right(end+5);
}
std::clock_t ender = std::clock();
ui->ping->setText(QString::number( (ender-starter)/ (double)CLOCKS_PER_SEC *100));
return;
}
...
void MainWindow::addQuery(int to, int x, int y, int z){
seriesAllD1->append(now, (x+y+z)/3 );
seriesXD1->append(now,x);
seriesYD1->append(now,y);
seriesZD1->append(now,z);
seriesAllD1->remove(0);
seriesXD1->remove(0);
seriesYD1->remove(0);
seriesZD1->remove(0);
chartAllD1->axisX()->setRange(now-100,now);
chartX->axisX()->setRange(now-100,now);
chartY->axisX()->setRange(now-100,now);
chartZ->axisX()->setRange(now-100,now);
now++;
return;
}
I try to describe code:
1) It gets data from arduino (Like a ":START:X:Y:Z:A:B:C:END::START:...").
2) It gets indicates from 2-sensors (3-axis accelometr). And draws Graphs.
I show some code block. After adding ~900 point it gets slow from 60 ms to 1000 ms. I think it is related to QtChart (i use them in project) or addind points to series. Please help me(
This is an old topic - however I also ran into it when working with QtCharts - so: for others who will stumble on the same problems with QtCharts and would also like to use QtCharts here are my solutions:
Use OpenGL:
call setUseOpenGL(true) on the series in the plot (will only work with QLineSeries and QScatterSeries) - unfortunately this is not the default, but it could be a problem on some systems
When not using OpenGL (i.e. also using QAreaSeries):
do not use AntiAliasing
when adding large amounts of data in one loop to the series use rather replace than add - see https://stackoverflow.com/a/52334549/2056545
And this special trick a colleague came up with when working with real time data:
set all series to signalsBlocked(true), start a timer when blocking, unblock after a second, block again and start timer when adding new data again - this will update the plots on (about) every second, but the performance impact is low
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.
I am trying to get a text edit box to display the current time every 5 seconds using QTimer. I am having the current time figured in a separate method and then having the QTimer call that method and display the current time. I can not for the life of me figure out how to pass the variable from the setCurrentTime method to the QTimer. Im sure it a really easy fix but I cant figure it out. Here is my code.
void noheatmode::setCurrentTime()
{
QTime time = QTime::currentTime();
QString sTime = time.toString("hh:mm:mm");
// ui->tempTimeNoHeatMode->append(sTime);
}
void noheatmode::on_timeButton_clicked()
{
QTimer *timer =new QTimer(this);
connect(timer,SIGNAL(timeout()), this, SLOT(setCurrentTime()));
timer->start(5000);
ui->tempTimeNoHeatMode->append(sTime);
}
If I got your problem right, you just have minutes variable instead of a seconds. Just change "hh:mm:mm" to "hh:mm:ss"
void noheatmode::setCurrentTime()
{
QTime time = QTime::currentTime();
QString sTime = time.toString("hh:mm:ss");
ui->tempTimeNoHeatMode->append(sTime);
}
With your code:
void noheatmode::on_timeButton_clicked()
{
QTimer *timer =new QTimer(this);
connect(timer,SIGNAL(timeout()), this, SLOT(setCurrentTime()));
timer->start(5000);
ui->tempTimeNoHeatMode->append(sTime);
}
This means that the function within SLOT will be called every 5000 milliseconds which = 5 seconds. What could be done then is that you set your function setCurrentTime() to update your text box every time it is called.
Example:
void Class::setCurrentTime()
{
QTime times = (QTime::currentTime());
QString currentTime=times.toString("hh:mm:ss");
ui->label->setText(currentTime);
//Assuming you are using a label to output text, else substitute for what you are using instead
//Every time this function is called, it will receive the current time
//and update label to display the time received
}
In my game I need a stopwatch to measure and show the elapsed time.
For this purpose I made a simple widget:
ZuulStopwatchWidget::ZuulStopwatchWidget(QWidget *parent) :
QWidget(parent)
{
num = new QLCDNumber(this); // create the display
num->setDigitCount(9);
time = new QTime();
time->setHMS(0,0,0,0); // set the time
timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(showTime()));
i=0;
QString text = time->toString("hh:mm:ss");
num->display(text);
//num->setStyleSheet("* { background-color:rgb(199,147,88);color:rgb(255,255,255); padding: 7px}}");
num->setSegmentStyle(QLCDNumber::Flat); //filled flat outline
//setStyleSheet("* { background-color:rgb(236,219,187)}}");
layout = new QVBoxLayout(this);
layout->addWidget(num);
setMinimumHeight(70);
}
ZuulStopwatchWidget::~ZuulStopwatchWidget()
{
// No need to delete any object that has a parent which is properly deleted.
}
void ZuulStopwatchWidget::resetTime()
{
time->setHMS(0,0,0);
QString text = time->toString("hh:mm:ss");
num->display(text);
i=0;
stopTime();
}
void ZuulStopwatchWidget::startTime()
{
//flag=0;
timer->start(1);
}
void ZuulStopwatchWidget::stopTime()
{
timer->stop();
}
void ZuulStopwatchWidget::showTime()
{
QTime newtime;
//if(flag==1)
//i=i-1;
i=i+1;
newtime=time->addMSecs(i);
QString text = newtime.toString("mm:ss:zzz");
num->display(text);
}
But when I run my game the CPU usage is at about 13% on a 2,5Ghz i5. I know this is not problematic but it sure is ridiculous for a stupid clock.
Am I doing it completely wrong or is this common practice ?!
Many thanks in advance.
Start(1) sets the timer to trigger every millisecond
You then want to format a string and print it on the screen 16times faster than the screen is probably updating anyway
I have a QTimeEdit which I want to set to some value and the each second I want to decrease by 1 the value that shows the QTimeEdit. So when it will be 0, the I want to have a QMeesageBox that says "Your time is off.". Can I some how do this with QTimeEdit interface, or I should use QTimer?
You can use QTimeEdit for displaying the time but you will have to use QTimer to decrease the time every second.
You can do something like this:
timeEdit->setTime(...); //set initial time
QTimer timer;
timer.start(1000); //timer will emit timeout() every second
connect(&timer, SIGNAL(timeout()), this, SLOT(slotTimeout()));
void slotTimeout()
{
QTime time = timeEdit->time().addSecs(-1);
timeEdit->setTime(time);
if (time == QTime(0, 0))
//time is zero, show message box
}