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

Nye kshegunov at gmail.com
Thu Mar 24 18:44:34 CET 2016


> I've fixed it with the GameReference class.

It seems an overkill to use a number of QObject instances to track a simple
integer, but I'm glad it's working at least.
Sorry I couldn't be of more help, but no one had been kind of enough to
pitch in and put me out of my misery.  :D

Kind regards.

On Thu, Mar 24, 2016 at 7:35 PM, Curtis Mitch <mitch.curtis at theqtcompany.com
> wrote:

> I've fixed it with the GameReference class.
>
>
> ------------------------------
> *From:* Nye <kshegunov at gmail.com>
> *Sent:* Thursday, 24 March 2016 18:28
>
> *To:* Curtis Mitch
> *Cc:* interest at 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.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/49c236a9/attachment.html>


More information about the Interest mailing list