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