[Interest] Ensuring that a queued invocation occurs after deferred deletion
Curtis Mitch
mitch.curtis at theqtcompany.com
Thu Mar 24 09:48:25 CET 2016
The problem with doing this is that you'd still need to identify the Loaders that are relevant to (have references to) the game, so you'd end up with more or less the same amount of extra QML code (a line or two). It also ties it to Loaders, but the same problem exists with the destroy() JavaScript function, for example.
________________________________
From: Nye <kshegunov at gmail.com>
Sent: Thursday, 24 March 2016 01:41
To: Curtis Mitch
Cc: interest at qt-project.org
Subject: Re: [Interest] Ensuring that a queued invocation occurs after deferred deletion
Hello,
> At one stage I thought about having a C++ object that could be created in QML and would somehow keep count of references to the game object. For example, each Loader whose source component has access to the game object would somehow register itself with the object, and the game wouldn’t start quitting until all references were gone. As long as the C++ doesn’t know about the UI, I think it could work quite well.
Unfortunately my QML knowledge is quite rudimentary, however I believe (correct me if I'm wrong) each component is a `QObject` instance and is parented to the parent component and so on until you reach the root context. So one thing that comes to mind is to "spy" (by installing an event filter) on the root context for when children are added or removed (QEvent::ChildAdded & QEvent::ChildRemoved) and if the children are Loaders then count them up. Respectively when they're destroyed you decrease the count and when it goes to zero you can unload/clean up. This approach would (hopefully) lift the need to do this:
GameScope {
game: root.game
}
Kind regards.
On Wed, Mar 23, 2016 at 10:41 AM, Curtis Mitch <mitch.curtis at theqtcompany.com<mailto:mitch.curtis at theqtcompany.com>> wrote:
Hi.
That does help, thanks. It means that I’d really need to use an arbitrarily long timer, or find the “proper” solution.
At one stage I thought about having a C++ object that could be created in QML and would somehow keep count of references to the game object. For example, each Loader whose source component has access to the game object would somehow register itself with the object, and the game wouldn’t start quitting until all references were gone. As long as the C++ doesn’t know about the UI, I think it could work quite well.
Something like this:
Loader {
// ... contains GameView
}
// GameView.qml
Item {
id: root
property alias game
GameScope {
game: root.game
}
}
// GameScope.cpp
GameScope::setGame(Game *game)
{
if (game == mGame)
return;
if (game)
game->increaseReferenceCount();
else
game->decreaseReferenceCount();
mGame = game;
}
GameScope::~GameScope()
{
if (game)
game->decreaseReferenceCount();
}
Each event loop after a quit has been requested, Game could check the reference count and begin the actual quitting if it’s 0.
It still feels like it shouldn’t be necessary, but at least there’s no guesswork involved.
From: Nye [mailto:kshegunov at gmail.com<mailto:kshegunov at gmail.com>]
Sent: Tuesday, 22 March 2016 10:33 PM
To: Curtis Mitch <mitch.curtis at theqtcompany.com<mailto:mitch.curtis at theqtcompany.com>>
Cc: interest at qt-project.org<mailto:interest at qt-project.org>
Subject: Re: [Interest] Ensuring that a queued invocation occurs after deferred deletion
Hello,
I don't work with QML, but I'm pretty sure the events are processed in the order of their appearance in the event queue. So if you have a `deleteLater` call (i.e. you have a QEvent::DeferredDelete, which is scheduled through the event loop) any queued call to a slot (i.e. QEvent::MetaCall) that was made before the deletion request should be happening before the actual deletion. So, if you're emitting signals from a single thread, their respective slots would be called in the order in which the signals had happened.
Now, with multiple threads it's a bit tricky, since there's the chance that two threads will be trying to post a deferred function invocation at the same time (hence the event queue is protected by a mutex). However that mutex can't guarantee in what order the events will be posted, or rather no one can.
> My only thought is to use a zero-second single-shot timer. The question is: is this guaranteed to happen *after* deferred deletion for a given iteration of an event loop?
This posts a timer event on the queue, so you can achieve the same with QMetaObject::invokeMethod(receiverObject, "method", Qt::QueuedConnection), and the same "restrictions" apply as mentioned above.
I hope this is of help.
Kind regards.
On Tue, Mar 22, 2016 at 7:50 PM, Curtis Mitch <mitch.curtis at theqtcompany.com<mailto:mitch.curtis at theqtcompany.com>> wrote:
I recently discovered [1] that Loader defers deletion of items via deleteLater(). Up until that point, I had been treating certain operations in my program as synchronous (as I haven't introduced threads yet). Now that I can't safely assume that UI items will be instantly destroyed, I have to convert these operations into asynchronous ones.
For example, previously, I had this code:
game.quitGame();
My idea is to turn it into this:
game.requestQuitGame();
Within this function, the Game object would set its "ready" property to false, emitting its associated property change signal so that Loaders can set active to false. Then, QMetaObject::invoke would be called with Qt::QueuedConnection to ensure that the Loader's deleteLater() calls would have been carried out *before* tearing down the game and its objects.
In order to confirm that invokeMethod() works the way I thought it did, I added the following debug statements to QEventLoop:
diff --git a/src/corelib/kernel/qeventloop.cpp b/src/corelib/kernel/qeventloop.cpp
index dca25ce..7dae9d0 100644
--- a/src/corelib/kernel/qeventloop.cpp
+++ b/src/corelib/kernel/qeventloop.cpp
@@ -151,6 +151,7 @@ bool QEventLoop::processEvents(ProcessEventsFlags flags)
\sa QCoreApplication::quit(), exit(), processEvents()
*/
+#include <QDebug>
int QEventLoop::exec(ProcessEventsFlags flags)
{
Q_D(QEventLoop);
@@ -200,8 +201,11 @@ int QEventLoop::exec(ProcessEventsFlags flags)
if (app && app->thread() == thread())
QCoreApplication::removePostedEvents(app, QEvent::Quit);
- while (!d->exit.loadAcquire())
+ while (!d->exit.loadAcquire()) {
+ qDebug() << Q_FUNC_INFO << "--- beginning event loop";
processEvents(flags | WaitForMoreEvents | EventLoopExec);
+ qDebug() << Q_FUNC_INFO << "--- ending event loop";
+ }
ref.exceptionCaught = false;
return d->returnCode.load();
It turns out that I misunderstood the documentation; it only says that the slot is invoked when control returns to the event loop of the receiver's thread. So, as I understand it, it's possible that the invocation could happen *before* the deferred deletion of the Loaders' items. As the documentation doesn't specify the order between these two things, I should probably assume that it's not safe to assume anything.
So, I'm left with the problem of how to ensure that a slot is invoked after the Loaders' items have been destroyed. My only thought is to use a zero-second single-shot timer. The question is: is this guaranteed to happen *after* deferred deletion for a given iteration of an event loop? I can't see such a guarantee in the documentation. I even checked the source code of e.g. qeventdispatcher_win.cpp to see if I could find anything, without success.
Another question that's in the back of my mind is: is there a better way to do this?
[1] https://bugreports.qt.io/browse/QTBUG-51995
[2] http://doc.qt.io/qt-5/qt.html#ConnectionType-enum
_______________________________________________
Interest mailing list
Interest at qt-project.org<mailto:Interest at qt-project.org>
http://lists.qt-project.org/mailman/listinfo/interest
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.qt-project.org/pipermail/interest/attachments/20160324/5d472e4d/attachment.html>
More information about the Interest
mailing list