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
178 In order to behave asynchronously and not introduce stutters or freezes in an application,
179 the process of creating objects a QQmlIncubators must be driven only during the
180 application's idle time. QQmlIncubationController allows the application to control
181 exactly when, how often and for how long this processing occurs.
183 A QQmlIncubationController derived instance should be created and set on a
184 QQmlEngine by calling the QQmlEngine::setIncubationController() method.
185 Processing is then controlled by calling the QQmlIncubationController::incubateFor()
186 or QQmlIncubationController::incubateWhile() methods as dictated by the application's
189 For example, this is an example of a incubation controller that will incubate for a maximum
190 of 5 milliseconds out of every 16 milliseconds.
193 class PeriodicIncubationController : public QObject,
194 public QQmlIncubationController
197 PeriodicIncubationController() {
202 virtual void timerEvent(QTimerEvent *) {
208 Although the previous example would work, it is not optimal. Real world incubation
209 controllers should try and maximize the amount of idle time they consume - rather
210 than a static amount like 5 milliseconds - while not disturbing the application.
214 Create a new incubation controller.
216 QQmlIncubationController::QQmlIncubationController()
222 QQmlIncubationController::~QQmlIncubationController()
224 if (d) QQmlEnginePrivate::get(d)->setIncubationController(0);
229 Return the QQmlEngine this incubation controller is set on, or 0 if it
230 has not been set on any engine.
232 QQmlEngine *QQmlIncubationController::engine() const
234 return QQmlEnginePrivate::get(d);
238 Return the number of objects currently incubating.
240 int QQmlIncubationController::incubatingObjectCount() const
243 return d->incubatorCount;
249 Called when the number of incubating objects changes. \a incubatingObjectCount is the
250 new number of incubating objects.
252 The default implementation does nothing.
254 void QQmlIncubationController::incubatingObjectCountChanged(int incubatingObjectCount)
256 Q_UNUSED(incubatingObjectCount);
259 void QQmlIncubatorPrivate::incubate(QQmlVME::Interrupt &i)
263 QML_MEMORY_SCOPE_URL(compiledData->url);
265 typedef QQmlIncubatorPrivate IP;
266 QRecursionWatcher<IP, &IP::recursion> watcher(this);
268 QQmlEngine *engine = compiledData->engine;
269 QQmlEnginePrivate *enginePriv = QQmlEnginePrivate::get(engine);
271 bool guardOk = vmeGuard.isOK();
276 error.setUrl(compiledData->url);
277 error.setDescription(QQmlComponent::tr("Object destroyed during incubation"));
279 progress = QQmlIncubatorPrivate::Completed;
284 if (progress == QQmlIncubatorPrivate::Execute) {
285 enginePriv->referenceScarceResources();
286 QObject *tresult = vme.execute(&errors, i);
287 enginePriv->dereferenceScarceResources();
289 if (watcher.hasRecursed())
293 if (errors.isEmpty() && result == 0)
297 QQmlData *ddata = QQmlData::get(result);
299 //see QQmlComponent::beginCreate for explanation of indestructible
300 ddata->indestructible = true;
301 ddata->explicitIndestructibleSet = true;
302 ddata->rootObjectInCreation = false;
303 q->setInitialState(result);
306 if (watcher.hasRecursed())
309 if (errors.isEmpty())
310 progress = QQmlIncubatorPrivate::Completing;
312 progress = QQmlIncubatorPrivate::Completed;
314 changeStatus(calculateStatus());
316 if (watcher.hasRecursed())
319 if (i.shouldInterrupt())
323 if (progress == QQmlIncubatorPrivate::Completing) {
325 if (watcher.hasRecursed())
328 QQmlContextData *ctxt = vme.complete(i);
331 progress = QQmlIncubatorPrivate::Completed;
334 } while (!i.shouldInterrupt());
338 if (progress == QQmlIncubatorPrivate::Completed && waitingFor.isEmpty()) {
339 typedef QQmlIncubatorPrivate IP;
341 QQmlIncubatorPrivate *isWaiting = waitingOnMe;
345 QRecursionWatcher<IP, &IP::recursion> watcher(isWaiting);
346 changeStatus(calculateStatus());
347 if (!watcher.hasRecursed())
348 isWaiting->incubate(i);
350 changeStatus(calculateStatus());
353 enginePriv->inProgressCreations--;
355 if (0 == enginePriv->inProgressCreations) {
356 while (enginePriv->erroredBindings) {
357 enginePriv->warning(enginePriv->erroredBindings);
358 enginePriv->erroredBindings->removeError();
362 vmeGuard.guard(&vme);
367 Incubate objects for \a msecs, or until there are no more objects to incubate.
369 void QQmlIncubationController::incubateFor(int msecs)
371 if (!d || d->incubatorCount == 0)
374 QQmlVME::Interrupt i(msecs * 1000000);
377 QQmlIncubatorPrivate *p = (QQmlIncubatorPrivate*)d->incubatorList.first();
379 } while (d && d->incubatorCount != 0 && !i.shouldInterrupt());
383 Incubate objects while the bool pointed to by \a flag is true, or until there are no
384 more objects to incubate, or up to msecs if msecs is not zero.
386 Generally this method is used in conjunction with a thread or a UNIX signal that sets
387 the bool pointed to by \a flag to false when it wants incubation to be interrupted.
389 void QQmlIncubationController::incubateWhile(volatile bool *flag, int msecs)
391 if (!d || d->incubatorCount == 0)
394 QQmlVME::Interrupt i(flag, msecs * 1000000);
397 QQmlIncubatorPrivate *p = (QQmlIncubatorPrivate*)d->incubatorList.first();
399 } while (d && d->incubatorCount != 0 && !i.shouldInterrupt());
404 \brief The QQmlIncubator class allows QML objects to be created asynchronously.
406 Creating QML objects - like delegates in a view, or a new page in an application - can take
407 a noticable amount of time, especially on resource constrained mobile devices. When an
408 application uses QQmlComponent::create() directly, the QML object instance is created
409 synchronously which, depending on the complexity of the object, can cause noticable pauses or
410 stutters in the application.
412 The use of QQmlIncubator gives more control over the creation of a QML object,
413 including allowing it to be created asynchronously using application idle time. The following
414 example shows a simple use of QQmlIncubator.
417 QQmlIncubator incubator;
418 component->create(incubator);
420 while (incubator.isReady()) {
421 QCoreApplication::processEvents(QEventLoop::AllEvents, 50);
424 QObject *object = incubator.object();
427 Asynchronous incubators are controlled by a QQmlIncubationController that is
428 set on the QQmlEngine, which lets the engine know when the application is idle and
429 incubating objects should be processed. If an incubation controller is not set on the
430 QQmlEngine, QQmlIncubator creates objects synchronously regardless of the
431 specified IncubationMode.
433 QQmlIncubator supports three incubation modes:
435 \li Synchronous The creation occurs synchronously. That is, once the
436 QQmlComponent::create() call returns, the incubator will already be in either the
437 Error or Ready state. A synchronous incubator has no real advantage compared to using
438 the synchronous creation methods on QQmlComponent directly, but it may simplify an
439 application's implementation to use the same API for both synchronous and asynchronous
442 \li Asynchronous (default) The creation occurs asynchronously, assuming a
443 QQmlIncubatorController is set on the QQmlEngine.
445 The incubator will remain in the Loading state until either the creation is complete or an error
446 occurs. The statusChanged() callback can be used to be notified of status changes.
448 Applications should use the Asynchronous incubation mode to create objects that are not needed
449 immediately. For example, the ListView type uses Asynchronous incubation to create objects
450 that are slightly off screen while the list is being scrolled. If, during asynchronous creation,
451 the object is needed immediately the QQmlIncubator::forceCompletion() method can be called
452 to complete the creation process synchronously.
454 \li AsynchronousIfNested The creation will occur asynchronously if part of a nested asynchronous
455 creation, or synchronously if not.
457 In most scenarios where a QML component wants the appearance of a synchronous
458 instantiation, it should use this mode.
460 This mode is best explained with an example. When the ListView type is first created, it needs
461 to populate itself with an initial set of delegates to show. If the ListView was 400 pixels high,
462 and each delegate was 100 pixels high, it would need to create four initial delegate instances. If
463 the ListView used the Asynchronous incubation mode, the ListView would always be created empty and
464 then, sometime later, the four initial items would appear.
466 Conversely, if the ListView was to use the Synchronous incubation mode it would behave correctly
467 but it may introduce stutters into the application. As QML would have to stop and instantiate the
468 ListView's delegates synchronously, if the ListView was part of a QML component that was being
469 instantiated asynchronously this would undo much of the benefit of asynchronous instantiation.
471 The AsynchronousIfNested mode reconciles this problem. By using AsynchronousIfNested, the ListView
472 delegates are instantiated asynchronously if the ListView itself is already part of an asynchronous
473 instantiation, and synchronously otherwise. In the case of a nested asynchronous instantiation, the
474 outer asynchronous instantiation will not complete until after all the nested instantiations have also
475 completed. This ensures that by the time the outer asynchronous instantitation completes, inner
476 items like ListView have already completed loading their initial delegates.
478 It is almost always incorrect to use the Synchronous incubation mode - elements or components that
479 want the appearance of synchronous instantiation, but without the downsides of introducing freezes
480 or stutters into the application, should use the AsynchronousIfNested incubation mode.
485 Create a new incubator with the specified \a mode
487 QQmlIncubator::QQmlIncubator(IncubationMode mode)
488 : d(new QQmlIncubatorPrivate(this, mode))
493 QQmlIncubator::~QQmlIncubator()
501 \enum QQmlIncubator::IncubationMode
503 Specifies the mode the incubator operates in. Regardless of the incubation mode, a
504 QQmlIncubator will behave synchronously if the QQmlEngine does not have
505 a QQmlIncubationController set.
507 \value Asynchronous The object will be created asynchronously.
508 \value AsynchronousIfNested If the object is being created in a context that is already part
509 of an asynchronous creation, this incubator will join that existing incubation and execute
510 asynchronously. The existing incubation will not become Ready until both it and this
511 incubation have completed. Otherwise, the incubation will execute synchronously.
512 \value Synchronous The object will be created synchronously.
516 \enum QQmlIncubator::Status
518 Specifies the status of the QQmlIncubator.
520 \value Null Incubation is not in progress. Call QQmlComponent::create() to begin incubating.
521 \value Ready The object is fully created and can be accessed by calling object().
522 \value Loading The object is in the process of being created.
523 \value Error An error occurred. The errors can be access by calling errors().
527 Clears the incubator. Any in-progress incubation is aborted. If the incubator is in the
528 Ready state, the created object is \b not deleted.
530 void QQmlIncubator::clear()
532 typedef QQmlIncubatorPrivate IP;
533 QRecursionWatcher<IP, &IP::recursion> watcher(d);
540 QQmlEnginePrivate *enginePriv = 0;
542 Q_ASSERT(d->compiledData);
543 enginePriv = QQmlEnginePrivate::get(d->compiledData->engine);
544 if (d->result) d->result->deleteLater();
550 // if we're waiting on any incubators then they should be cleared too.
551 while (d->waitingFor.first())
552 static_cast<QQmlIncubatorPrivate*>(d->waitingFor.first())->q->clear();
557 Q_ASSERT(d->compiledData == 0);
558 Q_ASSERT(d->waitingOnMe == 0);
559 Q_ASSERT(d->waitingFor.isEmpty());
560 Q_ASSERT(!d->nextWaitingFor.isInList());
563 d->progress = QQmlIncubatorPrivate::Execute;
567 Q_ASSERT(enginePriv);
569 enginePriv->inProgressCreations--;
570 if (0 == enginePriv->inProgressCreations) {
571 while (enginePriv->erroredBindings) {
572 enginePriv->warning(enginePriv->erroredBindings);
573 enginePriv->erroredBindings->removeError();
578 d->changeStatus(Null);
582 Force any in-progress incubation to finish synchronously. Once this call
583 returns, the incubator will not be in the Loading state.
585 void QQmlIncubator::forceCompletion()
587 QQmlVME::Interrupt i;
588 while (Loading == status()) {
589 while (Loading == status() && !d->waitingFor.isEmpty())
590 static_cast<QQmlIncubatorPrivate *>(d->waitingFor.first())->incubate(i);
591 if (Loading == status())
597 Returns true if the incubator's status() is Null.
599 bool QQmlIncubator::isNull() const
601 return status() == Null;
605 Returns true if the incubator's status() is Ready.
607 bool QQmlIncubator::isReady() const
609 return status() == Ready;
613 Returns true if the incubator's status() is Error.
615 bool QQmlIncubator::isError() const
617 return status() == Error;
621 Returns true if the incubator's status() is Loading.
623 bool QQmlIncubator::isLoading() const
625 return status() == Loading;
629 Return the list of errors encountered while incubating the object.
631 QList<QQmlError> QQmlIncubator::errors() const
637 Return the incubation mode passed to the QQmlIncubator constructor.
639 QQmlIncubator::IncubationMode QQmlIncubator::incubationMode() const
645 Return the current status of the incubator.
647 QQmlIncubator::Status QQmlIncubator::status() const
653 Return the incubated object if the status is Ready, otherwise 0.
655 QObject *QQmlIncubator::object() const
657 if (status() != Ready) return 0;
658 else return d->result;
662 Called when the status of the incubator changes. \a status is the new status.
664 The default implementation does nothing.
666 void QQmlIncubator::statusChanged(Status status)
672 Called after the object is first created, but before property bindings are
673 evaluated and, if applicable, QQmlParserStatus::componentComplete() is
674 called. This is equivalent to the point between QQmlComponent::beginCreate()
675 and QQmlComponent::endCreate(), and can be used to assign initial values
676 to the object's properties.
678 The default implementation does nothing.
680 void QQmlIncubator::setInitialState(QObject *object)
685 void QQmlIncubatorPrivate::changeStatus(QQmlIncubator::Status s)
691 q->statusChanged(status);
694 QQmlIncubator::Status QQmlIncubatorPrivate::calculateStatus() const
696 if (!errors.isEmpty())
697 return QQmlIncubator::Error;
698 else if (result && progress == QQmlIncubatorPrivate::Completed &&
699 waitingFor.isEmpty())
700 return QQmlIncubator::Ready;
701 else if (compiledData)
702 return QQmlIncubator::Loading;
704 return QQmlIncubator::Null;