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

Nye kshegunov at gmail.com
Thu Mar 24 18:28:57 CET 2016


> 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.curtis at theqtcompany.com
> wrote:

> 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> 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>
>> *Sent:* Thursday, 24 March 2016 11:25
>>
>> *To:* Curtis Mitch
>> *Cc:* 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> 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>
>>> *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> 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]
>>>> *Sent:* Tuesday, 22 March 2016 10:33 PM
>>>> *To:* Curtis Mitch <mitch.curtis at theqtcompany.com>
>>>> *Cc:* 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> 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
>>>> 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/c05551b6/attachment.html>


More information about the Interest mailing list