12a48a28860fdabf811b85c8b89975840e94a41f
[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 #include "qqmlmemoryprofiler_p.h"
49
50 // XXX TODO 
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)
54 {
55     QQmlIncubatorPrivate *p = i.d;
56
57     QQmlIncubator::IncubationMode mode = i.incubationMode();
58
59     if (!incubationController)
60         mode = QQmlIncubator::Synchronous;
61
62     if (mode == QQmlIncubator::AsynchronousIfNested) {
63         mode = QQmlIncubator::Synchronous;
64
65         // Need to find the first constructing context and see if it is asynchronous
66         QQmlIncubatorPrivate *parentIncubator = 0;
67         QQmlContextData *cctxt = forContext;
68         while (cctxt) {
69             if (cctxt->activeVMEData) {
70                 parentIncubator = (QQmlIncubatorPrivate *)cctxt->activeVMEData;
71                 break;
72             }
73             cctxt = cctxt->parent;
74         }
75
76         if (parentIncubator && parentIncubator->isAsynchronous) {
77             mode = QQmlIncubator::Asynchronous;
78             p->waitingOnMe = parentIncubator;
79             parentIncubator->waitingFor.insert(p);
80         }
81     }
82
83     p->isAsynchronous = (mode != QQmlIncubator::Synchronous);
84
85     inProgressCreations++;
86
87     if (mode == QQmlIncubator::Synchronous) {
88         typedef QQmlIncubatorPrivate IP;
89         QRecursionWatcher<IP, &IP::recursion> watcher(p);
90
91         p->changeStatus(QQmlIncubator::Loading);
92
93         if (!watcher.hasRecursed()) {
94             QQmlVME::Interrupt i;
95             p->incubate(i);
96         }
97     } else {
98         incubatorList.insert(p);
99         incubatorCount++;
100
101         p->vmeGuard.guard(&p->vme);
102         p->changeStatus(QQmlIncubator::Loading);
103
104         if (incubationController)
105             incubationController->incubatingObjectCountChanged(incubatorCount);
106     }
107 }
108
109 /*!
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.
112
113 \sa incubationController()
114 */
115 void QQmlEngine::setIncubationController(QQmlIncubationController *controller)
116 {
117     Q_D(QQmlEngine);
118     if (d->incubationController)
119         d->incubationController->d = 0;
120     d->incubationController = controller;
121     if (controller) controller->d = d;
122 }
123
124 /*!
125 Returns the currently set incubation controller, or 0 if no controller has been set.
126
127 \sa setIncubationController()
128 */
129 QQmlIncubationController *QQmlEngine::incubationController() const
130 {
131     Q_D(const QQmlEngine);
132     return d->incubationController;
133 }
134
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)
139 {
140 }
141
142 QQmlIncubatorPrivate::~QQmlIncubatorPrivate()
143 {
144 }
145
146 void QQmlIncubatorPrivate::clear()
147 {
148     if (next.isInList()) {
149         next.remove();
150         Q_ASSERT(compiledData);
151         QQmlEnginePrivate *enginePriv = QQmlEnginePrivate::get(compiledData->engine);
152         compiledData->release();
153         compiledData = 0;
154         enginePriv->incubatorCount--;
155         QQmlIncubationController *controller = enginePriv->incubationController;
156         if (controller)
157             controller->incubatingObjectCountChanged(enginePriv->incubatorCount);
158     } else if (compiledData) {
159         compiledData->release();
160         compiledData = 0;
161     }
162     if (!rootContext.isNull()) {
163         rootContext->activeVMEData = 0;
164         rootContext = 0;
165     }
166
167     if (nextWaitingFor.isInList()) {
168         Q_ASSERT(waitingOnMe);
169         nextWaitingFor.remove();
170         waitingOnMe = 0;
171     }
172 }
173
174 /*!
175 \class QQmlIncubationController
176 \brief QQmlIncubationController instances drive the progress of QQmlIncubators
177
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.
182
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
187 requirements.
188
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.
191
192 \code
193 class PeriodicIncubationController : public QObject, 
194                                      public QQmlIncubationController 
195 {
196 public:
197     PeriodicIncubationController() { 
198         startTimer(16); 
199     }
200
201 protected:
202     virtual void timerEvent(QTimerEvent *) {
203         incubateFor(5);
204     }
205 };
206 \endcode
207
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.  
211 */
212
213 /*!
214 Create a new incubation controller.
215 */
216 QQmlIncubationController::QQmlIncubationController()
217 : d(0)
218 {
219 }
220
221 /*! \internal */
222 QQmlIncubationController::~QQmlIncubationController()
223 {
224     if (d) QQmlEnginePrivate::get(d)->setIncubationController(0);
225     d = 0;
226 }
227
228 /*!
229 Return the QQmlEngine this incubation controller is set on, or 0 if it
230 has not been set on any engine.
231 */
232 QQmlEngine *QQmlIncubationController::engine() const
233 {
234     return QQmlEnginePrivate::get(d);
235 }
236
237 /*!
238 Return the number of objects currently incubating.
239 */
240 int QQmlIncubationController::incubatingObjectCount() const
241 {
242     if (d)
243         return d->incubatorCount;
244     else 
245         return 0;
246 }
247
248 /*!
249 Called when the number of incubating objects changes.  \a incubatingObjectCount is the 
250 new number of incubating objects.
251
252 The default implementation does nothing.
253 */
254 void QQmlIncubationController::incubatingObjectCountChanged(int incubatingObjectCount)
255 {
256     Q_UNUSED(incubatingObjectCount);
257 }
258
259 void QQmlIncubatorPrivate::incubate(QQmlVME::Interrupt &i)
260 {
261     if (!compiledData)
262         return;
263     QML_MEMORY_SCOPE_URL(compiledData->url);
264
265     typedef QQmlIncubatorPrivate IP;
266     QRecursionWatcher<IP, &IP::recursion> watcher(this);
267
268     QQmlEngine *engine = compiledData->engine;
269     QQmlEnginePrivate *enginePriv = QQmlEnginePrivate::get(engine);
270
271     bool guardOk = vmeGuard.isOK();
272     vmeGuard.clear();
273
274     if (!guardOk) {
275         QQmlError error;
276         error.setUrl(compiledData->url);
277         error.setDescription(QQmlComponent::tr("Object destroyed during incubation"));
278         errors << error;
279         progress = QQmlIncubatorPrivate::Completed;
280
281         goto finishIncubate;
282     }
283
284     if (progress == QQmlIncubatorPrivate::Execute) {
285         enginePriv->referenceScarceResources();
286         QObject *tresult = vme.execute(&errors, i);
287         enginePriv->dereferenceScarceResources();
288
289         if (watcher.hasRecursed())
290             return;
291
292         result = tresult;
293         if (errors.isEmpty() && result == 0) 
294             goto finishIncubate;
295
296         if (result) {
297             QQmlData *ddata = QQmlData::get(result);
298             Q_ASSERT(ddata);
299             //see QQmlComponent::beginCreate for explanation of indestructible
300             ddata->indestructible = true;
301             ddata->explicitIndestructibleSet = true;
302             ddata->rootObjectInCreation = false;
303             q->setInitialState(result);
304         }
305
306         if (watcher.hasRecursed())
307             return;
308
309         if (errors.isEmpty())
310             progress = QQmlIncubatorPrivate::Completing;
311         else
312             progress = QQmlIncubatorPrivate::Completed;
313
314         changeStatus(calculateStatus());
315
316         if (watcher.hasRecursed())
317             return;
318
319         if (i.shouldInterrupt())
320             goto finishIncubate;
321     }
322
323     if (progress == QQmlIncubatorPrivate::Completing) {
324         do {
325             if (watcher.hasRecursed())
326                 return;
327
328             QQmlContextData *ctxt = vme.complete(i);
329             if (ctxt) {
330                 rootContext = ctxt;
331                 progress = QQmlIncubatorPrivate::Completed;
332                 goto finishIncubate;
333             }
334         } while (!i.shouldInterrupt());
335     }
336
337 finishIncubate:
338     if (progress == QQmlIncubatorPrivate::Completed && waitingFor.isEmpty()) {
339         typedef QQmlIncubatorPrivate IP;
340
341         QQmlIncubatorPrivate *isWaiting = waitingOnMe;
342         clear();
343
344         if (isWaiting) {
345             QRecursionWatcher<IP, &IP::recursion> watcher(isWaiting);
346             changeStatus(calculateStatus());
347             if (!watcher.hasRecursed())
348                 isWaiting->incubate(i);
349         } else {
350             changeStatus(calculateStatus());
351         }
352
353         enginePriv->inProgressCreations--;
354
355         if (0 == enginePriv->inProgressCreations) {
356             while (enginePriv->erroredBindings) {
357                 enginePriv->warning(enginePriv->erroredBindings->error);
358                 enginePriv->erroredBindings->removeError();
359             }
360         }
361     } else {
362         vmeGuard.guard(&vme);
363     }
364 }
365
366 /*!
367 Incubate objects for \a msecs, or until there are no more objects to incubate.
368 */
369 void QQmlIncubationController::incubateFor(int msecs)
370 {
371     if (!d || d->incubatorCount == 0)
372         return;
373
374     QQmlVME::Interrupt i(msecs * 1000000);
375     i.reset();
376     do {
377         QQmlIncubatorPrivate *p = (QQmlIncubatorPrivate*)d->incubatorList.first();
378         p->incubate(i);
379     } while (d && d->incubatorCount != 0 && !i.shouldInterrupt());
380 }
381
382 /*!
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.
385
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.
388 */
389 void QQmlIncubationController::incubateWhile(volatile bool *flag, int msecs)
390 {
391     if (!d || d->incubatorCount == 0)
392         return;
393
394     QQmlVME::Interrupt i(flag, msecs * 1000000);
395     i.reset();
396     do {
397         QQmlIncubatorPrivate *p = (QQmlIncubatorPrivate*)d->incubatorList.first();
398         p->incubate(i);
399     } while (d && d->incubatorCount != 0 && !i.shouldInterrupt());
400 }
401
402 /*!
403 \class QQmlIncubator
404 \brief The QQmlIncubator class allows QML objects to be created asynchronously.
405
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.
411
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.
415
416 \code
417 QQmlIncubator incubator;
418 component->create(incubator);
419
420 while (incubator.isReady()) {
421     QCoreApplication::processEvents(QEventLoop::AllEvents, 50);
422 }
423
424 QObject *object = incubator.object();
425 \endcode
426
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.  
432
433 QQmlIncubator supports three incubation modes:
434 \list
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 
440 creations.
441
442 \li Asynchronous (default) The creation occurs asynchronously, assuming a
443 QQmlIncubatorController is set on the QQmlEngine.  
444
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.
447
448 Applications should use the Asynchronous incubation mode to create objects that are not needed
449 immediately.  For example, the ListView element 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.
453
454 \li AsynchronousIfNested The creation will occur asynchronously if part of a nested asynchronous
455 creation, or synchronously if not.  
456
457 In most scenarios where a QML element or component wants the appearance of a synchronous 
458 instantiation, it should use this mode.  
459
460 This mode is best explained with an example.  When the ListView element 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 elements would appear.  
465
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.
470
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 elements like ListView have already completed loading their initial delegates.
477
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.
481 \endlist
482 */
483
484 /*!
485 Create a new incubator with the specified \a mode
486 */
487 QQmlIncubator::QQmlIncubator(IncubationMode mode)
488 : d(new QQmlIncubatorPrivate(this, mode))
489 {
490 }
491
492 /*! \internal */
493 QQmlIncubator::~QQmlIncubator()
494 {
495     clear();
496
497     delete d; d = 0;
498 }
499
500 /*!
501 \enum QQmlIncubator::IncubationMode
502
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.
506
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.
513 */
514
515 /*!
516 \enum QQmlIncubator::Status
517
518 Specifies the status of the QQmlIncubator.
519
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().
524 */
525
526 /*!
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.
529 */
530 void QQmlIncubator::clear()
531 {
532     typedef QQmlIncubatorPrivate IP;
533     QRecursionWatcher<IP, &IP::recursion> watcher(d);
534
535     Status s = status();
536
537     if (s == Null)
538         return;
539
540     QQmlEnginePrivate *enginePriv = 0;
541     if (s == Loading) {
542         Q_ASSERT(d->compiledData);
543         enginePriv = QQmlEnginePrivate::get(d->compiledData->engine);
544         if (d->result) d->result->deleteLater();
545         d->result = 0;
546     }
547
548     d->clear();
549
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();
553
554     d->vme.reset();
555     d->vmeGuard.clear();
556
557     Q_ASSERT(d->compiledData == 0);
558     Q_ASSERT(d->waitingOnMe == 0);
559     Q_ASSERT(d->waitingFor.isEmpty());
560     Q_ASSERT(!d->nextWaitingFor.isInList());
561
562     d->errors.clear();
563     d->progress = QQmlIncubatorPrivate::Execute;
564     d->result = 0;
565
566     if (s == Loading) {
567         Q_ASSERT(enginePriv);
568
569         enginePriv->inProgressCreations--;
570         if (0 == enginePriv->inProgressCreations) {
571             while (enginePriv->erroredBindings) {
572                 enginePriv->warning(enginePriv->erroredBindings->error);
573                 enginePriv->erroredBindings->removeError();
574             }
575         }
576     }
577
578     d->changeStatus(Null);
579 }
580
581 /*!
582 Force any in-progress incubation to finish synchronously.  Once this call
583 returns, the incubator will not be in the Loading state.
584 */
585 void QQmlIncubator::forceCompletion()
586 {
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())
592             d->incubate(i);
593     }
594 }
595
596 /*!
597 Returns true if the incubator's status() is Null.
598 */
599 bool QQmlIncubator::isNull() const
600 {
601     return status() == Null;
602 }
603
604 /*!
605 Returns true if the incubator's status() is Ready.
606 */
607 bool QQmlIncubator::isReady() const
608 {
609     return status() == Ready;
610 }
611
612 /*!
613 Returns true if the incubator's status() is Error.
614 */
615 bool QQmlIncubator::isError() const
616 {
617     return status() == Error;
618 }
619
620 /*!
621 Returns true if the incubator's status() is Loading.
622 */
623 bool QQmlIncubator::isLoading() const
624 {
625     return status() == Loading;
626 }
627
628 /*!
629 Return the list of errors encountered while incubating the object.
630 */
631 QList<QQmlError> QQmlIncubator::errors() const
632 {
633     return d->errors;
634 }
635
636 /*!
637 Return the incubation mode passed to the QQmlIncubator constructor.
638 */
639 QQmlIncubator::IncubationMode QQmlIncubator::incubationMode() const
640 {
641     return d->mode;
642 }
643
644 /*!
645 Return the current status of the incubator.
646 */
647 QQmlIncubator::Status QQmlIncubator::status() const
648 {
649     return d->status;
650 }
651
652 /*!
653 Return the incubated object if the status is Ready, otherwise 0.
654 */
655 QObject *QQmlIncubator::object() const
656 {
657     if (status() != Ready) return 0;
658     else return d->result;
659 }
660
661 /*!
662 Called when the status of the incubator changes.  \a status is the new status.
663
664 The default implementation does nothing.
665 */
666 void QQmlIncubator::statusChanged(Status status)
667 {
668     Q_UNUSED(status);
669 }
670
671 /*!
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.
677
678 The default implementation does nothing.
679 */
680 void QQmlIncubator::setInitialState(QObject *object)
681 {
682     Q_UNUSED(object);
683 }
684
685 void QQmlIncubatorPrivate::changeStatus(QQmlIncubator::Status s)
686 {
687     if (s == status) 
688         return;
689
690     status = s;
691     q->statusChanged(status);
692 }
693
694 QQmlIncubator::Status QQmlIncubatorPrivate::calculateStatus() const
695 {
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;
703     else 
704         return QQmlIncubator::Null;
705 }
706