1// Copyright (C) 2017 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qquickdeferredexecute_p_p.h"
5
6#include <QtCore/qhash.h>
7#include <QtQml/qqmlengine.h>
8#include <QtQml/private/qqmldata_p.h>
9#include <QtQml/private/qqmlcomponent_p.h>
10#include <QtQml/private/qqmlobjectcreator_p.h>
11
12#include <private/qv4resolvedtypereference_p.h>
13
14#include <deque>
15
16QT_BEGIN_NAMESPACE
17
18/*!
19 \internal
20
21 For the history behind why these functions were introduced, see
22 the comments of QTBUG-50992, specifically
23 \l {https://bugreports.qt.io/browse/QTBUG-50992?focusedId=325677&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-325677}{this}.
24
25 The first commit to the QML engine to support deferred properties seems to
26 be 59b8d719d6122d86a4cc650911788cc4d778ce29.
27
28 The first commit to add support for it in Controls was
29 458eb65f730178bc93ba7b18f0e4dd2a13efad2e.
30
31 In short, deferred execution solved two issues:
32
33 \list 1
34 \li Incubation issues when using asynchronous loaders, AKA QTBUG-50992.
35 \li Performance issues from creating two items unnecessarily when a
36 styled control was customized, which is explained in more detail
37 in the commit message of 458eb65f730178bc93ba7b18f0e4dd2a13efad2e.
38 \endlist
39
40 \sa qmlExecuteDeferred
41*/
42
43namespace QtQuickPrivate {
44
45static void cancelDeferred(QQmlData *ddata, int propertyIndex)
46{
47 auto dit = ddata->deferredData.rbegin();
48 while (dit != ddata->deferredData.rend()) {
49 (*dit)->bindings.remove(key: propertyIndex);
50 ++dit;
51 }
52}
53
54static bool beginDeferred(QQmlEnginePrivate *enginePriv, const QQmlProperty &property, QQmlComponentPrivate::DeferredState *deferredState)
55{
56 QObject *object = property.object();
57 QQmlData *ddata = QQmlData::get(object);
58 Q_ASSERT(!ddata->deferredData.isEmpty());
59
60 if (!ddata->propertyCache)
61 ddata->propertyCache = QQmlMetaType::propertyCache(metaObject: object->metaObject());
62
63 int propertyIndex = property.index();
64 int wasInProgress = enginePriv->inProgressCreations;
65
66 /* we don't want deferred properties to suddenly depend on arbitrary
67 other properties which might have trigerred the construction of
68 objects as a consequence of a read.
69 */
70 auto bindingStatus = QtPrivate::suspendCurrentBindingStatus();
71 auto cleanup = qScopeGuard(f: [&](){
72 QtPrivate::restoreBindingStatus(status: bindingStatus);
73 });
74
75 for (auto dit = ddata->deferredData.rbegin(); dit != ddata->deferredData.rend(); ++dit) {
76 QQmlData::DeferredData *deferData = *dit;
77
78 auto bindings = deferData->bindings;
79 auto range = bindings.equal_range(key: propertyIndex);
80 if (range.first == bindings.end())
81 continue;
82
83 QQmlComponentPrivate::ConstructionState state;
84 state.setCompletePending(true);
85
86 QQmlContextData *creationContext = nullptr;
87
88 state.initCreator(parentContext: deferData->context->parent(), compilationUnit: deferData->compilationUnit, creationContext, inlineComponentName: deferData->inlineComponentName);
89
90 enginePriv->inProgressCreations++;
91
92 std::deque<const QV4::CompiledData::Binding *> reversedBindings;
93 std::copy(first: range.first, last: range.second, result: std::front_inserter(x&: reversedBindings));
94 state.creator()->beginPopulateDeferred(context: deferData->context);
95 for (const QV4::CompiledData::Binding *binding : reversedBindings)
96 state.creator()->populateDeferredBinding(qmlProperty: property, deferredIndex: deferData->deferredIdx, binding);
97 state.creator()->finalizePopulateDeferred();
98 state.appendCreatorErrors();
99
100 deferredState->push_back(x: std::move(state));
101
102 // Cleanup any remaining deferred bindings for this property, also in inner contexts,
103 // to avoid executing them later and overriding the property that was just populated.
104 cancelDeferred(ddata, propertyIndex);
105 break;
106 }
107
108 return enginePriv->inProgressCreations > wasInProgress;
109}
110
111void beginDeferred(QObject *object, const QString &property,
112 QQuickUntypedDeferredPointer *delegate, bool isOwnState, QQmlEngine *engine)
113{
114 QQmlData *data = QQmlData::get(object);
115 // If object is an attached object, its QQmlData won't have a context, hence why we provide
116 // the option to pass an engine explicitly.
117 if (data && !data->deferredData.isEmpty() && !data->wasDeleted(object) && (data->context || engine)) {
118 QQmlEnginePrivate *ep = QQmlEnginePrivate::get(
119 e: data->context && data->context->engine() ? data->context->engine() : engine);
120
121 QQmlComponentPrivate::DeferredState state;
122 if (beginDeferred(enginePriv: ep, property: QQmlProperty(object, property), deferredState: &state)) {
123 if (QQmlComponentPrivate::DeferredState *delegateState = delegate->deferredState())
124 delegateState->swap(x&: state);
125 } else if (isOwnState) {
126 delegate->clearDeferredState();
127 }
128
129 // Release deferred data for those compilation units that no longer have deferred bindings
130 data->releaseDeferredData();
131 } else if (isOwnState) {
132 delegate->clearDeferredState();
133 }
134}
135
136void cancelDeferred(QObject *object, const QString &property)
137{
138 QQmlData *data = QQmlData::get(object);
139 if (data)
140 cancelDeferred(ddata: data, propertyIndex: QQmlProperty(object, property).index());
141}
142
143void completeDeferred(QObject *object, const QString &property, QQuickUntypedDeferredPointer *delegate,
144 QQmlEngine *engine)
145{
146 Q_UNUSED(property);
147 QQmlComponentPrivate::DeferredState *state = delegate->deferredState();
148 if (!state)
149 return;
150
151 QQmlData *data = QQmlData::get(object);
152 if (data && !data->wasDeleted(object)) {
153 /* we don't want deferred properties to suddenly depend on arbitrary
154 other properties which might have trigerred the construction of
155 objects as a consequence of a read.
156 */
157 auto bindingStatus = QtPrivate::suspendCurrentBindingStatus();
158 auto cleanup = qScopeGuard(f: [&](){
159 QtPrivate::restoreBindingStatus(status: bindingStatus);
160 });
161
162 QQmlComponentPrivate::DeferredState localState = std::move(*state);
163 delegate->clearDeferredState();
164 QQmlEnginePrivate *ep = QQmlEnginePrivate::get(
165 e: data->context && data->context->engine() ? data->context->engine() : engine);
166 QQmlComponentPrivate::completeDeferred(enginePriv: ep, deferredState: &localState);
167 } else {
168 delegate->clearDeferredState();
169 }
170}
171
172} // QtQuickPrivate
173
174QT_END_NAMESPACE
175

source code of qtdeclarative/src/quicktemplates/qquickdeferredexecute.cpp