I've fixed it with the GameReference class.

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

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

That's why I moved player.destroy(); out of the if (state == "invalid") block. 
But my last comment applies: unless there's a way to track explicitly the 
objects' lifetimes in QML I don't see how you can fix this.

On Thu, Mar 24, 2016 at 7:21 PM, Curtis Mitch 
<mitch.cur...@theqtcompany.com<mailto:mitch.cur...@theqtcompany.com>> wrote:

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


________________________________
From: Nye <kshegu...@gmail.com<mailto:kshegu...@gmail.com>>
Sent: Thursday, 24 March 2016 17:45

To: Curtis Mitch
Cc: interest@qt-project.org<mailto:interest@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.cur...@theqtcompany.com<mailto:mitch.cur...@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 <kshegu...@gmail.com<mailto:kshegu...@gmail.com>>
Sent: Thursday, 24 March 2016 11:25

To: Curtis Mitch
Cc: interest@qt-project.org<mailto:interest@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.cur...@theqtcompany.com<mailto:mitch.cur...@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 <kshegu...@gmail.com<mailto:kshegu...@gmail.com>>
Sent: Thursday, 24 March 2016 01:41
To: Curtis Mitch

Cc: interest@qt-project.org<mailto:interest@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.cur...@theqtcompany.com<mailto:mitch.cur...@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:kshegu...@gmail.com<mailto:kshegu...@gmail.com>]
Sent: Tuesday, 22 March 2016 10:33 PM
To: Curtis Mitch 
<mitch.cur...@theqtcompany.com<mailto:mitch.cur...@theqtcompany.com>>
Cc: interest@qt-project.org<mailto:interest@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.cur...@theqtcompany.com<mailto:mitch.cur...@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@qt-project.org<mailto:Interest@qt-project.org>
http://lists.qt-project.org/mailman/listinfo/interest





_______________________________________________
Interest mailing list
Interest@qt-project.org
http://lists.qt-project.org/mailman/listinfo/interest

Reply via email to