1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtQml module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qqmlincubator.h"
41#include "qqmlcomponent.h"
42#include "qqmlincubator_p.h"
43
44#include "qqmlexpression_p.h"
45#include "qqmlobjectcreator_p.h"
46
47void QQmlEnginePrivate::incubate(QQmlIncubator &i, QQmlContextData *forContext)
48{
49 QExplicitlySharedDataPointer<QQmlIncubatorPrivate> p(i.d);
50
51 QQmlIncubator::IncubationMode mode = i.incubationMode();
52
53 if (!incubationController)
54 mode = QQmlIncubator::Synchronous;
55
56 if (mode == QQmlIncubator::AsynchronousIfNested) {
57 mode = QQmlIncubator::Synchronous;
58
59 // Need to find the first constructing context and see if it is asynchronous
60 QExplicitlySharedDataPointer<QQmlIncubatorPrivate> parentIncubator;
61 QQmlContextData *cctxt = forContext;
62 while (cctxt) {
63 if (cctxt->incubator) {
64 parentIncubator = cctxt->incubator;
65 break;
66 }
67 cctxt = cctxt->parent;
68 }
69
70 if (parentIncubator && parentIncubator->isAsynchronous) {
71 mode = QQmlIncubator::Asynchronous;
72 p->waitingOnMe = parentIncubator;
73 parentIncubator->waitingFor.insert(p.data());
74 }
75 }
76
77 p->isAsynchronous = (mode != QQmlIncubator::Synchronous);
78
79 inProgressCreations++;
80
81 if (mode == QQmlIncubator::Synchronous) {
82 QRecursionWatcher<QQmlIncubatorPrivate, &QQmlIncubatorPrivate::recursion> watcher(p.data());
83
84 p->changeStatus(QQmlIncubator::Loading);
85
86 if (!watcher.hasRecursed()) {
87 QQmlInstantiationInterrupt i;
88 p->incubate(i);
89 }
90 } else {
91 incubatorList.insert(p.data());
92 incubatorCount++;
93
94 p->vmeGuard.guard(p->creator.data());
95 p->changeStatus(QQmlIncubator::Loading);
96
97 if (incubationController)
98 incubationController->incubatingObjectCountChanged(incubatorCount);
99 }
100}
101
102/*!
103Sets the engine's incubation \a controller. The engine can only have one active controller
104and it does not take ownership of it.
105
106\sa incubationController()
107*/
108void QQmlEngine::setIncubationController(QQmlIncubationController *controller)
109{
110 Q_D(QQmlEngine);
111 if (d->incubationController)
112 d->incubationController->d = nullptr;
113 d->incubationController = controller;
114 if (controller) controller->d = d;
115}
116
117/*!
118Returns the currently set incubation controller, or 0 if no controller has been set.
119
120\sa setIncubationController()
121*/
122QQmlIncubationController *QQmlEngine::incubationController() const
123{
124 Q_D(const QQmlEngine);
125 return d->incubationController;
126}
127
128QQmlIncubatorPrivate::QQmlIncubatorPrivate(QQmlIncubator *q, QQmlIncubator::IncubationMode m)
129 : q(q), status(QQmlIncubator::Null), mode(m), isAsynchronous(false), progress(Execute),
130 result(nullptr), enginePriv(nullptr), waitingOnMe(nullptr)
131{
132}
133
134QQmlIncubatorPrivate::~QQmlIncubatorPrivate()
135{
136 clear();
137}
138
139void QQmlIncubatorPrivate::clear()
140{
141 compilationUnit = nullptr;
142 if (next.isInList()) {
143 next.remove();
144 enginePriv->incubatorCount--;
145 QQmlIncubationController *controller = enginePriv->incubationController;
146 if (controller)
147 controller->incubatingObjectCountChanged(enginePriv->incubatorCount);
148 }
149 enginePriv = nullptr;
150 if (!rootContext.isNull()) {
151 rootContext->incubator = nullptr;
152 rootContext = nullptr;
153 }
154
155 if (nextWaitingFor.isInList()) {
156 Q_ASSERT(waitingOnMe);
157 nextWaitingFor.remove();
158 waitingOnMe = nullptr;
159 }
160
161 // if we're waiting on any incubators then they should be cleared too.
162 while (waitingFor.first()) {
163 QQmlIncubator * i = static_cast<QQmlIncubatorPrivate*>(waitingFor.first())->q;
164 if (i)
165 i->clear();
166 }
167
168 bool guardOk = vmeGuard.isOK();
169
170 vmeGuard.clear();
171 if (creator && guardOk)
172 creator->clear();
173 creator.reset(nullptr);
174}
175
176/*!
177\class QQmlIncubationController
178\brief QQmlIncubationController instances drive the progress of QQmlIncubators.
179\inmodule QtQml
180
181In order to behave asynchronously and not introduce stutters or freezes in an application,
182the process of creating objects a QQmlIncubators must be driven only during the
183application's idle time. QQmlIncubationController allows the application to control
184exactly when, how often and for how long this processing occurs.
185
186A QQmlIncubationController derived instance should be created and set on a
187QQmlEngine by calling the QQmlEngine::setIncubationController() method.
188Processing is then controlled by calling the QQmlIncubationController::incubateFor()
189or QQmlIncubationController::incubateWhile() methods as dictated by the application's
190requirements.
191
192For example, this is an example of a incubation controller that will incubate for a maximum
193of 5 milliseconds out of every 16 milliseconds.
194
195\code
196class PeriodicIncubationController : public QObject,
197 public QQmlIncubationController
198{
199public:
200 PeriodicIncubationController() {
201 startTimer(16);
202 }
203
204protected:
205 void timerEvent(QTimerEvent *) override {
206 incubateFor(5);
207 }
208};
209\endcode
210
211Although the previous example would work, it is not optimal. Real world incubation
212controllers should try and maximize the amount of idle time they consume - rather
213than a static amount like 5 milliseconds - while not disturbing the application.
214*/
215
216/*!
217Create a new incubation controller.
218*/
219QQmlIncubationController::QQmlIncubationController()
220: d(nullptr)
221{
222}
223
224/*! \internal */
225QQmlIncubationController::~QQmlIncubationController()
226{
227 if (d) QQmlEnginePrivate::get(d)->setIncubationController(nullptr);
228 d = nullptr;
229}
230
231/*!
232Return the QQmlEngine this incubation controller is set on, or 0 if it
233has not been set on any engine.
234*/
235QQmlEngine *QQmlIncubationController::engine() const
236{
237 return QQmlEnginePrivate::get(d);
238}
239
240/*!
241Return the number of objects currently incubating.
242*/
243int QQmlIncubationController::incubatingObjectCount() const
244{
245 return d ? d->incubatorCount : 0;
246}
247
248/*!
249Called when the number of incubating objects changes. \a incubatingObjectCount is the
250new number of incubating objects.
251
252The default implementation does nothing.
253*/
254void QQmlIncubationController::incubatingObjectCountChanged(int incubatingObjectCount)
255{
256 Q_UNUSED(incubatingObjectCount);
257}
258
259void QQmlIncubatorPrivate::forceCompletion(QQmlInstantiationInterrupt &i)
260{
261 while (QQmlIncubator::Loading == status) {
262 while (QQmlIncubator::Loading == status && !waitingFor.isEmpty())
263 static_cast<QQmlIncubatorPrivate *>(waitingFor.first())->forceCompletion(i);
264 if (QQmlIncubator::Loading == status)
265 incubate(i);
266 }
267}
268
269void QQmlIncubatorPrivate::incubate(QQmlInstantiationInterrupt &i)
270{
271 if (!compilationUnit)
272 return;
273
274 QExplicitlySharedDataPointer<QQmlIncubatorPrivate> protectThis(this);
275
276 QRecursionWatcher<QQmlIncubatorPrivate, &QQmlIncubatorPrivate::recursion> watcher(this);
277 // get a copy of the engine pointer as it might get reset;
278 QQmlEnginePrivate *enginePriv = this->enginePriv;
279
280 if (!vmeGuard.isOK()) {
281 QQmlError error;
282 error.setMessageType(QtInfoMsg);
283 error.setUrl(compilationUnit->url());
284 error.setDescription(QQmlComponent::tr("Object or context destroyed during incubation"));
285 errors << error;
286 progress = QQmlIncubatorPrivate::Completed;
287
288 goto finishIncubate;
289 }
290
291 vmeGuard.clear();
292
293 if (progress == QQmlIncubatorPrivate::Execute) {
294 enginePriv->referenceScarceResources();
295 QObject *tresult = nullptr;
296 tresult = creator->create(subComponentToCreate, /*parent*/nullptr, &i);
297 if (!tresult)
298 errors = creator->errors;
299 enginePriv->dereferenceScarceResources();
300
301 if (watcher.hasRecursed())
302 return;
303
304 result = tresult;
305 if (errors.isEmpty() && result == nullptr)
306 goto finishIncubate;
307
308 if (result) {
309 QQmlData *ddata = QQmlData::get(result);
310 Q_ASSERT(ddata);
311 //see QQmlComponent::beginCreate for explanation of indestructible
312 ddata->indestructible = true;
313 ddata->explicitIndestructibleSet = true;
314 ddata->rootObjectInCreation = false;
315 if (q)
316 q->setInitialState(result);
317 }
318
319 if (watcher.hasRecursed())
320 return;
321
322 if (errors.isEmpty())
323 progress = QQmlIncubatorPrivate::Completing;
324 else
325 progress = QQmlIncubatorPrivate::Completed;
326
327 changeStatus(calculateStatus());
328
329 if (watcher.hasRecursed())
330 return;
331
332 if (i.shouldInterrupt())
333 goto finishIncubate;
334 }
335
336 if (progress == QQmlIncubatorPrivate::Completing) {
337 do {
338 if (watcher.hasRecursed())
339 return;
340
341 QQmlContextData *ctxt = nullptr;
342 ctxt = creator->finalize(i);
343 if (ctxt) {
344 rootContext = ctxt;
345 progress = QQmlIncubatorPrivate::Completed;
346 goto finishIncubate;
347 }
348 } while (!i.shouldInterrupt());
349 }
350
351finishIncubate:
352 if (progress == QQmlIncubatorPrivate::Completed && waitingFor.isEmpty()) {
353 QExplicitlySharedDataPointer<QQmlIncubatorPrivate> isWaiting = waitingOnMe;
354 clear();
355
356 if (isWaiting) {
357 QRecursionWatcher<QQmlIncubatorPrivate, &QQmlIncubatorPrivate::recursion> watcher(isWaiting.data());
358 changeStatus(calculateStatus());
359 if (!watcher.hasRecursed())
360 isWaiting->incubate(i);
361 } else {
362 changeStatus(calculateStatus());
363 }
364
365 enginePriv->inProgressCreations--;
366
367 if (0 == enginePriv->inProgressCreations) {
368 while (enginePriv->erroredBindings)
369 enginePriv->warning(enginePriv->erroredBindings->removeError());
370 }
371 } else if (!creator.isNull()) {
372 vmeGuard.guard(creator.data());
373 }
374}
375
376/*!
377Incubate objects for \a msecs, or until there are no more objects to incubate.
378*/
379void QQmlIncubationController::incubateFor(int msecs)
380{
381 if (!d || !d->incubatorCount)
382 return;
383
384 QQmlInstantiationInterrupt i(msecs * 1000000);
385 i.reset();
386 do {
387 static_cast<QQmlIncubatorPrivate*>(d->incubatorList.first())->incubate(i);
388 } while (d && d->incubatorCount != 0 && !i.shouldInterrupt());
389}
390
391/*!
392Incubate objects while the bool pointed to by \a flag is true, or until there are no
393more objects to incubate, or up to \a msecs if \a msecs is not zero.
394
395Generally this method is used in conjunction with a thread or a UNIX signal that sets
396the bool pointed to by \a flag to false when it wants incubation to be interrupted.
397*/
398void QQmlIncubationController::incubateWhile(volatile bool *flag, int msecs)
399{
400 if (!d || !d->incubatorCount)
401 return;
402
403 QQmlInstantiationInterrupt i(flag, msecs * 1000000);
404 i.reset();
405 do {
406 static_cast<QQmlIncubatorPrivate*>(d->incubatorList.first())->incubate(i);
407 } while (d && d->incubatorCount != 0 && !i.shouldInterrupt());
408}
409
410/*!
411\class QQmlIncubator
412\brief The QQmlIncubator class allows QML objects to be created asynchronously.
413\inmodule QtQml
414
415Creating QML objects - like delegates in a view, or a new page in an application - can take
416a noticeable amount of time, especially on resource constrained mobile devices. When an
417application uses QQmlComponent::create() directly, the QML object instance is created
418synchronously which, depending on the complexity of the object, can cause noticeable pauses or
419stutters in the application.
420
421The use of QQmlIncubator gives more control over the creation of a QML object,
422including allowing it to be created asynchronously using application idle time. The following
423example shows a simple use of QQmlIncubator.
424
425\code
426QQmlIncubator incubator;
427component->create(incubator);
428
429while (!incubator.isReady()) {
430 QCoreApplication::processEvents(QEventLoop::AllEvents, 50);
431}
432
433QObject *object = incubator.object();
434\endcode
435
436Asynchronous incubators are controlled by a QQmlIncubationController that is
437set on the QQmlEngine, which lets the engine know when the application is idle and
438incubating objects should be processed. If an incubation controller is not set on the
439QQmlEngine, QQmlIncubator creates objects synchronously regardless of the
440specified IncubationMode.
441
442QQmlIncubator supports three incubation modes:
443\list
444\li Synchronous The creation occurs synchronously. That is, once the
445QQmlComponent::create() call returns, the incubator will already be in either the
446Error or Ready state. A synchronous incubator has no real advantage compared to using
447the synchronous creation methods on QQmlComponent directly, but it may simplify an
448application's implementation to use the same API for both synchronous and asynchronous
449creations.
450
451\li Asynchronous (default) The creation occurs asynchronously, assuming a
452QQmlIncubatorController is set on the QQmlEngine.
453
454The incubator will remain in the Loading state until either the creation is complete or an error
455occurs. The statusChanged() callback can be used to be notified of status changes.
456
457Applications should use the Asynchronous incubation mode to create objects that are not needed
458immediately. For example, the ListView type uses Asynchronous incubation to create objects
459that are slightly off screen while the list is being scrolled. If, during asynchronous creation,
460the object is needed immediately the QQmlIncubator::forceCompletion() method can be called
461to complete the creation process synchronously.
462
463\li AsynchronousIfNested The creation will occur asynchronously if part of a nested asynchronous
464creation, or synchronously if not.
465
466In most scenarios where a QML component wants the appearance of a synchronous
467instantiation, it should use this mode.
468
469This mode is best explained with an example. When the ListView type is first created, it needs
470to populate itself with an initial set of delegates to show. If the ListView was 400 pixels high,
471and each delegate was 100 pixels high, it would need to create four initial delegate instances. If
472the ListView used the Asynchronous incubation mode, the ListView would always be created empty and
473then, sometime later, the four initial items would appear.
474
475Conversely, if the ListView was to use the Synchronous incubation mode it would behave correctly
476but it may introduce stutters into the application. As QML would have to stop and instantiate the
477ListView's delegates synchronously, if the ListView was part of a QML component that was being
478instantiated asynchronously this would undo much of the benefit of asynchronous instantiation.
479
480The AsynchronousIfNested mode reconciles this problem. By using AsynchronousIfNested, the ListView
481delegates are instantiated asynchronously if the ListView itself is already part of an asynchronous
482instantiation, and synchronously otherwise. In the case of a nested asynchronous instantiation, the
483outer asynchronous instantiation will not complete until after all the nested instantiations have also
484completed. This ensures that by the time the outer asynchronous instantitation completes, inner
485items like ListView have already completed loading their initial delegates.
486
487It is almost always incorrect to use the Synchronous incubation mode - elements or components that
488want the appearance of synchronous instantiation, but without the downsides of introducing freezes
489or stutters into the application, should use the AsynchronousIfNested incubation mode.
490\endlist
491*/
492
493/*!
494Create a new incubator with the specified \a mode
495*/
496QQmlIncubator::QQmlIncubator(IncubationMode mode)
497 : d(new QQmlIncubatorPrivate(this, mode))
498{
499 d->ref.ref();
500}
501
502/*! \internal */
503QQmlIncubator::~QQmlIncubator()
504{
505 d->q = nullptr;
506
507 if (!d->ref.deref()) {
508 delete d;
509 }
510 d = nullptr;
511}
512
513/*!
514\enum QQmlIncubator::IncubationMode
515
516Specifies the mode the incubator operates in. Regardless of the incubation mode, a
517QQmlIncubator will behave synchronously if the QQmlEngine does not have
518a QQmlIncubationController set.
519
520\value Asynchronous The object will be created asynchronously.
521\value AsynchronousIfNested If the object is being created in a context that is already part
522of an asynchronous creation, this incubator will join that existing incubation and execute
523asynchronously. The existing incubation will not become Ready until both it and this
524incubation have completed. Otherwise, the incubation will execute synchronously.
525\value Synchronous The object will be created synchronously.
526*/
527
528/*!
529\enum QQmlIncubator::Status
530
531Specifies the status of the QQmlIncubator.
532
533\value Null Incubation is not in progress. Call QQmlComponent::create() to begin incubating.
534\value Ready The object is fully created and can be accessed by calling object().
535\value Loading The object is in the process of being created.
536\value Error An error occurred. The errors can be access by calling errors().
537*/
538
539/*!
540Clears the incubator. Any in-progress incubation is aborted. If the incubator is in the
541Ready state, the created object is \b not deleted.
542*/
543void QQmlIncubator::clear()
544{
545 QRecursionWatcher<QQmlIncubatorPrivate, &QQmlIncubatorPrivate::recursion> watcher(d);
546
547 Status s = status();
548
549 if (s == Null)
550 return;
551
552 QQmlEnginePrivate *enginePriv = d->enginePriv;
553 if (s == Loading) {
554 Q_ASSERT(d->compilationUnit);
555 if (d->result) d->result->deleteLater();
556 d->result = nullptr;
557 }
558
559 d->clear();
560
561 Q_ASSERT(d->compilationUnit.isNull());
562 Q_ASSERT(d->waitingOnMe.data() == nullptr);
563 Q_ASSERT(d->waitingFor.isEmpty());
564
565 d->errors.clear();
566 d->progress = QQmlIncubatorPrivate::Execute;
567 d->result = nullptr;
568
569 if (s == Loading) {
570 Q_ASSERT(enginePriv);
571
572 enginePriv->inProgressCreations--;
573 if (0 == enginePriv->inProgressCreations) {
574 while (enginePriv->erroredBindings)
575 enginePriv->warning(enginePriv->erroredBindings->removeError());
576 }
577 }
578
579 d->changeStatus(Null);
580}
581
582/*!
583Force any in-progress incubation to finish synchronously. Once this call
584returns, the incubator will not be in the Loading state.
585*/
586void QQmlIncubator::forceCompletion()
587{
588 QQmlInstantiationInterrupt i;
589 d->forceCompletion(i);
590}
591
592/*!
593Returns true if the incubator's status() is Null.
594*/
595bool QQmlIncubator::isNull() const
596{
597 return status() == Null;
598}
599
600/*!
601Returns true if the incubator's status() is Ready.
602*/
603bool QQmlIncubator::isReady() const
604{
605 return status() == Ready;
606}
607
608/*!
609Returns true if the incubator's status() is Error.
610*/
611bool QQmlIncubator::isError() const
612{
613 return status() == Error;
614}
615
616/*!
617Returns true if the incubator's status() is Loading.
618*/
619bool QQmlIncubator::isLoading() const
620{
621 return status() == Loading;
622}
623
624/*!
625Return the list of errors encountered while incubating the object.
626*/
627QList<QQmlError> QQmlIncubator::errors() const
628{
629 return d->errors;
630}
631
632/*!
633Return the incubation mode passed to the QQmlIncubator constructor.
634*/
635QQmlIncubator::IncubationMode QQmlIncubator::incubationMode() const
636{
637 return d->mode;
638}
639
640/*!
641Return the current status of the incubator.
642*/
643QQmlIncubator::Status QQmlIncubator::status() const
644{
645 return d->status;
646}
647
648/*!
649Return the incubated object if the status is Ready, otherwise 0.
650*/
651QObject *QQmlIncubator::object() const
652{
653 if (status() != Ready)
654 return nullptr;
655 else
656 return d->result;
657}
658
659/*!
660Called when the status of the incubator changes. \a status is the new status.
661
662The default implementation does nothing.
663*/
664void QQmlIncubator::statusChanged(Status status)
665{
666 Q_UNUSED(status);
667}
668
669/*!
670Called after the \a object is first created, but before property bindings are
671evaluated and, if applicable, QQmlParserStatus::componentComplete() is
672called. This is equivalent to the point between QQmlComponent::beginCreate()
673and QQmlComponent::completeCreate(), and can be used to assign initial values
674to the object's properties.
675
676The default implementation does nothing.
677*/
678void QQmlIncubator::setInitialState(QObject *object)
679{
680 Q_UNUSED(object);
681}
682
683void QQmlIncubatorPrivate::changeStatus(QQmlIncubator::Status s)
684{
685 if (s == status)
686 return;
687
688 status = s;
689 if (q)
690 q->statusChanged(status);
691}
692
693QQmlIncubator::Status QQmlIncubatorPrivate::calculateStatus() const
694{
695 if (!errors.isEmpty())
696 return QQmlIncubator::Error;
697 else if (result && progress == QQmlIncubatorPrivate::Completed && waitingFor.isEmpty())
698 return QQmlIncubator::Ready;
699 else if (compilationUnit)
700 return QQmlIncubator::Loading;
701 else
702 return QQmlIncubator::Null;
703}
704
705