[Interest] Ensuring that a queued invocation occurs after deferred deletion

Curtis Mitch mitch.curtis at theqtcompany.com
Thu Mar 24 18:21:49 CET 2016


I've explained the problems with referencing the game object in the bug report. :)


________________________________
From: Nye <kshegunov at gmail.com>
Sent: Thursday, 24 March 2016 17:45
To: Curtis Mitch
Cc: interest at qt-project.org
Subject: Re: [Interest] Ensuring that a queued invocation occurs after deferred deletion

> The "loadedComponents" thing is more or less the same as the GameScope thing (I ended up calling it GameReference), except you need to remember to increment the count yourself, whereas with GameScope, it does it automatically.

Mostly yes, except that this is tied to the loader, not to the loaded item.

> The problem with the code you posted is as I mentioned earlier: Loader never immediately deletes its items, so the code would still reference the game object when it shouldn't.

Code referencing the game object should be perfectly fine until all the loaders have finished unloading their respective components (which I assume is signaled by loader.status == Loader.Null), then you know that you can destroy() whatever you wish as destroying it will happen (even if loaders queue their destructions) after all the loaded components have been destroyed. Isn't this what you wanted in the first place, or am I misunderstanding?

On Thu, Mar 24, 2016 at 1:53 PM, Curtis Mitch <mitch.curtis at theqtcompany.com<mailto:mitch.curtis at theqtcompany.com>> wrote:

The "loadedComponents" thing is more or less the same as the GameScope thing (I ended up calling it GameReference), except you need to remember to increment the count yourself, whereas with GameScope, it does it automatically.


The problem with the code you posted is as I mentioned earlier: Loader never immediately deletes its items, so the code would still reference the game object when it shouldn't.


________________________________
From: Nye <kshegunov at gmail.com<mailto:kshegunov at gmail.com>>
Sent: Thursday, 24 March 2016 11:25

To: Curtis Mitch
Cc: interest at qt-project.org<mailto:interest at qt-project.org>
Subject: Re: [Interest] Ensuring that a queued invocation occurs after deferred deletion

Okay,
It might sound stupid, but why not notify the game from your loaders? Supposedly setting the source of the loader to NULL will unload the component (at least that's what the documentation says).
Something like this (please bear with my tortured QML knowledge):


Item {
    id: theGame

    property bool isReady: false
    property int loadedComponents: 0

    onStateChanged: {
        if (state == "invalid") {
            print("game.isReady about to change to false...")
            isReady = false;
            print("... game.isReady changed to " + isReady);
        }  else if (state == "running") {
            player = Qt.createQmlObject("import QtQuick 2.0; Item { property color color: 'black' }", window);

            print("game.isReady about to change to true...")
            isReady = true;
            print("... game.isReady changed to true")
        }
        else if (state == "finished") {
            // ... Everything was cleaned now ... supposedly
        }
    }

    onLoadedComponentsChanged: {
        if (state == "invalid" && loadedComponents == 0)  {
            player.destroy();
            player = null;

            state = "finished";
            print("... game.state changed to finished; nothing should reference properties of game now");
        }
    }

    property Item player
}

Loader {
    id: loader

    Connections {
        target: theGame
        onIsReadyChanged: {
            if (theGame.isReady) {
                loader.setSource("qrc:/LoaderItem.qml", { "game": theGame });
                theGame.loadedComponents++;
            } else {
                loader.source = null;
            }
        }
    }

    onStatusChanged:  {
        if (loader.status == Loader.Null)  {
            theGame.loadedComponents--;
        }
    }
}

On Thu, Mar 24, 2016 at 10:48 AM, Curtis Mitch <mitch.curtis at theqtcompany.com<mailto:mitch.curtis at theqtcompany.com>> wrote:

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<mailto:kshegunov at gmail.com>>
Sent: Thursday, 24 March 2016 01:41
To: Curtis Mitch

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,
> 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/defff9e0/attachment.html>


More information about the Interest mailing list