1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qqmlincubator.h" |
5 | #include "qqmlcomponent.h" |
6 | #include "qqmlincubator_p.h" |
7 | |
8 | #include "qqmlobjectcreator_p.h" |
9 | #include <private/qqmlcomponent_p.h> |
10 | |
11 | void QQmlEnginePrivate::incubate( |
12 | QQmlIncubator &i, const QQmlRefPointer<QQmlContextData> &forContext) |
13 | { |
14 | QExplicitlySharedDataPointer<QQmlIncubatorPrivate> p(i.d); |
15 | |
16 | QQmlIncubator::IncubationMode mode = i.incubationMode(); |
17 | |
18 | if (!incubationController) |
19 | mode = QQmlIncubator::Synchronous; |
20 | |
21 | if (mode == QQmlIncubator::AsynchronousIfNested) { |
22 | mode = QQmlIncubator::Synchronous; |
23 | |
24 | // Need to find the first constructing context and see if it is asynchronous |
25 | QExplicitlySharedDataPointer<QQmlIncubatorPrivate> parentIncubator; |
26 | QQmlRefPointer<QQmlContextData> cctxt = forContext; |
27 | while (cctxt) { |
28 | if (QQmlIncubatorPrivate *incubator = cctxt->incubator()) { |
29 | parentIncubator = incubator; |
30 | break; |
31 | } |
32 | cctxt = cctxt->parent(); |
33 | } |
34 | |
35 | if (parentIncubator && parentIncubator->isAsynchronous) { |
36 | mode = QQmlIncubator::Asynchronous; |
37 | p->waitingOnMe = parentIncubator; |
38 | parentIncubator->waitingFor.insert(n: p.data()); |
39 | } |
40 | } |
41 | |
42 | p->isAsynchronous = (mode != QQmlIncubator::Synchronous); |
43 | |
44 | inProgressCreations++; |
45 | |
46 | if (mode == QQmlIncubator::Synchronous) { |
47 | QRecursionWatcher<QQmlIncubatorPrivate, &QQmlIncubatorPrivate::recursion> watcher(p.data()); |
48 | |
49 | p->changeStatus(QQmlIncubator::Loading); |
50 | |
51 | if (!watcher.hasRecursed()) { |
52 | QQmlInstantiationInterrupt i; |
53 | p->incubate(i); |
54 | } |
55 | } else { |
56 | incubatorList.insert(n: p.data()); |
57 | incubatorCount++; |
58 | |
59 | p->vmeGuard.guard(p->creator.data()); |
60 | p->changeStatus(QQmlIncubator::Loading); |
61 | |
62 | if (incubationController) |
63 | incubationController->incubatingObjectCountChanged(incubatorCount); |
64 | } |
65 | } |
66 | |
67 | /*! |
68 | Sets the engine's incubation \a controller. The engine can only have one active controller |
69 | and it does not take ownership of it. |
70 | |
71 | \sa incubationController() |
72 | */ |
73 | void QQmlEngine::setIncubationController(QQmlIncubationController *controller) |
74 | { |
75 | Q_D(QQmlEngine); |
76 | if (d->incubationController) |
77 | d->incubationController->d = nullptr; |
78 | d->incubationController = controller; |
79 | if (controller) controller->d = d; |
80 | } |
81 | |
82 | /*! |
83 | Returns the currently set incubation controller, or 0 if no controller has been set. |
84 | |
85 | \sa setIncubationController() |
86 | */ |
87 | QQmlIncubationController *QQmlEngine::incubationController() const |
88 | { |
89 | Q_D(const QQmlEngine); |
90 | return d->incubationController; |
91 | } |
92 | |
93 | QQmlIncubatorPrivate::QQmlIncubatorPrivate(QQmlIncubator *q, QQmlIncubator::IncubationMode m) |
94 | : q(q), status(QQmlIncubator::Null), mode(m), isAsynchronous(false), progress(Execute), |
95 | result(nullptr), enginePriv(nullptr), waitingOnMe(nullptr) |
96 | { |
97 | } |
98 | |
99 | QQmlIncubatorPrivate::~QQmlIncubatorPrivate() |
100 | { |
101 | clear(); |
102 | } |
103 | |
104 | void QQmlIncubatorPrivate::clear() |
105 | { |
106 | // reset the tagged pointer |
107 | if (requiredPropertiesFromComponent) |
108 | requiredPropertiesFromComponent = decltype(requiredPropertiesFromComponent){}; |
109 | compilationUnit.reset(); |
110 | if (next.isInList()) { |
111 | next.remove(); |
112 | enginePriv->incubatorCount--; |
113 | QQmlIncubationController *controller = enginePriv->incubationController; |
114 | if (controller) |
115 | controller->incubatingObjectCountChanged(enginePriv->incubatorCount); |
116 | } |
117 | enginePriv = nullptr; |
118 | if (!rootContext.isNull()) { |
119 | if (rootContext->incubator()) |
120 | rootContext->setIncubator(nullptr); |
121 | rootContext.setContextData({}); |
122 | } |
123 | |
124 | if (nextWaitingFor.isInList()) { |
125 | Q_ASSERT(waitingOnMe); |
126 | nextWaitingFor.remove(); |
127 | waitingOnMe = nullptr; |
128 | } |
129 | |
130 | // if we're waiting on any incubators then they should be cleared too. |
131 | while (waitingFor.first()) { |
132 | QQmlIncubator * i = static_cast<QQmlIncubatorPrivate*>(waitingFor.first())->q; |
133 | if (i) |
134 | i->clear(); |
135 | } |
136 | |
137 | bool guardOk = vmeGuard.isOK(); |
138 | |
139 | vmeGuard.clear(); |
140 | if (creator && guardOk) |
141 | creator->clear(); |
142 | creator.reset(other: nullptr); |
143 | } |
144 | |
145 | /*! |
146 | \class QQmlIncubationController |
147 | \brief QQmlIncubationController instances drive the progress of QQmlIncubators. |
148 | \inmodule QtQml |
149 | |
150 | In order to behave asynchronously and not introduce stutters or freezes in an application, |
151 | the process of creating objects a QQmlIncubators must be driven only during the |
152 | application's idle time. QQmlIncubationController allows the application to control |
153 | exactly when, how often and for how long this processing occurs. |
154 | |
155 | A QQmlIncubationController derived instance should be created and set on a |
156 | QQmlEngine by calling the QQmlEngine::setIncubationController() method. |
157 | Processing is then controlled by calling the QQmlIncubationController::incubateFor() |
158 | or QQmlIncubationController::incubateWhile() methods as dictated by the application's |
159 | requirements. |
160 | |
161 | For example, this is an example of a incubation controller that will incubate for a maximum |
162 | of 5 milliseconds out of every 16 milliseconds. |
163 | |
164 | \code |
165 | class PeriodicIncubationController : public QObject, |
166 | public QQmlIncubationController |
167 | { |
168 | public: |
169 | PeriodicIncubationController() { |
170 | startTimer(16); |
171 | } |
172 | |
173 | protected: |
174 | void timerEvent(QTimerEvent *) override { |
175 | incubateFor(5); |
176 | } |
177 | }; |
178 | \endcode |
179 | |
180 | Although the previous example would work, it is not optimal. Real world incubation |
181 | controllers should try and maximize the amount of idle time they consume - rather |
182 | than a static amount like 5 milliseconds - while not disturbing the application. |
183 | */ |
184 | |
185 | /*! |
186 | Create a new incubation controller. |
187 | */ |
188 | QQmlIncubationController::QQmlIncubationController() |
189 | : d(nullptr) |
190 | { |
191 | } |
192 | |
193 | /*! \internal */ |
194 | QQmlIncubationController::~QQmlIncubationController() |
195 | { |
196 | if (d) QQmlEnginePrivate::get(p: d)->setIncubationController(nullptr); |
197 | d = nullptr; |
198 | } |
199 | |
200 | /*! |
201 | Return the QQmlEngine this incubation controller is set on, or 0 if it |
202 | has not been set on any engine. |
203 | */ |
204 | QQmlEngine *QQmlIncubationController::engine() const |
205 | { |
206 | return QQmlEnginePrivate::get(p: d); |
207 | } |
208 | |
209 | /*! |
210 | Return the number of objects currently incubating. |
211 | */ |
212 | int QQmlIncubationController::incubatingObjectCount() const |
213 | { |
214 | return d ? d->incubatorCount : 0; |
215 | } |
216 | |
217 | /*! |
218 | Called when the number of incubating objects changes. \a incubatingObjectCount is the |
219 | new number of incubating objects. |
220 | |
221 | The default implementation does nothing. |
222 | */ |
223 | void QQmlIncubationController::incubatingObjectCountChanged(int incubatingObjectCount) |
224 | { |
225 | Q_UNUSED(incubatingObjectCount); |
226 | } |
227 | |
228 | void QQmlIncubatorPrivate::forceCompletion(QQmlInstantiationInterrupt &i) |
229 | { |
230 | while (QQmlIncubator::Loading == status) { |
231 | while (QQmlIncubator::Loading == status && !waitingFor.isEmpty()) |
232 | waitingFor.first()->forceCompletion(i); |
233 | if (QQmlIncubator::Loading == status) |
234 | incubate(i); |
235 | } |
236 | } |
237 | |
238 | |
239 | void QQmlIncubatorPrivate::incubate(QQmlInstantiationInterrupt &i) |
240 | { |
241 | if (!compilationUnit) |
242 | return; |
243 | |
244 | QExplicitlySharedDataPointer<QQmlIncubatorPrivate> protectThis(this); |
245 | |
246 | QRecursionWatcher<QQmlIncubatorPrivate, &QQmlIncubatorPrivate::recursion> watcher(this); |
247 | // get a copy of the engine pointer as it might get reset; |
248 | QQmlEnginePrivate *enginePriv = this->enginePriv; |
249 | |
250 | // Incubating objects takes quite a bit more stack space than our usual V4 function |
251 | enum { EstimatedSizeInV4Frames = 2 }; |
252 | QV4::ExecutionEngineCallDepthRecorder<EstimatedSizeInV4Frames> callDepthRecorder( |
253 | compilationUnit->engine); |
254 | if (callDepthRecorder.hasOverflow()) { |
255 | QQmlError error; |
256 | error.setMessageType(QtCriticalMsg); |
257 | error.setUrl(compilationUnit->url()); |
258 | error.setDescription(QQmlComponent::tr(s: "Maximum call stack size exceeded." )); |
259 | errors << error; |
260 | progress = QQmlIncubatorPrivate::Completed; |
261 | goto finishIncubate; |
262 | } |
263 | |
264 | if (!vmeGuard.isOK()) { |
265 | QQmlError error; |
266 | error.setMessageType(QtInfoMsg); |
267 | error.setUrl(compilationUnit->url()); |
268 | error.setDescription(QQmlComponent::tr(s: "Object or context destroyed during incubation" )); |
269 | errors << error; |
270 | progress = QQmlIncubatorPrivate::Completed; |
271 | |
272 | goto finishIncubate; |
273 | } |
274 | |
275 | vmeGuard.clear(); |
276 | |
277 | if (progress == QQmlIncubatorPrivate::Execute) { |
278 | enginePriv->referenceScarceResources(); |
279 | QObject *tresult = nullptr; |
280 | tresult = creator->create(subComponentIndex: subComponentToCreate, /*parent*/nullptr, interrupt: &i); |
281 | if (!tresult) |
282 | errors = creator->errors; |
283 | else { |
284 | RequiredProperties* requiredProperties = creator->requiredProperties(); |
285 | for (auto it = initialProperties.cbegin(); it != initialProperties.cend(); ++it) { |
286 | auto component = tresult; |
287 | auto name = it.key(); |
288 | QQmlProperty prop = QQmlComponentPrivate::removePropertyFromRequired( |
289 | createdComponent: component, name, requiredProperties, engine: QQmlEnginePrivate::get(p: enginePriv)); |
290 | if (!prop.isValid() || !prop.write(it.value())) { |
291 | QQmlError error{}; |
292 | error.setUrl(compilationUnit->url()); |
293 | error.setDescription(QLatin1String("Could not set property %1" ).arg(args&: name)); |
294 | errors.push_back(t: error); |
295 | } |
296 | } |
297 | } |
298 | enginePriv->dereferenceScarceResources(); |
299 | |
300 | if (watcher.hasRecursed()) |
301 | return; |
302 | |
303 | result = tresult; |
304 | if (errors.isEmpty() && result == nullptr) |
305 | goto finishIncubate; |
306 | |
307 | if (result) { |
308 | QQmlData *ddata = QQmlData::get(object: result); |
309 | Q_ASSERT(ddata); |
310 | //see QQmlComponent::beginCreate for explanation of indestructible |
311 | ddata->indestructible = true; |
312 | ddata->explicitIndestructibleSet = true; |
313 | ddata->rootObjectInCreation = false; |
314 | if (q) { |
315 | q->setInitialState(result); |
316 | if (creator && !creator->requiredProperties()->empty()) { |
317 | const RequiredProperties *unsetRequiredProperties = creator->requiredProperties(); |
318 | for (const auto& unsetRequiredProperty: *unsetRequiredProperties) |
319 | errors << QQmlComponentPrivate::unsetRequiredPropertyToQQmlError(unsetRequiredProperty); |
320 | } |
321 | } |
322 | } |
323 | |
324 | if (watcher.hasRecursed()) |
325 | return; |
326 | |
327 | if (errors.isEmpty()) |
328 | progress = QQmlIncubatorPrivate::Completing; |
329 | else |
330 | progress = QQmlIncubatorPrivate::Completed; |
331 | |
332 | changeStatus(calculateStatus()); |
333 | |
334 | if (watcher.hasRecursed()) |
335 | return; |
336 | |
337 | if (i.shouldInterrupt()) |
338 | goto finishIncubate; |
339 | } |
340 | |
341 | if (progress == QQmlIncubatorPrivate::Completing) { |
342 | do { |
343 | if (watcher.hasRecursed()) |
344 | return; |
345 | |
346 | if (creator->finalize(interrupt&: i)) { |
347 | rootContext = creator->rootContext(); |
348 | progress = QQmlIncubatorPrivate::Completed; |
349 | goto finishIncubate; |
350 | } |
351 | } while (!i.shouldInterrupt()); |
352 | } |
353 | |
354 | finishIncubate: |
355 | if (progress == QQmlIncubatorPrivate::Completed && waitingFor.isEmpty()) { |
356 | QExplicitlySharedDataPointer<QQmlIncubatorPrivate> isWaiting = waitingOnMe; |
357 | clear(); |
358 | |
359 | if (isWaiting) { |
360 | QRecursionWatcher<QQmlIncubatorPrivate, &QQmlIncubatorPrivate::recursion> watcher(isWaiting.data()); |
361 | changeStatus(calculateStatus()); |
362 | if (!watcher.hasRecursed()) |
363 | isWaiting->incubate(i); |
364 | } else { |
365 | changeStatus(calculateStatus()); |
366 | } |
367 | |
368 | enginePriv->inProgressCreations--; |
369 | |
370 | if (0 == enginePriv->inProgressCreations) { |
371 | while (enginePriv->erroredBindings) |
372 | enginePriv->warning(enginePriv->erroredBindings->removeError()); |
373 | } |
374 | } else if (!creator.isNull()) { |
375 | vmeGuard.guard(creator.data()); |
376 | } |
377 | } |
378 | |
379 | /*! |
380 | \internal |
381 | This is used to mimic the behavior of incubate when the |
382 | Component we want to incubate refers to a creatable |
383 | QQmlType (i.e., it is the result of loadFromModule). |
384 | */ |
385 | void QQmlIncubatorPrivate::incubateCppBasedComponent(QQmlComponent *component, QQmlContext *context) |
386 | { |
387 | auto compPriv = QQmlComponentPrivate::get(c: component); |
388 | Q_ASSERT(compPriv->loadedType.isCreatable()); |
389 | std::unique_ptr<QObject> object(component->beginCreate(context)); |
390 | component->setInitialProperties(component: object.get(), properties: initialProperties); |
391 | if (auto props = compPriv->state.requiredProperties()) { |
392 | requiredPropertiesFromComponent = props; |
393 | requiredPropertiesFromComponent.setTag(HadTopLevelRequired::Yes); |
394 | } |
395 | q->setInitialState(object.get()); |
396 | if (requiredPropertiesFromComponent && !requiredPropertiesFromComponent->isEmpty()) { |
397 | for (const RequiredPropertyInfo &unsetRequiredProperty : |
398 | std::as_const(t&: *requiredPropertiesFromComponent)) { |
399 | errors << QQmlComponentPrivate::unsetRequiredPropertyToQQmlError(unsetRequiredProperty); |
400 | } |
401 | } else { |
402 | compPriv->completeCreate(); |
403 | result = object.release(); |
404 | progress = QQmlIncubatorPrivate::Completed; |
405 | } |
406 | changeStatus(calculateStatus()); |
407 | |
408 | } |
409 | |
410 | /*! |
411 | Incubate objects for \a msecs, or until there are no more objects to incubate. |
412 | */ |
413 | void QQmlIncubationController::incubateFor(int msecs) |
414 | { |
415 | if (!d || !d->incubatorCount) |
416 | return; |
417 | |
418 | QDeadlineTimer deadline(msecs); |
419 | QQmlInstantiationInterrupt i(deadline); |
420 | do { |
421 | static_cast<QQmlIncubatorPrivate*>(d->incubatorList.first())->incubate(i); |
422 | } while (d && d->incubatorCount != 0 && !i.shouldInterrupt()); |
423 | } |
424 | |
425 | /*! |
426 | \since 5.15 |
427 | |
428 | Incubate objects while the atomic bool pointed to by \a flag is true, |
429 | or until there are no more objects to incubate, or up to \a msecs if \a |
430 | msecs is not zero. |
431 | |
432 | Generally this method is used in conjunction with a thread or a UNIX signal that sets |
433 | the bool pointed to by \a flag to false when it wants incubation to be interrupted. |
434 | |
435 | \note \a flag is read using acquire memory ordering. |
436 | */ |
437 | void QQmlIncubationController::incubateWhile(std::atomic<bool> *flag, int msecs) |
438 | { |
439 | if (!d || !d->incubatorCount) |
440 | return; |
441 | |
442 | QQmlInstantiationInterrupt i(flag, msecs ? QDeadlineTimer(msecs) : QDeadlineTimer::Forever); |
443 | do { |
444 | static_cast<QQmlIncubatorPrivate*>(d->incubatorList.first())->incubate(i); |
445 | } while (d && d->incubatorCount != 0 && !i.shouldInterrupt()); |
446 | } |
447 | |
448 | /*! |
449 | \class QQmlIncubator |
450 | \brief The QQmlIncubator class allows QML objects to be created asynchronously. |
451 | \inmodule QtQml |
452 | |
453 | Creating QML objects - like delegates in a view, or a new page in an application - can take |
454 | a noticeable amount of time, especially on resource constrained mobile devices. When an |
455 | application uses QQmlComponent::create() directly, the QML object instance is created |
456 | synchronously which, depending on the complexity of the object, can cause noticeable pauses or |
457 | stutters in the application. |
458 | |
459 | The use of QQmlIncubator gives more control over the creation of a QML object, |
460 | including allowing it to be created asynchronously using application idle time. The following |
461 | example shows a simple use of QQmlIncubator. |
462 | |
463 | \code |
464 | QQmlIncubator incubator; |
465 | component->create(incubator); |
466 | |
467 | while (!incubator.isReady()) { |
468 | QCoreApplication::processEvents(QEventLoop::AllEvents, 50); |
469 | } |
470 | |
471 | QObject *object = incubator.object(); |
472 | \endcode |
473 | |
474 | Asynchronous incubators are controlled by a QQmlIncubationController that is |
475 | set on the QQmlEngine, which lets the engine know when the application is idle and |
476 | incubating objects should be processed. If an incubation controller is not set on the |
477 | QQmlEngine, QQmlIncubator creates objects synchronously regardless of the |
478 | specified IncubationMode. |
479 | |
480 | QQmlIncubator supports three incubation modes: |
481 | \list |
482 | \li Synchronous The creation occurs synchronously. That is, once the |
483 | QQmlComponent::create() call returns, the incubator will already be in either the |
484 | Error or Ready state. A synchronous incubator has no real advantage compared to using |
485 | the synchronous creation methods on QQmlComponent directly, but it may simplify an |
486 | application's implementation to use the same API for both synchronous and asynchronous |
487 | creations. |
488 | |
489 | \li Asynchronous (default) The creation occurs asynchronously, assuming a |
490 | QQmlIncubatorController is set on the QQmlEngine. |
491 | |
492 | The incubator will remain in the Loading state until either the creation is complete or an error |
493 | occurs. The statusChanged() callback can be used to be notified of status changes. |
494 | |
495 | Applications should use the Asynchronous incubation mode to create objects that are not needed |
496 | immediately. For example, the ListView type uses Asynchronous incubation to create objects |
497 | that are slightly off screen while the list is being scrolled. If, during asynchronous creation, |
498 | the object is needed immediately the QQmlIncubator::forceCompletion() method can be called |
499 | to complete the creation process synchronously. |
500 | |
501 | \li AsynchronousIfNested The creation will occur asynchronously if part of a nested asynchronous |
502 | creation, or synchronously if not. |
503 | |
504 | In most scenarios where a QML component wants the appearance of a synchronous |
505 | instantiation, it should use this mode. |
506 | |
507 | This mode is best explained with an example. When the ListView type is first created, it needs |
508 | to populate itself with an initial set of delegates to show. If the ListView was 400 pixels high, |
509 | and each delegate was 100 pixels high, it would need to create four initial delegate instances. If |
510 | the ListView used the Asynchronous incubation mode, the ListView would always be created empty and |
511 | then, sometime later, the four initial items would appear. |
512 | |
513 | Conversely, if the ListView was to use the Synchronous incubation mode it would behave correctly |
514 | but it may introduce stutters into the application. As QML would have to stop and instantiate the |
515 | ListView's delegates synchronously, if the ListView was part of a QML component that was being |
516 | instantiated asynchronously this would undo much of the benefit of asynchronous instantiation. |
517 | |
518 | The AsynchronousIfNested mode reconciles this problem. By using AsynchronousIfNested, the ListView |
519 | delegates are instantiated asynchronously if the ListView itself is already part of an asynchronous |
520 | instantiation, and synchronously otherwise. In the case of a nested asynchronous instantiation, the |
521 | outer asynchronous instantiation will not complete until after all the nested instantiations have also |
522 | completed. This ensures that by the time the outer asynchronous instantitation completes, inner |
523 | items like ListView have already completed loading their initial delegates. |
524 | |
525 | It is almost always incorrect to use the Synchronous incubation mode - elements or components that |
526 | want the appearance of synchronous instantiation, but without the downsides of introducing freezes |
527 | or stutters into the application, should use the AsynchronousIfNested incubation mode. |
528 | \endlist |
529 | */ |
530 | |
531 | /*! |
532 | Create a new incubator with the specified \a mode |
533 | */ |
534 | QQmlIncubator::QQmlIncubator(IncubationMode mode) |
535 | : d(new QQmlIncubatorPrivate(this, mode)) |
536 | { |
537 | d->ref.ref(); |
538 | } |
539 | |
540 | /*! \internal */ |
541 | QQmlIncubator::~QQmlIncubator() |
542 | { |
543 | d->q = nullptr; |
544 | |
545 | if (!d->ref.deref()) { |
546 | delete d; |
547 | } |
548 | d = nullptr; |
549 | } |
550 | |
551 | /*! |
552 | \enum QQmlIncubator::IncubationMode |
553 | |
554 | Specifies the mode the incubator operates in. Regardless of the incubation mode, a |
555 | QQmlIncubator will behave synchronously if the QQmlEngine does not have |
556 | a QQmlIncubationController set. |
557 | |
558 | \value Asynchronous The object will be created asynchronously. |
559 | \value AsynchronousIfNested If the object is being created in a context that is already part |
560 | of an asynchronous creation, this incubator will join that existing incubation and execute |
561 | asynchronously. The existing incubation will not become Ready until both it and this |
562 | incubation have completed. Otherwise, the incubation will execute synchronously. |
563 | \value Synchronous The object will be created synchronously. |
564 | */ |
565 | |
566 | /*! |
567 | \enum QQmlIncubator::Status |
568 | |
569 | Specifies the status of the QQmlIncubator. |
570 | |
571 | \value Null Incubation is not in progress. Call QQmlComponent::create() to begin incubating. |
572 | \value Ready The object is fully created and can be accessed by calling object(). |
573 | \value Loading The object is in the process of being created. |
574 | \value Error An error occurred. The errors can be access by calling errors(). |
575 | */ |
576 | |
577 | /*! |
578 | Clears the incubator. Any in-progress incubation is aborted. If the incubator is in the |
579 | Ready state, the created object is \b not deleted. |
580 | */ |
581 | void QQmlIncubator::clear() |
582 | { |
583 | QRecursionWatcher<QQmlIncubatorPrivate, &QQmlIncubatorPrivate::recursion> watcher(d); |
584 | |
585 | Status s = status(); |
586 | |
587 | if (s == Null) |
588 | return; |
589 | |
590 | QQmlEnginePrivate *enginePriv = d->enginePriv; |
591 | if (s == Loading) { |
592 | Q_ASSERT(d->compilationUnit); |
593 | if (d->result) d->result->deleteLater(); |
594 | d->result = nullptr; |
595 | } |
596 | |
597 | d->clear(); |
598 | |
599 | Q_ASSERT(d->compilationUnit.isNull()); |
600 | Q_ASSERT(d->waitingOnMe.data() == nullptr); |
601 | Q_ASSERT(d->waitingFor.isEmpty()); |
602 | |
603 | d->errors.clear(); |
604 | d->progress = QQmlIncubatorPrivate::Execute; |
605 | d->result = nullptr; |
606 | |
607 | if (s == Loading) { |
608 | Q_ASSERT(enginePriv); |
609 | |
610 | enginePriv->inProgressCreations--; |
611 | if (0 == enginePriv->inProgressCreations) { |
612 | while (enginePriv->erroredBindings) |
613 | enginePriv->warning(enginePriv->erroredBindings->removeError()); |
614 | } |
615 | } |
616 | |
617 | d->changeStatus(Null); |
618 | } |
619 | |
620 | /*! |
621 | Force any in-progress incubation to finish synchronously. Once this call |
622 | returns, the incubator will not be in the Loading state. |
623 | */ |
624 | void QQmlIncubator::forceCompletion() |
625 | { |
626 | QQmlInstantiationInterrupt i; |
627 | d->forceCompletion(i); |
628 | } |
629 | |
630 | /*! |
631 | Returns true if the incubator's status() is Null. |
632 | */ |
633 | bool QQmlIncubator::isNull() const |
634 | { |
635 | return status() == Null; |
636 | } |
637 | |
638 | /*! |
639 | Returns true if the incubator's status() is Ready. |
640 | */ |
641 | bool QQmlIncubator::isReady() const |
642 | { |
643 | return status() == Ready; |
644 | } |
645 | |
646 | /*! |
647 | Returns true if the incubator's status() is Error. |
648 | */ |
649 | bool QQmlIncubator::isError() const |
650 | { |
651 | return status() == Error; |
652 | } |
653 | |
654 | /*! |
655 | Returns true if the incubator's status() is Loading. |
656 | */ |
657 | bool QQmlIncubator::isLoading() const |
658 | { |
659 | return status() == Loading; |
660 | } |
661 | |
662 | /*! |
663 | Return the list of errors encountered while incubating the object. |
664 | */ |
665 | QList<QQmlError> QQmlIncubator::errors() const |
666 | { |
667 | return d->errors; |
668 | } |
669 | |
670 | /*! |
671 | Return the incubation mode passed to the QQmlIncubator constructor. |
672 | */ |
673 | QQmlIncubator::IncubationMode QQmlIncubator::incubationMode() const |
674 | { |
675 | return d->mode; |
676 | } |
677 | |
678 | /*! |
679 | Return the current status of the incubator. |
680 | */ |
681 | QQmlIncubator::Status QQmlIncubator::status() const |
682 | { |
683 | return d->status; |
684 | } |
685 | |
686 | /*! |
687 | Return the incubated object if the status is Ready, otherwise 0. |
688 | */ |
689 | QObject *QQmlIncubator::object() const |
690 | { |
691 | if (status() != Ready) |
692 | return nullptr; |
693 | else |
694 | return d->result; |
695 | } |
696 | |
697 | /*! |
698 | Return a pointer to a list of properties which are required but haven't |
699 | been set yet. |
700 | This list can be modified, so that subclasses which implement special logic |
701 | setInitialProperties can mark properties set there as no longer required. |
702 | |
703 | \sa QQmlIncubator::setInitialProperties |
704 | \since 5.15 |
705 | */ |
706 | RequiredProperties *QQmlIncubatorPrivate::requiredProperties() |
707 | { |
708 | if (creator) |
709 | return creator->requiredProperties(); |
710 | else |
711 | return requiredPropertiesFromComponent.data(); |
712 | } |
713 | |
714 | bool QQmlIncubatorPrivate::hadTopLevelRequiredProperties() const |
715 | { |
716 | if (creator) |
717 | return creator->componentHadTopLevelRequiredProperties(); |
718 | else |
719 | return requiredPropertiesFromComponent.tag() == HadTopLevelRequired::Yes; |
720 | } |
721 | |
722 | /*! |
723 | Stores a mapping from property names to initial values, contained in |
724 | \a initialProperties, with which the incubated component will be initialized. |
725 | |
726 | \sa QQmlComponent::setInitialProperties |
727 | \since 5.15 |
728 | */ |
729 | void QQmlIncubator::setInitialProperties(const QVariantMap &initialProperties) |
730 | { |
731 | d->initialProperties = initialProperties; |
732 | } |
733 | |
734 | /*! |
735 | Called when the status of the incubator changes. \a status is the new status. |
736 | |
737 | The default implementation does nothing. |
738 | */ |
739 | void QQmlIncubator::statusChanged(Status status) |
740 | { |
741 | Q_UNUSED(status); |
742 | } |
743 | |
744 | /*! |
745 | Called after the \a object is first created, but before property bindings are |
746 | evaluated and, if applicable, QQmlParserStatus::componentComplete() is |
747 | called. This is equivalent to the point between QQmlComponent::beginCreate() |
748 | and QQmlComponent::completeCreate(), and can be used to assign initial values |
749 | to the object's properties. |
750 | |
751 | The default implementation does nothing. |
752 | */ |
753 | void QQmlIncubator::setInitialState(QObject *object) |
754 | { |
755 | Q_UNUSED(object); |
756 | } |
757 | |
758 | void QQmlIncubatorPrivate::changeStatus(QQmlIncubator::Status s) |
759 | { |
760 | if (s == status) |
761 | return; |
762 | |
763 | status = s; |
764 | if (q) |
765 | q->statusChanged(status); |
766 | } |
767 | |
768 | QQmlIncubator::Status QQmlIncubatorPrivate::calculateStatus() const |
769 | { |
770 | if (!errors.isEmpty()) |
771 | return QQmlIncubator::Error; |
772 | else if (result && progress == QQmlIncubatorPrivate::Completed && waitingFor.isEmpty()) |
773 | return QQmlIncubator::Ready; |
774 | else if (compilationUnit) |
775 | return QQmlIncubator::Loading; |
776 | else |
777 | return QQmlIncubator::Null; |
778 | } |
779 | |
780 | |