Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qquickasyncitembuilder.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 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
7#include <private/qquickvectorimageplugin_p.h>
8
9#include <QtCore/qcoreapplication.h>
10#include <QtCore/qdeadlinetimer.h>
11#include <QtCore/qmutex.h>
12#include <QtCore/qrunnable.h>
13#include <QtCore/qthread.h>
14#include <QtCore/qthreadpool.h>
15#include <QtQuick/qquickitem.h>
16
17#include <atomic>
18
20
21// State shared between the builder and the worker. The worker only posts to the receiver while
22// holding the mutex, and the builder clears the receiver under the same mutex before it goes
23// away, so the worker never posts to a deleted builder.
25{
26 std::atomic<bool> cancelled = false;
28 QObject *receiver = nullptr;
29};
30
31namespace {
32
33QEvent::Type resultEventType()
34{
35 static const QEvent::Type type = QEvent::Type(QEvent::registerEventType());
36 return type;
37}
38
39QEvent::Type initializeEventType()
40{
41 static const QEvent::Type type = QEvent::Type(QEvent::registerEventType());
42 return type;
43}
44
45// Carries the generated tree from the worker to the builder as well as any additional calls to
46// be executed on the main thread. If the event is never delivered (e.g. because the builder is
47// cancelled or destroyed first), it disposes of the tree.
48class QQuickAsyncItemBuilderResultEvent : public QEvent
49{
50public:
51 QQuickAsyncItemBuilderResultEvent() : QEvent(resultEventType()) { }
52 ~QQuickAsyncItemBuilderResultEvent() override
53 {
54 if (rootItem != nullptr)
55 rootItem->deleteLater();
56 }
57
58 QQuickItem *rootItem = nullptr;
59 QList<std::function<void()>> deferredCalls;
60 QQuickVectorImageGenerator::ErrorState errorState = QQuickVectorImageGenerator::NoError;
61};
62
63// Runs on the global thread pool, which QCoreApplication waits for on destruction. A cancelled
64// task finishes on its own and only shares QQuickAsyncItemBuilderShared with the builder.
65class QQuickAsyncItemBuilderTask : public QRunnable
66{
67public:
68 QQuickAsyncItemBuilderTask(const QQuickVectorImageSource &source,
69 QQuickVectorImageGenerator::GeneratorFlags flags,
70 QQmlContext *context,
71 QThread *targetThread,
72 std::vector<std::unique_ptr<QQuickVectorImagePluginGenerator>> pluginGenerators,
73 std::shared_ptr<QQuickAsyncItemBuilderShared> shared)
74 : m_source(source)
75 , m_flags(flags)
76 , m_context(context)
77 , m_targetThread(targetThread)
78 , m_pluginGenerators(std::move(pluginGenerators))
79 , m_shared(std::move(shared))
80 {
81 }
82
83 void run() override
84 {
85 auto *result = new QQuickAsyncItemBuilderResultEvent;
86
87 {
88 // The context is only stored by the generator here. It is used by the deferred calls,
89 // which are executed on the GUI thread.
90 QQuickItemGenerator generator(m_source, m_flags, m_context);
91 generator.setDeferGuiThreadCalls(true);
92 generator.setCancellationFlag(&m_shared->cancelled);
93
94 bool generatedWithPlugin = false;
95 for (const auto &pluginGenerator : std::as_const(m_pluginGenerators)) {
96 if (isCancelled())
97 break;
98 if ((generatedWithPlugin = pluginGenerator->generate(&generator)))
99 break;
100 }
101
102 if (!generatedWithPlugin && !isCancelled())
103 generator.generate();
104
105 result->errorState = isCancelled() ? QQuickVectorImageGenerator::Cancelled
106 : generator.errorState();
107
108 if (result->errorState == QQuickVectorImageGenerator::NoError) {
109 result->rootItem = generator.takeRootItem();
110 result->deferredCalls = generator.takeDeferredCalls();
111 if (result->rootItem != nullptr)
112 result->rootItem->moveToThread(m_targetThread);
113 }
114 }
115
116 QMutexLocker locker(&m_shared->mutex);
117 if (m_shared->receiver != nullptr) {
118 QCoreApplication::postEvent(m_shared->receiver, result);
119 } else {
120 locker.unlock();
121 delete result;
122 }
123 }
124
125private:
126 bool isCancelled() const { return m_shared->cancelled.load(std::memory_order_relaxed); }
127
128 QQuickVectorImageSource m_source;
129 QQuickVectorImageGenerator::GeneratorFlags m_flags;
130 QQmlContext *m_context;
131 QThread *m_targetThread;
132 std::vector<std::unique_ptr<QQuickVectorImagePluginGenerator>> m_pluginGenerators;
133 std::shared_ptr<QQuickAsyncItemBuilderShared> m_shared;
134};
135
136int initializationBudgetMs()
137{
138 static const int val = int(qEnvironmentVariableIntegerValue("QT_QUICKVECTORIMAGE_ASYNC_BUDGET_MS")
139 .value_or(5));
140 return val;
141}
142
143} // namespace
144
145QQuickAsyncItemBuilder::QQuickAsyncItemBuilder(const QQuickVectorImageSource &source,
146 QQuickVectorImageGenerator::GeneratorFlags flags,
147 QQmlContext *context,
148 QObject *parent)
149 : QObject(parent)
150 , m_source(source)
151 , m_flags(flags)
152 , m_context(context)
153 , m_shared(std::make_shared<QQuickAsyncItemBuilderShared>())
154{
155}
156
157QQuickAsyncItemBuilder::~QQuickAsyncItemBuilder()
158{
159 cancel();
160 delete m_rootItem;
161}
162
163void QQuickAsyncItemBuilder::start(std::vector<std::unique_ptr<QQuickVectorImagePluginGenerator>> pluginGenerators)
164{
165 Q_ASSERT(m_state == State::Idle);
166 m_state = State::Generating;
167
168 {
169 QMutexLocker locker(&m_shared->mutex);
170 m_shared->receiver = this;
171 }
172
173 QThreadPool::globalInstance()->start(new QQuickAsyncItemBuilderTask(m_source,
174 m_flags,
175 m_context,
176 thread(),
177 std::move(pluginGenerators),
178 m_shared));
179}
180
181// Stops generation without waiting for the worker thread. The worker notices the cancellation
182// the next time it enters the generator and stops descending into the document; the partial tree
183// is then destroyed on the worker. If the tree has already been handed over, it is destroyed here.
184void QQuickAsyncItemBuilder::cancel()
185{
186 if (!isRunning())
187 return;
188
189 m_state = State::Cancelled;
190 m_errorState = QQuickVectorImageGenerator::Cancelled;
191 detach();
192
193 m_deferredCalls.clear();
194 delete m_rootItem;
195 m_rootItem = nullptr;
196}
197
198void QQuickAsyncItemBuilder::detach()
199{
200 m_shared->cancelled = true;
201 {
202 QMutexLocker locker(&m_shared->mutex);
203 m_shared->receiver = nullptr;
204 }
205
206 // Deletes a pending result event, and with it the tree it carries
207 QCoreApplication::removePostedEvents(this, resultEventType());
208 QCoreApplication::removePostedEvents(this, initializeEventType());
209}
210
211QQuickItem *QQuickAsyncItemBuilder::takeRootItem()
212{
213 return std::exchange(m_rootItem, nullptr);
214}
215
216bool QQuickAsyncItemBuilder::event(QEvent *e)
217{
218 if (e->type() == resultEventType()) {
219 auto *result = static_cast<QQuickAsyncItemBuilderResultEvent *>(e);
220 if (m_state != State::Generating)
221 return true;
222
223 if (result->errorState != QQuickVectorImageGenerator::NoError
224 || result->rootItem == nullptr) {
225 finish(result->errorState != QQuickVectorImageGenerator::NoError
226 ? result->errorState
227 : QQuickVectorImageGenerator::LoadFailed);
228 return true;
229 }
230
231 Q_ASSERT(result->rootItem->thread() == thread());
232 m_rootItem = std::exchange(result->rootItem, nullptr);
233 m_deferredCalls = std::move(result->deferredCalls);
234 m_nextDeferredCall = 0;
235 m_state = State::Initializing;
236 initializeItems();
237 return true;
238 }
239
240 if (e->type() == initializeEventType()) {
241 initializeItems();
242 return true;
243 }
244
245 return QObject::event(e);
246}
247
248// Executes the deferred calls until they are done. Time slices in case certain deferred calls
249// are expensive.
250void QQuickAsyncItemBuilder::initializeItems()
251{
252 if (m_state != State::Initializing)
253 return;
254
255 QDeadlineTimer deadline(initializationBudgetMs(), Qt::PreciseTimer);
256 while (m_nextDeferredCall < m_deferredCalls.size()) {
257 // Release the call (and anything it has captured) once it has been executed
258 std::function<void()> call = std::exchange(m_deferredCalls[m_nextDeferredCall++], {});
259 call();
260
261 if (m_state != State::Initializing) // Cancelled from within the call
262 return;
263
264 if (deadline.hasExpired() && m_nextDeferredCall < m_deferredCalls.size()) {
265 QCoreApplication::postEvent(this, new QEvent(initializeEventType()));
266 return;
267 }
268 }
269
270 m_deferredCalls.clear();
271 finish(QQuickVectorImageGenerator::NoError);
272}
273
274void QQuickAsyncItemBuilder::finish(QQuickVectorImageGenerator::ErrorState errorState)
275{
276 Q_ASSERT(isRunning());
277
278 detach();
279 m_errorState = errorState;
280 if (errorState != QQuickVectorImageGenerator::NoError) {
281 qCWarning(lcQuickVectorImage) << "QQuickAsyncItemBuilder: failed to generate" << m_source
282 << "(errorState:" << errorState << ")";
283 delete m_rootItem;
284 m_rootItem = nullptr;
285 }
286
287 m_state = State::Finished;
288 emit finished();
289}
290
291QT_END_NAMESPACE
Combined button and popup list for selecting options.