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

Nye kshegunov at gmail.com
Thu Mar 24 18:20:29 CET 2016


Skimming through the source however, I'm not quite sure I reasoned
correctly in my previous mail, as it seems the Loader will just change its
status without tracking its children's lifetimes. So basically, Loader.Null
is set immediately when it is made inactive. Sadly, I have neither more
suggestions nor ideas how to fix this. With C++ you have the
QObject::destroyed signal to subscribe to, but I have no idea whether QML
exposes it (it's not documented in any case) or if would be of much help
(as this is already achieved in a similar fashion by your GameScope
approach).

Good luck!

On Thu, Mar 24, 2016 at 6:45 PM, Nye <kshegunov at gmail.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.
>
> 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/ac5dc063/attachment.html>


More information about the Interest mailing list