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