1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the QtQml module of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include "qqmlincubator.h"
43 #include "qqmlcomponent.h"
44 #include "qqmlincubator_p.h"
46 #include "qqmlcompiler_p.h"
47 #include "qqmlexpression_p.h"
48 #include "qqmlmemoryprofiler_p.h"
51 // - check that the Component.onCompleted behavior is the same as 4.8 in the synchronous and
52 // async if nested cases
53 void QQmlEnginePrivate::incubate(QQmlIncubator &i, QQmlContextData *forContext)
55 QQmlIncubatorPrivate *p = i.d;
57 QQmlIncubator::IncubationMode mode = i.incubationMode();
59 if (!incubationController)
60 mode = QQmlIncubator::Synchronous;
62 if (mode == QQmlIncubator::AsynchronousIfNested) {
63 mode = QQmlIncubator::Synchronous;
65 // Need to find the first constructing context and see if it is asynchronous
66 QQmlIncubatorPrivate *parentIncubator = 0;
67 QQmlContextData *cctxt = forContext;
69 if (cctxt->activeVMEData) {
70 parentIncubator = (QQmlIncubatorPrivate *)cctxt->activeVMEData;
73 cctxt = cctxt->parent;
76 if (parentIncubator && parentIncubator->isAsynchronous) {
77 mode = QQmlIncubator::Asynchronous;
78 p->waitingOnMe = parentIncubator;
79 parentIncubator->waitingFor.insert(p);
83 p->isAsynchronous = (mode != QQmlIncubator::Synchronous);
85 inProgressCreations++;
87 if (mode == QQmlIncubator::Synchronous) {
88 typedef QQmlIncubatorPrivate IP;
89 QRecursionWatcher<IP, &IP::recursion> watcher(p);
91 p->changeStatus(QQmlIncubator::Loading);
93 if (!watcher.hasRecursed()) {
98 incubatorList.insert(p);
101 p->vmeGuard.guard(&p->vme);
102 p->changeStatus(QQmlIncubator::Loading);
104 if (incubationController)
105 incubationController->incubatingObjectCountChanged(incubatorCount);
110 Sets the engine's incubation \a controller. The engine can only have one active controller
111 and it does not take ownership of it.
113 \sa incubationController()
115 void QQmlEngine::setIncubationController(QQmlIncubationController *controller)
118 if (d->incubationController)
119 d->incubationController->d = 0;
120 d->incubationController = controller;
121 if (controller) controller->d = d;
125 Returns the currently set incubation controller, or 0 if no controller has been set.
127 \sa setIncubationController()
129 QQmlIncubationController *QQmlEngine::incubationController() const
131 Q_D(const QQmlEngine);
132 return d->incubationController;
135 QQmlIncubatorPrivate::QQmlIncubatorPrivate(QQmlIncubator *q,
136 QQmlIncubator::IncubationMode m)
137 : q(q), status(QQmlIncubator::Null), mode(m), isAsynchronous(false), progress(Execute),
138 result(0), compiledData(0), vme(this), waitingOnMe(0)
142 QQmlIncubatorPrivate::~QQmlIncubatorPrivate()
146 void QQmlIncubatorPrivate::clear()
148 if (next.isInList()) {
150 Q_ASSERT(compiledData);
151 QQmlEnginePrivate *enginePriv = QQmlEnginePrivate::get(compiledData->engine);
152 compiledData->release();
154 enginePriv->incubatorCount--;
155 QQmlIncubationController *controller = enginePriv->incubationController;
157 controller->incubatingObjectCountChanged(enginePriv->incubatorCount);
158 } else if (compiledData) {
159 compiledData->release();
162 if (!rootContext.isNull()) {
163 rootContext->activeVMEData = 0;
167 if (nextWaitingFor.isInList()) {
168 Q_ASSERT(waitingOnMe);
169 nextWaitingFor.remove();
175 \class QQmlIncubationController
176 \brief QQmlIncubationController instances drive the progress of QQmlIncubators
179 In order to behave asynchronously and not introduce stutters or freezes in an application,
180 the process of creating objects a QQmlIncubators must be driven only during the
181 application's idle time. QQmlIncubationController allows the application to control
182 exactly when, how often and for how long this processing occurs.
184 A QQmlIncubationController derived instance should be created and set on a
185 QQmlEngine by calling the QQmlEngine::setIncubationController() method.
186 Processing is then controlled by calling the QQmlIncubationController::incubateFor()
187 or QQmlIncubationController::incubateWhile() methods as dictated by the application's
190 For example, this is an example of a incubation controller that will incubate for a maximum
191 of 5 milliseconds out of every 16 milliseconds.
194 class PeriodicIncubationController : public QObject,
195 public QQmlIncubationController
198 PeriodicIncubationController() {
203 virtual void timerEvent(QTimerEvent *) {
209 Although the previous example would work, it is not optimal. Real world incubation
210 controllers should try and maximize the amount of idle time they consume - rather
211 than a static amount like 5 milliseconds - while not disturbing the application.
215 Create a new incubation controller.
217 QQmlIncubationController::QQmlIncubationController()
223 QQmlIncubationController::~QQmlIncubationController()
225 if (d) QQmlEnginePrivate::get(d)->setIncubationController(0);
230 Return the QQmlEngine this incubation controller is set on, or 0 if it
231 has not been set on any engine.
233 QQmlEngine *QQmlIncubationController::engine() const
235 return QQmlEnginePrivate::get(d);
239 Return the number of objects currently incubating.
241 int QQmlIncubationController::incubatingObjectCount() const
244 return d->incubatorCount;
250 Called when the number of incubating objects changes. \a incubatingObjectCount is the
251 new number of incubating objects.
253 The default implementation does nothing.
255 void QQmlIncubationController::incubatingObjectCountChanged(int incubatingObjectCount)
257 Q_UNUSED(incubatingObjectCount);
260 void QQmlIncubatorPrivate::incubate(QQmlVME::Interrupt &i)
264 QML_MEMORY_SCOPE_URL(compiledData->url);
266 typedef QQmlIncubatorPrivate IP;
267 QRecursionWatcher<IP, &IP::recursion> watcher(this);
269 QQmlEngine *engine = compiledData->engine;
270 QQmlEnginePrivate *enginePriv = QQmlEnginePrivate::get(engine);
272 bool guardOk = vmeGuard.isOK();
277 error.setUrl(compiledData->url);
278 error.setDescription(QQmlComponent::tr("Object destroyed during incubation"));
280 progress = QQmlIncubatorPrivate::Completed;
285 if (progress == QQmlIncubatorPrivate::Execute) {
286 enginePriv->referenceScarceResources();
287 QObject *tresult = vme.execute(&errors, i);
288 enginePriv->dereferenceScarceResources();
290 if (watcher.hasRecursed())
294 if (errors.isEmpty() && result == 0)
298 QQmlData *ddata = QQmlData::get(result);
300 //see QQmlComponent::beginCreate for explanation of indestructible
301 ddata->indestructible = true;
302 ddata->explicitIndestructibleSet = true;
303 ddata->rootObjectInCreation = false;
304 q->setInitialState(result);
307 if (watcher.hasRecursed())
310 if (errors.isEmpty())
311 progress = QQmlIncubatorPrivate::Completing;
313 progress = QQmlIncubatorPrivate::Completed;
315 changeStatus(calculateStatus());
317 if (watcher.hasRecursed())
320 if (i.shouldInterrupt())
324 if (progress == QQmlIncubatorPrivate::Completing) {
326 if (watcher.hasRecursed())
329 QQmlContextData *ctxt = vme.complete(i);
332 progress = QQmlIncubatorPrivate::Completed;
335 } while (!i.shouldInterrupt());
339 if (progress == QQmlIncubatorPrivate::Completed && waitingFor.isEmpty()) {
340 typedef QQmlIncubatorPrivate IP;
342 QQmlIncubatorPrivate *isWaiting = waitingOnMe;
346 QRecursionWatcher<IP, &IP::recursion> watcher(isWaiting);
347 changeStatus(calculateStatus());
348 if (!watcher.hasRecursed())
349 isWaiting->incubate(i);
351 changeStatus(calculateStatus());
354 enginePriv->inProgressCreations--;
356 if (0 == enginePriv->inProgressCreations) {
357 while (enginePriv->erroredBindings) {
358 enginePriv->warning(enginePriv->erroredBindings);
359 enginePriv->erroredBindings->removeError();
363 vmeGuard.guard(&vme);
368 Incubate objects for \a msecs, or until there are no more objects to incubate.
370 void QQmlIncubationController::incubateFor(int msecs)
372 if (!d || d->incubatorCount == 0)
375 QQmlVME::Interrupt i(msecs * 1000000);
378 QQmlIncubatorPrivate *p = (QQmlIncubatorPrivate*)d->incubatorList.first();
380 } while (d && d->incubatorCount != 0 && !i.shouldInterrupt());
384 Incubate objects while the bool pointed to by \a flag is true, or until there are no
385 more objects to incubate, or up to msecs if msecs is not zero.
387 Generally this method is used in conjunction with a thread or a UNIX signal that sets
388 the bool pointed to by \a flag to false when it wants incubation to be interrupted.
390 void QQmlIncubationController::incubateWhile(volatile bool *flag, int msecs)
392 if (!d || d->incubatorCount == 0)
395 QQmlVME::Interrupt i(flag, msecs * 1000000);
398 QQmlIncubatorPrivate *p = (QQmlIncubatorPrivate*)d->incubatorList.first();
400 } while (d && d->incubatorCount != 0 && !i.shouldInterrupt());
405 \brief The QQmlIncubator class allows QML objects to be created asynchronously.
408 Creating QML objects - like delegates in a view, or a new page in an application - can take
409 a noticable amount of time, especially on resource constrained mobile devices. When an
410 application uses QQmlComponent::create() directly, the QML object instance is created
411 synchronously which, depending on the complexity of the object, can cause noticable pauses or
412 stutters in the application.
414 The use of QQmlIncubator gives more control over the creation of a QML object,
415 including allowing it to be created asynchronously using application idle time. The following
416 example shows a simple use of QQmlIncubator.
419 QQmlIncubator incubator;
420 component->create(incubator);
422 while (incubator.isReady()) {
423 QCoreApplication::processEvents(QEventLoop::AllEvents, 50);
426 QObject *object = incubator.object();
429 Asynchronous incubators are controlled by a QQmlIncubationController that is
430 set on the QQmlEngine, which lets the engine know when the application is idle and
431 incubating objects should be processed. If an incubation controller is not set on the
432 QQmlEngine, QQmlIncubator creates objects synchronously regardless of the
433 specified IncubationMode.
435 QQmlIncubator supports three incubation modes:
437 \li Synchronous The creation occurs synchronously. That is, once the
438 QQmlComponent::create() call returns, the incubator will already be in either the
439 Error or Ready state. A synchronous incubator has no real advantage compared to using
440 the synchronous creation methods on QQmlComponent directly, but it may simplify an
441 application's implementation to use the same API for both synchronous and asynchronous
444 \li Asynchronous (default) The creation occurs asynchronously, assuming a
445 QQmlIncubatorController is set on the QQmlEngine.
447 The incubator will remain in the Loading state until either the creation is complete or an error
448 occurs. The statusChanged() callback can be used to be notified of status changes.
450 Applications should use the Asynchronous incubation mode to create objects that are not needed
451 immediately. For example, the ListView type uses Asynchronous incubation to create objects
452 that are slightly off screen while the list is being scrolled. If, during asynchronous creation,
453 the object is needed immediately the QQmlIncubator::forceCompletion() method can be called
454 to complete the creation process synchronously.
456 \li AsynchronousIfNested The creation will occur asynchronously if part of a nested asynchronous
457 creation, or synchronously if not.
459 In most scenarios where a QML component wants the appearance of a synchronous
460 instantiation, it should use this mode.
462 This mode is best explained with an example. When the ListView type is first created, it needs
463 to populate itself with an initial set of delegates to show. If the ListView was 400 pixels high,
464 and each delegate was 100 pixels high, it would need to create four initial delegate instances. If
465 the ListView used the Asynchronous incubation mode, the ListView would always be created empty and
466 then, sometime later, the four initial items would appear.
468 Conversely, if the ListView was to use the Synchronous incubation mode it would behave correctly
469 but it may introduce stutters into the application. As QML would have to stop and instantiate the
470 ListView's delegates synchronously, if the ListView was part of a QML component that was being
471 instantiated asynchronously this would undo much of the benefit of asynchronous instantiation.
473 The AsynchronousIfNested mode reconciles this problem. By using AsynchronousIfNested, the ListView
474 delegates are instantiated asynchronously if the ListView itself is already part of an asynchronous
475 instantiation, and synchronously otherwise. In the case of a nested asynchronous instantiation, the
476 outer asynchronous instantiation will not complete until after all the nested instantiations have also
477 completed. This ensures that by the time the outer asynchronous instantitation completes, inner
478 items like ListView have already completed loading their initial delegates.
480 It is almost always incorrect to use the Synchronous incubation mode - elements or components that
481 want the appearance of synchronous instantiation, but without the downsides of introducing freezes
482 or stutters into the application, should use the AsynchronousIfNested incubation mode.
487 Create a new incubator with the specified \a mode
489 QQmlIncubator::QQmlIncubator(IncubationMode mode)
490 : d(new QQmlIncubatorPrivate(this, mode))
495 QQmlIncubator::~QQmlIncubator()
503 \enum QQmlIncubator::IncubationMode
505 Specifies the mode the incubator operates in. Regardless of the incubation mode, a
506 QQmlIncubator will behave synchronously if the QQmlEngine does not have
507 a QQmlIncubationController set.
509 \value Asynchronous The object will be created asynchronously.
510 \value AsynchronousIfNested If the object is being created in a context that is already part
511 of an asynchronous creation, this incubator will join that existing incubation and execute
512 asynchronously. The existing incubation will not become Ready until both it and this
513 incubation have completed. Otherwise, the incubation will execute synchronously.
514 \value Synchronous The object will be created synchronously.
518 \enum QQmlIncubator::Status
520 Specifies the status of the QQmlIncubator.
522 \value Null Incubation is not in progress. Call QQmlComponent::create() to begin incubating.
523 \value Ready The object is fully created and can be accessed by calling object().
524 \value Loading The object is in the process of being created.
525 \value Error An error occurred. The errors can be access by calling errors().
529 Clears the incubator. Any in-progress incubation is aborted. If the incubator is in the
530 Ready state, the created object is \b not deleted.
532 void QQmlIncubator::clear()
534 typedef QQmlIncubatorPrivate IP;
535 QRecursionWatcher<IP, &IP::recursion> watcher(d);
542 QQmlEnginePrivate *enginePriv = 0;
544 Q_ASSERT(d->compiledData);
545 enginePriv = QQmlEnginePrivate::get(d->compiledData->engine);
546 if (d->result) d->result->deleteLater();
552 // if we're waiting on any incubators then they should be cleared too.
553 while (d->waitingFor.first())
554 static_cast<QQmlIncubatorPrivate*>(d->waitingFor.first())->q->clear();
559 Q_ASSERT(d->compiledData == 0);
560 Q_ASSERT(d->waitingOnMe == 0);
561 Q_ASSERT(d->waitingFor.isEmpty());
562 Q_ASSERT(!d->nextWaitingFor.isInList());
565 d->progress = QQmlIncubatorPrivate::Execute;
569 Q_ASSERT(enginePriv);
571 enginePriv->inProgressCreations--;
572 if (0 == enginePriv->inProgressCreations) {
573 while (enginePriv->erroredBindings) {
574 enginePriv->warning(enginePriv->erroredBindings);
575 enginePriv->erroredBindings->removeError();
580 d->changeStatus(Null);
584 Force any in-progress incubation to finish synchronously. Once this call
585 returns, the incubator will not be in the Loading state.
587 void QQmlIncubator::forceCompletion()
589 QQmlVME::Interrupt i;
590 while (Loading == status()) {
591 while (Loading == status() && !d->waitingFor.isEmpty())
592 static_cast<QQmlIncubatorPrivate *>(d->waitingFor.first())->incubate(i);
593 if (Loading == status())
599 Returns true if the incubator's status() is Null.
601 bool QQmlIncubator::isNull() const
603 return status() == Null;
607 Returns true if the incubator's status() is Ready.
609 bool QQmlIncubator::isReady() const
611 return status() == Ready;
615 Returns true if the incubator's status() is Error.
617 bool QQmlIncubator::isError() const
619 return status() == Error;
623 Returns true if the incubator's status() is Loading.
625 bool QQmlIncubator::isLoading() const
627 return status() == Loading;
631 Return the list of errors encountered while incubating the object.
633 QList<QQmlError> QQmlIncubator::errors() const
639 Return the incubation mode passed to the QQmlIncubator constructor.
641 QQmlIncubator::IncubationMode QQmlIncubator::incubationMode() const
647 Return the current status of the incubator.
649 QQmlIncubator::Status QQmlIncubator::status() const
655 Return the incubated object if the status is Ready, otherwise 0.
657 QObject *QQmlIncubator::object() const
659 if (status() != Ready) return 0;
660 else return d->result;
664 Called when the status of the incubator changes. \a status is the new status.
666 The default implementation does nothing.
668 void QQmlIncubator::statusChanged(Status status)
674 Called after the object is first created, but before property bindings are
675 evaluated and, if applicable, QQmlParserStatus::componentComplete() is
676 called. This is equivalent to the point between QQmlComponent::beginCreate()
677 and QQmlComponent::endCreate(), and can be used to assign initial values
678 to the object's properties.
680 The default implementation does nothing.
682 void QQmlIncubator::setInitialState(QObject *object)
687 void QQmlIncubatorPrivate::changeStatus(QQmlIncubator::Status s)
693 q->statusChanged(status);
696 QQmlIncubator::Status QQmlIncubatorPrivate::calculateStatus() const
698 if (!errors.isEmpty())
699 return QQmlIncubator::Error;
700 else if (result && progress == QQmlIncubatorPrivate::Completed &&
701 waitingFor.isEmpty())
702 return QQmlIncubator::Ready;
703 else if (compiledData)
704 return QQmlIncubator::Loading;
706 return QQmlIncubator::Null;