aboutsummaryrefslogtreecommitdiffstats
path: root/src/qml/qml/qqmlincubator.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/qml/qml/qqmlincubator.cpp')
-rw-r--r--src/qml/qml/qqmlincubator.cpp256
1 files changed, 178 insertions, 78 deletions
diff --git a/src/qml/qml/qqmlincubator.cpp b/src/qml/qml/qqmlincubator.cpp
index 39da550d63..5c18230450 100644
--- a/src/qml/qml/qqmlincubator.cpp
+++ b/src/qml/qml/qqmlincubator.cpp
@@ -1,51 +1,15 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the QtQml module of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qqmlincubator.h"
#include "qqmlcomponent.h"
#include "qqmlincubator_p.h"
-#include "qqmlexpression_p.h"
-#include "qqmlmemoryprofiler_p.h"
#include "qqmlobjectcreator_p.h"
+#include <private/qqmlcomponent_p.h>
-void QQmlEnginePrivate::incubate(QQmlIncubator &i, QQmlContextData *forContext)
+void QQmlEnginePrivate::incubate(
+ QQmlIncubator &i, const QQmlRefPointer<QQmlContextData> &forContext)
{
QExplicitlySharedDataPointer<QQmlIncubatorPrivate> p(i.d);
@@ -59,13 +23,13 @@ void QQmlEnginePrivate::incubate(QQmlIncubator &i, QQmlContextData *forContext)
// Need to find the first constructing context and see if it is asynchronous
QExplicitlySharedDataPointer<QQmlIncubatorPrivate> parentIncubator;
- QQmlContextData *cctxt = forContext;
+ QQmlRefPointer<QQmlContextData> cctxt = forContext;
while (cctxt) {
- if (cctxt->incubator) {
- parentIncubator = cctxt->incubator;
+ if (QQmlIncubatorPrivate *incubator = cctxt->incubator()) {
+ parentIncubator = incubator;
break;
}
- cctxt = cctxt->parent;
+ cctxt = cctxt->parent();
}
if (parentIncubator && parentIncubator->isAsynchronous) {
@@ -139,7 +103,10 @@ QQmlIncubatorPrivate::~QQmlIncubatorPrivate()
void QQmlIncubatorPrivate::clear()
{
- compilationUnit = nullptr;
+ // reset the tagged pointer
+ if (requiredPropertiesFromComponent)
+ requiredPropertiesFromComponent = decltype(requiredPropertiesFromComponent){};
+ compilationUnit.reset();
if (next.isInList()) {
next.remove();
enginePriv->incubatorCount--;
@@ -149,8 +116,9 @@ void QQmlIncubatorPrivate::clear()
}
enginePriv = nullptr;
if (!rootContext.isNull()) {
- rootContext->incubator = nullptr;
- rootContext = nullptr;
+ if (rootContext->incubator())
+ rootContext->setIncubator(nullptr);
+ rootContext.setContextData({});
}
if (nextWaitingFor.isInList()) {
@@ -209,9 +177,15 @@ protected:
};
\endcode
-Although the previous example would work, it is not optimal. Real world incubation
-controllers should try and maximize the amount of idle time they consume - rather
-than a static amount like 5 milliseconds - while not disturbing the application.
+Although the example works, it is heavily simplified. Real world incubation controllers
+try and maximize the amount of idle time they consume while not disturbing the
+application. Using a static amount of 5 milliseconds like above may both leave idle
+time on the table in some frames and disturb the application in others.
+
+\l{QQuickWindow}, \l{QQuickView}, and \l{QQuickWidget} all pre-create an incubation
+controller that spaces out incubation over multiple frames using a more intelligent
+algorithm. You rarely have to write your own.
+
*/
/*!
@@ -261,25 +235,38 @@ void QQmlIncubatorPrivate::forceCompletion(QQmlInstantiationInterrupt &i)
{
while (QQmlIncubator::Loading == status) {
while (QQmlIncubator::Loading == status && !waitingFor.isEmpty())
- static_cast<QQmlIncubatorPrivate *>(waitingFor.first())->forceCompletion(i);
+ waitingFor.first()->forceCompletion(i);
if (QQmlIncubator::Loading == status)
incubate(i);
}
}
+
void QQmlIncubatorPrivate::incubate(QQmlInstantiationInterrupt &i)
{
if (!compilationUnit)
return;
- QML_MEMORY_SCOPE_URL(compilationUnit->finalUrl());
-
QExplicitlySharedDataPointer<QQmlIncubatorPrivate> protectThis(this);
QRecursionWatcher<QQmlIncubatorPrivate, &QQmlIncubatorPrivate::recursion> watcher(this);
// get a copy of the engine pointer as it might get reset;
QQmlEnginePrivate *enginePriv = this->enginePriv;
+ // Incubating objects takes quite a bit more stack space than our usual V4 function
+ enum { EstimatedSizeInV4Frames = 2 };
+ QV4::ExecutionEngineCallDepthRecorder<EstimatedSizeInV4Frames> callDepthRecorder(
+ compilationUnit->engine);
+ if (callDepthRecorder.hasOverflow()) {
+ QQmlError error;
+ error.setMessageType(QtCriticalMsg);
+ error.setUrl(compilationUnit->url());
+ error.setDescription(QQmlComponent::tr("Maximum call stack size exceeded."));
+ errors << error;
+ progress = QQmlIncubatorPrivate::Completed;
+ goto finishIncubate;
+ }
+
if (!vmeGuard.isOK()) {
QQmlError error;
error.setMessageType(QtInfoMsg);
@@ -299,6 +286,21 @@ void QQmlIncubatorPrivate::incubate(QQmlInstantiationInterrupt &i)
tresult = creator->create(subComponentToCreate, /*parent*/nullptr, &i);
if (!tresult)
errors = creator->errors;
+ else {
+ RequiredProperties* requiredProperties = creator->requiredProperties();
+ for (auto it = initialProperties.cbegin(); it != initialProperties.cend(); ++it) {
+ auto component = tresult;
+ auto name = it.key();
+ QQmlProperty prop = QQmlComponentPrivate::removePropertyFromRequired(
+ component, name, requiredProperties, QQmlEnginePrivate::get(enginePriv));
+ if (!prop.isValid() || !prop.write(it.value())) {
+ QQmlError error{};
+ error.setUrl(compilationUnit->url());
+ error.setDescription(QLatin1String("Could not set property %1").arg(name));
+ errors.push_back(error);
+ }
+ }
+ }
enginePriv->dereferenceScarceResources();
if (watcher.hasRecursed())
@@ -315,8 +317,14 @@ void QQmlIncubatorPrivate::incubate(QQmlInstantiationInterrupt &i)
ddata->indestructible = true;
ddata->explicitIndestructibleSet = true;
ddata->rootObjectInCreation = false;
- if (q)
+ if (q) {
q->setInitialState(result);
+ if (creator && !creator->requiredProperties()->empty()) {
+ const RequiredProperties *unsetRequiredProperties = creator->requiredProperties();
+ for (const auto& unsetRequiredProperty: *unsetRequiredProperties)
+ errors << QQmlComponentPrivate::unsetRequiredPropertyToQQmlError(unsetRequiredProperty);
+ }
+ }
}
if (watcher.hasRecursed())
@@ -341,10 +349,8 @@ void QQmlIncubatorPrivate::incubate(QQmlInstantiationInterrupt &i)
if (watcher.hasRecursed())
return;
- QQmlContextData *ctxt = nullptr;
- ctxt = creator->finalize(i);
- if (ctxt) {
- rootContext = ctxt;
+ if (creator->finalize(i)) {
+ rootContext = creator->rootContext();
progress = QQmlIncubatorPrivate::Completed;
goto finishIncubate;
}
@@ -377,6 +383,37 @@ finishIncubate:
}
/*!
+ \internal
+ This is used to mimic the behavior of incubate when the
+ Component we want to incubate refers to a creatable
+ QQmlType (i.e., it is the result of loadFromModule).
+ */
+void QQmlIncubatorPrivate::incubateCppBasedComponent(QQmlComponent *component, QQmlContext *context)
+{
+ auto compPriv = QQmlComponentPrivate::get(component);
+ Q_ASSERT(compPriv->loadedType.isCreatable());
+ std::unique_ptr<QObject> object(component->beginCreate(context));
+ component->setInitialProperties(object.get(), initialProperties);
+ if (auto props = compPriv->state.requiredProperties()) {
+ requiredPropertiesFromComponent = props;
+ requiredPropertiesFromComponent.setTag(HadTopLevelRequired::Yes);
+ }
+ q->setInitialState(object.get());
+ if (requiredPropertiesFromComponent && !requiredPropertiesFromComponent->isEmpty()) {
+ for (const RequiredPropertyInfo &unsetRequiredProperty :
+ std::as_const(*requiredPropertiesFromComponent)) {
+ errors << QQmlComponentPrivate::unsetRequiredPropertyToQQmlError(unsetRequiredProperty);
+ }
+ } else {
+ compPriv->completeCreate();
+ result = object.release();
+ progress = QQmlIncubatorPrivate::Completed;
+ }
+ changeStatus(calculateStatus());
+
+}
+
+/*!
Incubate objects for \a msecs, or until there are no more objects to incubate.
*/
void QQmlIncubationController::incubateFor(int msecs)
@@ -384,27 +421,31 @@ void QQmlIncubationController::incubateFor(int msecs)
if (!d || !d->incubatorCount)
return;
- QQmlInstantiationInterrupt i(msecs * 1000000);
- i.reset();
+ QDeadlineTimer deadline(msecs);
+ QQmlInstantiationInterrupt i(deadline);
do {
static_cast<QQmlIncubatorPrivate*>(d->incubatorList.first())->incubate(i);
} while (d && d->incubatorCount != 0 && !i.shouldInterrupt());
}
/*!
-Incubate objects while the bool pointed to by \a flag is true, or until there are no
-more objects to incubate, or up to \a msecs if \a msecs is not zero.
+\since 5.15
+
+Incubate objects while the atomic bool pointed to by \a flag is true,
+or until there are no more objects to incubate, or up to \a msecs if \a
+msecs is not zero.
Generally this method is used in conjunction with a thread or a UNIX signal that sets
the bool pointed to by \a flag to false when it wants incubation to be interrupted.
+
+\note \a flag is read using acquire memory ordering.
*/
-void QQmlIncubationController::incubateWhile(volatile bool *flag, int msecs)
+void QQmlIncubationController::incubateWhile(std::atomic<bool> *flag, int msecs)
{
if (!d || !d->incubatorCount)
return;
- QQmlInstantiationInterrupt i(flag, msecs * 1000000);
- i.reset();
+ QQmlInstantiationInterrupt i(flag, msecs ? QDeadlineTimer(msecs) : QDeadlineTimer::Forever);
do {
static_cast<QQmlIncubatorPrivate*>(d->incubatorList.first())->incubate(i);
} while (d && d->incubatorCount != 0 && !i.shouldInterrupt());
@@ -422,25 +463,37 @@ synchronously which, depending on the complexity of the object, can cause notic
stutters in the application.
The use of QQmlIncubator gives more control over the creation of a QML object,
-including allowing it to be created asynchronously using application idle time. The following
+including allowing it to be created asynchronously using application idle time. The following
example shows a simple use of QQmlIncubator.
\code
+// Initialize the incubator
QQmlIncubator incubator;
component->create(incubator);
+\endcode
-while (!incubator.isReady()) {
- QCoreApplication::processEvents(QEventLoop::AllEvents, 50);
-}
+Let the incubator run for a while (normally by returning control to the event loop),
+then poll it. There are a number of ways to get back to the incubator later. You may
+want to connect to one of the signals sent by \l{QQuickWindow}, or you may want to run
+a \l{QTimer} especially for that. You may also need the object for some specific
+purpose and poll the incubator when that purpose arises.
-QObject *object = incubator.object();
+\code
+// Poll the incubator
+if (incubator.isReady()) {
+ QObject *object = incubator.object();
+ // Use created object
+}
\endcode
-Asynchronous incubators are controlled by a QQmlIncubationController that is
-set on the QQmlEngine, which lets the engine know when the application is idle and
+Asynchronous incubators are controlled by a \l{QQmlIncubationController} that is
+set on the \l{QQmlEngine}, which lets the engine know when the application is idle and
incubating objects should be processed. If an incubation controller is not set on the
-QQmlEngine, QQmlIncubator creates objects synchronously regardless of the
-specified IncubationMode.
+\l{QQmlEngine}, \l{QQmlIncubator} creates objects synchronously regardless of the
+specified IncubationMode. By default, no incubation controller is set. However,
+\l{QQuickView}, \l{QQuickWindow} and \l{QQuickWidget} all set incubation controllers
+on their respective \l{QQmlEngine}s. These incubation controllers space out incubations
+across multiple frames while the view is being rendered.
QQmlIncubator supports three incubation modes:
\list
@@ -660,6 +713,43 @@ QObject *QQmlIncubator::object() const
}
/*!
+Return a pointer to a list of properties which are required but haven't
+been set yet.
+This list can be modified, so that subclasses which implement special logic
+setInitialProperties can mark properties set there as no longer required.
+
+\sa QQmlIncubator::setInitialProperties
+\since 5.15
+*/
+RequiredProperties *QQmlIncubatorPrivate::requiredProperties()
+{
+ if (creator)
+ return creator->requiredProperties();
+ else
+ return requiredPropertiesFromComponent.data();
+}
+
+bool QQmlIncubatorPrivate::hadTopLevelRequiredProperties() const
+{
+ if (creator)
+ return creator->componentHadTopLevelRequiredProperties();
+ else
+ return requiredPropertiesFromComponent.tag() == HadTopLevelRequired::Yes;
+}
+
+/*!
+Stores a mapping from property names to initial values, contained in
+\a initialProperties, with which the incubated component will be initialized.
+
+\sa QQmlComponent::setInitialProperties
+\since 5.15
+*/
+void QQmlIncubator::setInitialProperties(const QVariantMap &initialProperties)
+{
+ d->initialProperties = initialProperties;
+}
+
+/*!
Called when the status of the incubator changes. \a status is the new status.
The default implementation does nothing.
@@ -670,13 +760,23 @@ void QQmlIncubator::statusChanged(Status status)
}
/*!
-Called after the \a object is first created, but before property bindings are
-evaluated and, if applicable, QQmlParserStatus::componentComplete() is
-called. This is equivalent to the point between QQmlComponent::beginCreate()
+Called after the \a object is first created, but before complex property
+bindings are evaluated and, if applicable, QQmlParserStatus::componentComplete()
+is called. This is equivalent to the point between QQmlComponent::beginCreate()
and QQmlComponent::completeCreate(), and can be used to assign initial values
to the object's properties.
The default implementation does nothing.
+
+\note Simple bindings such as numeric literals are evaluated before
+setInitialState() is called. The categorization of bindings into simple and
+complex ones is intentionally unspecified and may change between versions of
+Qt and depending on whether and how you are using \l{qmlcachegen}. You should
+not rely on any particular binding to be evaluated either before or after
+setInitialState() is called. For example, a constant expression like
+\e{MyType.EnumValue} may be recognized as such at compile time or deferred
+to be executed as binding. The same holds for constant expressions like
+\e{-(5)} or \e{"a" + " constant string"}.
*/
void QQmlIncubator::setInitialState(QObject *object)
{