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 QtDeclarative 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 "qdeclarativeincubator.h"
43 #include "qdeclarativecomponent.h"
44 #include "qdeclarativeincubator_p.h"
46 #include "qdeclarativecompiler_p.h"
47 #include "qdeclarativeexpression_p.h"
50 // - check that the Component.onCompleted behavior is the same as 4.8 in the synchronous and
51 // async if nested cases
52 void QDeclarativeEnginePrivate::incubate(QDeclarativeIncubator &i, QDeclarativeContextData *forContext)
54 QDeclarativeIncubatorPrivate *p = i.d;
56 QDeclarativeIncubator::IncubationMode mode = i.incubationMode();
58 if (!incubationController)
59 mode = QDeclarativeIncubator::Synchronous;
61 if (mode == QDeclarativeIncubator::AsynchronousIfNested) {
62 mode = QDeclarativeIncubator::Synchronous;
64 // Need to find the first constructing context and see if it is asynchronous
65 QDeclarativeIncubatorPrivate *parentIncubator = 0;
66 QDeclarativeContextData *cctxt = forContext;
68 if (cctxt->activeVMEData) {
69 parentIncubator = (QDeclarativeIncubatorPrivate *)cctxt->activeVMEData;
72 cctxt = cctxt->parent;
75 if (parentIncubator && parentIncubator->isAsynchronous) {
76 mode = QDeclarativeIncubator::Asynchronous;
77 p->waitingOnMe = parentIncubator;
78 parentIncubator->waitingFor.insert(p);
82 p->isAsynchronous = (mode != QDeclarativeIncubator::Synchronous);
84 inProgressCreations++;
86 if (mode == QDeclarativeIncubator::Synchronous) {
87 typedef QDeclarativeIncubatorPrivate IP;
88 QRecursionWatcher<IP, &IP::recursion> watcher(p);
90 p->changeStatus(QDeclarativeIncubator::Loading);
92 if (!watcher.hasRecursed()) {
93 QDeclarativeVME::Interrupt i;
97 incubatorList.insert(p);
100 p->vmeGuard.guard(&p->vme);
101 p->changeStatus(QDeclarativeIncubator::Loading);
103 if (incubationController)
104 incubationController->incubatingObjectCountChanged(incubatorCount);
109 Sets the engine's incubation \a controller. The engine can only have one active controller
110 and it does not take ownership of it.
112 \sa incubationController()
114 void QDeclarativeEngine::setIncubationController(QDeclarativeIncubationController *controller)
116 Q_D(QDeclarativeEngine);
117 if (d->incubationController)
118 d->incubationController->d = 0;
119 d->incubationController = controller;
120 if (controller) controller->d = d;
124 Returns the currently set incubation controller, or 0 if no controller has been set.
126 \sa setIncubationController()
128 QDeclarativeIncubationController *QDeclarativeEngine::incubationController() const
130 Q_D(const QDeclarativeEngine);
131 return d->incubationController;
134 QDeclarativeIncubatorPrivate::QDeclarativeIncubatorPrivate(QDeclarativeIncubator *q,
135 QDeclarativeIncubator::IncubationMode m)
136 : q(q), status(QDeclarativeIncubator::Null), mode(m), isAsynchronous(false), progress(Execute),
137 result(0), component(0), vme(this), waitingOnMe(0)
141 QDeclarativeIncubatorPrivate::~QDeclarativeIncubatorPrivate()
145 void QDeclarativeIncubatorPrivate::clear()
147 if (next.isInList()) {
150 QDeclarativeEnginePrivate *enginePriv = QDeclarativeEnginePrivate::get(component->engine);
151 component->release();
153 enginePriv->incubatorCount--;
154 QDeclarativeIncubationController *controller = enginePriv->incubationController;
156 controller->incubatingObjectCountChanged(enginePriv->incubatorCount);
157 } else if (component) {
158 component->release();
161 if (!rootContext.isNull()) {
162 rootContext->activeVMEData = 0;
166 if (nextWaitingFor.isInList()) {
167 Q_ASSERT(waitingOnMe);
168 nextWaitingFor.remove();
174 \class QDeclarativeIncubationController
175 \brief QDeclarativeIncubationController instances drive the progress of QDeclarativeIncubators
177 In order to behave asynchronously and not introduce stutters or freezes in an application,
178 the process of creating objects a QDeclarativeIncubators must be driven only during the
179 application's idle time. QDeclarativeIncubationController allows the application to control
180 exactly when, how often and for how long this processing occurs.
182 A QDeclarativeIncubationController derived instance should be created and set on a
183 QDeclarativeEngine by calling the QDeclarativeEngine::setIncubationController() method.
184 Processing is then controlled by calling the QDeclarativeIncubationController::incubateFor()
185 or QDeclarativeIncubationController::incubateWhile() methods as dictated by the application's
188 For example, this is an example of a incubation controller that will incubate for a maximum
189 of 5 milliseconds out of every 16 milliseconds.
192 class PeriodicIncubationController : public QObject,
193 public QDeclarativeIncubationController
196 PeriodicIncubationController() {
201 virtual void timerEvent(QTimerEvent *) {
207 Although the previous example would work, it is not optimal. Real world incubation
208 controllers should try and maximize the amount of idle time they consume - rather
209 than a static amount like 5 milliseconds - while not disturbing the application.
213 Create a new incubation controller.
215 QDeclarativeIncubationController::QDeclarativeIncubationController()
221 QDeclarativeIncubationController::~QDeclarativeIncubationController()
223 if (d) QDeclarativeEnginePrivate::get(d)->setIncubationController(0);
228 Return the QDeclarativeEngine this incubation controller is set on, or 0 if it
229 has not been set on any engine.
231 QDeclarativeEngine *QDeclarativeIncubationController::engine() const
233 return QDeclarativeEnginePrivate::get(d);
237 Return the number of objects currently incubating.
239 int QDeclarativeIncubationController::incubatingObjectCount() const
242 return d->incubatorCount;
248 Called when the number of incubating objects changes. \a incubatingObjectCount is the
249 new number of incubating objects.
251 The default implementation does nothing.
253 void QDeclarativeIncubationController::incubatingObjectCountChanged(int incubatingObjectCount)
255 Q_UNUSED(incubatingObjectCount);
258 void QDeclarativeIncubatorPrivate::incubate(QDeclarativeVME::Interrupt &i)
262 typedef QDeclarativeIncubatorPrivate IP;
263 QRecursionWatcher<IP, &IP::recursion> watcher(this);
265 QDeclarativeEngine *engine = component->engine;
266 QDeclarativeEnginePrivate *enginePriv = QDeclarativeEnginePrivate::get(engine);
268 bool guardOk = vmeGuard.isOK();
272 QDeclarativeError error;
273 error.setUrl(component->url);
274 error.setDescription(QDeclarativeComponent::tr("Object destroyed during incubation"));
276 progress = QDeclarativeIncubatorPrivate::Completed;
281 if (progress == QDeclarativeIncubatorPrivate::Execute) {
282 enginePriv->referenceScarceResources();
283 QObject *tresult = vme.execute(&errors, i);
284 enginePriv->dereferenceScarceResources();
286 if (watcher.hasRecursed())
290 if (errors.isEmpty() && result == 0)
294 QDeclarativeData *ddata = QDeclarativeData::get(result);
296 ddata->indestructible = true;
298 q->setInitialState(result);
301 if (watcher.hasRecursed())
304 if (errors.isEmpty())
305 progress = QDeclarativeIncubatorPrivate::Completing;
307 progress = QDeclarativeIncubatorPrivate::Completed;
309 changeStatus(calculateStatus());
311 if (watcher.hasRecursed())
314 if (i.shouldInterrupt())
318 if (progress == QDeclarativeIncubatorPrivate::Completing) {
320 if (watcher.hasRecursed())
323 QDeclarativeContextData *ctxt = vme.complete(i);
326 progress = QDeclarativeIncubatorPrivate::Completed;
329 } while (!i.shouldInterrupt());
333 if (progress == QDeclarativeIncubatorPrivate::Completed && waitingFor.isEmpty()) {
334 typedef QDeclarativeIncubatorPrivate IP;
336 QDeclarativeIncubatorPrivate *isWaiting = waitingOnMe;
340 QRecursionWatcher<IP, &IP::recursion> watcher(isWaiting);
341 changeStatus(calculateStatus());
342 if (!watcher.hasRecursed())
343 isWaiting->incubate(i);
345 changeStatus(calculateStatus());
348 enginePriv->inProgressCreations--;
350 if (0 == enginePriv->inProgressCreations) {
351 while (enginePriv->erroredBindings) {
352 enginePriv->warning(enginePriv->erroredBindings->error);
353 enginePriv->erroredBindings->removeError();
357 vmeGuard.guard(&vme);
362 Incubate objects for \a msecs, or until there are no more objects to incubate.
364 void QDeclarativeIncubationController::incubateFor(int msecs)
366 if (!d || d->incubatorCount == 0)
369 QDeclarativeVME::Interrupt i(msecs * 1000000);
372 QDeclarativeIncubatorPrivate *p = (QDeclarativeIncubatorPrivate*)d->incubatorList.first();
374 } while (d && d->incubatorCount != 0 && !i.shouldInterrupt());
378 Incubate objects while the bool pointed to by \a flag is true, or until there are no
379 more objects to incubate.
381 Generally this method is used in conjunction with a thread or a UNIX signal that sets
382 the bool pointed to by \a flag to false when it wants incubation to be interrupted.
384 void QDeclarativeIncubationController::incubateWhile(bool *flag)
386 if (!d || d->incubatorCount == 0)
389 QDeclarativeVME::Interrupt i(flag);
391 QDeclarativeIncubatorPrivate *p = (QDeclarativeIncubatorPrivate*)d->incubatorList.first();
393 } while (d && d->incubatorCount != 0 && !i.shouldInterrupt());
397 \class QDeclarativeIncubator
398 \brief The QDeclarativeIncubator class allows QML objects to be created asynchronously.
400 Creating QML objects - like delegates in a view, or a new page in an application - can take
401 a noticable amount of time, especially on resource constrained mobile devices. When an
402 application uses QDeclarativeComponent::create() directly, the QML object instance is created
403 synchronously which, depending on the complexity of the object, can cause noticable pauses or
404 stutters in the application.
406 The use of QDeclarativeIncubator gives more control over the creation of a QML object,
407 including allowing it to be created asynchronously using application idle time. The following
408 example shows a simple use of QDeclarativeIncubator.
411 QDeclarativeIncubator incubator;
412 component->create(incubator);
414 while (incubator.isReady()) {
415 QCoreApplication::processEvents(QEventLoop::AllEvents, 50);
418 QObject *object = incubator.object();
421 Asynchronous incubators are controlled by a QDeclarativeIncubationController that is
422 set on the QDeclarativeEngine, which lets the engine know when the application is idle and
423 incubating objects should be processed. If an incubation controller is not set on the
424 QDeclarativeEngine, QDeclarativeIncubator creates objects synchronously regardless of the
425 specified IncubationMode.
427 QDeclarativeIncubator supports three incubation modes:
429 \i Synchronous The creation occurs synchronously. That is, once the
430 QDeclarativeComponent::create() call returns, the incubator will already be in either the
431 Error or Ready state. A synchronous incubator has no real advantage compared to using
432 the synchronous creation methods on QDeclarativeComponent directly, but it may simplify an
433 application's implementation to use the same API for both synchronous and asynchronous
436 \i Asynchronous (default) The creation occurs asynchronously, assuming a
437 QDeclarativeIncubatorController is set on the QDeclarativeEngine.
439 The incubator will remain in the Loading state until either the creation is complete or an error
440 occurs. The statusChanged() callback can be used to be notified of status changes.
442 Applications should use the Asynchronous incubation mode to create objects that are not needed
443 immediately. For example, the ListView element uses Asynchronous incubation to create objects
444 that are slightly off screen while the list is being scrolled. If, during asynchronous creation,
445 the object is needed immediately the QDeclarativeIncubator::forceCompletion() method can be called
446 to complete the creation process synchronously.
448 \i AsynchronousIfNested The creation will occur asynchronously if part of a nested asynchronous
449 creation, or synchronously if not.
451 In most scenarios where a QML element or component wants the appearance of a synchronous
452 instantiation, it should use this mode.
454 This mode is best explained with an example. When the ListView element is first created, it needs
455 to populate itself with an initial set of delegates to show. If the ListView was 400 pixels high,
456 and each delegate was 100 pixels high, it would need to create four initial delegate instances. If
457 the ListView used the Asynchronous incubation mode, the ListView would always be created empty and
458 then, sometime later, the four initial elements would appear.
460 Conversely, if the ListView was to use the Synchronous incubation mode it would behave correctly
461 but it may introduce stutters into the application. As QML would have to stop and instantiate the
462 ListView's delegates synchronously, if the ListView was part of a QML component that was being
463 instantiated asynchronously this would undo much of the benefit of asynchronous instantiation.
465 The AsynchronousIfNested mode reconciles this problem. By using AsynchronousIfNested, the ListView
466 delegates are instantiated asynchronously if the ListView itself is already part of an asynchronous
467 instantiation, and synchronously otherwise. In the case of a nested asynchronous instantiation, the
468 outer asynchronous instantiation will not complete until after all the nested instantiations have also
469 completed. This ensures that by the time the outer asynchronous instantitation completes, inner
470 elements like ListView have already completed loading their initial delegates.
472 It is almost always incorrect to use the Synchronous incubation mode - elements or components that
473 want the appearance of synchronous instantiation, but without the downsides of introducing freezes
474 or stutters into the application, should use the AsynchronousIfNested incubation mode.
479 Create a new incubator with the specified \a mode
481 QDeclarativeIncubator::QDeclarativeIncubator(IncubationMode mode)
482 : d(new QDeclarativeIncubatorPrivate(this, mode))
487 QDeclarativeIncubator::~QDeclarativeIncubator()
495 \enum QDeclarativeIncubator::IncubationMode
497 Specifies the mode the incubator operates in. Regardless of the incubation mode, a
498 QDeclarativeIncubator will behave synchronously if the QDeclarativeEngine does not have
499 a QDeclarativeIncubationController set.
501 \value Asynchronous The object will be created asynchronously.
502 \value AsynchronousIfNested If the object is being created in a context that is already part
503 of an asynchronous creation, this incubator will join that existing incubation and execute
504 asynchronously. The existing incubation will not become Ready until both it and this
505 incubation have completed. Otherwise, the incubation will execute synchronously.
506 \value Synchronous The object will be created synchronously.
510 \enum QDeclarativeIncubator::Status
512 Specifies the status of the QDeclarativeIncubator.
514 \value Null Incubation is not in progress. Call QDeclarativeComponent::create() to begin incubating.
515 \value Ready The object is fully created and can be accessed by calling object().
516 \value Loading The object is in the process of being created.
517 \value Error An error occurred. The errors can be access by calling errors().
521 Clears the incubator. Any in-progress incubation is aborted. If the incubator is in the
522 Ready state, the created object is \b not deleted.
524 void QDeclarativeIncubator::clear()
526 typedef QDeclarativeIncubatorPrivate IP;
527 QRecursionWatcher<IP, &IP::recursion> watcher(d);
534 QDeclarativeEnginePrivate *enginePriv = 0;
536 Q_ASSERT(d->component);
537 enginePriv = QDeclarativeEnginePrivate::get(d->component->engine);
538 if (d->result) d->result->deleteLater();
547 Q_ASSERT(d->component == 0);
548 Q_ASSERT(d->waitingOnMe == 0);
549 Q_ASSERT(d->waitingFor.isEmpty());
550 Q_ASSERT(!d->nextWaitingFor.isInList());
553 d->progress = QDeclarativeIncubatorPrivate::Execute;
557 Q_ASSERT(enginePriv);
559 enginePriv->inProgressCreations--;
560 if (0 == enginePriv->inProgressCreations) {
561 while (enginePriv->erroredBindings) {
562 enginePriv->warning(enginePriv->erroredBindings->error);
563 enginePriv->erroredBindings->removeError();
568 d->changeStatus(Null);
572 Force any in-progress incubation to finish synchronously. Once this call
573 returns, the incubator will not be in the Loading state.
575 void QDeclarativeIncubator::forceCompletion()
577 QDeclarativeVME::Interrupt i;
578 while (Loading == status()) {
579 while (Loading == status() && !d->waitingFor.isEmpty())
580 static_cast<QDeclarativeIncubatorPrivate *>(d->waitingFor.first())->incubate(i);
581 if (Loading == status())
587 Returns true if the incubator's status() is Null.
589 bool QDeclarativeIncubator::isNull() const
591 return status() == Null;
595 Returns true if the incubator's status() is Ready.
597 bool QDeclarativeIncubator::isReady() const
599 return status() == Ready;
603 Returns true if the incubator's status() is Error.
605 bool QDeclarativeIncubator::isError() const
607 return status() == Error;
611 Returns true if the incubator's status() is Loading.
613 bool QDeclarativeIncubator::isLoading() const
615 return status() == Loading;
619 Return the list of errors encountered while incubating the object.
621 QList<QDeclarativeError> QDeclarativeIncubator::errors() const
627 Return the incubation mode passed to the QDeclarativeIncubator constructor.
629 QDeclarativeIncubator::IncubationMode QDeclarativeIncubator::incubationMode() const
635 Return the current status of the incubator.
637 QDeclarativeIncubator::Status QDeclarativeIncubator::status() const
643 Return the incubated object if the status is Ready, otherwise 0.
645 QObject *QDeclarativeIncubator::object() const
647 if (status() != Ready) return 0;
648 else return d->result;
652 Called when the status of the incubator changes. \a status is the new status.
654 The default implementation does nothing.
656 void QDeclarativeIncubator::statusChanged(Status status)
662 Called after the object is first created, but before property bindings are
663 evaluated and, if applicable, QDeclarativeParserStatus::componentComplete() is
664 called. This is equivalent to the point between QDeclarativeComponent::beginCreate()
665 and QDeclarativeComponent::endCreate(), and can be used to assign initial values
666 to the object's properties.
668 The default implementation does nothing.
670 void QDeclarativeIncubator::setInitialState(QObject *object)
675 void QDeclarativeIncubatorPrivate::changeStatus(QDeclarativeIncubator::Status s)
681 q->statusChanged(status);
684 QDeclarativeIncubator::Status QDeclarativeIncubatorPrivate::calculateStatus() const
686 if (!errors.isEmpty())
687 return QDeclarativeIncubator::Error;
688 else if (result && progress == QDeclarativeIncubatorPrivate::Completed &&
689 waitingFor.isEmpty())
690 return QDeclarativeIncubator::Ready;
692 return QDeclarativeIncubator::Loading;
694 return QDeclarativeIncubator::Null;