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 --- src/qml/qml/qqmldata_p.h | 3 + src/qml/qml/qqmlengine.cpp | 35 +++++- src/qml/qml/qqmlobjectcreator.cpp | 85 +++++++++++-- src/qml/qml/qqmlobjectcreator_p.h | 3 +- tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp | 152 +++++++++++++++++++++++ 5 files changed, 265 insertions(+), 13 deletions(-) diff --git a/src/qml/qml/qqmldata_p.h b/src/qml/qml/qqmldata_p.h index d692feb975..63994a392d 100644 --- a/src/qml/qml/qqmldata_p.h +++ b/src/qml/qml/qqmldata_p.h @@ -76,6 +76,7 @@ class QQmlNotifierEndpoint; namespace QV4 { namespace CompiledData { struct CompilationUnit; +struct Binding; } } @@ -216,12 +217,14 @@ public: struct DeferredData { unsigned int deferredIdx; + QMultiHash bindings; QV4::CompiledData::CompilationUnit *compilationUnit;//Not always the same as the other compilation unit QQmlContextData *context;//Could be either context or outerContext }; QV4::CompiledData::CompilationUnit *compilationUnit; QVector deferredData; + void deferData(int objectIndex, QV4::CompiledData::CompilationUnit *, QQmlContextData *); void releaseDeferredData(); QV4::WeakValue jsWrapper; diff --git a/src/qml/qml/qqmlengine.cpp b/src/qml/qml/qqmlengine.cpp index d99bec4c52..612c3439c1 100644 --- a/src/qml/qml/qqmlengine.cpp +++ b/src/qml/qml/qqmlengine.cpp @@ -1639,13 +1639,40 @@ void QQmlData::NotifyList::layout() todo = 0; } +void QQmlData::deferData(int objectIndex, QV4::CompiledData::CompilationUnit *compilationUnit, QQmlContextData *context) +{ + QQmlData::DeferredData *deferData = new QQmlData::DeferredData; + deferData->deferredIdx = objectIndex; + deferData->compilationUnit = compilationUnit; + deferData->compilationUnit->addref(); + deferData->context = context; + + const QV4::CompiledData::Object *compiledObject = compilationUnit->objectAt(objectIndex); + const QV4::CompiledData::BindingPropertyData &propertyData = compilationUnit->bindingPropertyDataPerObject.at(objectIndex); + + const QV4::CompiledData::Binding *binding = compiledObject->bindingTable(); + for (quint32 i = 0; i < compiledObject->nBindings; ++i, ++binding) { + const QQmlPropertyData *property = propertyData.at(i); + if (property && binding->flags & QV4::CompiledData::Binding::IsDeferredBinding) + deferData->bindings.insert(property->coreIndex(), binding); + } + + deferredData.append(deferData); +} + void QQmlData::releaseDeferredData() { - for (DeferredData *deferData : qAsConst(deferredData)) { - deferData->compilationUnit->release(); - delete deferData; + auto it = deferredData.begin(); + while (it != deferredData.end()) { + DeferredData *deferData = *it; + if (deferData->bindings.isEmpty()) { + deferData->compilationUnit->release(); + delete deferData; + it = deferredData.erase(it); + } else { + ++it; + } } - deferredData.clear(); } void QQmlData::addNotify(int index, QQmlNotifierEndpoint *endpoint) diff --git a/src/qml/qml/qqmlobjectcreator.cpp b/src/qml/qml/qqmlobjectcreator.cpp index b2f1421bcb..3663b06d55 100644 --- a/src/qml/qml/qqmlobjectcreator.cpp +++ b/src/qml/qml/qqmlobjectcreator.cpp @@ -233,6 +233,7 @@ QObject *QQmlObjectCreator::create(int subComponentIndex, QObject *parent, QQmlI return instance; } +// ### unify or keep in sync with populateDeferredBinding() bool QQmlObjectCreator::populateDeferredProperties(QObject *instance, QQmlData::DeferredData *deferredData) { QQmlData *declarativeData = QQmlData::get(instance); @@ -283,6 +284,80 @@ bool QQmlObjectCreator::populateDeferredProperties(QObject *instance, QQmlData:: qSwap(_qmlContext, qmlContext); qSwap(_scopeObject, scopeObject); + deferredData->bindings.clear(); + phase = ObjectsCreated; + + return errors.isEmpty(); +} + +// ### unify or keep in sync with populateDeferredProperties() +bool QQmlObjectCreator::populateDeferredBinding(const QQmlProperty &qmlProperty, QQmlData::DeferredData *deferredData, const QV4::CompiledData::Binding *binding) +{ + Q_ASSERT(binding->flags & QV4::CompiledData::Binding::IsDeferredBinding); + + QObject *instance = qmlProperty.object(); + QQmlData *declarativeData = QQmlData::get(instance); + context = deferredData->context; + sharedState->rootContext = context; + + QObject *bindingTarget = instance; + + QQmlRefPointer cache = declarativeData->propertyCache; + QQmlVMEMetaObject *vmeMetaObject = QQmlVMEMetaObject::get(instance); + + QObject *scopeObject = instance; + qSwap(_scopeObject, scopeObject); + + QV4::Scope valueScope(v4); + + Q_ASSERT(topLevelCreator); + if (!sharedState->allJavaScriptObjects) + sharedState->allJavaScriptObjects = valueScope.alloc(compilationUnit->totalObjectCount); + + QV4::QmlContext *qmlContext = static_cast(valueScope.alloc(1)); + + qSwap(_qmlContext, qmlContext); + + qSwap(_propertyCache, cache); + qSwap(_qobject, instance); + + int objectIndex = deferredData->deferredIdx; + qSwap(_compiledObjectIndex, objectIndex); + + const QV4::CompiledData::Object *obj = qmlUnit->objectAt(_compiledObjectIndex); + qSwap(_compiledObject, obj); + + qSwap(_ddata, declarativeData); + qSwap(_bindingTarget, bindingTarget); + qSwap(_vmeMetaObject, vmeMetaObject); + + QQmlListProperty savedList; + qSwap(_currentList, savedList); + + const QQmlPropertyData &property = QQmlPropertyPrivate::get(qmlProperty)->core; + + if (property.isQList()) { + void *argv[1] = { (void*)&_currentList }; + QMetaObject::metacall(_qobject, QMetaObject::ReadProperty, property.coreIndex(), argv); + } else if (_currentList.object) { + _currentList = QQmlListProperty(); + } + + setPropertyBinding(&property, binding); + + qSwap(_currentList, savedList); + + qSwap(_vmeMetaObject, vmeMetaObject); + qSwap(_bindingTarget, bindingTarget); + qSwap(_ddata, declarativeData); + qSwap(_compiledObject, obj); + qSwap(_compiledObjectIndex, objectIndex); + qSwap(_qobject, instance); + qSwap(_propertyCache, cache); + + qSwap(_qmlContext, qmlContext); + qSwap(_scopeObject, scopeObject); + phase = ObjectsCreated; return errors.isEmpty(); @@ -1341,14 +1416,8 @@ bool QQmlObjectCreator::populateInstance(int index, QObject *instance, QObject * qSwap(_propertyCache, cache); qSwap(_vmeMetaObject, vmeMetaObject); - if (_compiledObject->flags & QV4::CompiledData::Object::HasDeferredBindings) { - QQmlData::DeferredData *deferData = new QQmlData::DeferredData; - deferData->deferredIdx = _compiledObjectIndex; - deferData->compilationUnit = compilationUnit; - deferData->compilationUnit->addref(); - deferData->context = context; - _ddata->deferredData.append(deferData); - } + if (_compiledObject->flags & QV4::CompiledData::Object::HasDeferredBindings) + _ddata->deferData(_compiledObjectIndex, compilationUnit, context); if (_compiledObject->nFunctions > 0) setupFunctions(); diff --git a/src/qml/qml/qqmlobjectcreator_p.h b/src/qml/qml/qqmlobjectcreator_p.h index 0c2d427c58..e2371cb4f1 100644 --- a/src/qml/qml/qqmlobjectcreator_p.h +++ b/src/qml/qml/qqmlobjectcreator_p.h @@ -81,7 +81,7 @@ struct QQmlObjectCreatorSharedState : public QSharedData QRecursionNode recursionNode; }; -class QQmlObjectCreator +class Q_QML_PRIVATE_EXPORT QQmlObjectCreator { Q_DECLARE_TR_FUNCTIONS(QQmlObjectCreator) public: @@ -90,6 +90,7 @@ public: QObject *create(int subComponentIndex = -1, QObject *parent = 0, QQmlInstantiationInterrupt *interrupt = 0); bool populateDeferredProperties(QObject *instance, QQmlData::DeferredData *deferredData); + bool populateDeferredBinding(const QQmlProperty &qmlProperty, QQmlData::DeferredData *deferredData, const QV4::CompiledData::Binding *binding); QQmlContextData *finalize(QQmlInstantiationInterrupt &interrupt); void cancel(QObject *object); void clear(); 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