> 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.cur...@theqtcompany.com > wrote: > 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> wrote: > >> I've explained the problems with referencing the game object in the bug >> report. :) >> >> >> ------------------------------ >> *From:* Nye <kshegu...@gmail.com> >> *Sent:* Thursday, 24 March 2016 17:45 >> >> *To:* Curtis Mitch >> *Cc:* 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> 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> >>> *Sent:* Thursday, 24 March 2016 11:25 >>> >>> *To:* Curtis Mitch >>> *Cc:* 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> 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> >>>> *Sent:* Thursday, 24 March 2016 01:41 >>>> *To:* Curtis Mitch >>>> >>>> *Cc:* 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> 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] >>>>> *Sent:* Tuesday, 22 March 2016 10:33 PM >>>>> *To:* Curtis Mitch <mitch.cur...@theqtcompany.com> >>>>> *Cc:* 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> 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 >>>>> http://lists.qt-project.org/mailman/listinfo/interest >>>>> >>>>> >>>>> >>>> >>>> >>> >> >
_______________________________________________ Interest mailing list Interest@qt-project.org http://lists.qt-project.org/mailman/listinfo/interest