6262d2eed0a56dca8c1177232169441cb0363b78
[profile/ivi/qtdeclarative.git] / src / declarative / qml / qdeclarativeincubator.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: http://www.qt-project.org/
6 **
7 ** This file is part of the QtDeclarative module of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser General Public
12 ** License version 2.1 as published by the Free Software Foundation and
13 ** appearing in the file LICENSE.LGPL included in the packaging of this
14 ** file. Please review the following information to ensure the GNU Lesser
15 ** General Public License version 2.1 requirements will be met:
16 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 **
18 ** In addition, as a special exception, Nokia gives you certain additional
19 ** rights. These rights are described in the Nokia Qt LGPL Exception
20 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 **
22 ** GNU General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU General
24 ** Public License version 3.0 as published by the Free Software Foundation
25 ** and appearing in the file LICENSE.GPL included in the packaging of this
26 ** file. Please review the following information to ensure the GNU General
27 ** Public License version 3.0 requirements will be met:
28 ** http://www.gnu.org/copyleft/gpl.html.
29 **
30 ** Other Usage
31 ** Alternatively, this file may be used in accordance with the terms and
32 ** conditions contained in a signed written agreement between you and Nokia.
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "qdeclarativeincubator.h"
43 #include "qdeclarativecomponent.h"
44 #include "qdeclarativeincubator_p.h"
45
46 #include "qdeclarativecompiler_p.h"
47 #include "qdeclarativeexpression_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 QDeclarativeEnginePrivate::incubate(QDeclarativeIncubator &i, QDeclarativeContextData *forContext)
53 {
54     QDeclarativeIncubatorPrivate *p = i.d;
55
56     QDeclarativeIncubator::IncubationMode mode = i.incubationMode();
57
58     if (!incubationController)
59         mode = QDeclarativeIncubator::Synchronous;
60
61     if (mode == QDeclarativeIncubator::AsynchronousIfNested) {
62         mode = QDeclarativeIncubator::Synchronous;
63
64         // Need to find the first constructing context and see if it is asynchronous
65         QDeclarativeIncubatorPrivate *parentIncubator = 0;
66         QDeclarativeContextData *cctxt = forContext;
67         while (cctxt) {
68             if (cctxt->activeVMEData) {
69                 parentIncubator = (QDeclarativeIncubatorPrivate *)cctxt->activeVMEData;
70                 break;
71             }
72             cctxt = cctxt->parent;
73         }
74
75         if (parentIncubator && parentIncubator->isAsynchronous) {
76             mode = QDeclarativeIncubator::Asynchronous;
77             p->waitingOnMe = parentIncubator;
78             parentIncubator->waitingFor.insert(p);
79         }
80     }
81
82     p->isAsynchronous = (mode != QDeclarativeIncubator::Synchronous);
83
84     inProgressCreations++;
85
86     if (mode == QDeclarativeIncubator::Synchronous) {
87         typedef QDeclarativeIncubatorPrivate IP;
88         QRecursionWatcher<IP, &IP::recursion> watcher(p);
89
90         p->changeStatus(QDeclarativeIncubator::Loading);
91
92         if (!watcher.hasRecursed()) {
93             QDeclarativeVME::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(QDeclarativeIncubator::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 QDeclarativeEngine::setIncubationController(QDeclarativeIncubationController *controller)
115 {
116     Q_D(QDeclarativeEngine);
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 QDeclarativeIncubationController *QDeclarativeEngine::incubationController() const
129 {
130     Q_D(const QDeclarativeEngine);
131     return d->incubationController;
132 }
133
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)
138 {
139 }
140
141 QDeclarativeIncubatorPrivate::~QDeclarativeIncubatorPrivate()
142 {
143 }
144
145 void QDeclarativeIncubatorPrivate::clear()
146 {
147     if (next.isInList()) {
148         next.remove();
149         Q_ASSERT(component);
150         QDeclarativeEnginePrivate *enginePriv = QDeclarativeEnginePrivate::get(component->engine);
151         component->release();
152         component = 0;
153         enginePriv->incubatorCount--;
154         QDeclarativeIncubationController *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 QDeclarativeIncubationController
175 \brief QDeclarativeIncubationController instances drive the progress of QDeclarativeIncubators
176
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.
181
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
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 QDeclarativeIncubationController 
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 QDeclarativeIncubationController::QDeclarativeIncubationController()
216 : d(0)
217 {
218 }
219
220 /*! \internal */
221 QDeclarativeIncubationController::~QDeclarativeIncubationController()
222 {
223     if (d) QDeclarativeEnginePrivate::get(d)->setIncubationController(0);
224     d = 0;
225 }
226
227 /*!
228 Return the QDeclarativeEngine this incubation controller is set on, or 0 if it
229 has not been set on any engine.
230 */
231 QDeclarativeEngine *QDeclarativeIncubationController::engine() const
232 {
233     return QDeclarativeEnginePrivate::get(d);
234 }
235
236 /*!
237 Return the number of objects currently incubating.
238 */
239 int QDeclarativeIncubationController::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 QDeclarativeIncubationController::incubatingObjectCountChanged(int incubatingObjectCount)
254 {
255     Q_UNUSED(incubatingObjectCount);
256 }
257
258 void QDeclarativeIncubatorPrivate::incubate(QDeclarativeVME::Interrupt &i)
259 {
260     if (!component)
261         return;
262     typedef QDeclarativeIncubatorPrivate IP;
263     QRecursionWatcher<IP, &IP::recursion> watcher(this);
264
265     QDeclarativeEngine *engine = component->engine;
266     QDeclarativeEnginePrivate *enginePriv = QDeclarativeEnginePrivate::get(engine);
267
268     bool guardOk = vmeGuard.isOK();
269     vmeGuard.clear();
270
271     if (!guardOk) {
272         QDeclarativeError error;
273         error.setUrl(component->url);
274         error.setDescription(QDeclarativeComponent::tr("Object destroyed during incubation"));
275         errors << error;
276         progress = QDeclarativeIncubatorPrivate::Completed;
277
278         goto finishIncubate;
279     }
280
281     if (progress == QDeclarativeIncubatorPrivate::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             QDeclarativeData *ddata = QDeclarativeData::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 = QDeclarativeIncubatorPrivate::Completing;
306         else
307             progress = QDeclarativeIncubatorPrivate::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 == QDeclarativeIncubatorPrivate::Completing) {
319         do {
320             if (watcher.hasRecursed())
321                 return;
322
323             QDeclarativeContextData *ctxt = vme.complete(i);
324             if (ctxt) {
325                 rootContext = ctxt;
326                 progress = QDeclarativeIncubatorPrivate::Completed;
327                 goto finishIncubate;
328             }
329         } while (!i.shouldInterrupt());
330     }
331
332 finishIncubate:
333     if (progress == QDeclarativeIncubatorPrivate::Completed && waitingFor.isEmpty()) {
334         typedef QDeclarativeIncubatorPrivate IP;
335
336         QDeclarativeIncubatorPrivate *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 QDeclarativeIncubationController::incubateFor(int msecs)
365 {
366     if (!d || d->incubatorCount == 0)
367         return;
368
369     QDeclarativeVME::Interrupt i(msecs * 1000000);
370     i.reset();
371     do {
372         QDeclarativeIncubatorPrivate *p = (QDeclarativeIncubatorPrivate*)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.
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 QDeclarativeIncubationController::incubateWhile(bool *flag)
385 {
386     if (!d || d->incubatorCount == 0)
387         return;
388
389     QDeclarativeVME::Interrupt i(flag);
390     do {
391         QDeclarativeIncubatorPrivate *p = (QDeclarativeIncubatorPrivate*)d->incubatorList.first();
392         p->incubate(i);
393     } while (d && d->incubatorCount != 0 && !i.shouldInterrupt());
394 }
395
396 /*!
397 \class QDeclarativeIncubator
398 \brief The QDeclarativeIncubator class allows QML objects to be created asynchronously.
399
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.
405
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.
409
410 \code
411 QDeclarativeIncubator incubator;
412 component->create(incubator);
413
414 while (incubator.isReady()) {
415     QCoreApplication::processEvents(QEventLoop::AllEvents, 50);
416 }
417
418 QObject *object = incubator.object();
419 \endcode
420
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.  
426
427 QDeclarativeIncubator supports three incubation modes:
428 \list
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 
434 creations.
435
436 \i Asynchronous (default) The creation occurs asynchronously, assuming a 
437 QDeclarativeIncubatorController is set on the QDeclarativeEngine.  
438
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.
441
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.
447
448 \i AsynchronousIfNested The creation will occur asynchronously if part of a nested asynchronous 
449 creation, or synchronously if not.  
450
451 In most scenarios where a QML element or component wants the appearance of a synchronous 
452 instantiation, it should use this mode.  
453
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.  
459
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.
464
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.
471
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.
475 \endlist
476 */
477
478 /*!
479 Create a new incubator with the specified \a mode
480 */
481 QDeclarativeIncubator::QDeclarativeIncubator(IncubationMode mode)
482 : d(new QDeclarativeIncubatorPrivate(this, mode))
483 {
484 }
485
486 /*! \internal */
487 QDeclarativeIncubator::~QDeclarativeIncubator()
488 {
489     clear();
490
491     delete d; d = 0;
492 }
493
494 /*!
495 \enum QDeclarativeIncubator::IncubationMode
496
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.
500
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.
507 */
508
509 /*!
510 \enum QDeclarativeIncubator::Status
511
512 Specifies the status of the QDeclarativeIncubator.
513
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().
518 */
519
520 /*!
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.
523 */
524 void QDeclarativeIncubator::clear()
525 {
526     typedef QDeclarativeIncubatorPrivate IP;
527     QRecursionWatcher<IP, &IP::recursion> watcher(d);
528
529     Status s = status();
530
531     if (s == Null)
532         return;
533
534     QDeclarativeEnginePrivate *enginePriv = 0;
535     if (s == Loading) {
536         Q_ASSERT(d->component);
537         enginePriv = QDeclarativeEnginePrivate::get(d->component->engine);
538         if (d->result) d->result->deleteLater();
539         d->result = 0;
540     }
541
542     d->clear();
543
544     d->vme.reset();
545     d->vmeGuard.clear();
546
547     Q_ASSERT(d->component == 0);
548     Q_ASSERT(d->waitingOnMe == 0);
549     Q_ASSERT(d->waitingFor.isEmpty());
550     Q_ASSERT(!d->nextWaitingFor.isInList());
551
552     d->errors.clear();
553     d->progress = QDeclarativeIncubatorPrivate::Execute;
554     d->result = 0;
555
556     if (s == Loading) {
557         Q_ASSERT(enginePriv);
558
559         enginePriv->inProgressCreations--;
560         if (0 == enginePriv->inProgressCreations) {
561             while (enginePriv->erroredBindings) {
562                 enginePriv->warning(enginePriv->erroredBindings->error);
563                 enginePriv->erroredBindings->removeError();
564             }
565         }
566     }
567
568     d->changeStatus(Null);
569 }
570
571 /*!
572 Force any in-progress incubation to finish synchronously.  Once this call
573 returns, the incubator will not be in the Loading state.
574 */
575 void QDeclarativeIncubator::forceCompletion()
576 {
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())
582             d->incubate(i);
583     }
584 }
585
586 /*!
587 Returns true if the incubator's status() is Null.
588 */
589 bool QDeclarativeIncubator::isNull() const
590 {
591     return status() == Null;
592 }
593
594 /*!
595 Returns true if the incubator's status() is Ready.
596 */
597 bool QDeclarativeIncubator::isReady() const
598 {
599     return status() == Ready;
600 }
601
602 /*!
603 Returns true if the incubator's status() is Error.
604 */
605 bool QDeclarativeIncubator::isError() const
606 {
607     return status() == Error;
608 }
609
610 /*!
611 Returns true if the incubator's status() is Loading.
612 */
613 bool QDeclarativeIncubator::isLoading() const
614 {
615     return status() == Loading;
616 }
617
618 /*!
619 Return the list of errors encountered while incubating the object.
620 */
621 QList<QDeclarativeError> QDeclarativeIncubator::errors() const
622 {
623     return d->errors;
624 }
625
626 /*!
627 Return the incubation mode passed to the QDeclarativeIncubator constructor.
628 */
629 QDeclarativeIncubator::IncubationMode QDeclarativeIncubator::incubationMode() const
630 {
631     return d->mode;
632 }
633
634 /*!
635 Return the current status of the incubator.
636 */
637 QDeclarativeIncubator::Status QDeclarativeIncubator::status() const
638 {
639     return d->status;
640 }
641
642 /*!
643 Return the incubated object if the status is Ready, otherwise 0.
644 */
645 QObject *QDeclarativeIncubator::object() const
646 {
647     if (status() != Ready) return 0;
648     else return d->result;
649 }
650
651 /*!
652 Called when the status of the incubator changes.  \a status is the new status.
653
654 The default implementation does nothing.
655 */
656 void QDeclarativeIncubator::statusChanged(Status status)
657 {
658     Q_UNUSED(status);
659 }
660
661 /*!
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.
667
668 The default implementation does nothing.
669 */
670 void QDeclarativeIncubator::setInitialState(QObject *object)
671 {
672     Q_UNUSED(object);
673 }
674
675 void QDeclarativeIncubatorPrivate::changeStatus(QDeclarativeIncubator::Status s)
676 {
677     if (s == status) 
678         return;
679
680     status = s;
681     q->statusChanged(status);
682 }
683
684 QDeclarativeIncubator::Status QDeclarativeIncubatorPrivate::calculateStatus() const
685 {
686     if (!errors.isEmpty()) 
687         return QDeclarativeIncubator::Error;
688     else if (result && progress == QDeclarativeIncubatorPrivate::Completed && 
689              waitingFor.isEmpty()) 
690         return QDeclarativeIncubator::Ready;
691     else if (component) 
692         return QDeclarativeIncubator::Loading;
693     else 
694         return QDeclarativeIncubator::Null;
695 }
696