Add missing QT_{BEGIN,END}_NAMESPACE
[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 \inmodule QtQml
178
179 In order to behave asynchronously and not introduce stutters or freezes in an application,
180 the process of creating objects a QQmlIncubators must be driven only during the
181 application's idle time.  QQmlIncubationController allows the application to control
182 exactly when, how often and for how long this processing occurs.
183
184 A QQmlIncubationController derived instance should be created and set on a 
185 QQmlEngine by calling the QQmlEngine::setIncubationController() method.
186 Processing is then controlled by calling the QQmlIncubationController::incubateFor()
187 or QQmlIncubationController::incubateWhile() methods as dictated by the application's
188 requirements.
189
190 For example, this is an example of a incubation controller that will incubate for a maximum
191 of 5 milliseconds out of every 16 milliseconds.
192
193 \code
194 class PeriodicIncubationController : public QObject, 
195                                      public QQmlIncubationController 
196 {
197 public:
198     PeriodicIncubationController() { 
199         startTimer(16); 
200     }
201
202 protected:
203     virtual void timerEvent(QTimerEvent *) {
204         incubateFor(5);
205     }
206 };
207 \endcode
208
209 Although the previous example would work, it is not optimal.  Real world incubation
210 controllers should try and maximize the amount of idle time they consume - rather
211 than a static amount like 5 milliseconds - while not disturbing the application.  
212 */
213
214 /*!
215 Create a new incubation controller.
216 */
217 QQmlIncubationController::QQmlIncubationController()
218 : d(0)
219 {
220 }
221
222 /*! \internal */
223 QQmlIncubationController::~QQmlIncubationController()
224 {
225     if (d) QQmlEnginePrivate::get(d)->setIncubationController(0);
226     d = 0;
227 }
228
229 /*!
230 Return the QQmlEngine this incubation controller is set on, or 0 if it
231 has not been set on any engine.
232 */
233 QQmlEngine *QQmlIncubationController::engine() const
234 {
235     return QQmlEnginePrivate::get(d);
236 }
237
238 /*!
239 Return the number of objects currently incubating.
240 */
241 int QQmlIncubationController::incubatingObjectCount() const
242 {
243     if (d)
244         return d->incubatorCount;
245     else 
246         return 0;
247 }
248
249 /*!
250 Called when the number of incubating objects changes.  \a incubatingObjectCount is the 
251 new number of incubating objects.
252
253 The default implementation does nothing.
254 */
255 void QQmlIncubationController::incubatingObjectCountChanged(int incubatingObjectCount)
256 {
257     Q_UNUSED(incubatingObjectCount);
258 }
259
260 void QQmlIncubatorPrivate::incubate(QQmlVME::Interrupt &i)
261 {
262     if (!compiledData)
263         return;
264     QML_MEMORY_SCOPE_URL(compiledData->url);
265
266     typedef QQmlIncubatorPrivate IP;
267     QRecursionWatcher<IP, &IP::recursion> watcher(this);
268
269     QQmlEngine *engine = compiledData->engine;
270     QQmlEnginePrivate *enginePriv = QQmlEnginePrivate::get(engine);
271
272     bool guardOk = vmeGuard.isOK();
273     vmeGuard.clear();
274
275     if (!guardOk) {
276         QQmlError error;
277         error.setUrl(compiledData->url);
278         error.setDescription(QQmlComponent::tr("Object destroyed during incubation"));
279         errors << error;
280         progress = QQmlIncubatorPrivate::Completed;
281
282         goto finishIncubate;
283     }
284
285     if (progress == QQmlIncubatorPrivate::Execute) {
286         enginePriv->referenceScarceResources();
287         QObject *tresult = vme.execute(&errors, i);
288         enginePriv->dereferenceScarceResources();
289
290         if (watcher.hasRecursed())
291             return;
292
293         result = tresult;
294         if (errors.isEmpty() && result == 0) 
295             goto finishIncubate;
296
297         if (result) {
298             QQmlData *ddata = QQmlData::get(result);
299             Q_ASSERT(ddata);
300             //see QQmlComponent::beginCreate for explanation of indestructible
301             ddata->indestructible = true;
302             ddata->explicitIndestructibleSet = true;
303             ddata->rootObjectInCreation = false;
304             q->setInitialState(result);
305         }
306
307         if (watcher.hasRecursed())
308             return;
309
310         if (errors.isEmpty())
311             progress = QQmlIncubatorPrivate::Completing;
312         else
313             progress = QQmlIncubatorPrivate::Completed;
314
315         changeStatus(calculateStatus());
316
317         if (watcher.hasRecursed())
318             return;
319
320         if (i.shouldInterrupt())
321             goto finishIncubate;
322     }
323
324     if (progress == QQmlIncubatorPrivate::Completing) {
325         do {
326             if (watcher.hasRecursed())
327                 return;
328
329             QQmlContextData *ctxt = vme.complete(i);
330             if (ctxt) {
331                 rootContext = ctxt;
332                 progress = QQmlIncubatorPrivate::Completed;
333                 goto finishIncubate;
334             }
335         } while (!i.shouldInterrupt());
336     }
337
338 finishIncubate:
339     if (progress == QQmlIncubatorPrivate::Completed && waitingFor.isEmpty()) {
340         typedef QQmlIncubatorPrivate IP;
341
342         QQmlIncubatorPrivate *isWaiting = waitingOnMe;
343         clear();
344
345         if (isWaiting) {
346             QRecursionWatcher<IP, &IP::recursion> watcher(isWaiting);
347             changeStatus(calculateStatus());
348             if (!watcher.hasRecursed())
349                 isWaiting->incubate(i);
350         } else {
351             changeStatus(calculateStatus());
352         }
353
354         enginePriv->inProgressCreations--;
355
356         if (0 == enginePriv->inProgressCreations) {
357             while (enginePriv->erroredBindings) {
358                 enginePriv->warning(enginePriv->erroredBindings);
359                 enginePriv->erroredBindings->removeError();
360             }
361         }
362     } else {
363         vmeGuard.guard(&vme);
364     }
365 }
366
367 /*!
368 Incubate objects for \a msecs, or until there are no more objects to incubate.
369 */
370 void QQmlIncubationController::incubateFor(int msecs)
371 {
372     if (!d || d->incubatorCount == 0)
373         return;
374
375     QQmlVME::Interrupt i(msecs * 1000000);
376     i.reset();
377     do {
378         QQmlIncubatorPrivate *p = (QQmlIncubatorPrivate*)d->incubatorList.first();
379         p->incubate(i);
380     } while (d && d->incubatorCount != 0 && !i.shouldInterrupt());
381 }
382
383 /*!
384 Incubate objects while the bool pointed to by \a flag is true, or until there are no
385 more objects to incubate, or up to msecs if msecs is not zero.
386
387 Generally this method is used in conjunction with a thread or a UNIX signal that sets
388 the bool pointed to by \a flag to false when it wants incubation to be interrupted.
389 */
390 void QQmlIncubationController::incubateWhile(volatile bool *flag, int msecs)
391 {
392     if (!d || d->incubatorCount == 0)
393         return;
394
395     QQmlVME::Interrupt i(flag, msecs * 1000000);
396     i.reset();
397     do {
398         QQmlIncubatorPrivate *p = (QQmlIncubatorPrivate*)d->incubatorList.first();
399         p->incubate(i);
400     } while (d && d->incubatorCount != 0 && !i.shouldInterrupt());
401 }
402
403 /*!
404 \class QQmlIncubator
405 \brief The QQmlIncubator class allows QML objects to be created asynchronously.
406 \inmodule QtQml
407
408 Creating QML objects - like delegates in a view, or a new page in an application - can take
409 a noticable amount of time, especially on resource constrained mobile devices.  When an
410 application uses QQmlComponent::create() directly, the QML object instance is created
411 synchronously which, depending on the complexity of the object,  can cause noticable pauses or 
412 stutters in the application.
413
414 The use of QQmlIncubator gives more control over the creation of a QML object, 
415 including allowing it to be created asynchronously using application idle time.  The following 
416 example shows a simple use of QQmlIncubator.
417
418 \code
419 QQmlIncubator incubator;
420 component->create(incubator);
421
422 while (incubator.isReady()) {
423     QCoreApplication::processEvents(QEventLoop::AllEvents, 50);
424 }
425
426 QObject *object = incubator.object();
427 \endcode
428
429 Asynchronous incubators are controlled by a QQmlIncubationController that is 
430 set on the QQmlEngine, which lets the engine know when the application is idle and
431 incubating objects should be processed.  If an incubation controller is not set on the
432 QQmlEngine, QQmlIncubator creates objects synchronously regardless of the
433 specified IncubationMode.  
434
435 QQmlIncubator supports three incubation modes:
436 \list
437 \li Synchronous The creation occurs synchronously.  That is, once the
438 QQmlComponent::create() call returns, the incubator will already be in either the
439 Error or Ready state.  A synchronous incubator has no real advantage compared to using
440 the synchronous creation methods on QQmlComponent directly, but it may simplify an 
441 application's implementation to use the same API for both synchronous and asynchronous 
442 creations.
443
444 \li Asynchronous (default) The creation occurs asynchronously, assuming a
445 QQmlIncubatorController is set on the QQmlEngine.  
446
447 The incubator will remain in the Loading state until either the creation is complete or an error 
448 occurs.  The statusChanged() callback can be used to be notified of status changes.
449
450 Applications should use the Asynchronous incubation mode to create objects that are not needed
451 immediately.  For example, the ListView type uses Asynchronous incubation to create objects
452 that are slightly off screen while the list is being scrolled.  If, during asynchronous creation,
453 the object is needed immediately the QQmlIncubator::forceCompletion() method can be called
454 to complete the creation process synchronously.
455
456 \li AsynchronousIfNested The creation will occur asynchronously if part of a nested asynchronous
457 creation, or synchronously if not.  
458
459 In most scenarios where a QML component wants the appearance of a synchronous
460 instantiation, it should use this mode.  
461
462 This mode is best explained with an example.  When the ListView type is first created, it needs
463 to populate itself with an initial set of delegates to show.  If the ListView was 400 pixels high, 
464 and each delegate was 100 pixels high, it would need to create four initial delegate instances.  If
465 the ListView used the Asynchronous incubation mode, the ListView would always be created empty and
466 then, sometime later, the four initial items would appear.
467
468 Conversely, if the ListView was to use the Synchronous incubation mode it would behave correctly 
469 but it may introduce stutters into the application.  As QML would have to stop and instantiate the 
470 ListView's delegates synchronously, if the ListView was part of a QML component that was being 
471 instantiated asynchronously this would undo much of the benefit of asynchronous instantiation.
472
473 The AsynchronousIfNested mode reconciles this problem.  By using AsynchronousIfNested, the ListView
474 delegates are instantiated asynchronously if the ListView itself is already part of an asynchronous
475 instantiation, and synchronously otherwise.  In the case of a nested asynchronous instantiation, the
476 outer asynchronous instantiation will not complete until after all the nested instantiations have also
477 completed.  This ensures that by the time the outer asynchronous instantitation completes, inner 
478 items like ListView have already completed loading their initial delegates.
479
480 It is almost always incorrect to use the Synchronous incubation mode - elements or components that
481 want the appearance of synchronous instantiation, but without the downsides of introducing freezes 
482 or stutters into the application, should use the AsynchronousIfNested incubation mode.
483 \endlist
484 */
485
486 /*!
487 Create a new incubator with the specified \a mode
488 */
489 QQmlIncubator::QQmlIncubator(IncubationMode mode)
490 : d(new QQmlIncubatorPrivate(this, mode))
491 {
492 }
493
494 /*! \internal */
495 QQmlIncubator::~QQmlIncubator()
496 {
497     clear();
498
499     delete d; d = 0;
500 }
501
502 /*!
503 \enum QQmlIncubator::IncubationMode
504
505 Specifies the mode the incubator operates in.  Regardless of the incubation mode, a 
506 QQmlIncubator will behave synchronously if the QQmlEngine does not have
507 a QQmlIncubationController set.
508
509 \value Asynchronous The object will be created asynchronously.
510 \value AsynchronousIfNested If the object is being created in a context that is already part
511 of an asynchronous creation, this incubator will join that existing incubation and execute 
512 asynchronously.  The existing incubation will not become Ready until both it and this 
513 incubation have completed.  Otherwise, the incubation will execute synchronously.
514 \value Synchronous The object will be created synchronously.
515 */
516
517 /*!
518 \enum QQmlIncubator::Status
519
520 Specifies the status of the QQmlIncubator.
521
522 \value Null Incubation is not in progress.  Call QQmlComponent::create() to begin incubating.
523 \value Ready The object is fully created and can be accessed by calling object().
524 \value Loading The object is in the process of being created.
525 \value Error An error occurred.  The errors can be access by calling errors().
526 */
527
528 /*!
529 Clears the incubator.  Any in-progress incubation is aborted.  If the incubator is in the 
530 Ready state, the created object is \b not deleted.
531 */
532 void QQmlIncubator::clear()
533 {
534     typedef QQmlIncubatorPrivate IP;
535     QRecursionWatcher<IP, &IP::recursion> watcher(d);
536
537     Status s = status();
538
539     if (s == Null)
540         return;
541
542     QQmlEnginePrivate *enginePriv = 0;
543     if (s == Loading) {
544         Q_ASSERT(d->compiledData);
545         enginePriv = QQmlEnginePrivate::get(d->compiledData->engine);
546         if (d->result) d->result->deleteLater();
547         d->result = 0;
548     }
549
550     d->clear();
551
552     // if we're waiting on any incubators then they should be cleared too.
553     while (d->waitingFor.first())
554         static_cast<QQmlIncubatorPrivate*>(d->waitingFor.first())->q->clear();
555
556     d->vme.reset();
557     d->vmeGuard.clear();
558
559     Q_ASSERT(d->compiledData == 0);
560     Q_ASSERT(d->waitingOnMe == 0);
561     Q_ASSERT(d->waitingFor.isEmpty());
562     Q_ASSERT(!d->nextWaitingFor.isInList());
563
564     d->errors.clear();
565     d->progress = QQmlIncubatorPrivate::Execute;
566     d->result = 0;
567
568     if (s == Loading) {
569         Q_ASSERT(enginePriv);
570
571         enginePriv->inProgressCreations--;
572         if (0 == enginePriv->inProgressCreations) {
573             while (enginePriv->erroredBindings) {
574                 enginePriv->warning(enginePriv->erroredBindings);
575                 enginePriv->erroredBindings->removeError();
576             }
577         }
578     }
579
580     d->changeStatus(Null);
581 }
582
583 /*!
584 Force any in-progress incubation to finish synchronously.  Once this call
585 returns, the incubator will not be in the Loading state.
586 */
587 void QQmlIncubator::forceCompletion()
588 {
589     QQmlVME::Interrupt i;
590     while (Loading == status()) {
591         while (Loading == status() && !d->waitingFor.isEmpty())
592             static_cast<QQmlIncubatorPrivate *>(d->waitingFor.first())->incubate(i);
593         if (Loading == status())
594             d->incubate(i);
595     }
596 }
597
598 /*!
599 Returns true if the incubator's status() is Null.
600 */
601 bool QQmlIncubator::isNull() const
602 {
603     return status() == Null;
604 }
605
606 /*!
607 Returns true if the incubator's status() is Ready.
608 */
609 bool QQmlIncubator::isReady() const
610 {
611     return status() == Ready;
612 }
613
614 /*!
615 Returns true if the incubator's status() is Error.
616 */
617 bool QQmlIncubator::isError() const
618 {
619     return status() == Error;
620 }
621
622 /*!
623 Returns true if the incubator's status() is Loading.
624 */
625 bool QQmlIncubator::isLoading() const
626 {
627     return status() == Loading;
628 }
629
630 /*!
631 Return the list of errors encountered while incubating the object.
632 */
633 QList<QQmlError> QQmlIncubator::errors() const
634 {
635     return d->errors;
636 }
637
638 /*!
639 Return the incubation mode passed to the QQmlIncubator constructor.
640 */
641 QQmlIncubator::IncubationMode QQmlIncubator::incubationMode() const
642 {
643     return d->mode;
644 }
645
646 /*!
647 Return the current status of the incubator.
648 */
649 QQmlIncubator::Status QQmlIncubator::status() const
650 {
651     return d->status;
652 }
653
654 /*!
655 Return the incubated object if the status is Ready, otherwise 0.
656 */
657 QObject *QQmlIncubator::object() const
658 {
659     if (status() != Ready) return 0;
660     else return d->result;
661 }
662
663 /*!
664 Called when the status of the incubator changes.  \a status is the new status.
665
666 The default implementation does nothing.
667 */
668 void QQmlIncubator::statusChanged(Status status)
669 {
670     Q_UNUSED(status);
671 }
672
673 /*!
674 Called after the object is first created, but before property bindings are
675 evaluated and, if applicable, QQmlParserStatus::componentComplete() is
676 called.  This is equivalent to the point between QQmlComponent::beginCreate()
677 and QQmlComponent::endCreate(), and can be used to assign initial values
678 to the object's properties.
679
680 The default implementation does nothing.
681 */
682 void QQmlIncubator::setInitialState(QObject *object)
683 {
684     Q_UNUSED(object);
685 }
686
687 void QQmlIncubatorPrivate::changeStatus(QQmlIncubator::Status s)
688 {
689     if (s == status) 
690         return;
691
692     status = s;
693     q->statusChanged(status);
694 }
695
696 QQmlIncubator::Status QQmlIncubatorPrivate::calculateStatus() const
697 {
698     if (!errors.isEmpty()) 
699         return QQmlIncubator::Error;
700     else if (result && progress == QQmlIncubatorPrivate::Completed && 
701              waitingFor.isEmpty()) 
702         return QQmlIncubator::Ready;
703     else if (compiledData)
704         return QQmlIncubator::Loading;
705     else 
706         return QQmlIncubator::Null;
707 }
708