PDA

View Full Version : QTimeLine in QtConcurrent - C++



monnzz
11th December 2016, 15:44
Hallo, I have the following question, I'm trying to do the next thing, when a player kills 2 enemies they should die simultaneously with simple animation
(a few pics change over time). So in order to achieve this parallelism I create a number of threads equal to the number of enemies to be killed
(e.g. 2 enemies to be killed = 2 threads to be created)

below the code of creating threads, it works just fine:


std::vector<QFuture<void>> threadResults;
for(int i = 0; i < blantsToKill.size(); ++i) {
threadResults.push_back(QtConcurrent::run(this, &GameManager::killBlant, std::ref(blantsToKill[i])));
}
std::for_each(threadResults.begin(), threadResults.end(), std::bind(&QFuture<void>::waitForFinished, std::placeholders::_1));

But here the problem appears, the function is called, and slots called as well, however i get the following message "QObject::startTimer: Timers cannot be started from another thread" and pictures for my QGraphicsPixmapItem are not updated.
I tried Qt::QueuedConnection and it didn't help a bit.



void Blant::die()
{
QTimeLine* timeLine = new QTimeLine(m_animationMoveTime);
timeLine->setUpdateInterval(100);
timeLine->setDuration(400);
connect(timeLine, SIGNAL(valueChanged(qreal)), this, SLOT(animateDying(qreal)), Qt::DirectConnection);
connect(timeLine, SIGNAL(finished()), this, SLOT(finishDying()), Qt::DirectConnection);
timeLine->start();

// so the function wouldn't return before animation finished.
delay(400 + 100 * 2);
}

I guess, it's important to note, that if I don't use threads my code works fine, but without parallel execution the enemies killed subsequently.
Also except TimeLine created in this function, at this particular moment no other timers work.
I thought problem could be with delay, however after substituting it with a code (just to check how it works without delay) like this:


volatile long i = 0;
while(i < 10000000000) {
++i;
}

it's still the same problem.

this is slot animateDying, finishDying is mostly the same.

void Blant::animateDying(qreal)
{
qDebug() << "ololo" << m_nextFrame;
setPixmap((m_blantsPictures[m_blantColor].dyings[m_nextFrame++]));
if(m_nextFrame == m_blantsPictures[m_blantColor].dyings.size() - 1)
m_nextFrame = 0;

}

So any help would be much appreciated, including any thoughts how the simultaneous effect could be achieved without addittional threads.

d_stranz
11th December 2016, 17:04
Qt GUI class instances can only be used in the main thread, i.e. the one started when your app starts. Possibly QTimeLine also falls into this category. In any case, you won't be able to access QGraphicsItem instances from a different thread, and updates to GUI class instances will always occur in the order in which the main event loop calls their paintEvents. You can't update different things on the GUI simultaneously no matter what.

In the main thread you -can- use the Qt animation framework to nearly simultaneously make changes to your two bad guys by reworking your QTimeLine into QAbstractAnimation instead.

monnzz
11th December 2016, 17:38
d_stranz, thank you very much for your help:) now I know where to look further.

anda_skoa
12th December 2016, 10:39
Possibly QTimeLine also falls into this category.

Unlikely.

But if die() is executed by a secondary thread then, due to the enforced DirectConnection, so will animateDying() and the setPixmap() call is definitely UI.




In any case, you won't be able to access QGraphicsItem instances from a different thread, and updates to GUI class instances will always occur in the order in which the main event loop calls their paintEvents. You can't update different things on the GUI simultaneously no matter what.

Well, yes and no.
Obviously the items will be repainted one after each other, but that could still be in the same frame update for the view, thus appearing simultaniously updated.



In the main thread you -can- use the Qt animation framework to nearly simultaneously make changes to your two bad guys by reworking your QTimeLine into QAbstractAnimation instead.

Exactly. Even QTimeLine can do that.
Using threads here is only necessary if the goal is to make the program as complex as possible.

Cheers,
_

monnzz
14th December 2016, 18:08
anda_skoa, thank you for the explanation:)
So I've changed my program structure to do my "die" animation in one thread and it works like a charm:)

In a few threads it was not possible to update my view. I could see slot working but animation wasn't going at all. I could see only the first state and the last, which is removing an enemy from the scene but it doesn't have anything to do with the animation, because clearly I remove items after the animation finished (by finished i mean after even finished() slot is finished :rolleyes:.)

Also I had that strange warning "QObject::startTimer: Timers cannot be started from another thread", and I didn't manage to figure out what was the cause. I don't have any external timers and, as you can see in my first post, QTimeLine is created in a child thread.

Finally, I'm very grateful to you for your support, but it would be nice if you could suggest any explanation for that warning with timers in threads.

anda_skoa
15th December 2016, 11:33
Finally, I'm very grateful to you for your support, but it would be nice if you could suggest any explanation for that warning with timers in threads.
Hard to tell, maybe in your delay() function?

If you want to debug this set a break point in QObject::startTimer at the place where it writes that warning and look at where the call is coming from.

Cheers,
_