diff options
author | J-P Nurmi <jpnurmi@qt.io> | 2017-10-17 16:54:22 +0200 |
---|---|---|
committer | J-P Nurmi <jpnurmi@qt.io> | 2017-11-25 06:21:04 +0000 |
commit | efe1926598c69a09c9365673bba6961a83936d49 (patch) | |
tree | c944b5e30f90ea1e1a5525b3974b67e6f8da1ad6 /tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp | |
parent | c5567cc706afb954ff6f679f35fe96a078bd4f46 (diff) |
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 <simon.hausmann@qt.io>
Diffstat (limited to 'tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp')
-rw-r--r-- | tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp | 152 |
1 files changed, 152 insertions, 0 deletions
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 <private/qqmlglobal_p.h> #include <private/qqmlscriptstring_p.h> #include <private/qqmlvmemetaobject_p.h> +#include <private/qqmlcomponent_p.h> #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<QObject> listProperty = object->property("listProperty").value<QQmlListProperty<QObject>>(); 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<QObject *>(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<int, const QV4::CompiledData::Binding *> QV4PropertyBindingHash; + auto it = std::reverse_iterator<QV4PropertyBindingHash::iterator>(range.second); + auto last = std::reverse_iterator<QV4PropertyBindingHash::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<QObject> object(component.create()); + QVERIFY(!object.isNull()); + + QObjectList innerObjsAtCreation = object->findChildren<QObject *>(QStringLiteral("innerobj")); + QVERIFY(innerObjsAtCreation.isEmpty()); + + QObjectList outerObjsAtCreation = object->findChildren<QObject *>(QStringLiteral("outerobj")); + QVERIFY(outerObjsAtCreation.isEmpty()); + + QObject *groupProperty = object->property("groupProperty").value<QObject *>(); + QVERIFY(!groupProperty); + + QQmlListProperty<QObject> listProperty = object->property("listProperty").value<QQmlListProperty<QObject>>(); + 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<QObject *>(QStringLiteral("innerobj")); // MyDeferredListProperty.qml + QVERIFY(innerObjsAfterFirstExecute.isEmpty()); + + QObjectList outerObjsAfterFirstExecute = object->findChildren<QObject *>(QStringLiteral("outerobj")); // deferredListProperty.qml + QCOMPARE(outerObjsAfterFirstExecute.count(), 1); + QCOMPARE(outerObjsAfterFirstExecute.first()->property("wasCompleted"), QVariant(true)); + + groupProperty = object->property("groupProperty").value<QObject *>(); + QCOMPARE(groupProperty, outerObjsAfterFirstExecute.first()); + + listProperty = object->property("listProperty").value<QQmlListProperty<QObject>>(); + 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<QObject *>(QStringLiteral("innerobj")); // MyDeferredListProperty.qml + QVERIFY(innerObjsAfterSecondExecute.isEmpty()); + + QObjectList outerObjsAfterSecondExecute = object->findChildren<QObject *>(QStringLiteral("outerobj")); // deferredListProperty.qml + QCOMPARE(outerObjsAfterFirstExecute, outerObjsAfterSecondExecute); + + groupProperty = object->property("groupProperty").value<QObject *>(); + QCOMPARE(groupProperty, outerObjsAfterFirstExecute.first()); + + listProperty = object->property("listProperty").value<QQmlListProperty<QObject>>(); + 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<QQmlListProperty<QObject>>(); + 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); |