From efe1926598c69a09c9365673bba6961a83936d49 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Tue, 17 Oct 2017 16:54:22 +0200 Subject: More fine-grained deferred property execution This allows Qt Quick Controls 2 to defer the execution of certain building blocks until needed. For example, a button control can defer its background item so that the default background is not executed at all when replaced by a custom background. First of all, this gives a massive performance boost for customized controls. Secondly, this avoids the most burning issue in QQC2, problems with asynchronous incubation ("Object destroyed during incubation"). Task-number: QTBUG-50992 Change-Id: If3616c9dac70e3a474a20070ad0452874d267164 Reviewed-by: Simon Hausmann --- tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp | 152 +++++++++++++++++++++++ 1 file changed, 152 insertions(+) (limited to 'tests/auto/qml/qqmllanguage') diff --git a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp index f4d31d9e60..d04a83cd5b 100644 --- a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp +++ b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp @@ -44,6 +44,7 @@ #include #include #include +#include #include "testtypes.h" #include "testhttpserver.h" @@ -249,6 +250,7 @@ private slots: void rootObjectInCreationNotForSubObjects(); void lazyDeferredSubObject(); void deferredProperties(); + void executeDeferredPropertiesOnce(); void noChildEvents(); @@ -4279,8 +4281,17 @@ void tst_qqmllanguage::deferredProperties() QQmlListProperty listProperty = object->property("listProperty").value>(); QCOMPARE(listProperty.count(&listProperty), 0); + QQmlData *qmlData = QQmlData::get(object.data()); + QVERIFY(qmlData); + + QCOMPARE(qmlData->deferredData.count(), 2); // MyDeferredListProperty.qml + deferredListProperty.qml + QCOMPARE(qmlData->deferredData.first()->bindings.count(), 3); // "innerobj", "innerlist1", "innerlist2" + QCOMPARE(qmlData->deferredData.last()->bindings.count(), 3); // "outerobj", "outerlist1", "outerlist2" + qmlExecuteDeferred(object.data()); + QCOMPARE(qmlData->deferredData.count(), 0); + innerObj = object->findChild(QStringLiteral("innerobj")); // MyDeferredListProperty.qml QVERIFY(innerObj); QCOMPARE(innerObj->property("wasCompleted"), QVariant(true)); @@ -4306,6 +4317,147 @@ void tst_qqmllanguage::deferredProperties() QCOMPARE(listProperty.at(&listProperty, 3)->property("wasCompleted"), QVariant(true)); } +static void beginDeferredOnce(QQmlEnginePrivate *enginePriv, + const QQmlProperty &property, QQmlComponentPrivate::DeferredState *deferredState) +{ + QObject *object = property.object(); + QQmlData *ddata = QQmlData::get(object); + Q_ASSERT(!ddata->deferredData.isEmpty()); + + int propertyIndex = property.index(); + + for (auto dit = ddata->deferredData.rbegin(); dit != ddata->deferredData.rend(); ++dit) { + QQmlData::DeferredData *deferData = *dit; + + auto range = deferData->bindings.equal_range(propertyIndex); + if (range.first == deferData->bindings.end()) + continue; + + QQmlComponentPrivate::ConstructionState *state = new QQmlComponentPrivate::ConstructionState; + state->completePending = true; + + QQmlContextData *creationContext = nullptr; + state->creator.reset(new QQmlObjectCreator(deferData->context->parent, deferData->compilationUnit, creationContext)); + + enginePriv->inProgressCreations++; + + typedef QMultiHash QV4PropertyBindingHash; + auto it = std::reverse_iterator(range.second); + auto last = std::reverse_iterator(range.first); + while (it != last) { + if (!state->creator->populateDeferredBinding(property, deferData, *it)) + state->errors << state->creator->errors; + ++it; + } + + deferredState->constructionStates += state; + + // Cleanup any remaining deferred bindings for this property, also in inner contexts, + // to avoid executing them later and overriding the property that was just populated. + while (dit != ddata->deferredData.rend()) { + (*dit)->bindings.remove(propertyIndex); + ++dit; + } + break; + } +} + +static void testExecuteDeferredOnce(const QQmlProperty &property) +{ + QObject *object = property.object(); + QQmlData *data = QQmlData::get(object); + if (data && !data->deferredData.isEmpty() && !data->wasDeleted(object)) { + QQmlEnginePrivate *ep = QQmlEnginePrivate::get(data->context->engine); + + QQmlComponentPrivate::DeferredState state; + beginDeferredOnce(ep, property, &state); + + // Release deferred data for those compilation units that no longer have deferred bindings + data->releaseDeferredData(); + + QQmlComponentPrivate::completeDeferred(ep, &state); + } +} + +void tst_qqmllanguage::executeDeferredPropertiesOnce() +{ + QQmlComponent component(&engine, testFile("deferredProperties.qml")); + VERIFY_ERRORS(0); + QScopedPointer object(component.create()); + QVERIFY(!object.isNull()); + + QObjectList innerObjsAtCreation = object->findChildren(QStringLiteral("innerobj")); + QVERIFY(innerObjsAtCreation.isEmpty()); + + QObjectList outerObjsAtCreation = object->findChildren(QStringLiteral("outerobj")); + QVERIFY(outerObjsAtCreation.isEmpty()); + + QObject *groupProperty = object->property("groupProperty").value(); + QVERIFY(!groupProperty); + + QQmlListProperty listProperty = object->property("listProperty").value>(); + QCOMPARE(listProperty.count(&listProperty), 0); + + QQmlData *qmlData = QQmlData::get(object.data()); + QVERIFY(qmlData); + + QCOMPARE(qmlData->deferredData.count(), 2); // MyDeferredListProperty.qml + deferredListProperty.qml + QCOMPARE(qmlData->deferredData.first()->bindings.count(), 3); // "innerobj", "innerlist1", "innerlist2" + QCOMPARE(qmlData->deferredData.last()->bindings.count(), 3); // "outerobj", "outerlist1", "outerlist2" + + // first execution creates the outer object + testExecuteDeferredOnce(QQmlProperty(object.data(), "groupProperty")); + + QCOMPARE(qmlData->deferredData.count(), 2); // MyDeferredListProperty.qml + deferredListProperty.qml + QCOMPARE(qmlData->deferredData.first()->bindings.count(), 2); // "innerlist1", "innerlist2" + QCOMPARE(qmlData->deferredData.last()->bindings.count(), 2); // "outerlist1", "outerlist2" + + QObjectList innerObjsAfterFirstExecute = object->findChildren(QStringLiteral("innerobj")); // MyDeferredListProperty.qml + QVERIFY(innerObjsAfterFirstExecute.isEmpty()); + + QObjectList outerObjsAfterFirstExecute = object->findChildren(QStringLiteral("outerobj")); // deferredListProperty.qml + QCOMPARE(outerObjsAfterFirstExecute.count(), 1); + QCOMPARE(outerObjsAfterFirstExecute.first()->property("wasCompleted"), QVariant(true)); + + groupProperty = object->property("groupProperty").value(); + QCOMPARE(groupProperty, outerObjsAfterFirstExecute.first()); + + listProperty = object->property("listProperty").value>(); + QCOMPARE(listProperty.count(&listProperty), 0); + + // re-execution does nothing (to avoid overriding the property) + testExecuteDeferredOnce(QQmlProperty(object.data(), "groupProperty")); + + QCOMPARE(qmlData->deferredData.count(), 2); // MyDeferredListProperty.qml + deferredListProperty.qml + QCOMPARE(qmlData->deferredData.first()->bindings.count(), 2); // "innerlist1", "innerlist2" + QCOMPARE(qmlData->deferredData.last()->bindings.count(), 2); // "outerlist1", "outerlist2" + + QObjectList innerObjsAfterSecondExecute = object->findChildren(QStringLiteral("innerobj")); // MyDeferredListProperty.qml + QVERIFY(innerObjsAfterSecondExecute.isEmpty()); + + QObjectList outerObjsAfterSecondExecute = object->findChildren(QStringLiteral("outerobj")); // deferredListProperty.qml + QCOMPARE(outerObjsAfterFirstExecute, outerObjsAfterSecondExecute); + + groupProperty = object->property("groupProperty").value(); + QCOMPARE(groupProperty, outerObjsAfterFirstExecute.first()); + + listProperty = object->property("listProperty").value>(); + QCOMPARE(listProperty.count(&listProperty), 0); + + // execution of a list property should execute all outer list bindings + testExecuteDeferredOnce(QQmlProperty(object.data(), "listProperty")); + + QCOMPARE(qmlData->deferredData.count(), 0); + + listProperty = object->property("listProperty").value>(); + QCOMPARE(listProperty.count(&listProperty), 2); + + QCOMPARE(listProperty.at(&listProperty, 0)->objectName(), QStringLiteral("outerlist1")); // deferredListProperty.qml + QCOMPARE(listProperty.at(&listProperty, 0)->property("wasCompleted"), QVariant(true)); + QCOMPARE(listProperty.at(&listProperty, 1)->objectName(), QStringLiteral("outerlist2")); // deferredListProperty.qml + QCOMPARE(listProperty.at(&listProperty, 1)->property("wasCompleted"), QVariant(true)); +} + void tst_qqmllanguage::noChildEvents() { QQmlComponent component(&engine); -- cgit v1.2.3